diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index eaf034abc8..0000000000
--- a/.editorconfig
+++ /dev/null
@@ -1,25 +0,0 @@
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 2
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[Makefile]
-indent_style = tab
-
-[*.go]
-indent_style = tab
-
-[*.{pug,coffee}]
-indent_style = tab
-
-[*.{pug,patch}]
-trim_trailing_whitespace = false
-
-[Jenkinsfile]
-insert_final_newline = false
-max_line_length = off
diff --git a/develop/.gitignore b/develop/.gitignore
deleted file mode 100644
index bb5888b864..0000000000
--- a/develop/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/compiles/*
-!.gitkeep
-.env
\ No newline at end of file
diff --git a/develop/README.md b/develop/README.md
deleted file mode 100644
index 2b494cc70b..0000000000
--- a/develop/README.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Overleaf Community Edition, development environment
-
-## Building and running
-
-In this `develop` directory, build the services:
-
-```shell
-bin/build
-```
-
-> [!NOTE]
-> If Docker is running out of RAM while building the services in parallel, create a `.env` file in this directory containing `COMPOSE_PARALLEL_LIMIT=1`.
-
-Then start the services:
-
-```shell
-bin/up
-```
-
-Once the services are running, open to create the first admin account.
-
-## Development
-
-To avoid running `bin/build && bin/up` after every code change, you can run Overleaf
-Community Edition in _development mode_, where services will automatically update on code changes.
-
-To do this, use the included `bin/dev` script:
-
-```shell
-bin/dev
-```
-
-This will start all services using `node --watch`, which will automatically monitor the code and restart the services as necessary.
-
-To improve performance, you can start only a subset of the services in development mode by providing a space-separated list to the `bin/dev` script:
-
-```shell
-bin/dev [service1] [service2] ... [serviceN]
-```
-
-> [!NOTE]
-> Starting the `web` service in _development mode_ will only update the `web`
-> service when backend code changes. In order to automatically update frontend
-> code as well, make sure to start the `webpack` service in _development mode_
-> as well.
-
-If no services are named, all services will start in development mode.
-
-## Debugging
-
-When run in _development mode_ most services expose a debugging port to which
-you can attach a debugger such as
-[the inspector in Chrome's Dev Tools](chrome://inspect/) or one integrated into
-an IDE. The following table shows the port exposed on the **host machine** for
-each service:
-
-| Service | Port |
-| ------------------ | ---- |
-| `web` | 9229 |
-| `clsi` | 9230 |
-| `chat` | 9231 |
-| `docstore` | 9233 |
-| `document-updater` | 9234 |
-| `filestore` | 9235 |
-| `notifications` | 9236 |
-| `real-time` | 9237 |
-| `history-v1` | 9239 |
-| `project-history` | 9240 |
-
-To attach to a service using Chrome's _remote debugging_, go to
- and make sure _Discover network targets_ is checked. Next
-click _Configure..._ and add an entry `localhost:[service port]` for each of the
-services you want to attach a debugger to.
-
-After adding an entry, the service will show up as a _Remote Target_ that you
-can inspect and debug.
diff --git a/develop/bin/build b/develop/bin/build
deleted file mode 100755
index bd30abc13f..0000000000
--- a/develop/bin/build
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-docker compose build --pull "$@"
diff --git a/develop/bin/dev b/develop/bin/dev
deleted file mode 100755
index 1dca01c92b..0000000000
--- a/develop/bin/dev
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-docker-compose -f docker-compose.yml -f docker-compose.dev.yml up --no-deps --detach "$@"
diff --git a/develop/bin/down b/develop/bin/down
deleted file mode 100755
index 3eaad36736..0000000000
--- a/develop/bin/down
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-docker compose down "$@"
diff --git a/develop/bin/logs b/develop/bin/logs
deleted file mode 100755
index 3288ec05da..0000000000
--- a/develop/bin/logs
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-docker compose logs --follow --tail 10 --no-color "$@" \
- | ggrep --line-buffered --invert-match "global.gc" \
- | ggrep --line-buffered --invert-match "health.check" \
- | ggrep --line-buffered --invert-match "slow event loop" \
- | ggrep --line-buffered --invert-match "process.memoryUsage" \
- | ggrep --line-buffered --only-matching "[{].*" \
- | bunyan --output short
diff --git a/develop/bin/shell b/develop/bin/shell
deleted file mode 100755
index 0929c51d12..0000000000
--- a/develop/bin/shell
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-docker compose exec -it "$@" /bin/bash
diff --git a/develop/bin/up b/develop/bin/up
deleted file mode 100755
index b39c4e3710..0000000000
--- a/develop/bin/up
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-docker compose up --detach "$@"
diff --git a/develop/compiles/.gitkeep b/develop/compiles/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/develop/dev.env b/develop/dev.env
deleted file mode 100644
index 35da8a8c86..0000000000
--- a/develop/dev.env
+++ /dev/null
@@ -1,24 +0,0 @@
-CHAT_HOST=chat
-CLSI_HOST=clsi
-DOWNLOAD_HOST=clsi-nginx
-DOCSTORE_HOST=docstore
-DOCUMENT_UPDATER_HOST=document-updater
-FILESTORE_HOST=filestore
-GRACEFUL_SHUTDOWN_DELAY_SECONDS=0
-HISTORY_V1_HOST=history-v1
-HISTORY_REDIS_HOST=redis
-LISTEN_ADDRESS=0.0.0.0
-MONGO_HOST=mongo
-MONGO_URL=mongodb://mongo/sharelatex?directConnection=true
-NOTIFICATIONS_HOST=notifications
-PROJECT_HISTORY_HOST=project-history
-QUEUES_REDIS_HOST=redis
-DSMP_REDIS_HOST=redis
-REALTIME_HOST=real-time
-REDIS_HOST=redis
-SESSION_SECRET=foo
-V1_HISTORY_HOST=history-v1
-WEBPACK_HOST=webpack
-WEB_API_PASSWORD=overleaf
-WEB_API_USER=overleaf
-WEB_HOST=web
diff --git a/develop/docker-compose.dev.yml b/develop/docker-compose.dev.yml
deleted file mode 100644
index 69b31f803d..0000000000
--- a/develop/docker-compose.dev.yml
+++ /dev/null
@@ -1,128 +0,0 @@
-services:
- clsi:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9230:9229"
- volumes:
- - ../services/clsi/app:/overleaf/services/clsi/app
- - ../services/clsi/app.js:/overleaf/services/clsi/app.js
- - ../services/clsi/config:/overleaf/services/clsi/config
-
- chat:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9231:9229"
- volumes:
- - ../services/chat/app:/overleaf/services/chat/app
- - ../services/chat/app.js:/overleaf/services/chat/app.js
- - ../services/chat/config:/overleaf/services/chat/config
-
- docstore:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9233:9229"
- volumes:
- - ../services/docstore/app:/overleaf/services/docstore/app
- - ../services/docstore/app.js:/overleaf/services/docstore/app.js
- - ../services/docstore/config:/overleaf/services/docstore/config
-
- document-updater:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9234:9229"
- volumes:
- - ../services/document-updater/app:/overleaf/services/document-updater/app
- - ../services/document-updater/app.js:/overleaf/services/document-updater/app.js
- - ../services/document-updater/config:/overleaf/services/document-updater/config
-
- filestore:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9235:9229"
- volumes:
- - ../services/filestore/app:/overleaf/services/filestore/app
- - ../services/filestore/app.js:/overleaf/services/filestore/app.js
- - ../services/filestore/config:/overleaf/services/filestore/config
-
- history-v1:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9239:9229"
- volumes:
- - ../services/history-v1/api:/overleaf/services/history-v1/api
- - ../services/history-v1/app.js:/overleaf/services/history-v1/app.js
- - ../services/history-v1/config:/overleaf/services/history-v1/config
- - ../services/history-v1/storage:/overleaf/services/history-v1/storage
- - ../services/history-v1/knexfile.js:/overleaf/services/history-v1/knexfile.js
- - ../services/history-v1/migrations:/overleaf/services/history-v1/migrations
-
- notifications:
- command: ["node", "--watch", "app.ts"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9236:9229"
- volumes:
- - ../services/notifications/app:/overleaf/services/notifications/app
- - ../services/notifications/app.ts:/overleaf/services/notifications/app.ts
- - ../services/notifications/config:/overleaf/services/notifications/config
-
- project-history:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9240:9229"
- volumes:
- - ../services/project-history/app:/overleaf/services/project-history/app
- - ../services/project-history/app.js:/overleaf/services/project-history/app.js
- - ../services/project-history/config:/overleaf/services/project-history/config
-
- real-time:
- command: ["node", "--watch", "app.js"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9237:9229"
- volumes:
- - ../services/real-time/app:/overleaf/services/real-time/app
- - ../services/real-time/app.js:/overleaf/services/real-time/app.js
- - ../services/real-time/config:/overleaf/services/real-time/config
-
- web:
- command: ["node", "--watch", "app.mjs", "--watch-locales"]
- environment:
- - NODE_OPTIONS=--inspect=0.0.0.0:9229
- ports:
- - "127.0.0.1:9229:9229"
- volumes:
- - ../services/web/app:/overleaf/services/web/app
- - ../services/web/app.mjs:/overleaf/services/web/app.mjs
- - ../services/web/config:/overleaf/services/web/config
- - ../services/web/locales:/overleaf/services/web/locales
- - ../services/web/modules:/overleaf/services/web/modules
- - ../services/web/public:/overleaf/services/web/public
-
- webpack:
- volumes:
- - ../services/web/app:/overleaf/services/web/app
- - ../services/web/config:/overleaf/services/web/config
- - ../services/web/frontend:/overleaf/services/web/frontend
- - ../services/web/locales:/overleaf/services/web/locales
- - ../services/web/modules:/overleaf/services/web/modules
- - ../services/web/public:/overleaf/services/web/public
- - ../services/web/transform:/overleaf/services/web/transform
- - ../services/web/types:/overleaf/services/web/types
- - ../services/web/webpack-plugins:/overleaf/services/web/webpack-plugins
diff --git a/develop/docker-compose.yml b/develop/docker-compose.yml
deleted file mode 100644
index bf65609f53..0000000000
--- a/develop/docker-compose.yml
+++ /dev/null
@@ -1,175 +0,0 @@
-volumes:
- clsi-cache:
- filestore-public-files:
- filestore-template-files:
- filestore-uploads:
- filestore-user-files:
- mongo-data:
- redis-data:
- sharelatex-data:
- web-data:
- history-v1-buckets:
-
-services:
- chat:
- build:
- context: ..
- dockerfile: services/chat/Dockerfile
- env_file:
- - dev.env
-
- clsi:
- build:
- context: ..
- dockerfile: services/clsi/Dockerfile
- target: with-texlive
- env_file:
- - dev.env
- environment:
- - SANDBOXED_COMPILES=false
- user: root
- volumes:
- - ${PWD}/compiles:/overleaf/services/clsi/compiles
- - ${PWD}/output:/overleaf/services/clsi/output
- - ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock
- - clsi-cache:/overleaf/services/clsi/cache
-
- clsi-nginx:
- image: nginx:1.28
- read_only: true
- tmpfs:
- - /tmp
- - /var/cache/nginx
- - /run
- volumes:
- - ${PWD}/output:/output:ro
- - ../services/clsi/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
-
- docstore:
- build:
- context: ..
- dockerfile: services/docstore/Dockerfile
- env_file:
- - dev.env
-
- document-updater:
- build:
- context: ..
- dockerfile: services/document-updater/Dockerfile
- env_file:
- - dev.env
-
- filestore:
- build:
- context: ..
- dockerfile: services/filestore/Dockerfile
- env_file:
- - dev.env
- # environment:
- # - ENABLE_CONVERSIONS=true
- volumes:
- - filestore-public-files:/overleaf/services/filestore/public_files
- - filestore-template-files:/overleaf/services/filestore/template_files
- - filestore-uploads:/overleaf/services/filestore/uploads
-
- history-v1:
- build:
- context: ..
- dockerfile: services/history-v1/Dockerfile
- env_file:
- - dev.env
- environment:
- OVERLEAF_EDITOR_ANALYTICS_BUCKET: "/buckets/analytics"
- OVERLEAF_EDITOR_BLOBS_BUCKET: "/buckets/blobs"
- OVERLEAF_EDITOR_CHUNKS_BUCKET: "/buckets/chunks"
- OVERLEAF_EDITOR_PROJECT_BLOBS_BUCKET: "/buckets/project_blobs"
- OVERLEAF_EDITOR_ZIPS_BUCKET: "/buckets/zips"
- PERSISTOR_BACKEND: fs
- volumes:
- - history-v1-buckets:/buckets
-
- mongo:
- image: mongo:8
- command: --replSet overleaf
- ports:
- - "127.0.0.1:27017:27017" # for debugging
- volumes:
- - mongo-data:/data/db
- - ../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- notifications:
- build:
- context: ..
- dockerfile: services/notifications/Dockerfile
- env_file:
- - dev.env
-
- project-history:
- build:
- context: ..
- dockerfile: services/project-history/Dockerfile
- env_file:
- - dev.env
-
- real-time:
- build:
- context: ..
- dockerfile: services/real-time/Dockerfile
- env_file:
- - dev.env
-
- redis:
- image: redis:7
- ports:
- - "127.0.0.1:6379:6379" # for debugging
- volumes:
- - redis-data:/data
-
- web:
- build:
- context: ..
- dockerfile: services/web/Dockerfile
- target: dev
- env_file:
- - dev.env
- environment:
- - APP_NAME=Overleaf Community Edition
- - ENABLED_LINKED_FILE_TYPES=project_file,project_output_file
- - EMAIL_CONFIRMATION_DISABLED=true
- - NODE_ENV=development
- - OVERLEAF_ALLOW_PUBLIC_ACCESS=true
- command: ["node", "app.mjs"]
- volumes:
- - sharelatex-data:/var/lib/overleaf
- - web-data:/overleaf/services/web/data
- depends_on:
- - mongo
- - redis
- - chat
- - clsi
- - docstore
- - document-updater
- - filestore
- - history-v1
- - notifications
- - project-history
- - real-time
-
- webpack:
- build:
- context: ..
- dockerfile: services/web/Dockerfile
- target: webpack
- command:
- ["npx", "webpack", "serve", "--config", "webpack.config.dev-env.js"]
- ports:
- - "127.0.0.1:80:3808"
- volumes:
- - ./webpack.config.dev-env.js:/overleaf/services/web/webpack.config.dev-env.js
diff --git a/develop/webpack.config.dev-env.js b/develop/webpack.config.dev-env.js
deleted file mode 100644
index 49f85408a8..0000000000
--- a/develop/webpack.config.dev-env.js
+++ /dev/null
@@ -1,23 +0,0 @@
-const { merge } = require('webpack-merge')
-
-const base = require('./webpack.config.dev')
-
-module.exports = merge(base, {
- devServer: {
- allowedHosts: 'auto',
- devMiddleware: {
- index: false,
- },
- proxy: [
- {
- context: '/socket.io/**',
- target: 'http://real-time:3026',
- ws: true,
- },
- {
- context: ['!**/*.js', '!**/*.css', '!**/*.json'],
- target: 'http://web:3000',
- },
- ],
- },
-})
diff --git a/dockerfiles/cypress/Dockerfile b/dockerfiles/cypress/Dockerfile
deleted file mode 100644
index b6042a0ea3..0000000000
--- a/dockerfiles/cypress/Dockerfile
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM cypress/included:15.12.0
-ARG USER_UID=1000
-ARG USER_GID=1000
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf
-
-RUN sed -i s/node:x:1000:/node:x:${USER_GID}:/ /etc/group \
-&& sed -i s_node:x:1000:1000::/home/node:/bin/bash_node:x:${USER_UID}:${USER_GID}::/home/node:/bin/bash_ /etc/passwd \
-&& chown -R node:node /home/node \
-&& chown node:node /overleaf
-
-USER node
diff --git a/libraries/access-token-encryptor/.nvmrc b/libraries/access-token-encryptor/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/access-token-encryptor/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/access-token-encryptor/Makefile b/libraries/access-token-encryptor/Makefile
deleted file mode 100644
index 1ea1f5ce7f..0000000000
--- a/libraries/access-token-encryptor/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/access-token-encryptor monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/access-token-encryptor
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/access-token-encryptor
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/access-token-encryptor
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/access-token-encryptor/buildscript.txt b/libraries/access-token-encryptor/buildscript.txt
deleted file mode 100644
index 81715c79c8..0000000000
--- a/libraries/access-token-encryptor/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-access-token-encryptor
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/fetch-utils/.nvmrc b/libraries/fetch-utils/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/fetch-utils/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/fetch-utils/Makefile b/libraries/fetch-utils/Makefile
deleted file mode 100644
index 0b6e32ed77..0000000000
--- a/libraries/fetch-utils/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/fetch-utils monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/fetch-utils
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/fetch-utils
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/fetch-utils
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/fetch-utils/buildscript.txt b/libraries/fetch-utils/buildscript.txt
deleted file mode 100644
index 75739abe50..0000000000
--- a/libraries/fetch-utils/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-fetch-utils
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/logger/.nvmrc b/libraries/logger/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/logger/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/logger/Makefile b/libraries/logger/Makefile
deleted file mode 100644
index ffcd2f5aee..0000000000
--- a/libraries/logger/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/logger monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/logger
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/logger
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/logger
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/logger/buildscript.txt b/libraries/logger/buildscript.txt
deleted file mode 100644
index c7dac83e16..0000000000
--- a/libraries/logger/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-logger
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/metrics/.nvmrc b/libraries/metrics/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/metrics/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/metrics/Makefile b/libraries/metrics/Makefile
deleted file mode 100644
index c215d44659..0000000000
--- a/libraries/metrics/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/metrics monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/metrics
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/metrics
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/metrics
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/metrics/buildscript.txt b/libraries/metrics/buildscript.txt
deleted file mode 100644
index 1fdfe2d004..0000000000
--- a/libraries/metrics/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-metrics
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/mongo-utils/.nvmrc b/libraries/mongo-utils/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/mongo-utils/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/mongo-utils/Makefile b/libraries/mongo-utils/Makefile
deleted file mode 100644
index 8005de3691..0000000000
--- a/libraries/mongo-utils/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/mongo-utils monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/mongo-utils
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/mongo-utils
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/mongo-utils
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/mongo-utils/buildscript.txt b/libraries/mongo-utils/buildscript.txt
deleted file mode 100644
index a677218ba0..0000000000
--- a/libraries/mongo-utils/buildscript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-mongo-utils
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
---tsconfig-no-implicit-any=True
diff --git a/libraries/o-error/.nvmrc b/libraries/o-error/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/o-error/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/o-error/Makefile b/libraries/o-error/Makefile
deleted file mode 100644
index c8cce47422..0000000000
--- a/libraries/o-error/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/o-error monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/o-error
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/o-error
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/o-error
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/o-error/buildscript.txt b/libraries/o-error/buildscript.txt
deleted file mode 100644
index 97f08794b6..0000000000
--- a/libraries/o-error/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-o-error
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/object-persistor/.nvmrc b/libraries/object-persistor/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/object-persistor/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/object-persistor/Makefile b/libraries/object-persistor/Makefile
deleted file mode 100644
index 32123b3633..0000000000
--- a/libraries/object-persistor/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/object-persistor monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/object-persistor
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/object-persistor
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/object-persistor
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/object-persistor/buildscript.txt b/libraries/object-persistor/buildscript.txt
deleted file mode 100644
index aebfb17c56..0000000000
--- a/libraries/object-persistor/buildscript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-object-persistor
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
---tsconfig-no-implicit-any=True
diff --git a/libraries/overleaf-editor-core/.nvmrc b/libraries/overleaf-editor-core/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/overleaf-editor-core/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/overleaf-editor-core/Makefile b/libraries/overleaf-editor-core/Makefile
deleted file mode 100644
index e3290027c7..0000000000
--- a/libraries/overleaf-editor-core/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/overleaf-editor-core monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/overleaf-editor-core
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/overleaf-editor-core
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/overleaf-editor-core
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/overleaf-editor-core/buildscript.txt b/libraries/overleaf-editor-core/buildscript.txt
deleted file mode 100644
index 68a9d83f67..0000000000
--- a/libraries/overleaf-editor-core/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-overleaf-editor-core
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=44
---public-repo=False
diff --git a/libraries/promise-utils/.nvmrc b/libraries/promise-utils/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/promise-utils/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/promise-utils/Makefile b/libraries/promise-utils/Makefile
deleted file mode 100644
index 75db05eb39..0000000000
--- a/libraries/promise-utils/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/promise-utils monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/promise-utils
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/promise-utils
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/promise-utils
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/promise-utils/buildscript.txt b/libraries/promise-utils/buildscript.txt
deleted file mode 100644
index f87efde6d1..0000000000
--- a/libraries/promise-utils/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-promise-utils
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/ranges-tracker/.nvmrc b/libraries/ranges-tracker/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/ranges-tracker/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/ranges-tracker/Makefile b/libraries/ranges-tracker/Makefile
deleted file mode 100644
index c043077989..0000000000
--- a/libraries/ranges-tracker/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/ranges-tracker monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/ranges-tracker
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/ranges-tracker
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/ranges-tracker
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/ranges-tracker/buildscript.txt b/libraries/ranges-tracker/buildscript.txt
deleted file mode 100644
index 5406adf4d0..0000000000
--- a/libraries/ranges-tracker/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-ranges-tracker
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=44
---public-repo=False
diff --git a/libraries/redis-wrapper/.nvmrc b/libraries/redis-wrapper/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/redis-wrapper/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/redis-wrapper/Makefile b/libraries/redis-wrapper/Makefile
deleted file mode 100644
index 3feaaaaa68..0000000000
--- a/libraries/redis-wrapper/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/redis-wrapper monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/redis-wrapper
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/redis-wrapper
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/redis-wrapper
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/redis-wrapper/buildscript.txt b/libraries/redis-wrapper/buildscript.txt
deleted file mode 100644
index 5dbce753d6..0000000000
--- a/libraries/redis-wrapper/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-redis-wrapper
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/settings/.nvmrc b/libraries/settings/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/settings/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/settings/Makefile b/libraries/settings/Makefile
deleted file mode 100644
index c695f8aed6..0000000000
--- a/libraries/settings/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/settings monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/settings
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/settings
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/settings
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/settings/buildscript.txt b/libraries/settings/buildscript.txt
deleted file mode 100644
index 3a4a6868a0..0000000000
--- a/libraries/settings/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-settings
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/stream-utils/.nvmrc b/libraries/stream-utils/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/stream-utils/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/stream-utils/Makefile b/libraries/stream-utils/Makefile
deleted file mode 100644
index 94815017e1..0000000000
--- a/libraries/stream-utils/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/stream-utils monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/stream-utils
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/stream-utils
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/stream-utils
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/stream-utils/buildscript.txt b/libraries/stream-utils/buildscript.txt
deleted file mode 100644
index fadffdcafb..0000000000
--- a/libraries/stream-utils/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-stream-utils
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
diff --git a/libraries/validation-tools/.nvmrc b/libraries/validation-tools/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/libraries/validation-tools/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/libraries/validation-tools/Makefile b/libraries/validation-tools/Makefile
deleted file mode 100644
index 02b2acbf7f..0000000000
--- a/libraries/validation-tools/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-# Run the test commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/libraries/validation-tools monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/prettier/
-../../node_modules/.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../node_modules/.cache/eslint/
-../../node_modules/.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format libraries/validation-tools
-
-format_ci:
- $(RUN_LINTING_MONOREPO) format libraries/validation-tools
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix libraries/validation-tools
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING) types:check
-
-test: test_unit
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(RUN_LINTING) test:unit
-endif
-
-test: test_acceptance
-test_acceptance:
-ifneq (,$(wildcard test/acceptance))
- $(RUN_LINTING) test:acceptance
-endif
diff --git a/libraries/validation-tools/buildscript.txt b/libraries/validation-tools/buildscript.txt
deleted file mode 100644
index 01be6f67a8..0000000000
--- a/libraries/validation-tools/buildscript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-validation-tools
---dependencies=None
---env-add=
---env-pass-through=
---esmock-loader=False
---is-library=True
---node-version=24.14.1
---public-repo=False
---test-acceptance-vitest=True
---test-unit-vitest=True
diff --git a/server-ce/eslint.config.mjs b/server-ce/eslint.config.mjs
deleted file mode 100644
index ae3c155726..0000000000
--- a/server-ce/eslint.config.mjs
+++ /dev/null
@@ -1,33 +0,0 @@
-import { defineConfig, globalIgnores } from 'eslint/config'
-import cypress from 'eslint-plugin-cypress/flat'
-import path from 'node:path'
-import baseConfig from '../eslint.config.mjs'
-
-const ROOT_DIR = path.resolve(import.meta.dirname, '..')
-
-export default defineConfig([
- globalIgnores(['**/hotfix/', '**/develop/']),
- {
- basePath: ROOT_DIR,
- extends: baseConfig,
- languageOptions: {
- ecmaVersion: 2020,
- },
- },
- {
- // The cypress block in baseConfig has patterns rooted at the
- // monorepo root (`server-ce/test/helpers/*.ts`). When ESLint loads
- // this file (server-ce/eslint.config.mjs) as the closest config --
- // which happens when running `yarn run lint` from
- // /overleaf/server-ce/test/ -- patterns from baseConfig are
- // resolved relative to /overleaf/server-ce/, so those cross-dir
- // patterns don't match. Re-declare with paths relative to this
- // config file.
- files: [
- 'test/helpers/*.ts',
- 'test/cypress/support/*.{js,jsx,mjs,cjs,ts,tsx}',
- '**/*.spec.ts',
- ],
- ...cypress.configs.recommended,
- },
-])
diff --git a/server-ce/test/.gitignore b/server-ce/test/.gitignore
deleted file mode 100644
index 56be476514..0000000000
--- a/server-ce/test/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-data/
-docker-mailtrap/
diff --git a/server-ce/test/Dockerfile b/server-ce/test/Dockerfile
deleted file mode 100644
index 5680b3e677..0000000000
--- a/server-ce/test/Dockerfile
+++ /dev/null
@@ -1,15 +0,0 @@
-FROM node:24.14.1
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \
-&& echo \
- "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
- > /etc/apt/sources.list.d/docker.list \
-&& apt-get update \
-&& apt-get install -y docker-ce-cli docker-compose-plugin \
-&& rm -rf /var/lib/apt/lists/*
diff --git a/server-ce/test/Makefile b/server-ce/test/Makefile
deleted file mode 100644
index 367059ef04..0000000000
--- a/server-ce/test/Makefile
+++ /dev/null
@@ -1,126 +0,0 @@
-all: test-e2e
-
-# We are updating the docker compose config via the host-admin service.
-# The host-admin service is running inside docker and has its own file-system layout.
-# We need to have both file-system layouts agree on the path for the docker compose project.
-# Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance).
-export PWD = $(shell pwd)
-export MONOREPO = $(shell cd ../../ && pwd)
-
-export TEX_LIVE_DOCKER_IMAGE ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2023.1
-export ALL_TEX_LIVE_DOCKER_IMAGES ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2023.1,us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2022.1
-export IMAGE_TAG_CE ?= sharelatex/sharelatex:main
-export IMAGE_TAG_PRO ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/pro:main
-export IMAGE_TAG_GIT_BRIDGE ?= us-east1-docker.pkg.dev/overleaf-ops/ol-docker/git-bridge:main
-export CYPRESS_SHARD ?=
-export COMPOSE_PROJECT_NAME ?= test
-export USER_UID=$(shell id -u)
-export USER_GID=$(shell id -g)
-IMAGE_TAG_PRO_BASE := $(shell echo $(IMAGE_TAG_PRO) | sed -E s/:.+//)
-
-print-debug-logs:
- -docker compose logs --timestamps --tail=100 e2e
- -docker compose logs --timestamps --tail=100 mongo
- -docker compose logs --timestamps --tail=100 redis
- -docker compose logs --timestamps --tail=100 mailtrap
- -docker compose logs --timestamps --tail=100 saml
- -docker compose logs --timestamps --tail=100 ldap
- -docker compose logs --timestamps --tail=100 git-bridge
- -docker compose logs --timestamps --tail=100 host-admin
- -docker compose logs --timestamps sharelatex
- -docker compose exec sharelatex bash -c 'tail -n100 /var/log/overleaf/*.log'
-
-test-e2e-native:
- docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix sharelatex host-admin -d
- CYPRESS_BASE_URL='http://127.0.0.1:3231' \
- CYPRESS_HOST_ADMIN_URL='http://127.0.0.1:3232' \
- CYPRESS_SAML_URL='http://127.0.0.1:3233' \
- CYPRESS_MAILTRAP_URL='http://127.0.0.1:3234' \
- yarn run cypress:open
-
-# For testing "native" on Linux
-test-e2e-native-linux:
- docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix sharelatex host-admin -d
- CYPRESS_BASE_URL='http://127.0.0.1:3231' \
- CYPRESS_HOST_ADMIN_URL='http://127.0.0.1:3232' \
- CYPRESS_SAML_URL='http://127.0.0.1:3233' \
- CYPRESS_MAILTRAP_URL='http://127.0.0.1:3234' \
- docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix cypress
-
-test-e2e:
- docker compose build host-admin e2e
- docker compose up --detach --wait host-admin
- docker compose up --detach --wait mongo
- docker compose up --no-log-prefix --exit-code-from=e2e e2e host-admin
-
-test-e2e-open:
- docker compose up --detach --wait host-admin
- docker compose up --detach --wait mongo
- docker compose up --no-log-prefix --exit-code-from=e2e-open e2e-open host-admin
-
-SHARD_PROJECT_NAMES = \
- test-ce-default \
- test-ce-custom-1 \
- test-pro-default-1 \
- test-pro-default-2 \
- test-pro-custom-1 \
- test-pro-custom-2 \
- test-pro-custom-3 \
- test-pro-custom-4
-CLEAN_SHARDS=$(addprefix clean/,$(SHARD_PROJECT_NAMES))
-clean: $(CLEAN_SHARDS)
- -docker compose run --no-deps --rm --entrypoint rm host-admin -rf docker-compose.override.yml docker-compose.*_*.yml reports/ data/
- -docker compose down --remove-orphans --rmi local --timeout 0 --volumes
-
-$(CLEAN_SHARDS): clean/%:
- -COMPOSE_PROJECT_NAME=$* docker compose down --remove-orphans --rmi local --timeout 0 --volumes
-
-prefetch: prefetch_default
-prefetch_default: prefetch_default_compose
-prefetch_default_compose:
- docker compose pull e2e mongo redis
-
-prefetch_default: prefetch_default_compose_build
-prefetch_default_compose_build:
- docker compose build host-admin e2e
-
-prefetch: prefetch_custom
-prefetch_custom: prefetch_custom_compose_pull
-prefetch_custom_compose_pull:
- docker compose pull saml ldap
-
-prefetch_custom: prefetch_custom_texlive_2023
-prefetch_custom_texlive_2023:
- docker pull us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2023.1
- docker tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2023.1 quay.io/sharelatex/texlive-full:2023.1
-
-prefetch_custom: prefetch_custom_texlive_2022
-prefetch_custom_texlive_2022:
- docker pull us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2022.1
- docker tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/texlive-full:2022.1 quay.io/sharelatex/texlive-full:2022.1
-
-prefetch_custom: prefetch_old_4_2
-prefetch_old_4_2:
- docker pull $(IMAGE_TAG_PRO_BASE):4.2
-
-prefetch_custom: prefetch_old_5_0
-prefetch_old_5_0:
- docker pull $(IMAGE_TAG_PRO_BASE):5.0
-
-prefetch_custom: build_hotfix_5_5_5
-build_hotfix_5_5_5:
- docker pull $(IMAGE_TAG_PRO_BASE):5.5.4
- cd ../../server-pro/hotfix/5.5.5 && docker build --build-arg BASE_IMAGE=$(IMAGE_TAG_PRO_BASE):5.5.4 -t $(IMAGE_TAG_PRO_BASE):5.5.5 .
-
-# Google Cloud Build runs on a very ancient Docker version that does not support the subdir flag.
-# Use services -> mailtrap -> build -> context = https://github.com/dbck/docker-mailtrap.git#v1.5.0:build in docker-compose.yml eventually.
-prefetch_default_compose_build: build_mailtrap
-build_mailtrap:
- git clone https://github.com/dbck/docker-mailtrap.git || true && cd docker-mailtrap && git checkout v1.5.0
- docker build -t mailtrap docker-mailtrap/build
-
-prefetch_default_compose_build: build_cypress
-build_cypress:
- docker compose build e2e
-
-.PHONY: test-e2e test-e2e-open
diff --git a/server-ce/test/accounts.spec.ts b/server-ce/test/accounts.spec.ts
deleted file mode 100644
index a64da80414..0000000000
--- a/server-ce/test/accounts.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { createMongoUser, ensureUserExists, login } from './helpers/login'
-import { isExcludedBySharding, startWith } from './helpers/config'
-
-describe('Accounts', function () {
- if (isExcludedBySharding('CE_DEFAULT')) return
- startWith({})
- ensureUserExists({ email: 'user@example.com' })
-
- it('can log in and out', function () {
- login('user@example.com')
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Account' }).click()
- cy.findByRole('menuitem', { name: 'Log Out' }).click()
- cy.url().should('include', '/login')
- cy.visit('/project')
- cy.url().should('include', '/login')
- })
-
- it('should render the email on the user activate screen', function () {
- const email = 'not-activated-user@example.com'
- cy.then(async () => {
- const { url } = await createMongoUser({ email })
- return url
- }).as('url')
- cy.get('@url').then(url => {
- cy.visit(`${url}`)
- cy.url().should('contain', '/user/activate')
- cy.findByRole('heading', { name: 'Please set a password' })
- cy.findByLabelText('Email').should('be.visible')
- cy.findByLabelText('Password').should('be.visible')
- cy.findByRole('button', { name: 'Activate' })
- })
- })
-})
diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts
deleted file mode 100644
index 1c3c56a522..0000000000
--- a/server-ce/test/admin.spec.ts
+++ /dev/null
@@ -1,402 +0,0 @@
-import { isExcludedBySharding, startWith } from './helpers/config'
-import {
- activateUser,
- createMongoUser,
- ensureUserExists,
- login,
-} from './helpers/login'
-import { v4 as uuid } from 'uuid'
-import { createProject } from './helpers/project'
-import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
-import { openEmail } from './helpers/email'
-
-describe('admin panel', function () {
- function registrationTests() {
- it('via GUI and opening URL manually', function () {
- const user = `${uuid()}@example.com`
- cy.findByLabelText('Emails to register new users').type(user + '{enter}')
-
- cy.findByRole('cell', { name: /\/user\/activate/ }).then($td => {
- const url = $td.text().trim()
- activateUser(url)
- })
- })
-
- it('via GUI and email', function () {
- const user = `${uuid()}@example.com`
- cy.findByLabelText('Emails to register new users').type(user + '{enter}')
-
- let url: string
- cy.findByRole('cell', { name: /\/user\/activate/ }).then($td => {
- url = $td.text().trim()
- })
-
- cy.then(() => {
- openEmail(
- 'Activate your E2E test Account',
- (frame, { url }) => {
- frame.contains('a', 'Set password').then(el => {
- expect(el.attr('href')!).to.equal(url)
- })
- },
- { url }
- )
- // Run activateUser in the main origin instead of inside openEmail. See docs on openEmail.
- activateUser(url)
- })
- })
- it('via script and opening URL manually', function () {
- const user = `${uuid()}@example.com`
- let url: string
- cy.then(async () => {
- ;({ url } = await createMongoUser({ email: user }))
- })
- cy.then(() => {
- activateUser(url)
- })
- })
- it('via script and email', function () {
- const user = `${uuid()}@example.com`
- let url: string
- cy.then(async () => {
- ;({ url } = await createMongoUser({ email: user }))
- })
- cy.then(() => {
- openEmail(
- 'Activate your E2E test Account',
- (frame, { url }) => {
- frame.contains('a', 'Set password').then(el => {
- expect(el.attr('href')!).to.equal(url)
- })
- },
- { url }
- )
- // Run activateUser in the main origin instead of inside openEmail. See docs on openEmail.
- activateUser(url)
- })
- })
- }
-
- describe('in CE', function () {
- if (isExcludedBySharding('CE_DEFAULT')) return
- startWith({ pro: false, version: 'latest' })
- const admin = 'admin@example.com'
- const user = `user+${uuid()}@example.com`
- ensureUserExists({ email: admin, isAdmin: true })
- ensureUserExists({ email: user })
-
- describe('create users', function () {
- beforeEach(function () {
- login(admin)
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menuitem', { name: 'Manage Users' }).click()
- })
- registrationTests()
- })
- })
-
- describe('in server pro', function () {
- const admin = 'admin@example.com'
- const user1 = 'user@example.com'
- const user2 = 'user2@example.com'
-
- let testProjectName = ''
- let testProjectId = ''
- let deletedProjectName = ''
- let projectToDeleteId = ''
-
- const findProjectRow = (projectName: string) => {
- cy.log('find project row')
- return cy.findByText(projectName).parent().parent()
- }
-
- if (isExcludedBySharding('PRO_DEFAULT_2')) return
- startWith({
- pro: true,
- })
- ensureUserExists({ email: admin, isAdmin: true })
- ensureUserExists({ email: user1 })
- ensureUserExists({ email: user2 })
-
- beforeWithReRunOnTestRetry(() => {
- testProjectName = `project-${uuid()}`
- deletedProjectName = `deleted-project-${uuid()}`
- login(user1)
- createProject(testProjectName, { open: false }).then(
- id => (testProjectId = id)
- )
- createProject(deletedProjectName, { open: false }).then(
- id => (projectToDeleteId = id)
- )
- })
-
- describe('admin menu items', function () {
- beforeEach(function () {
- login(admin)
- cy.visit('/project')
- })
-
- it('displays expected admin menu items', function () {
- const menuitems = ['Manage Site', 'Manage Users', 'Project URL Lookup']
- menuitems.forEach(name => {
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menu')
- .findAllByRole('menuitem')
- .should('have.length', menuitems.length)
- cy.findByRole('menu').findByRole('menuitem', { name }).click()
- })
- })
- })
-
- describe('manage site', function () {
- beforeEach(function () {
- login(admin)
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menuitem', { name: 'Manage Site' }).click()
- })
-
- it('publish and clear admin messages', function () {
- const message = 'Admin Message ' + uuid()
-
- cy.log('create system message')
- cy.findByRole('tab', { name: 'System Messages' }).click()
- cy.findByLabelText('Message').type(message)
- cy.findByRole('button', { name: 'Post Message' }).click()
- cy.findByText(message)
-
- login(user1)
- cy.visit('/project')
- cy.findByText(message)
-
- cy.log('clear system messages')
- login(admin)
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menuitem', { name: 'Manage Site' }).click()
- cy.findByRole('tab', { name: 'System Messages' }).click()
- cy.findByRole('button', { name: 'Clear all messages' }).click()
-
- cy.log('verify system messages are no longer displayed')
- login(user1)
- cy.visit('/project')
- cy.findByText(message).should('not.exist')
- })
- })
-
- describe('manage users', function () {
- beforeEach(function () {
- login(admin)
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menuitem', { name: 'Manage Users' }).click()
- })
-
- it('displays expected tabs', function () {
- const tabs = ['Users', 'License Usage']
- cy.findAllByRole('tab').should('have.length', tabs.length)
- tabs.forEach(tabName => {
- cy.findByRole('tab', { name: tabName }).click()
- })
- })
-
- it('license usage tab', function () {
- cy.findByRole('tab', { name: 'License Usage' }).click()
- cy.findByText(
- 'An active user is one who has opened a project in this Server Pro instance in the last 12 months.'
- )
- })
-
- describe('create users', function () {
- beforeEach(function () {
- cy.findByRole('link', { name: 'New User' }).click()
- })
- registrationTests()
- })
-
- it('user list RegExp search', function () {
- cy.findByLabelText('RegExp').click()
- cy.findByPlaceholderText('Search users by email or id…').type(
- 'user[0-9]{enter}'
- )
- cy.findByRole('link', { name: user2 })
- cy.findByRole('link', { name: user1 }).should('not.exist')
- })
- })
-
- describe('user page', function () {
- beforeEach(function () {
- login(admin)
- cy.visit('/project')
- cy.findByRole('menuitem', { name: 'Admin' }).click()
- cy.findByRole('menuitem', { name: 'Manage Users' }).click()
- cy.findByPlaceholderText('Search users by email or id…').type(
- user1 + '{enter}'
- )
- cy.findByRole('link', { name: user1 }).click()
- cy.url().should('match', /\/admin\/user\/[a-fA-F0-9]{24}/)
- })
-
- it('displays expected tabs', function () {
- const tabs = [
- 'User Info',
- 'Projects',
- 'Deleted Projects',
- 'Audit Log',
- 'Sessions',
- 'Personal Access Tokens',
- ]
- cy.findAllByRole('tab').should('have.length', tabs.length)
- tabs.forEach(tabName => {
- cy.findByRole('tab', { name: tabName }).click()
- })
- })
-
- describe('user info tab', function () {
- beforeEach(function () {
- cy.findByRole('tab', { name: 'User Info' }).click()
- })
-
- it('displays required sections', function () {
- // not exhaustive list, checks the tab content is rendered
- cy.findByRole('heading', { name: 'Profile' })
- cy.findByRole('heading', { name: 'Editor Settings' })
- })
-
- it('should not display SaaS-only sections', function () {
- cy.findByLabelText('Referred User Count').should('not.exist')
- cy.findByRole('heading', { name: /Split Test Assignments/ }).should(
- 'not.exist'
- )
- cy.findByRole('heading', { name: 'Experimental Features' }).should(
- 'not.exist'
- )
- cy.findByRole('heading', { name: 'Service Integration' }).should(
- 'not.exist'
- )
- cy.findByRole('heading', { name: 'SSO Integrations' }).should(
- 'not.exist'
- )
- cy.findByRole('heading', { name: 'Security' }).should('not.exist')
- })
- })
-
- it('transfer project ownership', function () {
- cy.log("access project admin through owners' project list")
- cy.findByRole('tablist').within(() => {
- cy.findByRole('tab', { name: 'Projects' }).click()
- })
- cy.findByRole('link', { name: testProjectName })
- .parent()
- .parent()
- .within(() => {
- cy.findByRole('link', { name: 'Project information' }).click()
- })
-
- cy.findByRole('button', { name: 'Transfer Ownership' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('heading', { name: 'Transfer Ownership of Project' })
- cy.findByRole('button', { name: 'Find' }).should('be.disabled')
- cy.findByRole('button', { name: 'Confirm' }).should('be.disabled')
- cy.findByPlaceholderText('User ID or Email').type(user2)
- cy.findByRole('button', { name: 'Find' }).should('not.be.disabled')
- cy.findByRole('button', { name: 'Find' }).click()
- cy.findByText('Transfer project to this user?')
- cy.findByRole('cell', { name: 'ID' })
- cy.findByRole('cell', { name: 'Name' })
- cy.findByRole('cell', { name: 'Email' })
- cy.findByRole('cell', { name: user2 })
- cy.findByRole('button', { name: 'Confirm' }).should('not.be.disabled')
- cy.findByRole('button', { name: 'Confirm' }).click()
- })
-
- cy.log('check the project is displayed in the new owner projects tab')
- cy.findByPlaceholderText('Search users by email or id…').type(
- user2 + '{enter}'
- )
- cy.findByRole('link', { name: user2 }).click()
- cy.findByRole('tablist').within(() => {
- cy.findByRole('tab', { name: 'Projects' }).click()
- })
- cy.findByRole('link', { name: testProjectName })
- .parent()
- .parent()
- .within(() => {
- cy.findByRole('link', { name: 'Project information' }).click()
- })
- })
- })
-
- describe('project page', function () {
- beforeEach(function () {
- login(admin)
- cy.visit(`/admin/project/${testProjectId}`)
- })
-
- it('displays expected tabs', function () {
- const tabs = ['Project Info', 'Deleted Docs', 'Audit Log']
- cy.findAllByRole('tab').should('have.length', tabs.length)
- tabs.forEach(tabName => {
- cy.findByRole('tab', { name: tabName }).click()
- })
- })
- })
-
- it('restore deleted projects', function () {
- login(user1)
- cy.visit('/project')
-
- cy.log('select project to delete')
- findProjectRow(deletedProjectName).within(() =>
- cy
- .findByRole('checkbox', { name: `Select ${deletedProjectName}` })
- .first()
- .check()
- )
- cy.log('delete project')
- findProjectRow(deletedProjectName).within(() =>
- cy.findByRole('button', { name: 'Trash' }).click()
- )
- cy.findByRole('button', { name: 'Confirm' }).click()
- cy.findByRole('link', { name: deletedProjectName }).should('not.exist')
-
- cy.log('navigate to thrashed projects and delete the project')
- cy.findByRole('navigation', {
- name: 'Project categories and tags',
- })
- .findByRole('button', { name: 'Trashed projects' })
- .click()
- findProjectRow(deletedProjectName).within(() =>
- cy.findByRole('button', { name: 'Delete' }).click()
- )
- cy.findByRole('button', { name: 'Confirm' }).click()
- cy.findByRole('link', { name: deletedProjectName }).should('not.exist')
-
- cy.log('login as an admin and navigate to the deleted project')
- login(admin)
- cy.visit('/admin/user')
- cy.findByPlaceholderText('Search users by email or id…').type(
- user1 + '{enter}'
- )
- cy.findByRole('link', { name: user1 }).click()
- cy.findByRole('tab', { name: 'Deleted Projects' }).click()
- cy.findByRole('link', { name: deletedProjectName }).click()
-
- cy.log('undelete the project')
- cy.findByRole('button', { name: 'Undelete' }).click()
- cy.findByRole('button', { name: 'Undelete' }).should('not.exist')
- cy.url().should('contain', `/admin/project/${projectToDeleteId}`)
-
- cy.log('login as the user and verify the project is restored')
- login(user1)
- cy.visit('/project')
- cy.findByRole('navigation', {
- name: 'Project categories and tags',
- })
- .findByRole('button', { name: 'Trashed projects' })
- .click()
- cy.findByRole('link', { name: `${deletedProjectName} (Restored)` })
- })
- })
-})
diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts
deleted file mode 100644
index ad2b1ba382..0000000000
--- a/server-ce/test/create-and-compile-project.spec.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import { ensureUserExists, login } from './helpers/login'
-import {
- createProject,
- createProjectAndOpenInNewEditor,
- openProjectViaInviteNotification,
-} from './helpers/project'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-
-const USER = 'user@example.com'
-const COLLABORATOR = 'collaborator@example.com'
-describe('Project creation and compilation', function () {
- if (isExcludedBySharding('CE_DEFAULT')) return
- startWith({})
- ensureUserExists({ email: USER })
- ensureUserExists({ email: COLLABORATOR })
-
- it('users can create project and compile it', function () {
- login(USER)
- const { recompile, waitForCompile, waitForCompileRateLimitCoolOff } =
- prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('test-project')
- })
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
- })
- waitForCompileRateLimitCoolOff()
- recompile()
- cy.findByRole('region', { name: 'PDF preview' }).within(() => {
- cy.findByLabelText(/Page.*1/i).should('be.visible')
- cy.findByText('Test Section').should('be.visible')
- })
- })
-
- it('create and edit markdown file', function () {
- const fileName = `test-${Date.now()}.md`
- const markdownContent = '# Markdown title'
- login(USER)
- createProjectAndOpenInNewEditor('test-project')
-
- cy.findByRole('button', { name: 'New file' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByLabelText('File Name').as('fileName').clear()
- cy.get('@fileName').type(fileName)
- cy.findByRole('button', { name: 'Create' }).click()
- })
- cy.findByRole('treeitem', { name: fileName }).click()
- // wait until we've switched to the newly created empty file
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'have.length',
- 1
- )
- cy.findByRole('textbox', { name: 'Source Editor editing' }).type(
- markdownContent
- )
- cy.findByRole('treeitem', { name: 'main.tex' }).click()
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\maketitle'
- )
- cy.findByRole('treeitem', { name: fileName }).click()
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- markdownContent
- )
- })
-
- it('can link and display linked image from other project', function () {
- const sourceProjectName = `test-project-${Date.now()}`
- const targetProjectName = `${sourceProjectName}-target`
- login(USER)
-
- createProject(sourceProjectName, {
- type: 'Example project',
- open: false,
- newEditor: true,
- }).as('sourceProjectId')
- createProjectAndOpenInNewEditor(targetProjectName)
-
- // link the image from `projectName` into this project
- cy.findByRole('button', { name: 'New file' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'From another project' }).click()
- cy.findByLabelText('Select a Project').select(sourceProjectName)
- cy.findByLabelText('Select a File').select('frog.jpg')
- cy.findByRole('button', { name: 'Create' }).click()
- })
- cy.findByRole('treeitem', { name: 'frog.jpg' }).click()
- cy.findByRole('link', { name: 'Another project' })
- .should('have.attr', 'href')
- .then(href => {
- cy.get('@sourceProjectId').then(sourceProjectId => {
- expect(href).to.equal(`/project/${sourceProjectId}`)
- })
- })
- })
-
- it('can refresh linked files as collaborator', function () {
- const sourceProjectName = `test-project-${Date.now()}`
- const targetProjectName = `${sourceProjectName}-target`
- login(USER)
- createProject(sourceProjectName, {
- type: 'Example project',
- open: false,
- newEditor: true,
- }).as('sourceProjectId')
- createProjectAndOpenInNewEditor(targetProjectName).as('targetProjectId')
-
- // link the image from `projectName` into this project
- cy.findByRole('button', { name: 'New file' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'From another project' }).click()
- cy.findByLabelText('Select a Project').select(sourceProjectName)
- cy.findByLabelText('Select a File').select('frog.jpg')
- cy.findByRole('button', { name: 'Create' }).click()
- })
-
- cy.findByRole('navigation', { name: 'Project actions' }).within(() => {
- cy.findByRole('button', { name: 'Share' }).click()
- })
- cy.findByRole('dialog').within(() => {
- cy.findByTestId('collaborator-email-input').type(COLLABORATOR + ',')
- cy.findByRole('button', { name: 'Invite' }).click()
- cy.findByText('Invite not yet accepted.')
- })
-
- login(COLLABORATOR)
- openProjectViaInviteNotification(targetProjectName, true)
- cy.get('@targetProjectId').then(targetProjectId => {
- cy.url().should('include', targetProjectId)
- })
-
- cy.findByRole('treeitem', { name: 'frog.jpg' }).click()
- cy.findByRole('link', { name: 'Another project' })
- .should('have.attr', 'href')
- .then(href => {
- cy.get('@sourceProjectId').then(sourceProjectId => {
- expect(href).to.equal(`/project/${sourceProjectId}`)
- })
- })
- })
-})
diff --git a/server-ce/test/customization.spec.ts b/server-ce/test/customization.spec.ts
deleted file mode 100644
index af8503c680..0000000000
--- a/server-ce/test/customization.spec.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { isExcludedBySharding, startWith } from './helpers/config'
-
-describe('Customization', function () {
- if (isExcludedBySharding('CE_CUSTOM_1')) return
-
- describe('default settings', function () {
- startWith({})
-
- it('should display the default right footer', function () {
- cy.visit('/')
- cy.findByRole('contentinfo').findByRole('link', {
- name: 'Fork on GitHub!',
- })
- })
- })
-
- describe('custom settings', function () {
- startWith({
- vars: {
- OVERLEAF_APP_NAME: 'CUSTOM APP NAME',
- OVERLEAF_LEFT_FOOTER: JSON.stringify([{ text: 'CUSTOM LEFT FOOTER' }]),
- OVERLEAF_RIGHT_FOOTER: JSON.stringify([
- { text: 'CUSTOM RIGHT FOOTER' },
- ]),
- },
- })
-
- it('should display custom name', function () {
- cy.visit('/')
- cy.findByRole('navigation', { name: 'Primary' }).findByText(
- 'CUSTOM APP NAME'
- )
- })
-
- it('should display custom left footer', function () {
- cy.visit('/')
- cy.findByRole('contentinfo').findByText('CUSTOM LEFT FOOTER')
- })
- it('should display custom right footer', function () {
- cy.visit('/')
- cy.findByRole('contentinfo').findByText('CUSTOM RIGHT FOOTER')
- })
- })
-})
diff --git a/server-ce/test/cypress.config.ts b/server-ce/test/cypress.config.ts
deleted file mode 100644
index e04e8a5384..0000000000
--- a/server-ce/test/cypress.config.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { defineConfig } from 'cypress'
-import { readFileInZip, readPdf } from './helpers/read-file'
-import fs from 'node:fs'
-
-if (process.env.CYPRESS_SHARD && !process.env.SPEC_PATTERN) {
- // Running Cypress on all the specs is wasteful (~1min) when only few of them
- // will have relevant tasks to run for a given shard. Filter the spec files
- // based on the existence of the shard identifier in the spec source.
- const files = []
- for (const name of fs.readdirSync('.')) {
- if (!name.endsWith('.spec.ts')) continue
- const src = fs.readFileSync(name, 'utf-8')
- if (!src.includes('isExcludedBySharding(')) {
- throw new Error(
- `Spec ${name} is not using sharding. Add an appropriate "'if (isExcludedBySharding('...')) return" call.`
- )
- }
- if (!src.includes(process.env.CYPRESS_SHARD)) continue
- files.push(name)
- }
- if (files.length === 0) {
- throw new Error(
- `Bad process.env.CYPRESS_SHARD=${process.env.CYPRESS_SHARD}; no spec files matched!`
- )
- }
- if (files.length === 1) {
- // Cypress does not like `{single-file}`. Make this a special case.
- process.env.SPEC_PATTERN = `./${files[0]}`
- } else {
- process.env.SPEC_PATTERN = `./{${files.join(',')}}`
- }
-}
-
-const specPattern = process.env.SPEC_PATTERN || './**/*.spec.ts'
-
-let reporterOptions = {}
-if (process.env.CI) {
- reporterOptions = {
- reporter: `${process.env.MONOREPO}/node_modules/cypress-multi-reporters`,
- reporterOptions: {
- configFile: 'cypress/cypress-multi-reporters.cjs',
- },
- }
-}
-
-export default defineConfig({
- defaultCommandTimeout: 10_000,
- fixturesFolder: 'cypress/fixtures',
- video: process.env.CYPRESS_VIDEO === 'true',
- screenshotsFolder: 'cypress/results',
- videosFolder: 'cypress/results',
- viewportHeight: 768,
- viewportWidth: 1024,
- e2e: {
- setupNodeEvents(on) {
- on('task', {
- readPdf,
- readFileInZip,
- })
- },
- specPattern,
- },
- retries: {
- runMode: 3,
- },
- ...reporterOptions,
-})
diff --git a/server-ce/test/cypress/.gitignore b/server-ce/test/cypress/.gitignore
deleted file mode 100644
index 5b36de24c2..0000000000
--- a/server-ce/test/cypress/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-downloads/
-results/
-data/
diff --git a/server-ce/test/cypress/cypress-multi-reporters.cjs b/server-ce/test/cypress/cypress-multi-reporters.cjs
deleted file mode 100644
index 869ddd1547..0000000000
--- a/server-ce/test/cypress/cypress-multi-reporters.cjs
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = {
- reporterEnabled: 'spec, mocha-junit-reporter',
- mochaJunitReporterReporterOptions: {
- mochaFile: `reports/junit-${process.env.CYPRESS_SHARD}-[suiteFilename].xml`,
- includePending: true,
- jenkinsMode: true,
- jenkinsClassnamePrefix: 'Server Pro E2E tests',
- rootSuiteTitle: 'Server Pro E2E tests',
- },
-}
diff --git a/server-ce/test/cypress/support/e2e.ts b/server-ce/test/cypress/support/e2e.ts
deleted file mode 100644
index f8f1eac89c..0000000000
--- a/server-ce/test/cypress/support/e2e.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import '@testing-library/cypress/add-commands'
-
-Cypress.on('uncaught:exception', (err, runnable) => {
- if (err.message.includes('ResizeObserver')) {
- // spurious error from PDF preview
- return false
- }
- if (err.message.includes('rcube_webmail')) {
- // spurious error from mailtrap
- return false
- }
-})
diff --git a/server-ce/test/docker-compose.native.yml b/server-ce/test/docker-compose.native.yml
deleted file mode 100644
index 879dc37da7..0000000000
--- a/server-ce/test/docker-compose.native.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-services:
- sharelatex:
- ports:
- - "127.0.0.1:3231:80"
- environment:
- OVERLEAF_SITE_URL: "http://127.0.0.1:3231"
-
- host-admin:
- ports:
- - "127.0.0.1:3232:80"
- environment:
- NATIVE_CYPRESS: "true"
- ACCESS_CONTROL_ALLOW_ORIGIN: "http://127.0.0.1:3231"
-
- saml:
- ports:
- - "127.0.0.1:3233:80"
- environment:
- SAML_BASE_URL_PATH: "http://127.0.0.1:3233/simplesaml/"
- SAML_TEST_SP_LOCATION: "http://127.0.0.1:3231/saml/callback"
-
- mailtrap:
- ports:
- - "127.0.0.1:3234:80"
-
- # For testing "native" on Linux
- cypress:
- build:
- context: ../../dockerfiles/cypress
- args:
- - USER_UID
- - USER_GID
- volumes:
- - ../../:/overleaf
- - /tmp/.X11-unix:/tmp/.X11-unix
- - ${XAUTHORITY:-/dev/null}:/home/node/.Xauthority
- working_dir: /overleaf/server-ce/test
- entrypoint: npm
- command: run cypress:open
- environment:
- CI:
- VERBOSE_LOGGING:
- DISPLAY: ${DISPLAY:-:0}
- CYPRESS_BASE_URL:
- CYPRESS_HOST_ADMIN_URL:
- CYPRESS_SAML_URL:
- CYPRESS_MAILTRAP_URL:
- network_mode: host
diff --git a/server-ce/test/docker-compose.yml b/server-ce/test/docker-compose.yml
deleted file mode 100644
index 26da756b2b..0000000000
--- a/server-ce/test/docker-compose.yml
+++ /dev/null
@@ -1,158 +0,0 @@
-services:
- sharelatex:
- image: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest}
- stop_grace_period: 0s
- depends_on:
- mongo:
- condition: service_healthy
- redis:
- condition: service_started
- mailtrap:
- condition: service_started
- environment:
- OVERLEAF_SITE_URL: "http://sharelatex"
- OVERLEAF_APP_NAME: E2E test
- OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
- OVERLEAF_REDIS_HOST: redis
- REDIS_HOST: redis
- OVERLEAF_EMAIL_FROM_ADDRESS: "welcome@example.com"
- OVERLEAF_EMAIL_SMTP_HOST: "mailtrap"
- OVERLEAF_EMAIL_SMTP_PORT: "25"
- OVERLEAF_EMAIL_SMTP_IGNORE_TLS: "true"
- ENABLED_LINKED_FILE_TYPES: "project_file,project_output_file"
- ENABLE_CONVERSIONS: "true"
- EMAIL_CONFIRMATION_DISABLED: "true"
- OVERLEAF_INVITE_TOKEN_SECRET: "invite-token-secret"
- healthcheck:
- test: curl --fail http://localhost:3000/status
- interval: 3s
- timeout: 3s
- retries: 30
-
- mailtrap:
- image: mailtrap
- environment:
- MAILTRAP_PASSWORD: "password-for-mailtrap"
-
- mongo:
- image: mongo:8.0.11
- command: "--replSet overleaf"
- volumes:
- - ../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the replica set.
- # This override is not needed when running the setup after starting up mongo.
- - mongo:127.0.0.1
- healthcheck:
- test: echo 'db.stats().ok' | mongosh localhost:27017/test --quiet
- interval: 3s
- timeout: 3s
- retries: 30
-
- redis:
- image: redis:7.2.1
-
- git-bridge:
- image: quay.io/sharelatex/git-bridge:latest
- environment:
- GIT_BRIDGE_API_BASE_URL: "http://sharelatex:3000/api/v0/" # "http://sharelatex/api/v0/" for version 4.1.6 and earlier
- GIT_BRIDGE_OAUTH2_SERVER: "http://sharelatex"
- GIT_BRIDGE_POSTBACK_BASE_URL: "http://git-bridge:8000"
- GIT_BRIDGE_ROOT_DIR: "/data/git-bridge"
- user: root
- command: ["/server-pro-start.sh"]
-
- e2e:
- build:
- context: ../../dockerfiles/cypress
- args:
- - USER_UID
- - USER_GID
- stop_grace_period: 0s
- entrypoint: yarn
- command: run cypress:run
- # See comment in Makefile regarding matching file paths
- working_dir: $PWD
- volumes:
- - $PWD:$PWD
- - $MONOREPO/libraries:$MONOREPO/libraries
- - $MONOREPO/node_modules:$MONOREPO/node_modules
- - $MONOREPO/.yarn:$MONOREPO/.yarn
- - $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml
- - $MONOREPO/package.json:$MONOREPO/package.json
- - $MONOREPO/yarn.lock:$MONOREPO/yarn.lock
- environment:
- MONOREPO:
- CYPRESS_SHARD:
- CYPRESS_BASE_URL: http://sharelatex
- CYPRESS_FULL_FILESTORE_MIGRATION:
- depends_on:
- sharelatex:
- condition: service_healthy
- host-admin:
- condition: service_healthy
-
- e2e-open:
- extends:
- service: e2e
- command: run cypress:open
- volumes:
- - /tmp/.X11-unix:/tmp/.X11-unix
- - ${XAUTHORITY:-/dev/null}:/home/node/.Xauthority
- environment:
- DISPLAY: ${DISPLAY:-:0}
-
- host-admin:
- build: .
- entrypoint: ["node", "--watch", "host-admin.js"]
- # See comment in Makefile regarding matching file paths
- working_dir: $PWD
- volumes:
- - $PWD:$PWD
- - $MONOREPO/libraries:$MONOREPO/libraries:ro
- - $MONOREPO/node_modules:$MONOREPO/node_modules:ro
- - $MONOREPO/.yarn:$MONOREPO/.yarn:ro
- - $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml:ro
- - $MONOREPO/package.json:$MONOREPO/package.json:ro
- - $MONOREPO/yarn.lock:$MONOREPO/yarn.lock:ro
- - /var/run/docker.sock:/var/run/docker.sock
- stop_grace_period: 0s
- environment:
- CI:
- PWD:
- MONOREPO:
- CYPRESS_SHARD:
- COMPOSE_PROJECT_NAME:
- TEX_LIVE_DOCKER_IMAGE:
- ALL_TEX_LIVE_DOCKER_IMAGES:
- IMAGE_TAG_CE:
- IMAGE_TAG_PRO:
- IMAGE_TAG_GIT_BRIDGE:
- healthcheck:
- test: curl --fail http://localhost/status
- interval: 3s
- timeout: 3s
- retries: 30
-
- saml:
- restart: always
- image: us-east1-docker.pkg.dev/overleaf-ops/ol-docker/saml-test
- environment:
- SAML_TEST_SP_ENTITY_ID: "sharelatex-test-saml"
- SAML_BASE_URL_PATH: "http://saml/simplesaml/"
- SAML_TEST_SP_LOCATION: "http://sharelatex/saml/callback"
-
- ldap:
- restart: always
- image: rroemhild/test-openldap:1.1
- # Workaround large memory allocation (using the max-open-files-limit as socket buffer scale).
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63877553
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63871343
- command:
- - "bash"
- - "-c"
- - "ulimit -n 1024 && exec bash /run.sh"
diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts
deleted file mode 100644
index 79013bdc87..0000000000
--- a/server-ce/test/editor.spec.ts
+++ /dev/null
@@ -1,451 +0,0 @@
-import {
- createNewFile,
- createProjectAndOpenInNewEditor,
- openProjectById,
- openProjectByName,
- shareProjectByEmailAndAcceptInviteViaDash,
- testNewFileUpload,
-} from './helpers/project'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { ensureUserExists, login } from './helpers/login'
-import { v4 as uuid } from 'uuid'
-import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-
-const USER = 'user@example.com'
-const COLLABORATOR = 'collaborator@example.com'
-
-describe('editor', function () {
- if (isExcludedBySharding('PRO_DEFAULT_1')) return
- startWith({ pro: true })
- ensureUserExists({ email: USER })
- ensureUserExists({ email: COLLABORATOR })
-
- let projectName: string
- let projectId: string
- let recompile: () => void
- let waitForCompile: (fn: () => void) => void
- beforeWithReRunOnTestRetry(() => {
- projectName = `project-${uuid()}`
- login(USER)
- createProjectAndOpenInNewEditor(projectName, {
- type: 'Example project',
- }).then(id => (projectId = id))
- ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
- })
-
- beforeEach(function () {
- login(USER)
- waitForCompile(() => {
- openProjectById(projectId, true)
- })
- })
-
- describe('spelling', function () {
- function changeSpellCheckLanguageTo(lng: string) {
- cy.log(`change project language to '${lng}'`)
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('tab', { name: 'Spelling and language' }).click()
- cy.findByLabelText('Spellcheck language').select(lng)
- })
- cy.get('body').type('{esc}')
- }
-
- afterEach(function () {
- changeSpellCheckLanguageTo('Off')
- })
-
- it('word dictionary and spelling', function () {
- changeSpellCheckLanguageTo('English (American)')
- createNewFile()
- const word = createRandomLetterString()
-
- cy.log('edit project file')
- cy.get('.cm-line').type(word)
- cy.findByText(word).should('have.class', 'ol-cm-spelling-error')
-
- changeSpellCheckLanguageTo('Off')
- cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error')
-
- changeSpellCheckLanguageTo('Spanish')
- cy.findByText(word).should('have.class', 'ol-cm-spelling-error')
-
- cy.log('add word to dictionary')
- cy.findByText(word).rightclick()
- cy.findByRole('menuitem', { name: 'Add to dictionary' }).click()
- cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error')
-
- cy.log('remove word from dictionary')
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('tab', { name: 'Spelling and language' }).click()
- cy.findByLabelText('Dictionary').click()
- })
- cy.findByTestId('dictionary-modal').within(() => {
- cy.findByText(word)
- .parent()
- .within(() =>
- cy.findByRole('button', { name: 'Remove from dictionary' }).click()
- )
-
- cy.findByRole('button', { name: 'Close dialog' }).click()
- })
-
- cy.log('close modal')
- cy.get('body').type('{esc}')
-
- cy.log('rewrite word to force spelling error')
- cy.get('.cm-line').type('{selectAll}{del}' + word + '{enter}')
-
- cy.get('.ol-cm-spelling-error').should('contain.text', word)
- })
- })
-
- describe('editor', function () {
- it('renders jpg', function () {
- cy.findByRole('treeitem', { name: 'frog.jpg' }).click({ force: true })
-
- cy.get('[alt="frog.jpg"]')
- .should('be.visible')
- .and('have.prop', 'naturalWidth')
- .should('be.greaterThan', 0)
- })
-
- it('symbol palette', function () {
- createNewFile()
-
- cy.get('button[aria-label="Insert symbol"]').click({
- force: true,
- })
- cy.get('button').contains('𝜉').click()
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\xi'
- )
-
- cy.log('recompile to force flush and avoid "unsaved changes" prompt')
- recompile()
- })
- })
-
- describe('add new file to project', function () {
- beforeEach(function () {
- cy.findByRole('button', { name: 'New file' }).click()
- })
-
- testNewFileUpload()
-
- it('should not display import from URL', function () {
- cy.findByRole('button', { name: 'From external URL' }).should('not.exist')
- })
- })
-
- describe('file menu', function () {
- it('can download project sources', function () {
- cy.findByRole('button', { name: 'File' }).click()
- cy.findByRole('menuitem', { name: 'Download' }).click()
- cy.findByRole('menuitem', { name: 'Download as source (.zip)' }).click()
- const zipName = projectName.replaceAll('-', '_')
- cy.task('readFileInZip', {
- pathToZip: `cypress/downloads/${zipName}.zip`,
- fileToRead: 'main.tex',
- }).should('contain', 'Your introduction goes here')
- })
-
- it('can download project PDF', function () {
- cy.log('ensure project is compiled')
- cy.findByRole('region', { name: 'PDF preview' }).should(
- 'contain.text',
- 'Your Paper'
- )
-
- cy.findByRole('button', { name: 'File' }).click()
- cy.findByRole('menuitem', { name: 'Download' }).click()
- cy.findByRole('menuitem', { name: 'Download as PDF' }).click()
- const pdfName = projectName.replaceAll('-', '_')
- cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
- 'contain',
- 'Your introduction goes here'
- )
- })
-
- it('word count', function () {
- cy.log('ensure project is compiled')
- cy.findByRole('region', { name: 'PDF preview' }).should(
- 'contain.text',
- 'Your Paper'
- )
-
- cy.findByRole('button', { name: 'File' }).click()
- cy.findByRole('menuitem', { name: 'Word count' }).click()
-
- cy.findByTestId('word-count-modal').within(() => {
- cy.findByText('Total Words:')
- cy.findByText('607')
- cy.findByText('Headers:')
- cy.findByText('14')
- cy.findByText('Math Inline:')
- cy.findByText('6')
- cy.findByText('Math Display:')
- cy.findByText('1')
- })
- })
- })
-
- describe('cite key search', function () {
- it('can insert citation from cite key', function () {
- createNewFile()
- cy.get('.cm-line').type('\\cite{{}gre')
- cy.findByRole('listbox').within(() => {
- cy.findByRole('option').should('contain.text', 'greenwade93').click()
- })
- cy.get('.cm-line').should('have.text', '\\cite{greenwade93}')
- })
-
- it('updates citation search when bib file is changed', function () {
- createNewFile()
- cy.get('.cm-line').type('\\cite{{}new')
- // Wait a reasonable time to ensure the autocomplete would've appeared if there were any matches
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.wait(200)
- cy.findByRole('listbox').should('not.exist')
- cy.findByRole('treeitem', { name: 'sample.bib' }).click()
- cy.get('.cm-line')
- .last()
- .type(
- '\n@article{{}newkey2024,\n author = {{}Doe, John},\n title = {{}A New Article},\n journal = {{}Journal of Testing},\n year = 2024\n}\n'
- )
- createNewFile()
- cy.get('.cm-line').type('\\cite{{}new')
- cy.findByRole('listbox').within(() => {
- cy.findByRole('option').should('contain.text', 'newkey2024').click()
- })
- cy.get('.cm-line').should('have.text', '\\cite{newkey2024}')
- })
- })
-
- describe('layout selector', function () {
- it('show editor only and switch between editor and pdf', function () {
- cy.findByRole('region', { name: 'PDF preview' }).should('be.visible')
- cy.get('.cm-editor').should('be.visible')
-
- cy.findByRole('button', { name: 'Layout options' }).click()
- cy.findByRole('menu').within(() => {
- cy.findByRole('menuitem', { name: /Editor only/ }).click()
- })
-
- cy.findByRole('region', { name: 'PDF preview' }).should('not.be.visible')
- cy.get('.cm-editor').should('be.visible')
-
- // force click as tooltip may cover button for some reason
- cy.findByRole('button', { name: 'Switch to PDF' }).click({ force: true })
-
- cy.findByRole('region', { name: 'PDF preview' }).should('be.visible')
- cy.get('.cm-editor').should('not.be.visible')
-
- cy.findByRole('button', { name: 'Switch to editor' }).click()
-
- cy.findByRole('region', { name: 'PDF preview' }).should('not.be.visible')
- cy.get('.cm-editor').should('be.visible')
- })
-
- it('show PDF only and go back to split view', function () {
- cy.findByRole('region', { name: 'PDF preview' }).should('be.visible')
- cy.get('.cm-editor').should('be.visible')
-
- cy.findByRole('button', { name: 'Layout options' }).click()
- cy.findByRole('menu').within(() => {
- cy.findByRole('menuitem', { name: /PDF only/ }).click()
- })
-
- cy.findByRole('region', { name: 'PDF preview' }).should('be.visible')
- cy.get('.cm-editor').should('not.be.visible')
-
- cy.findByRole('button', { name: 'Layout options' }).click()
- cy.findByRole('menu').within(() => {
- cy.findByRole('menuitem', { name: 'Split view' }).click()
- })
-
- cy.findByRole('region', { name: 'PDF preview' }).should('be.visible')
- cy.get('.cm-editor').should('be.visible')
- })
-
- it('PDF in a separate tab (tests editor only)', function () {
- cy.findByTestId('pdf-viewer').should('be.visible')
- cy.get('.cm-editor').should('be.visible')
-
- cy.findByRole('button', { name: 'Layout options' }).click()
- cy.findByRole('menu').within(() => {
- cy.findByRole('menuitem', { name: 'Open PDF in separate tab' }).click()
- })
-
- cy.findByTestId('pdf-viewer').should('not.exist')
- cy.get('.cm-editor').should('be.visible')
- })
- })
-
- describe('full project search', function () {
- it('can search for text in project files', function () {
- cy.findByRole('tab', { name: 'Project search' }).click()
-
- cy.findByRole('searchbox', { name: 'Search' })
- .should('be.visible')
- .type('Some examples to get started')
- cy.get('button').contains('Search').click()
-
- cy.findByRole('listbox').within(() => {
- cy.findByRole('option', {
- name: /Some examples to get started/,
- }).should('be.visible')
- })
- })
- })
-
- describe('theming', function () {
- it('can change overall theme in settings menu', function () {
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('tab', { name: 'Appearance' }).click()
- cy.findByLabelText('Overall theme').select('Dark')
- })
- cy.get('body').should('have.attr', 'data-theme', 'default')
- cy.get('body').type('{esc}')
-
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('tab', { name: 'Appearance' }).click()
- cy.findByLabelText('Overall theme').select('Light')
- })
- cy.get('body').should('have.attr', 'data-theme', 'light')
- cy.get('body').type('{esc}')
- })
- })
-})
-
-describe('editing mode', function () {
- if (isExcludedBySharding('PRO_DEFAULT_1')) return
- startWith({ pro: true })
-
- const OWNER = USER
- const EDITOR = COLLABORATOR
- const REVIEWER = 'reviewer@example.com'
- const VIEWER = 'viewer@example.com'
- ensureUserExists({ email: OWNER })
- ensureUserExists({ email: EDITOR })
- ensureUserExists({ email: REVIEWER })
- ensureUserExists({ email: VIEWER })
-
- let projectName: string
-
- function openEditingModeMenu() {
- cy.findByRole('button', { name: 'View' }).click()
- cy.findByRole('menuitem', { name: 'Editing mode' }).click()
- }
-
- beforeWithReRunOnTestRetry(() => {
- projectName = `project-${uuid()}`
- login(OWNER)
- createProjectAndOpenInNewEditor(projectName, { type: 'Example project' })
-
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- EDITOR,
- 'Editor',
- true
- )
- login(OWNER)
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- REVIEWER,
- 'Reviewer',
- true
- )
- login(OWNER)
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- VIEWER,
- 'Viewer',
- true
- )
- })
-
- it('lets an editor switch between editing and reviewing', function () {
- login(EDITOR)
- openProjectByName(projectName, true)
-
- openEditingModeMenu()
- cy.findByRole('menuitem', { name: 'Editing' })
- .should('have.attr', 'aria-selected', 'true')
- .and('not.have.attr', 'aria-disabled')
- cy.findByRole('menuitem', { name: 'Reviewing' })
- .should('have.attr', 'aria-selected', 'false')
- .and('not.have.attr', 'aria-disabled')
- cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist')
-
- cy.findByRole('menuitem', { name: 'Reviewing' }).click()
- cy.findByRole('button', { name: 'Reviewing' }).should('exist')
-
- openEditingModeMenu()
- cy.findByRole('menuitem', { name: 'Reviewing' }).should(
- 'have.attr',
- 'aria-selected',
- 'true'
- )
- cy.findByRole('menuitem', { name: 'Editing' }).should(
- 'have.attr',
- 'aria-selected',
- 'false'
- )
-
- cy.findByRole('menuitem', { name: 'Editing' }).click()
- cy.findByRole('button', { name: 'Editing' }).should('exist')
- })
-
- it('shows a reviewer in reviewing mode with editing disabled', function () {
- login(REVIEWER)
- openProjectByName(projectName, true)
-
- openEditingModeMenu()
- cy.findByRole('menuitem', { name: 'Reviewing' })
- .should('have.attr', 'aria-selected', 'true')
- .and('not.have.attr', 'aria-disabled')
- cy.findByRole('menuitem', { name: 'Editing' }).should(
- 'have.attr',
- 'aria-disabled',
- 'true'
- )
- cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist')
- })
-
- it('shows a viewer in viewing mode with editing and reviewing disabled', function () {
- login(VIEWER)
- openProjectByName(projectName, true)
-
- openEditingModeMenu()
- cy.findByRole('menuitem', { name: 'Viewing' }).should(
- 'have.attr',
- 'aria-selected',
- 'true'
- )
- cy.findByRole('menuitem', { name: 'Editing' }).should(
- 'have.attr',
- 'aria-disabled',
- 'true'
- )
- cy.findByRole('menuitem', { name: 'Reviewing' }).should(
- 'have.attr',
- 'aria-disabled',
- 'true'
- )
- })
-})
-
-function createRandomLetterString() {
- const chars = 'abcdefghijklmnopqrstuvwxyz'
- let result = ''
- for (let i = 0; i < 12; i++) {
- result += chars.charAt(Math.floor(Math.random() * chars.length))
- }
- return result
-}
diff --git a/server-ce/test/external-auth.spec.ts b/server-ce/test/external-auth.spec.ts
deleted file mode 100644
index 8c287f2543..0000000000
--- a/server-ce/test/external-auth.spec.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { createProject } from './helpers/project'
-
-describe('SAML', function () {
- if (isExcludedBySharding('PRO_CUSTOM_1')) return
- const samlURL = Cypress.env('SAML_URL') || 'http://saml'
-
- startWith({
- pro: true,
- vars: {
- EXTERNAL_AUTH: 'saml',
- OVERLEAF_SAML_ENTRYPOINT: `${samlURL}/simplesaml/saml2/idp/SSOService.php`,
- OVERLEAF_SAML_CALLBACK_URL: `${Cypress.config().baseUrl}/saml/callback`,
- OVERLEAF_SAML_ISSUER: 'sharelatex-test-saml',
- OVERLEAF_SAML_IDENTITY_SERVICE_NAME: 'SAML Test Server',
- OVERLEAF_SAML_EMAIL_FIELD: 'email',
- OVERLEAF_SAML_FIRST_NAME_FIELD: 'givenName',
- OVERLEAF_SAML_LAST_NAME_FIELD: 'sn',
- OVERLEAF_SAML_UPDATE_USER_DETAILS_ON_LOGIN: 'true',
- OVERLEAF_SAML_CERT:
- 'MIIDXTCCAkWgAwIBAgIJAOvOeQ4xFTzsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMTE1MTQxMjU5WhcNMjYxMTE1MTQxMjU5WjBFMQswCQYDVQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCT6MBe5G9VoLU8MfztOEbUhnwLp17ak8eFUqxqeXkkqtWB0b/cmIBU3xoQoO3dIF8PBzfqehqfYVhrNt/TFgcmDfmJnPJRL1RJWMW3VmiP5odJ3LwlkKbZpkeT3wZ8HEJIR1+zbpxiBNkbd2GbdR1iumcsHzMYX1A2CBj+ZMV5VijC+K4P0e9c05VsDEUtLmfeAasJAiumQoVVgAe/BpiXjICGGewa6EPFI7mKkifIRKOGxdRESwZZjxP30bI31oDN0cgKqIgSJtJ9nfCn9jgBMBkQHu42WMuaWD4jrGd7+vYdX+oIfArs9aKgAH5kUGhGdew2R9SpBefrhbNxG8QIDAQABo1AwTjAdBgNVHQ4EFgQU+aSojSyyLChP/IpZcafvSdhj7KkwHwYDVR0jBBgwFoAU+aSojSyyLChP/IpZcafvSdhj7KkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABl3+OOVLBWMKs6PjA8lPuloWDNzSr3v76oUcHqAb+cfbucjXrOVsS9RJ0X9yxvCQyfM9FfY43DbspnN3izYhdvbJD8kKLNf0LA5st+ZxLfy0ACyL2iyAwICaqndqxAjQYplFAHmpUiu1DiHckyBPekokDJd+ze95urHMOsaGS5RWPoKJVE0bkaAeZCmEu0NNpXRSBiuxXSTeSAJfv6kyE/rkdhzUKyUl/cGQFrsVYfAFQVA+W6CKOh74ErSEzSHQQYndl7nD33snD/YqdU1ROxV6aJzLKCg+sdj+wRXSP2u/UHnM4jW9TGJfhO42jzL6WVuEvr9q4l7zWzUQKKKhtQ==',
- },
- })
-
- it('login', function () {
- cy.visit('/')
- cy.findByRole('link', { name: 'Log in with SAML Test Server' }).click()
-
- cy.origin(samlURL, () => {
- cy.get('input[name="username"]').type('sally')
- cy.get('input[name="password"]').type('sally123')
- cy.get('button[type="submit"]').click()
- })
-
- cy.log('wait for login to finish')
- cy.url().should('contain', '/project')
-
- createProject('via SAML')
- })
-})
-
-describe('LDAP', function () {
- if (isExcludedBySharding('PRO_CUSTOM_1')) return
- startWith({
- pro: true,
- vars: {
- EXTERNAL_AUTH: 'ldap',
- OVERLEAF_LDAP_URL: 'ldap://ldap:389',
- OVERLEAF_LDAP_SEARCH_BASE: 'ou=people,dc=planetexpress,dc=com',
- OVERLEAF_LDAP_SEARCH_FILTER: '(uid={{username}})',
- OVERLEAF_LDAP_BIND_DN: 'cn=admin,dc=planetexpress,dc=com',
- OVERLEAF_LDAP_BIND_CREDENTIALS: 'GoodNewsEveryone',
- OVERLEAF_LDAP_EMAIL_ATT: 'mail',
- OVERLEAF_LDAP_NAME_ATT: 'cn',
- OVERLEAF_LDAP_LAST_NAME_ATT: 'sn',
- OVERLEAF_LDAP_UPDATE_USER_DETAILS_ON_LOGIN: 'true',
- },
- })
-
- it('login', function () {
- cy.visit('/')
- cy.findByRole('heading', { name: 'Log in LDAP' })
-
- cy.findByLabelText('Username').type('fry')
- cy.findByLabelText('Password').type('fry')
- cy.findByRole('button', { name: 'Login' }).click()
-
- cy.log('wait for login to finish')
- cy.url().should('contain', '/project')
-
- createProject('via LDAP')
- })
-})
diff --git a/server-ce/test/filestore-migration.spec.ts b/server-ce/test/filestore-migration.spec.ts
deleted file mode 100644
index 3693d4a362..0000000000
--- a/server-ce/test/filestore-migration.spec.ts
+++ /dev/null
@@ -1,433 +0,0 @@
-import { DEFAULT_PASSWORD, ensureUserExists, login } from './helpers/login'
-import {
- createProject,
- expectFileExists,
- openProjectById,
- prepareFileUploadTest,
-} from './helpers/project'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-import { v4 as uuid } from 'uuid'
-import {
- purgeFilestoreData,
- runGruntTask,
- runScript,
- setMongoFeatureCompatibilityVersion,
-} from './helpers/hostAdminClient'
-
-function activateUserVersion1x(url: string, password = DEFAULT_PASSWORD) {
- cy.session(url, () => {
- cy.visit(url)
- cy.url().then(url => {
- if (url.includes('/login')) return
- cy.url().should('contain', '/user/password/set')
- cy.get('input[type="password"]').type(password)
- cy.findByRole('button', { name: 'Set new password' }).click()
- })
- })
-}
-
-describe('filestore migration', function () {
- if (isExcludedBySharding('PRO_CUSTOM_5')) return
- const email = 'user@example.com'
- // Branding of env vars changed in 5.x
- const sharelatexBrandedVars = {
- SHARELATEX_SITE_URL: 'http://sharelatex',
- SHARELATEX_MONGO_URL: 'mongodb://mongo/sharelatex',
- SHARELATEX_REDIS_HOST: 'redis',
- }
- const projectName = `project-${uuid()}`
- let defaultImage: string
- let projectId: string
- let waitForCompile: (fn: () => void) => void
- const previousBinaryFiles: (() => void)[] = []
-
- function avoid502() {
- // The next step will likely restart the instance and any following
- // requests will fail with a 502/bad gateway. Avoid this by navigating
- // away from the editor, which will reload upon receiving a
- // 'forceDisconnect' socket.io message.
- cy.visit('/project')
- }
-
- function addNewBinaryFileAndCheckPrevious(
- universeSelector = `img[alt="${defaultImage}"]`
- ) {
- before(function () {
- login(email)
- waitForCompile(() => {
- cy.visit(`/project/${projectId}`)
- })
- previousBinaryFiles.push(prepareFileUploadTest(true))
- cy.log('check binary files')
- for (const check of previousBinaryFiles) {
- check()
- }
- cy.findByRole('treeitem', { name: defaultImage }).click()
- cy.get(universeSelector)
- .should('be.visible')
- .and('have.prop', 'naturalWidth')
- .should('be.greaterThan', 0)
-
- avoid502()
- })
- }
-
- if (Cypress.env('FULL_FILESTORE_MIGRATION')) {
- // --------------
- // Server Pro 1.x
- startWith({
- pro: true,
- resetData: true,
- withDataDir: true,
- vars: sharelatexBrandedVars,
- version: '1.2.4',
- mongoVersion: '5.0',
- })
- defaultImage = 'universe.jpg'
-
- let activateURL: string
- before(async function () {
- const { stdout } = await runGruntTask({
- task: 'user:create-admin',
- args: ['--email', email],
- })
- ;[activateURL] = stdout.match(
- /http:\/\/.+\/user\/password\/set\?passwordResetToken=\S+/
- )!
- })
- before(function () {
- activateUserVersion1x(activateURL)
- login(email)
- cy.visit('/project')
-
- // Legacy angular based UI uses links instead of buttons
- cy.findByRole('link', {
- name: /Create First Project|New Project/,
- }).click()
- cy.findByRole('link', { name: 'Example Project' }).click()
- cy.findByLabelText('Project name').type(projectName)
- cy.findByRole('button', { name: 'Create' }).click()
- cy.url()
- .should('match', /\/project\/[a-fA-F0-9]{24}/)
- .then(url => (projectId = url.split('/').pop()!))
- let queueReset
- ;({ waitForCompile, queueReset } = prepareWaitForNextCompileSlot())
- queueReset()
-
- // Create a new binary file
- cy.get(`a[tooltip="Upload"]`).click()
- const name = `${uuid()}.txt`
- // Binary file detection is not sophisticated in version 1.x
- const binName = name.replace('.txt', '.bin')
- const content = `Test File Content ${name} \x00`
- cy.get('input[type=file]')
- .first()
- .selectFile(
- {
- contents: Cypress.Buffer.from(content),
- fileName: binName,
- lastModified: Date.now(),
- },
- { force: true }
- )
- // Rename back to .txt to enable preview
- cy.findByText(binName).click()
- cy.findByText(binName).dblclick()
- cy.focused().type(name + '{del}'.repeat('.bin'.length) + '{enter}')
- // Switch back and forth
- cy.findByText('universe.jpg').click()
- cy.findByText(name).click()
- cy.findByText(content)
- .parent()
- .parent()
- .should('have.class', 'text-preview')
-
- previousBinaryFiles.push(() => expectFileExists(name, true, content))
- avoid502()
- })
-
- // --------------
- // Server Pro 2.x
- startWith({
- pro: true,
- withDataDir: true,
- vars: sharelatexBrandedVars,
- version: '2.7.1',
- mongoVersion: '5.0',
- })
- before(function () {
- // Cypress strips the Content-Length header: https://github.com/cypress-io/cypress/issues/16469
- // Server Pro 2.x does not gracefully handle a missing value.
- cy.intercept(
- {
- method: 'HEAD',
- url: `http://sharelatex/project/${projectId}/file/*`,
- times: previousBinaryFiles.length + 1,
- },
- req => {
- req.continue(res => {
- res.headers['Content-Length'] = '60'
- })
- }
- )
- })
- // Server Pro 2.x does not have alt tags on images.
- addNewBinaryFileAndCheckPrevious('img')
-
- // ----------------------------------
- // Server Pro 3.x + history migration
- startWith({
- pro: true,
- withDataDir: true,
- vars: sharelatexBrandedVars,
- version: '3.5.13',
- mongoVersion: '5.0',
- })
- addNewBinaryFileAndCheckPrevious() // before history migration
- before(async function () {
- await runScript({
- cwd: 'services/web',
- script: 'scripts/history/migrate_history.js',
- args: [
- '--force-clean',
- '--fix-invalid-characters',
- '--convert-large-docs-to-file',
- ],
- hasOverleafEnv: false,
- user: 'root',
- })
- })
- before(async function () {
- await runScript({
- cwd: 'services/web',
- script: 'scripts/history/clean_sl_history_data.js',
- hasOverleafEnv: false,
- })
- })
- addNewBinaryFileAndCheckPrevious() // after history migration
-
- // ------------------------------
- // Server Pro 4.x + mongo upgrade
- startWith({
- pro: true,
- withDataDir: true,
- vars: sharelatexBrandedVars,
- version: '4.2.9',
- mongoVersion: '5.0',
- })
- startWith({
- pro: true,
- withDataDir: true,
- vars: sharelatexBrandedVars,
- version: '4.2.9',
- mongoVersion: '6.0',
- })
- before(async function () {
- await setMongoFeatureCompatibilityVersion('6.0')
- })
- addNewBinaryFileAndCheckPrevious()
-
- // ------------------------------------------
- // Server Pro 5.x + mongo upgrade 6 -> 7 -> 8
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- mongoVersion: '6.0',
- })
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- mongoVersion: '7.0',
- })
- before(async function () {
- await setMongoFeatureCompatibilityVersion('7.0')
- })
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- // implicit mongo upgrade to 8.0
- })
- before(async function () {
- await setMongoFeatureCompatibilityVersion('8.0')
- })
- } else {
- // 5.x
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- resetData: true,
- })
- defaultImage = 'frog.jpg'
- ensureUserExists({ email })
- before(function () {
- login(email)
- createProject(projectName, { type: 'Example project', open: false }).then(
- id => (projectId = id)
- )
- ;({ waitForCompile } = prepareWaitForNextCompileSlot())
- })
- }
- addNewBinaryFileAndCheckPrevious()
-
- function ensureStopOnFirstErrorIsActive() {
- cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
- cy.findByRole('menuitem', {
- name: 'Stop on first error',
- }).then(el => {
- // NOTE: THIS IS BAD, but the selected option is otherwise not accessible :/
- if (
- el.get()[0]?.querySelector('.material-symbol')?.textContent !== 'check'
- ) {
- cy.findByRole('menuitem', {
- name: 'Stop on first error',
- }).click()
- // Clicking on "Stop on first error" closes the mode. Open it again.
- cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
- }
- })
- cy.findByRole('menuitem', {
- name: 'Stop on first error',
- }).within(() => {
- cy.findByText('check').should('be.visible')
- })
- cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
- }
-
- // -------------------
- // filestore-migration
- beforeEach(function () {
- login(email)
- waitForCompile(() => {
- openProjectById(projectId)
- })
- ensureStopOnFirstErrorIsActive()
- })
-
- function checkFilesAreAccessible() {
- it('can upload new binary file and read previous uploads', function () {
- previousBinaryFiles.push(prepareFileUploadTest(true))
- for (const check of previousBinaryFiles) {
- check()
- }
- })
-
- it('renders image of example project', function () {
- cy.findByTestId('file-tree').findByText(defaultImage).click()
- cy.get(`[alt="${defaultImage}"]`)
- .should('be.visible')
- .and('have.prop', 'naturalWidth')
- .should('be.greaterThan', 0)
- })
-
- it('can recompile from scratch', function () {
- const id = uuid()
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle')
- .parent()
- .type(`\n\\section{{}Test Section ${id}}`)
-
- waitForCompile(() => {
- cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
-
- cy.findByRole('menuitem', {
- name: 'Recompile from scratch',
- }).trigger('click')
- })
-
- cy.get('.pdf-viewer').should('contain.text', `Test Section ${id}`)
- })
- }
-
- describe('OVERLEAF_FILESTORE_MIGRATION_LEVEL not set', function () {
- startWith({ version: '5.5.5', pro: true, withDataDir: true, vars: {} })
- checkFilesAreAccessible()
- })
-
- describe('OVERLEAF_FILESTORE_MIGRATION_LEVEL=0', function () {
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- vars: { OVERLEAF_FILESTORE_MIGRATION_LEVEL: '0' },
- })
- checkFilesAreAccessible()
-
- describe('OVERLEAF_FILESTORE_MIGRATION_LEVEL=1', function () {
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- vars: { OVERLEAF_FILESTORE_MIGRATION_LEVEL: '1' },
- })
- checkFilesAreAccessible()
-
- describe('OVERLEAF_FILESTORE_MIGRATION_LEVEL=2', function () {
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- vars: { OVERLEAF_FILESTORE_MIGRATION_LEVEL: '1' },
- })
- before(async function () {
- await runScript({
- cwd: 'services/history-v1',
- script: 'storage/scripts/back_fill_file_hash.mjs',
- args: ['--all'],
- })
- })
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- vars: { OVERLEAF_FILESTORE_MIGRATION_LEVEL: '2' },
- })
- checkFilesAreAccessible()
-
- describe('purge filestore data', function () {
- before(async function () {
- const deleted = await purgeFilestoreData()
- expect(deleted).to.have.length.greaterThan(
- previousBinaryFiles.length
- )
- expect(deleted).to.include(
- "removed directory '/var/lib/overleaf/data/user_files'"
- )
- })
- checkFilesAreAccessible()
-
- describe('after next restart', function () {
- startWith({
- version: '5.5.5',
- pro: true,
- withDataDir: true,
- vars: {
- OVERLEAF_APP_NAME: 'change-config',
- OVERLEAF_FILESTORE_MIGRATION_LEVEL: '2',
- },
- })
- it('should not recreate the user_files folder', async function () {
- expect(await purgeFilestoreData()).to.deep.equal([])
- })
- })
-
- describe('latest', function () {
- startWith({
- pro: true,
- withDataDir: true,
- vars: { OVERLEAF_FILESTORE_MIGRATION_LEVEL: '2' },
- })
- it('should not recreate the user_files folder', async function () {
- expect(await purgeFilestoreData()).to.deep.equal([])
- })
- checkFilesAreAccessible()
- })
- })
- })
- })
- })
-})
diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts
deleted file mode 100644
index 7c8d8c47a2..0000000000
--- a/server-ce/test/git-bridge.spec.ts
+++ /dev/null
@@ -1,458 +0,0 @@
-import { v4 as uuid } from 'uuid'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { ensureUserExists, login } from './helpers/login'
-import {
- createProject,
- createProjectAndOpenInNewEditor,
- enableLinkSharing,
- openProjectByName,
- openProjectViaLinkSharingAsUser,
- shareProjectByEmailAndAcceptInviteViaDash,
-} from './helpers/project'
-
-import git from 'isomorphic-git'
-import http from 'isomorphic-git/http/web'
-import LightningFS from '@isomorphic-git/lightning-fs'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-
-const USER = 'user@example.com'
-
-describe('git-bridge', function () {
- const ENABLED_VARS = {
- GIT_BRIDGE_ENABLED: 'true',
- GIT_BRIDGE_HOST: 'git-bridge',
- GIT_BRIDGE_PORT: '8000',
- V1_HISTORY_URL: 'http://sharelatex:3100/api',
- }
-
- function gitURL(projectId: string) {
- const url = new URL(Cypress.config().baseUrl!)
- url.username = 'git'
- url.pathname = `/git/${projectId}`
- return url
- }
-
- describe('enabled in Server Pro', function () {
- if (isExcludedBySharding('PRO_CUSTOM_1')) return
- startWith({
- pro: true,
- vars: ENABLED_VARS,
- })
- ensureUserExists({ email: USER })
-
- function clearAllTokens() {
- cy.findAllByRole('button', { name: 'Remove' })
- .not('[disabled]')
- .each($button => {
- cy.wrap($button).click()
- cy.findByRole('button', { name: 'Delete token' }).click()
- })
- cy.findByRole('dialog').should('not.exist')
- }
-
- function maybeClearAllTokens() {
- cy.visit('/user/settings')
- cy.findByRole('heading', { name: 'Git integration' })
- cy.findByRole('button', {
- name: /Generate token|Add another token/i,
- }).then(btn => {
- if (btn.text() === 'Add another token') {
- clearAllTokens()
- }
- })
- }
-
- beforeEach(function () {
- login(USER)
- })
-
- it('should render the git-bridge UI in the settings', function () {
- maybeClearAllTokens()
- cy.visit('/user/settings')
- cy.findByRole('heading', { name: 'Git integration' })
- cy.findByRole('button', {
- name: 'Git integration Generate token',
- }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByLabelText('Git authentication token')
- .contains(/olp_[a-zA-Z0-9]{16}/)
- .then(el => el.text())
- .as('newToken')
- cy.findByRole('button', { name: 'Close dialog' }).click()
- })
- cy.get('@newToken').then(token => {
- // There can be more than one token with the same prefix when retrying
- cy.findAllByText(
- `${token.slice(0, 'olp_1234'.length)}${'*'.repeat(12)}`
- ).should('have.length.at.least', 1)
- })
- cy.findByRole('button', {
- name: 'Git integration Generate token',
- }).should('not.exist')
- cy.findByRole('button', { name: 'Add another token' }).should('exist')
- clearAllTokens()
- cy.findByRole('button', {
- name: 'Git integration Generate token',
- }).should('exist')
- cy.findByRole('button', { name: 'Add another token' }).should('not.exist')
- })
-
- it('should render the git-bridge UI in the editor', function () {
- maybeClearAllTokens()
- createProjectAndOpenInNewEditor('git').as('projectId')
- cy.findByRole('tab', { name: 'Integrations' }).click()
- cy.findByText('Git clone this project.').click()
-
- cy.findByTestId('git-bridge-modal').within(() => {
- cy.get('@projectId').then(id => {
- cy.findByLabelText('Git clone project command').contains(
- `git clone ${gitURL(id.toString())}`
- )
- })
- cy.findByRole('button', {
- name: 'Generate token',
- }).click()
- cy.findByLabelText('Git authentication token').contains(
- /olp_[a-zA-Z0-9]{16}/
- )
- })
-
- // Re-open
- cy.url().then(url => cy.visit(url))
- cy.findByText('Git clone this project.').click()
-
- cy.findByTestId('git-bridge-modal').within(() => {
- cy.get('@projectId').then(id => {
- cy.findByLabelText('Git clone project command').contains(
- `git clone ${gitURL(id.toString())}`
- )
- })
- cy.findByRole('button', {
- name: 'Generate token',
- }).should('not.exist')
- cy.findByText(/generate a new one in Account settings/)
- cy.findByRole('link', { name: 'Go to settings' })
- .should('have.attr', 'target', '_blank')
- .and('have.attr', 'href', '/user/settings')
- })
- })
-
- describe('git access', function () {
- ensureUserExists({ email: 'collaborator-rw@example.com' })
- ensureUserExists({ email: 'collaborator-ro@example.com' })
- ensureUserExists({ email: 'collaborator-link-rw@example.com' })
- ensureUserExists({ email: 'collaborator-link-ro@example.com' })
-
- let projectName: string
- let recompile: () => void
- let waitForCompile: (triggerCompile: () => void) => void
- beforeEach(function () {
- projectName = uuid()
- createProject(projectName, { open: false }).as('projectId')
- ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
- })
-
- it('should expose r/w interface to owner', function () {
- maybeClearAllTokens()
- waitForCompile(() => {
- openProjectByName(projectName, true)
- })
- checkGitAccess('readAndWrite')
- })
-
- it('should expose r/w interface to invited r/w collaborator', function () {
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- 'collaborator-rw@example.com',
- 'Editor',
- true
- )
- maybeClearAllTokens()
- waitForCompile(() => {
- openProjectByName(projectName, true)
- })
- checkGitAccess('readAndWrite')
- })
-
- it('should expose r/o interface to invited r/o collaborator', function () {
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- 'collaborator-ro@example.com',
- 'Viewer',
- true
- )
- maybeClearAllTokens()
- waitForCompile(() => {
- openProjectByName(projectName, true)
- })
- checkGitAccess('readOnly')
- })
-
- it('should expose r/w interface to link-sharing r/w collaborator', function () {
- openProjectByName(projectName, true)
- enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
- const email = 'collaborator-link-rw@example.com'
- login(email)
- maybeClearAllTokens()
- waitForCompile(() => {
- openProjectViaLinkSharingAsUser(
- linkSharingReadAndWrite,
- projectName,
- email,
- true
- )
- })
- checkGitAccess('readAndWrite')
- })
- })
-
- it('should expose r/o interface to link-sharing r/o collaborator', function () {
- waitForCompile(() => {
- openProjectByName(projectName, true)
- })
- enableLinkSharing().then(({ linkSharingReadOnly }) => {
- const email = 'collaborator-link-ro@example.com'
- login(email)
- maybeClearAllTokens()
- waitForCompile(() => {
- openProjectViaLinkSharingAsUser(
- linkSharingReadOnly,
- projectName,
- email,
- true
- )
- })
- checkGitAccess('readOnly')
- })
- })
-
- function checkGitAccess(access: 'readOnly' | 'readAndWrite') {
- cy.findByRole('tab', { name: 'Integrations' }).click()
- cy.findByText('Git clone this project.').click()
-
- cy.get('@projectId').then(projectId => {
- cy.findByTestId('git-bridge-modal').within(() => {
- cy.findByLabelText('Git clone project command').contains(
- `git clone ${gitURL(projectId.toString())}`
- )
- cy.findByRole('heading', { name: 'Clone with Git' })
- cy.findByRole('button', {
- name: 'Generate token',
- }).click()
- })
- cy.findByLabelText('Git authentication token')
- .contains(/olp_[a-zA-Z0-9]{16}/)
- .then(async tokenEl => {
- const token = tokenEl.text()
-
- // close Git modal
- cy.findByRole('button', { name: 'Close dialog' }).click()
- cy.findByTestId('git-bridge-modal').should('not.exist')
- // close the modal
- cy.get('body').type('{esc}')
- cy.findByTestId('left-menu').should('not.exist')
- const fs = new LightningFS('fs')
- const dir = `/${projectId}`
-
- async function readFile(path: string): Promise {
- return await new Promise((resolve, reject) => {
- fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
- if (err) return reject(err)
- resolve(blob as string)
- })
- })
- }
-
- async function writeFile(path: string, data: string) {
- return await new Promise((resolve, reject) => {
- fs.writeFile(path, data, undefined, err => {
- if (err) return reject(err)
- resolve()
- })
- })
- }
-
- const commonOptions = {
- dir,
- fs,
- }
- const url = gitURL(projectId.toString())
- url.username = '' // basic auth is specified separately.
- const httpOptions = {
- http,
- url: url.toString(),
- headers: {
- Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`,
- },
- }
- const authorOptions = {
- author: { name: 'user', email: USER },
- committer: { name: 'user', email: USER },
- }
- const mainTex = `${dir}/main.tex`
-
- // Clone
- cy.then({ timeout: 10_000 }, async () => {
- await git.clone({
- ...commonOptions,
- ...httpOptions,
- })
- })
- cy.findByText(/\\documentclass/)
- .parent()
- .parent()
- .then(async editor => {
- const onDisk = await readFile(mainTex)
- expect(onDisk.replaceAll('\n', '')).to.equal(editor.text())
- })
- const text = `
-\\documentclass{article}
-\\begin{document}
-Hello world
-\\end{document}
-`
-
- // Make a change
- cy.then(async () => {
- await writeFile(mainTex, text)
- await git.add({
- ...commonOptions,
- filepath: 'main.tex',
- })
- await git.commit({
- ...commonOptions,
- ...authorOptions,
- message: 'Swap main.tex',
- })
- })
-
- if (access === 'readAndWrite') {
- // check history before push
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'History' })
- .click()
- cy.findByText('(via Git)').should('not.exist')
- cy.findAllByText('Back to editor').last().click()
- cy.then(async () => {
- await git.push({
- ...commonOptions,
- ...httpOptions,
- })
- })
- } else {
- cy.then(async () => {
- try {
- await git.push({
- ...commonOptions,
- ...httpOptions,
- })
- expect.fail('push should have failed')
- } catch (err) {
- expect(err).to.match(/branches were not updated/)
- expect(err).to.match(/forbidden/)
- }
- })
-
- return // return early, below are write access bits
- }
-
- // check push in editor
- cy.findByText(/\\documentclass/)
- .parent()
- .parent()
- .should('have.text', text.replaceAll('\n', ''))
-
- // Wait for history sync - trigger flush by toggling the UI
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'History' })
- .click()
- cy.findAllByText('Back to editor').last().click()
-
- // check push in history
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'History' })
- .click()
- cy.findByText(/Hello world/)
- cy.findByText('(via Git)').should('exist')
-
- // Back to the editor
- cy.findAllByText('Back to editor').last().click()
- cy.findByText(/\\documentclass/)
- .parent()
- .parent()
- .as('editor')
- .click()
- cy.get('@editor').type('% via editor{enter}')
-
- // Trigger flush via compile
- recompile()
-
- // Back into the history, check what we just added
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'History' })
- .click()
- cy.findByText(/% via editor/)
-
- // Pull the change
- cy.then(async () => {
- await git.pull({
- ...commonOptions,
- ...httpOptions,
- ...authorOptions,
- })
-
- expect(await readFile(mainTex)).to.equal(
- text + '% via editor\n'
- )
- })
- })
- })
- }
- })
- })
-
- function checkDisabled() {
- ensureUserExists({ email: USER })
-
- it('should not render the git-bridge UI in the settings', function () {
- login(USER)
- cy.visit('/user/settings')
- cy.findByRole('heading', { name: 'Git integration' }).should('not.exist')
- })
- it('should not render the git-bridge UI in the editor', function () {
- login(USER)
- createProjectAndOpenInNewEditor('maybe git')
- cy.findByRole('tab', {
- name: 'File tree',
- }).should('exist') // Wait for load
- cy.findByRole('tab', {
- name: 'Integrations',
- }).should('not.exist')
- })
- }
-
- describe('disabled in Server Pro', function () {
- if (isExcludedBySharding('PRO_DEFAULT_1')) return
- startWith({
- pro: true,
- })
- checkDisabled()
- })
-
- describe('unavailable in CE', function () {
- if (isExcludedBySharding('CE_CUSTOM_1')) return
- startWith({
- pro: false,
- vars: ENABLED_VARS,
- })
- checkDisabled()
- })
-})
diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts
deleted file mode 100644
index 11c8d518c1..0000000000
--- a/server-ce/test/graceful-shutdown.spec.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { ensureUserExists, login } from './helpers/login'
-import {
- isExcludedBySharding,
- STARTUP_TIMEOUT,
- startWith,
-} from './helpers/config'
-import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient'
-import { createProjectAndOpenInNewEditor } from './helpers/project'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-
-const USER = 'user@example.com'
-const PROJECT_NAME = 'Old Project'
-
-function bringServerProBackUp() {
- cy.log('bring server pro back up')
- cy.then({ timeout: STARTUP_TIMEOUT }, async () => {
- await dockerCompose('up', '--detach', '--wait', 'sharelatex')
- })
-}
-
-describe('GracefulShutdown', function () {
- if (isExcludedBySharding('PRO_CUSTOM_1')) return
- startWith({
- pro: true,
- withDataDir: true,
- resetData: true,
- })
- ensureUserExists({ email: USER })
-
- let projectId: string
- it('should display banner and flush changes out of redis', function () {
- bringServerProBackUp()
- login(USER)
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor(PROJECT_NAME).then(id => {
- projectId = id
- })
- })
-
- cy.log('add additional content')
- cy.findByRole('region', { name: 'Editor' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type(`\n\\section{{}New Section}`)
- })
- recompile()
-
- cy.log(
- 'check flush from frontend to backend: should include new section in PDF'
- )
- cy.findByRole('region', { name: 'PDF preview' }).should(
- 'contain.text',
- 'New Section'
- )
-
- cy.log('should have unflushed content in redis before shutdown')
- cy.then(async () => {
- const keys = await getRedisKeys()
- expect(keys).to.contain(`DocsIn:${projectId}`)
- expect(keys).to.contain(`ProjectHistory:Ops:{${projectId}}`)
- })
-
- cy.log('trigger graceful shutdown')
- let pendingShutdown: Promise
- cy.then(() => {
- pendingShutdown = dockerCompose('stop', '--timeout=60', 'sharelatex')
- })
-
- cy.log('wait for banner')
- cy.findByRole('dialog').findByText(/performing maintenance/)
- cy.log('wait for page reload')
- cy.findByRole('heading', { name: 'Maintenance' })
- cy.findByText(/is currently down for maintenance/)
-
- cy.log('wait for shutdown to complete')
- cy.then({ timeout: 60 * 1000 }, async () => {
- await pendingShutdown
- })
-
- cy.log('should not have any unflushed content in redis after shutdown')
- cy.then(async () => {
- const keys = await getRedisKeys()
- expect(keys).to.not.contain(`DocsIn:${projectId}`)
- expect(keys).to.not.contain(`ProjectHistory:Ops:{${projectId}}`)
- })
-
- bringServerProBackUp()
-
- cy.then(() => {
- cy.visit(`/project/${projectId}?trick-cypress-into-page-reload=true`)
- })
-
- cy.log('check loading doc from mongo')
- cy.findByRole('region', { name: 'Editor' }).findByText('New Section')
-
- cy.log('check PDF')
- cy.findByRole('region', { name: 'PDF preview' }).should(
- 'contain.text',
- 'New Section'
- )
- cy.log('check history')
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'History' })
- .click()
- cy.findByText(/\\section\{New Section}/)
- })
-})
diff --git a/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts b/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts
deleted file mode 100644
index 0b67e3c910..0000000000
--- a/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export function beforeWithReRunOnTestRetry(fn: () => void | Promise) {
- let ranOnce = false
- beforeEach(function () {
- if (ranOnce && Cypress.currentRetry === 0) return
- ranOnce = true
- return fn()
- })
-}
diff --git a/server-ce/test/helpers/compile.ts b/server-ce/test/helpers/compile.ts
deleted file mode 100644
index 3ceb14e320..0000000000
--- a/server-ce/test/helpers/compile.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * Helper function for throttling clicks on the recompile button to avoid hitting server side rate limits.
- * The naive approach is waiting a fixed a mount of time (3s) just before clicking the button.
- * This helper takes into account that other UI interactions take time. We can deduce that latency from the fixed delay (3s minus other latency). This can bring down the effective waiting time to 0s.
- */
-
-export function stopCompile(options: { delay?: number } = {}) {
- const { delay = 0 } = options
- cy.wait(delay)
- cy.log('Stop compile')
- cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
- cy.findByRole('menuitem', { name: 'Stop compilation' })
- .should('not.have.class', 'disabled')
- .and('not.have.attr', 'aria-disabled', 'true')
- .click()
-}
-
-export function prepareWaitForNextCompileSlot() {
- let lastCompile = 0
- function queueReset() {
- cy.then(() => {
- lastCompile = Date.now()
- })
- }
- function waitForCompileRateLimitCoolOff() {
- cy.then(() => {
- cy.log('Wait for recompile rate-limit to cool off')
- const msSinceLastCompile = Date.now() - lastCompile
- cy.wait(Math.max(0, 1_000 - msSinceLastCompile))
- queueReset()
- })
- }
- function waitForCompile(triggerCompile: () => void) {
- waitForCompileRateLimitCoolOff()
- cy.then(() => {
- let compilingVisible: () => void
- const waitForCompilingVisible = new Promise(resolve => {
- compilingVisible = resolve
- })
- cy.intercept(
- {
- method: 'POST',
- pathname: /\/project\/[a-fA-F0-9]{24}\/compile$/,
- times: 1,
- },
- async req => {
- await waitForCompilingVisible
- req.continue()
- }
- ).as('recompile')
- triggerCompile()
- cy.log('Wait for compile to finish')
- cy.findByRole('button', { name: 'Compiling…' }).then(() =>
- compilingVisible()
- )
- cy.wait('@recompile')
- cy.findByRole('button', { name: 'Compiling…' }).should('not.exist')
- cy.findByRole('button', { name: 'Recompile' }).should('be.visible')
- })
- }
- function recompile() {
- waitForCompile(() => {
- cy.findByRole('button', { name: 'Recompile' }).click()
- })
- }
- return {
- queueReset,
- waitForCompileRateLimitCoolOff,
- waitForCompile,
- recompile,
- }
-}
diff --git a/server-ce/test/helpers/config.ts b/server-ce/test/helpers/config.ts
deleted file mode 100644
index 494345dd6b..0000000000
--- a/server-ce/test/helpers/config.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import { reconfigure } from './hostAdminClient'
-import { resetActivateUserRateLimit, resetCreatedUsersCache } from './login'
-
-export const STARTUP_TIMEOUT =
- parseInt(Cypress.env('STARTUP_TIMEOUT'), 10) || 120_000
-
-export function isExcludedBySharding(
- shard:
- | 'LOCAL_ONLY'
- | 'CE_DEFAULT'
- | 'CE_CUSTOM_1'
- | 'PRO_DEFAULT_1'
- | 'PRO_DEFAULT_2'
- | 'PRO_CUSTOM_1'
- | 'PRO_CUSTOM_2'
- | 'PRO_CUSTOM_3'
- | 'PRO_CUSTOM_4'
- | 'PRO_CUSTOM_5'
-) {
- const SHARD = Cypress.env('SHARD')
- return SHARD && shard !== SHARD
-}
-
-let previousConfigFrontend: string
-
-export function startWith({
- pro = false,
- version = 'latest',
- vars = {},
- varsFn = () => ({}),
- withDataDir = false,
- resetData = false,
- mongoVersion = '',
-}) {
- before(async function () {
- Object.assign(vars, varsFn())
- const cfg = JSON.stringify({
- pro,
- version,
- vars,
- withDataDir,
- resetData,
- mongoVersion,
- })
- if (resetData) {
- cy.log('resetting data and sessions')
- resetCreatedUsersCache()
- resetActivateUserRateLimit()
- // no return here, always reconfigure when resetting data
- } else if (previousConfigFrontend === cfg) {
- cy.log(`already running with ${cfg}`)
- return
- }
- cy.log(`starting with ${cfg}`)
-
- this.timeout(STARTUP_TIMEOUT)
- previousConfigFrontend = ''
- const { previousConfigServer } = await reconfigure({
- pro,
- version,
- vars,
- withDataDir,
- resetData,
- mongoVersion,
- })
- if (previousConfigServer !== cfg) {
- await Cypress.session.clearAllSavedSessions()
- }
- previousConfigFrontend = cfg
- })
-}
-
-// Allow reloading the server in other places, e.g. beforeEach hooks.
-export async function reloadWith({
- pro = false,
- version = 'latest',
- vars = {},
- varsFn = () => ({}),
- withDataDir = false,
- resetData = false,
- mongoVersion = '',
-}) {
- Object.assign(vars, varsFn())
- const cfg = JSON.stringify({
- pro,
- version,
- vars,
- withDataDir,
- resetData,
- mongoVersion,
- })
- if (resetData) {
- resetCreatedUsersCache()
- resetActivateUserRateLimit()
- // no return here, always reconfigure when resetting data
- } else if (previousConfigFrontend === cfg) {
- return
- }
- previousConfigFrontend = ''
- const { previousConfigServer } = await reconfigure({
- pro,
- version,
- vars,
- withDataDir,
- resetData,
- mongoVersion,
- })
- if (previousConfigServer !== cfg) {
- await Cypress.session.clearAllSavedSessions()
- }
- previousConfigFrontend = cfg
-}
-
-export { reconfigure }
diff --git a/server-ce/test/helpers/email.ts b/server-ce/test/helpers/email.ts
deleted file mode 100644
index ab5150a33f..0000000000
--- a/server-ce/test/helpers/email.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Helper function for opening an email in Roundcube based mailtrap.
- * We need to cross an origin boundary, which complicates the use of variables.
- * Any variables need to be explicitly defined and the "runner" may only reference these and none from its scope.
- * It is not possible to use Cypress helper functions, e.g. from the testing library or other functions like "activateUser", inside the "runner".
- * REF: https://github.com/testing-library/cypress-testing-library/issues/221
- */
-export function openEmail(
- subject: string | RegExp,
- runner: (
- frame: Cypress.Chainable>,
- args: T
- ) => void,
- args?: T
-) {
- const runnerS = runner.toString()
- cy.origin(
- Cypress.env('MAILTRAP_URL') || 'http://mailtrap',
- { args: { args, runnerS, subject } },
- ({ args, runnerS, subject }) => {
- cy.visit('/')
- cy.get('input[name="_user"]').type('mailtrap')
- cy.get('input[name="_pass"]').type('password-for-mailtrap')
- cy.get('button[type="submit"]').click()
- cy.url().then(url => {
- if (!url.includes('?_task=login')) return
- cy.log('mailtrap login is flaky in cypress, submit again')
- cy.get('input[name="_pass"]').type('password-for-mailtrap')
- cy.get('button[type="submit"]').click()
- })
- // Use force as the subject is partially hidden
- cy.contains(subject).click({ force: true })
- cy.log('wait for iframe loading')
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.wait(1000)
- cy.get('iframe[id="messagecontframe"]').then(frame => {
- // runnerS='(frame, args) => { runner body }'. Extract the runnable function.
- // eslint-disable-next-line no-new-func
- const runner = new Function('return ' + runnerS)()
- runner(cy.wrap(frame.prop('contentWindow').document.body), args)
- })
- }
- )
-}
diff --git a/server-ce/test/helpers/hostAdminClient.ts b/server-ce/test/helpers/hostAdminClient.ts
deleted file mode 100644
index 5934fa1fca..0000000000
--- a/server-ce/test/helpers/hostAdminClient.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-const hostAdminURL = Cypress.env('HOST_ADMIN_URL') || 'http://host-admin'
-
-export async function dockerCompose(cmd: string, ...args: string[]) {
- return await fetchJSON(`${hostAdminURL}/docker/compose/${cmd}`, {
- method: 'POST',
- body: JSON.stringify({
- args,
- }),
- })
-}
-
-export async function reconfigure({
- pro = false,
- version = 'latest',
- vars = {},
- withDataDir = false,
- resetData = false,
- mongoVersion = '',
-}): Promise<{ previousConfigServer: string }> {
- return await fetchJSON(`${hostAdminURL}/reconfigure`, {
- method: 'POST',
- body: JSON.stringify({
- pro,
- version,
- vars,
- withDataDir,
- resetData,
- mongoVersion,
- }),
- })
-}
-
-async function fetchJSON(
- input: RequestInfo,
- init?: RequestInit
-): Promise {
- if (init?.body) {
- init.headers = { 'Content-Type': 'application/json' }
- }
- let res
- for (let attempt = 0; attempt < 5; attempt++) {
- try {
- res = await fetch(input, init)
- break
- } catch {
- await sleep(3_000)
- }
- }
- if (!res) {
- res = await fetch(input, init)
- }
- const { error, stdout, stderr, ...rest } = await res.json()
- if (error) {
- console.error(input, init, 'failed:', error)
- if (stdout) console.log(stdout)
- if (stderr) console.warn(stderr)
- const err = new Error(error.message)
- Object.assign(err, error)
- throw err
- }
- return { stdout, stderr, ...rest }
-}
-
-export async function runScript({
- cwd,
- script,
- args = [],
- user = 'www-data',
- hasOverleafEnv = true,
-}: {
- cwd: string
- script: string
- args?: string[]
- user?: string
- hasOverleafEnv?: boolean
-}) {
- return await fetchJSON(`${hostAdminURL}/run/script`, {
- method: 'POST',
- body: JSON.stringify({
- cwd,
- script,
- args,
- user,
- hasOverleafEnv,
- }),
- })
-}
-
-export async function runGruntTask({
- task,
- args = [],
-}: {
- task: string
- args?: string[]
-}) {
- return await fetchJSON(`${hostAdminURL}/run/gruntTask`, {
- method: 'POST',
- body: JSON.stringify({
- task,
- args,
- }),
- })
-}
-
-export async function getRedisKeys() {
- const { stdout } = await fetchJSON(`${hostAdminURL}/redis/keys`, {
- method: 'GET',
- })
- return stdout.split('\n')
-}
-
-export async function setMongoFeatureCompatibilityVersion(
- mongoVersion: string
-) {
- cy.log(`advancing mongo featureCompatibilityVersion to ${mongoVersion}`)
- await fetchJSON(`${hostAdminURL}/mongo/setFeatureCompatibilityVersion`, {
- method: 'POST',
- body: JSON.stringify({
- mongoVersion,
- }),
- })
-}
-
-export async function purgeFilestoreData() {
- const { stdout } = await fetchJSON(`${hostAdminURL}/data/user_files`, {
- method: 'DELETE',
- })
- if (!stdout.trim()) return []
- return stdout.trim().split('\n')
-}
-
-async function sleep(ms: number) {
- return await new Promise(resolve => {
- setTimeout(resolve, ms)
- })
-}
diff --git a/server-ce/test/helpers/login.ts b/server-ce/test/helpers/login.ts
deleted file mode 100644
index e34d45ff1b..0000000000
--- a/server-ce/test/helpers/login.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { runScript } from './hostAdminClient'
-
-export const DEFAULT_PASSWORD = 'Passw0rd!'
-
-const createdUsers = new Set()
-
-export function resetCreatedUsersCache() {
- createdUsers.clear()
-}
-
-export async function createMongoUser({
- email,
- isAdmin = false,
-}: {
- email: string
- isAdmin?: boolean
-}) {
- const t0 = Math.floor(Date.now() / 1000)
- const { stdout } = await runScript({
- cwd: 'services/web',
- script: 'modules/server-ce-scripts/scripts/create-user.js',
- args: [`--email=${email}`, `--admin=${isAdmin}`],
- })
- const [url] = stdout.match(/http:\/\/.+\/user\/activate\?token=\S+/)!
- const userId = new URL(url, location.origin).searchParams.get('user_id')!
- const signupDate = parseInt(userId.slice(0, 8), 16)
- if (signupDate < t0) {
- return { url, exists: true }
- }
- return { url, exists: false }
-}
-
-export function ensureUserExists({
- email,
- password = DEFAULT_PASSWORD,
- isAdmin = false,
-}: {
- email: string
- password?: string
- isAdmin?: boolean
-}) {
- let url: string
- let exists: boolean
- before(async function () {
- exists = createdUsers.has(email)
- if (exists) return
- ;({ url, exists } = await createMongoUser({ email, isAdmin }))
- })
- before(function () {
- if (exists) return
- activateUser(url, password)
- cy.then(() => {
- createdUsers.add(email)
- })
- })
-}
-
-export function login(username: string, password = DEFAULT_PASSWORD) {
- cy.session(
- ['via-login', username, password],
- () => {
- cy.visit('/login')
- cy.get('input[name="email"]').type(username)
- cy.get('input[name="password"]').type(password)
- cy.findByRole('button', { name: 'Login' }).click()
- cy.url().should('contain', '/project')
- },
- {
- cacheAcrossSpecs: true,
- async validate() {
- // Hit a cheap endpoint that is behind AuthenticationController.requireLogin().
- cy.request({ url: '/user/personal_info', followRedirect: false }).then(
- response => {
- expect(response.status).to.equal(200)
- }
- )
- },
- }
- )
-}
-
-let activateRateLimitState = { count: 0, reset: 0 }
-export function resetActivateUserRateLimit() {
- activateRateLimitState = { count: 0, reset: 0 }
-}
-
-function handleActivateUserRateLimit() {
- cy.then(() => {
- activateRateLimitState.count++
- if (activateRateLimitState.reset < Date.now()) {
- activateRateLimitState.reset = Date.now() + 65_000
- activateRateLimitState.count = 1
- } else if (activateRateLimitState.count >= 6) {
- cy.wait(activateRateLimitState.reset - Date.now())
- activateRateLimitState.count = 1
- }
- })
-}
-
-export function activateUser(url: string, password = DEFAULT_PASSWORD) {
- handleActivateUserRateLimit()
-
- cy.session(url, () => {
- cy.visit(url)
- cy.url().then(url => {
- if (url.includes('/login')) return
- cy.url().should('contain', '/user/activate')
- cy.get('input[name="password"]').type(password)
- cy.findByRole('button', { name: 'Activate' }).click()
- cy.url().should('contain', '/project')
- })
- })
-}
diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts
deleted file mode 100644
index 9c70947474..0000000000
--- a/server-ce/test/helpers/project.ts
+++ /dev/null
@@ -1,402 +0,0 @@
-import { login } from './login'
-import { openEmail } from './email'
-import { v4 as uuid } from 'uuid'
-import { prepareWaitForNextCompileSlot } from './compile'
-
-export const NEW_PROJECT_BUTTON_MATCHER = /new project/i
-
-const NEW_EDITOR_QUERY_PARAMS = ''
-const OLD_EDITOR_QUERY_PARAMS = '?old-editor-override=true'
-
-export function redirectEditorUrlWithQueryParams(newEditor: boolean) {
- const queryString = newEditor
- ? NEW_EDITOR_QUERY_PARAMS
- : OLD_EDITOR_QUERY_PARAMS
- cy.intercept(
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
- req => {
- // Intercept redirect and add the query param to use the new editor
- req.redirect(`${req.url}${queryString}`)
- }
- )
-}
-
-export function createProject(
- name: string,
- {
- type = 'Blank project',
- newProjectButtonMatcher = NEW_PROJECT_BUTTON_MATCHER,
- open = true,
- newEditor = false,
- }: {
- type?: 'Blank project' | 'Example project'
- newProjectButtonMatcher?: RegExp
- open?: boolean
- newEditor?: boolean
- } = {}
-): Cypress.Chainable {
- cy.url().then(url => {
- if (!url.endsWith('/project')) {
- cy.visit('/project')
- }
- })
- const interceptId = uuid()
- let projectId = ''
- if (!open) {
- cy.then(() => {
- // Register intercept just before creating the project, otherwise we might
- // intercept a request from a prior createProject invocation.
- cy.intercept(
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
- req => {
- projectId = req.url.split('/').pop()!.split('?')[0]
- // Redirect back to the project dashboard, effectively reload the page.
- req.redirect('/project')
- }
- ).as(interceptId)
- })
- } else {
- redirectEditorUrlWithQueryParams(newEditor)
- }
- cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
- // FIXME: This should only look in the left menu
- // The upgrading tests create projects in older versions of Server Pro which used different casing of the project type. Use case-insensitive match.
- cy.findAllByText(type, { exact: false }).first().click()
- cy.findByRole('dialog').within(() => {
- cy.get('input').type(name)
- cy.findByRole('button', { name: 'Create' }).click()
- })
- if (open) {
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- waitForMainDocToLoad()
- return cy
- .url()
- .should('match', /\/project\/[a-fA-F0-9]{24}/)
- .then(url => url.split('/').pop()?.split('?')[0])
- } else {
- const alias = `@${interceptId}` // IDEs do not like computed values in cy.wait().
- cy.wait(alias)
- return cy.then(() => projectId)
- }
-}
-
-// TODO ide-redesign-cleanup: Remove this and just use createProject directly
-export function createProjectAndOpenInNewEditor(
- projectName: string,
- options: {
- type?: 'Blank project' | 'Example project'
- newProjectButtonMatcher?: RegExp
- } = {}
-) {
- return createProject(projectName, { ...options, open: false }).then(
- projectId => {
- // Open the new project in the new editor
- openProjectById(projectId, true)
- return cy.then(() => projectId)
- }
- )
-}
-
-export function openProjectByName(projectName: string, newEditor = false) {
- cy.visit('/project')
-
- redirectEditorUrlWithQueryParams(newEditor)
-
- cy.findByText(projectName).click()
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
-}
-
-export function openProjectById(projectId: string, newEditor = false) {
- const url = newEditor
- ? `/project/${projectId}${NEW_EDITOR_QUERY_PARAMS}`
- : `/project/${projectId}${OLD_EDITOR_QUERY_PARAMS}`
- cy.visit(url)
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
-}
-
-export function openProjectViaLinkSharingAsAnon(
- url: string,
- newEditor = false
-) {
- redirectEditorUrlWithQueryParams(newEditor)
- cy.visit(url)
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
-}
-
-export function openProjectViaLinkSharingAsUser(
- url: string,
- projectName: string,
- email: string,
- newEditor: boolean = false
-) {
- cy.visit(url)
- cy.findByText(projectName) // wait for lazy loading
- cy.contains(`as ${email}`)
-
- redirectEditorUrlWithQueryParams(newEditor)
-
- cy.findByText('OK, join project').click()
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
-}
-
-export function openProjectViaInviteNotification(
- projectName: string,
- newEditor: boolean = false
-) {
- cy.visit('/project')
- cy.findByText(projectName)
- .parent()
- .parent()
- .within(() => {
- cy.findByText('Join Project').click()
- })
-
- cy.intercept(
- // NOTE: Struggling to work out why but this only seems to work with times: 2.
- // This is temporary code so leaving it for now.
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 2 },
- req => {
- const queryString = newEditor
- ? NEW_EDITOR_QUERY_PARAMS
- : OLD_EDITOR_QUERY_PARAMS
- // Intercept redirect and add the query param to use the new editor
- req.redirect(`${req.url}${queryString}`)
- }
- )
-
- const { waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- cy.findByText('Open Project').click()
-
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- })
-}
-
-function shareProjectByEmail(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor' | 'Reviewer',
- newEditor: boolean = false
-) {
- openProjectByName(projectName, newEditor)
- cy.findByRole('button', { name: 'Share' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByLabelText('Add email address', { selector: 'input' }).type(
- `${email},`
- )
- cy.findByLabelText('Add email address', { selector: 'input' })
- .parents('form')
- .within(() => {
- cy.findByTestId('add-collaborator-select').as('select').click()
- cy.get('@select').then(() => {
- cy.findByRole('option', { name: level }).click()
- })
- })
- cy.findByRole('button', { name: 'Invite' }).click()
- cy.findByText('Invite not yet accepted.')
- })
-}
-
-export function shareProjectByEmailAndAcceptInviteViaDash(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor' | 'Reviewer',
- newEditor: boolean = false
-) {
- shareProjectByEmail(projectName, email, level, newEditor)
-
- login(email)
- openProjectViaInviteNotification(projectName, newEditor)
-}
-
-export function getSpamSafeProjectName() {
- while (true) {
- // Move from hex/16 to base64/64 possible characters per char in string
- const name = Buffer.from(uuid().replaceAll('-', ''), 'hex')
- .toString('base64')
- .replaceAll('/', 'x')
- .replaceAll('+', 'y')
- .slice(0, 10)
- const nDigits = (name.match(/\d/g) || []).length
- if (nDigits < 6) return name
- }
-}
-
-export function shareProjectByEmailAndAcceptInviteViaEmail(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor',
- newEditor: boolean = false
-) {
- shareProjectByEmail(projectName, email, level, newEditor)
-
- login(email)
-
- openEmail(projectName, frame => {
- frame.contains('View project').then(a => {
- cy.log(
- 'bypass target=_blank and navigate current browser tab/cypress-iframe to project invite'
- )
- cy.visit(a.attr('href')!)
- })
- })
- cy.url().should('match', /\/project\/[a-f0-9]+\/invite\/token\/[a-f0-9]+/)
- cy.findByText(/user would like you to join/)
- cy.contains(new RegExp(`You are accepting this invite as ${email}`))
-
- redirectEditorUrlWithQueryParams(newEditor)
-
- cy.findByText('Join Project').click()
- waitForMainDocToLoad()
-
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
-}
-
-export function enableLinkSharing() {
- let linkSharingReadOnly: string
- let linkSharingReadAndWrite: string
- const origin = new URL(Cypress.config().baseUrl!).origin
-
- waitForMainDocToLoad()
-
- cy.findByText('Share').click()
- cy.findByText('Turn on link sharing').click()
- cy.findByText('Anyone with this link can view this project')
- .next()
- .should('contain.text', origin + '/read')
- .then(el => {
- linkSharingReadOnly = el.text()
- })
- cy.findByText('Anyone with this link can edit this project')
- .next()
- .should('contain.text', origin + '/')
- .then(el => {
- linkSharingReadAndWrite = el.text()
- })
-
- return cy.then(() => {
- return { linkSharingReadOnly, linkSharingReadAndWrite }
- })
-}
-
-export function waitForMainDocToLoad() {
- cy.log('Wait for main doc to load; it will steal the focus after loading')
- cy.get('.cm-content').should('contain.text', 'Introduction')
-}
-
-export function openFile(fileName: string, waitFor: string) {
- // force: The file-tree pane is too narrow to display the full name.
- cy.findByRole('navigation', { name: 'Project files and outline' })
- .findByRole('treeitem', { name: fileName })
- .click({ force: true })
-
- // wait until we've switched to the selected file
- cy.findByRole('status').should('not.exist')
- cy.findByText(waitFor)
-}
-
-export function createNewFile() {
- const fileName = `${uuid()}.tex`
-
- cy.log('create new project file')
- cy.get('button').contains('New file').click({ force: true })
- cy.findByRole('dialog').within(() => {
- cy.get('input').clear()
- cy.get('input').type(fileName)
- cy.findByText('Create').click()
- })
- // force: The file-tree pane is too narrow to display the full name.
- cy.findByTestId('file-tree')
- .findByRole('treeitem', { name: fileName })
- .click({ force: true })
-
- // wait until we've switched to the newly created empty file
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByRole('status').should('not.exist')
- cy.get('.cm-line').should('have.length', 1)
- })
- cy.get('.cm-line').should('have.length', 1)
-
- return fileName
-}
-
-export function expectFileExists(
- name: string,
- binary: boolean,
- content: string
-) {
- cy.findByRole('treeitem', { name }).click()
- if (binary) {
- cy.findByText(content).should('not.have.class', 'cm-line')
- } else {
- cy.findByText(content).should('have.class', 'cm-line')
- }
-}
-
-export function prepareFileUploadTest(binary = false) {
- const name = `${uuid()}.txt`
- const content = `Test File Content ${name}${binary ? ' \x00' : ''}`
- cy.get('button').contains('Upload').click({ force: true })
- cy.get('input[type=file]')
- .first()
- .selectFile(
- {
- contents: Cypress.Buffer.from(content),
- fileName: name,
- lastModified: Date.now(),
- },
- { force: true }
- )
-
- // wait for the upload to finish
- cy.findByRole('treeitem', { name })
-
- return () => expectFileExists(name, binary, content)
-}
-
-export function testNewFileUpload() {
- it('can upload text file', function () {
- const check = prepareFileUploadTest(false)
- check()
- })
-
- it('can upload binary file', function () {
- const check = prepareFileUploadTest(true)
- check()
- })
-}
diff --git a/server-ce/test/helpers/read-file.ts b/server-ce/test/helpers/read-file.ts
deleted file mode 100644
index bdd2ad11a5..0000000000
--- a/server-ce/test/helpers/read-file.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import fs from 'node:fs'
-import path from 'node:path'
-import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs'
-import AdmZip from 'adm-zip'
-import { setTimeout } from 'node:timers/promises'
-
-const MAX_ATTEMPTS = 15
-const POLL_INTERVAL = 500
-
-type ReadFileInZipArgs = {
- pathToZip: string
- fileToRead: string
-}
-
-export async function readFileInZip({
- pathToZip,
- fileToRead,
-}: ReadFileInZipArgs) {
- let attempt = 0
- while (attempt < MAX_ATTEMPTS) {
- if (fs.existsSync(pathToZip)) {
- const zip = new AdmZip(path.resolve(pathToZip))
- const entry = zip
- .getEntries()
- .find(entry => entry.entryName === fileToRead)
- if (entry) {
- return entry.getData().toString('utf8')
- } else {
- throw new Error(`${fileToRead} not found in ${pathToZip}`)
- }
- }
- await setTimeout(POLL_INTERVAL)
- attempt++
- }
- throw new Error(`${pathToZip} not found`)
-}
-
-export async function readPdf(file: string) {
- let attempt = 0
- while (attempt < MAX_ATTEMPTS) {
- if (fs.existsSync(file)) {
- const pdf = await getDocument(file).promise
- const text = []
- try {
- for (let i = 1; i <= pdf.numPages; i++) {
- const page = await pdf.getPage(i)
- const content = await page.getTextContent()
- for (const item of content.items) {
- if ('str' in item) text.push(item.str)
- }
- }
- return text.join('\n')
- } finally {
- await pdf.destroy()
- }
- }
- await setTimeout(POLL_INTERVAL)
- attempt++
- }
- throw new Error(`${file} not found`)
-}
diff --git a/server-ce/test/helpers/waitUntilScrollingFinished.ts b/server-ce/test/helpers/waitUntilScrollingFinished.ts
deleted file mode 100644
index af4c5657d1..0000000000
--- a/server-ce/test/helpers/waitUntilScrollingFinished.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-export function waitUntilScrollingFinished(selector: string, start = -1) {
- const pollSlow = 100
- const pollFast = 10
- const deadline =
- performance.now() + Cypress.config('defaultCommandTimeout') - pollSlow * 2
- return cy.get(selector).then(el => {
- cy.log(
- `waiting until scrolling finished for ${selector}, starting from ${start}`
- )
- return new Cypress.Promise((resolve, reject) => {
- const waitForStable = (prev: number, stableFor: number) => {
- if (performance.now() > deadline) {
- return reject(new Error('timeout waiting for scrolling to finish'))
- }
- const current = el.scrollTop()!
- if (current !== prev) {
- setTimeout(() => waitForStable(current, 0), pollFast)
- } else if (stableFor < 5) {
- setTimeout(() => waitForStable(current, stableFor + 1), pollFast)
- } else {
- resolve(current)
- }
- }
-
- const waitForChange = () => {
- if (performance.now() > deadline) {
- return reject(new Error('timeout waiting for scrolling to start'))
- }
- const current = el.scrollTop()!
- if (current === start) {
- setTimeout(() => waitForChange(), pollSlow)
- } else {
- setTimeout(() => waitForStable(current, 0), pollFast)
- }
- }
- waitForChange()
- })
- })
-}
diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts
deleted file mode 100644
index 3afb975557..0000000000
--- a/server-ce/test/history.spec.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { createProjectAndOpenInNewEditor } from './helpers/project'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-import { ensureUserExists, login } from './helpers/login'
-import { isExcludedBySharding, startWith } from './helpers/config'
-
-describe('History', function () {
- if (isExcludedBySharding('CE_DEFAULT')) return
- startWith({})
- ensureUserExists({ email: 'user@example.com' })
- beforeEach(function () {
- login('user@example.com')
- })
-
- function addLabel(name: string) {
- cy.log(`add label ${JSON.stringify(name)}`)
- // The input is not clickable due to being visually hidden, click its label instead
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findByRole('group', {
- name: 'Show all of the project history or only labelled versions.',
- }).within(() => {
- cy.findByText('Labels').click()
- })
- cy.findByRole('radio', { name: 'Labels' }).should('be.checked')
- cy.findByRole('radio', { name: 'All history' }).should('not.be.checked')
- cy.findAllByTestId('history-version-details')
- .first()
- .within(() => {
- cy.findByRole('button', { name: 'More actions' }).click()
- cy.findByRole('menuitem', { name: 'Label this version' }).click()
- })
- })
- cy.findByRole('dialog').within(() => {
- cy.findByLabelText('New label name').type(`${name}{enter}`)
- })
- }
-
- function downloadVersion(name: string) {
- cy.log(`download version ${JSON.stringify(name)}`)
- // The input is not clickable due to being visually hidden, click its label instead
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findByRole('group', {
- name: 'Show all of the project history or only labelled versions.',
- }).within(() => {
- cy.findByText('Labels').click()
- })
- cy.findByRole('radio', { name: 'Labels' }).should('be.checked')
- cy.findByRole('radio', { name: 'All history' }).should('not.be.checked')
- cy.findByText(name)
- .closest('[data-testid="history-version-details"]')
- .within(() => {
- cy.findByRole('button', { name: 'More actions' }).click()
- cy.findByRole('menuitem', { name: 'Download this version' }).click()
- })
- })
- }
-
- const CLASS_ADDITION = 'ol-cm-addition-marker'
- const CLASS_DELETION = 'ol-cm-deletion-marker'
-
- it('should support labels, comparison and download', function () {
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('labels')
- })
-
- cy.log('add content, including a line that will get removed soon')
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type('\n% added')
- cy.findByText('\\maketitle').parent().type('\n% to be removed')
- })
- recompile()
- cy.findByRole('button', { name: 'History' }).click()
-
- cy.log('expect to see additions in history')
- cy.get('.document-diff-container').within(() => {
- cy.findByText('% to be removed').should('have.class', CLASS_ADDITION)
- cy.findByText('% added').should('have.class', CLASS_ADDITION)
- })
-
- addLabel('Before removal')
-
- cy.log('remove content')
- cy.findByRole('button', { name: 'Back to editor' }).click()
- cy.findByText('% to be removed').parent().type('{end}{shift}{upArrow}{del}')
- recompile()
- cy.findByRole('button', { name: 'History' }).click()
-
- cy.log('expect to see annotation for newly removed content in history')
- cy.get('.document-diff-container').within(() => {
- cy.findByText('% to be removed').should('have.class', CLASS_DELETION)
- cy.findByText('% added').should('not.have.class', CLASS_ADDITION)
- })
-
- addLabel('After removal')
-
- cy.log('add more content after labeling')
- cy.findByRole('button', { name: 'Back to editor' }).click()
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type('\n% more')
- })
- recompile()
-
- cy.log('compare non current versions')
- cy.findByRole('button', { name: 'History' }).click()
- // The input is not clickable due to being visually hidden, click its label instead
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findByRole('group', {
- name: 'Show all of the project history or only labelled versions.',
- }).within(() => {
- cy.findByText('Labels').click()
- })
- cy.findByRole('radio', { name: 'Labels' }).should('be.checked')
- cy.findByRole('radio', { name: 'All history' }).should('not.be.checked')
- cy.findAllByTestId('compare-icon-version').last().click()
- cy.findAllByTestId('compare-icon-version').filter(':visible').click()
- cy.findByRole('menuitem', {
- name: 'Compare up to this version',
- }).click()
- })
- cy.log(
- 'expect to see annotation for removed content between the two versions'
- )
- cy.get('.document-diff-container').within(() => {
- cy.findByText('% to be removed').should('have.class', CLASS_DELETION)
- cy.findByText('% added').should('not.have.class', CLASS_ADDITION)
- cy.findByText('% more').should('not.exist')
- })
-
- downloadVersion('Before removal')
- cy.task('readFileInZip', {
- pathToZip: `cypress/downloads/labels (Version 2).zip`,
- fileToRead: 'main.tex',
- })
- .should('contain', '% added')
- .should('contain', '% to be removed')
- .should('not.contain', '% more')
-
- downloadVersion('After removal')
- cy.task('readFileInZip', {
- pathToZip: `cypress/downloads/labels (Version 3).zip`,
- fileToRead: 'main.tex',
- })
- .should('contain', '% added')
- .should('not.contain', '% to be removed')
- .should('not.contain', '% more')
-
- downloadVersion('Current state')
- cy.task('readFileInZip', {
- pathToZip: `cypress/downloads/labels (Version 4).zip`,
- fileToRead: 'main.tex',
- })
- .should('contain', '% added')
- .should('not.contain', '% to be removed')
- .should('contain', '% more')
- })
-})
diff --git a/server-ce/test/host-admin.js b/server-ce/test/host-admin.js
deleted file mode 100644
index 6cceae4b2c..0000000000
--- a/server-ce/test/host-admin.js
+++ /dev/null
@@ -1,494 +0,0 @@
-import fs from 'node:fs'
-import Path from 'node:path'
-import { promisify } from 'node:util'
-import { execFile as execFileCb } from 'node:child_process'
-import bodyParser from 'body-parser'
-import express from 'express'
-import YAML from 'js-yaml'
-import {
- InvalidParamsError,
- InvalidRequestError,
- parseReq,
- z,
-} from '@overleaf/validation-tools'
-import { expressify } from '@overleaf/promise-utils'
-
-const execFile = promisify(execFileCb)
-
-const DATA_DIR = Path.join(
- import.meta.dirname,
- 'data',
- // Give each shard their own data dir.
- process.env.CYPRESS_SHARD || 'default'
-)
-const PATHS = {
- DOCKER_COMPOSE_FILE: 'docker-compose.yml',
- // Give each shard their own override file.
- DOCKER_COMPOSE_OVERRIDE: `docker-compose.${process.env.CYPRESS_SHARD || 'override'}.yml`,
- DOCKER_COMPOSE_NATIVE: 'docker-compose.native.yml',
- DATA_DIR,
- SANDBOXED_COMPILES_HOST_DIR: Path.join(DATA_DIR, 'compiles'),
-}
-const IMAGES = {
- CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''),
- PRO: process.env.IMAGE_TAG_PRO.replace(/:.+/, ''),
- GIT_BRIDGE: process.env.IMAGE_TAG_GIT_BRIDGE.replace(/:.+/, ''),
-}
-const LATEST = {
- CE: process.env.IMAGE_TAG_CE.replace(/.+:/, '') || 'latest',
- PRO: process.env.IMAGE_TAG_PRO.replace(/.+:/, '') || 'latest',
- GIT_BRIDGE: process.env.IMAGE_TAG_GIT_BRIDGE.replace(/.+:/, '') || 'latest',
-}
-
-function defaultDockerComposeOverride() {
- return {
- services: {
- sharelatex: {
- environment: {},
- },
- 'git-bridge': {},
- mongo: {},
- },
- }
-}
-
-let previousConfig = ''
-
-function readDockerComposeOverride() {
- try {
- return YAML.load(fs.readFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, 'utf-8'))
- } catch (error) {
- if (error.code !== 'ENOENT') {
- throw error
- }
- return defaultDockerComposeOverride()
- }
-}
-
-function writeDockerComposeOverride(cfg) {
- fs.writeFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, YAML.dump(cfg))
-}
-
-async function runDockerCompose(command, args) {
- const files = ['-f', PATHS.DOCKER_COMPOSE_FILE]
- if (process.env.NATIVE_CYPRESS) {
- files.push('-f', PATHS.DOCKER_COMPOSE_NATIVE)
- }
- if (fs.existsSync(PATHS.DOCKER_COMPOSE_OVERRIDE)) {
- files.push('-f', PATHS.DOCKER_COMPOSE_OVERRIDE)
- }
- return await execFile('docker', ['compose', ...files, command, ...args])
-}
-
-function purgeDataDir() {
- fs.rmSync(PATHS.DATA_DIR, { recursive: true, force: true })
-}
-
-const app = express()
-app.get('/status', (req, res) => {
- res.send('host-admin is up')
-})
-
-app.use(bodyParser.json())
-app.use((req, res, next) => {
- // Basic access logs
- if (process.env.CI !== 'true') {
- console.log(req.method, req.url, req.body)
- }
- const json = res.json
- res.json = body => {
- if (process.env.CI !== 'true' || body.error) {
- console.log(req.method, req.url, req.body, '->', body)
- }
- json.call(res, body)
- }
- next()
-})
-app.use((req, res, next) => {
- // Add CORS headers
- const accessControlAllowOrigin =
- process.env.ACCESS_CONTROL_ALLOW_ORIGIN || 'http://sharelatex'
- res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
- res.setHeader('Access-Control-Max-Age', '3600')
- res.setHeader('Access-Control-Allow-Methods', 'DELETE, GET, HEAD, POST, PUT')
- next()
-})
-
-app.post(
- '/run/script',
- expressify(async (req, res) => {
- const {
- body: { cwd, script, args, user, hasOverleafEnv },
- } = parseReq(
- req,
- z.object({
- body: z.object({
- cwd: z.string(),
- script: z.string(),
- args: z.array(z.string()),
- user: z.string(),
- hasOverleafEnv: z.boolean(),
- }),
- })
- )
-
- const env = hasOverleafEnv
- ? 'source /etc/overleaf/env.sh || source /etc/sharelatex/env.sh'
- : 'true'
- try {
- const { stdout, stderr } = await runDockerCompose('exec', [
- '--workdir',
- `/overleaf/${cwd}`,
- 'sharelatex',
- 'bash',
- '-c',
- `source /etc/container_environment.sh && ${env} && /sbin/setuser ${user} node ${script} ${args.map(a => JSON.stringify(a)).join(' ')}`,
- ])
- res.json({
- stdout,
- stderr,
- })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-app.post(
- '/run/gruntTask',
- expressify(async (req, res) => {
- const {
- body: { task, args },
- } = parseReq(
- req,
- z.object({
- body: z.object({
- task: z.string(),
- args: z.array(z.string()),
- }),
- })
- )
-
- try {
- const { stdout, stderr } = await runDockerCompose('exec', [
- '--workdir',
- '/var/www/sharelatex',
- 'sharelatex',
- 'bash',
- '-c',
- `source /etc/container_environment.sh && /sbin/setuser www-data grunt ${JSON.stringify(task)} ${args.map(a => JSON.stringify(a)).join(' ')}`,
- ])
- res.json({ stdout, stderr })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-const allowedVars = z.object(
- Object.fromEntries(
- [
- 'OVERLEAF_APP_NAME',
- 'OVERLEAF_LEFT_FOOTER',
- 'OVERLEAF_RIGHT_FOOTER',
- 'OVERLEAF_PROXY_LEARN',
- 'GIT_BRIDGE_ENABLED',
- 'GIT_BRIDGE_HOST',
- 'GIT_BRIDGE_PORT',
- 'V1_HISTORY_URL',
- 'SANDBOXED_COMPILES',
- 'ALL_TEX_LIVE_DOCKER_IMAGE_NAMES',
- 'OVERLEAF_FILESTORE_MIGRATION_LEVEL',
- 'OVERLEAF_TEMPLATES_USER_ID',
- 'OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS',
- 'OVERLEAF_ALLOW_PUBLIC_ACCESS',
- 'OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING',
- 'OVERLEAF_DISABLE_LINK_SHARING',
- 'EXTERNAL_AUTH',
- 'OVERLEAF_SAML_ENTRYPOINT',
- 'OVERLEAF_SAML_CALLBACK_URL',
- 'OVERLEAF_SAML_ISSUER',
- 'OVERLEAF_SAML_IDENTITY_SERVICE_NAME',
- 'OVERLEAF_SAML_EMAIL_FIELD',
- 'OVERLEAF_SAML_FIRST_NAME_FIELD',
- 'OVERLEAF_SAML_LAST_NAME_FIELD',
- 'OVERLEAF_SAML_UPDATE_USER_DETAILS_ON_LOGIN',
- 'OVERLEAF_SAML_CERT',
- 'OVERLEAF_LDAP_URL',
- 'OVERLEAF_LDAP_SEARCH_BASE',
- 'OVERLEAF_LDAP_SEARCH_FILTER',
- 'OVERLEAF_LDAP_BIND_DN',
- 'OVERLEAF_LDAP_BIND_CREDENTIALS',
- 'OVERLEAF_LDAP_EMAIL_ATT',
- 'OVERLEAF_LDAP_NAME_ATT',
- 'OVERLEAF_LDAP_LAST_NAME_ATT',
- 'OVERLEAF_LDAP_UPDATE_USER_DETAILS_ON_LOGIN',
- // Old branding, used for upgrade tests
- 'SHARELATEX_SITE_URL',
- 'SHARELATEX_MONGO_URL',
- 'SHARELATEX_REDIS_HOST',
- ].map(name => [name, z.string().optional()])
- )
-)
-
-function setVarsDockerCompose({
- pro,
- vars,
- version,
- withDataDir,
- mongoVersion,
-}) {
- const cfg = readDockerComposeOverride()
-
- cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version === 'latest' ? (pro ? LATEST.PRO : LATEST.CE) : version}`
- cfg.services['git-bridge'].image =
- `${IMAGES.GIT_BRIDGE}:${version === 'latest' ? LATEST.GIT_BRIDGE : version}`
-
- cfg.services.sharelatex.environment = vars
-
- if (cfg.services.sharelatex.environment.GIT_BRIDGE_ENABLED === 'true') {
- cfg.services.sharelatex.depends_on = ['git-bridge']
- } else {
- cfg.services.sharelatex.depends_on = []
- }
-
- if (['ldap', 'saml'].includes(vars.EXTERNAL_AUTH)) {
- cfg.services.sharelatex.depends_on.push(vars.EXTERNAL_AUTH)
- }
-
- const dataDirInContainer =
- version === 'latest' || version >= '5.0'
- ? '/var/lib/overleaf'
- : '/var/lib/sharelatex'
-
- cfg.services.sharelatex.volumes = []
- if (withDataDir) {
- cfg.services.sharelatex.volumes.push(
- `${PATHS.DATA_DIR}:${dataDirInContainer}`
- )
- }
-
- if (cfg.services.sharelatex.environment.SANDBOXED_COMPILES === 'true') {
- cfg.services.sharelatex.environment.SANDBOXED_COMPILES_HOST_DIR =
- PATHS.SANDBOXED_COMPILES_HOST_DIR
- cfg.services.sharelatex.environment.TEX_LIVE_DOCKER_IMAGE =
- process.env.TEX_LIVE_DOCKER_IMAGE
- cfg.services.sharelatex.environment.ALL_TEX_LIVE_DOCKER_IMAGES =
- process.env.ALL_TEX_LIVE_DOCKER_IMAGES
- cfg.services.sharelatex.volumes.push(
- '/var/run/docker.sock:/var/run/docker.sock'
- )
- if (!withDataDir) {
- cfg.services.sharelatex.volumes.push(
- `${PATHS.SANDBOXED_COMPILES_HOST_DIR}:${dataDirInContainer}/data/compiles`
- )
- }
- }
-
- if (mongoVersion) {
- cfg.services.mongo.image = `mongo:${mongoVersion}`
- } else {
- delete cfg.services.mongo.image
- }
-
- if (version === 'latest') {
- cfg.services.mongo.command = '--replSet overleaf --notablescan'
- } else {
- delete cfg.services.mongo.command
- }
-
- writeDockerComposeOverride(cfg)
-}
-
-app.post(
- '/docker/compose/:cmd',
- expressify(async (req, res) => {
- const {
- params: { cmd },
- body: { args },
- } = parseReq(
- req,
- z.object({
- params: z.object({
- cmd: z.literal(['up', 'stop', 'down', 'ps', 'logs']),
- }),
- body: z.object({
- args: z.array(
- z.literal([
- '--detach',
- '--wait',
- '--volumes',
- '--timeout=60',
- 'sharelatex',
- 'git-bridge',
- 'mongo',
- 'redis',
- ])
- ),
- }),
- })
- )
-
- try {
- const { stdout, stderr } = await runDockerCompose(cmd, args)
- res.json({ stdout, stderr })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-async function maybeResetData(resetData) {
- if (!resetData) return
-
- previousConfig = ''
- await runDockerCompose('down', [
- '--timeout=0',
- '--volumes',
- 'mongo',
- 'redis',
- 'sharelatex',
- ])
- purgeDataDir()
-}
-
-app.post(
- '/reconfigure',
- expressify(async (req, res) => {
- const {
- body: { pro, version, vars, withDataDir, resetData, mongoVersion },
- } = parseReq(
- req,
- z.object({
- body: z.object({
- pro: z.boolean(),
- version: z.string(),
- vars: allowedVars,
- withDataDir: z.boolean(),
- resetData: z.boolean(),
- mongoVersion: z.string(),
- }),
- })
- )
- try {
- await maybeResetData(resetData)
- } catch (error) {
- return res.json({ error })
- }
-
- const previousConfigServer = previousConfig
- const newConfig = JSON.stringify(req.body)
- if (previousConfig === newConfig) {
- return res.json({ previousConfigServer })
- }
-
- try {
- setVarsDockerCompose({ pro, version, vars, withDataDir, mongoVersion })
- } catch (error) {
- return res.json({ error })
- }
-
- try {
- const { stdout, stderr } = await runDockerCompose('up', [
- '--detach',
- '--wait',
- 'sharelatex',
- ])
- res.json({ stdout, stderr, previousConfigServer })
- } catch (error) {
- return res.json({
- error,
- previousConfigServer,
- })
- } finally {
- previousConfig = newConfig
- }
- })
-)
-
-app.post(
- '/mongo/setFeatureCompatibilityVersion',
- expressify(async (req, res) => {
- const {
- body: { mongoVersion },
- } = parseReq(
- req,
- z.object({
- body: z.object({
- mongoVersion: z.string(),
- }),
- })
- )
- const mongosh = mongoVersion > '5' ? 'mongosh' : 'mongo'
- const params = {
- setFeatureCompatibilityVersion: mongoVersion,
- }
- if (mongoVersion >= '7.0') {
- // MongoServerError: Once you have upgraded to 7.0, you will not be able to downgrade FCV and binary version without support assistance. Please re-run this command with 'confirm: true' to acknowledge this and continue with the FCV upgrade.
- // NOTE: 6.0 does not know about this flag. So conditionally add it.
- // MongoServerError: BSON field 'setFeatureCompatibilityVersion.confirm' is an unknown field.
- params.confirm = true
- }
- try {
- const { stdout, stderr } = await runDockerCompose('exec', [
- 'mongo',
- mongosh,
- '--eval',
- `db.adminCommand(${JSON.stringify(params)})`,
- ])
- res.json({ stdout, stderr })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-app.get(
- '/redis/keys',
- expressify(async (req, res) => {
- try {
- const { stdout, stderr } = await runDockerCompose('exec', [
- 'redis',
- 'redis-cli',
- 'KEYS',
- '*',
- ])
- res.json({ stdout, stderr })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-app.delete(
- '/data/user_files',
- expressify(async (req, res) => {
- try {
- const { stdout, stderr } = await runDockerCompose('exec', [
- 'sharelatex',
- 'rm',
- '-vrf',
- '/var/lib/overleaf/data/user_files',
- ])
- res.json({ stdout, stderr })
- } catch (error) {
- return res.json({ error })
- }
- })
-)
-
-app.use((error, req, res, next) => {
- if (error instanceof InvalidParamsError) {
- res.status(404).json({ error })
- } else if (error instanceof InvalidRequestError) {
- res.status(400).json({ error: error.zodError })
- } else {
- next(error)
- }
-})
-
-purgeDataDir()
-writeDockerComposeOverride(defaultDockerComposeOverride())
-
-app.listen(80)
diff --git a/server-ce/test/learn-wiki.spec.ts b/server-ce/test/learn-wiki.spec.ts
deleted file mode 100644
index ab6a55ae4a..0000000000
--- a/server-ce/test/learn-wiki.spec.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { ensureUserExists, login } from './helpers/login'
-
-describe('LearnWiki', function () {
- const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project'
- const UPLOADING_A_PROJECT_URL = '/learn/how-to/Uploading_a_project'
-
- const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
- const REGULAR_USER = 'user@example.com'
-
- // Re-use value for "exists" and "does not exist" tests
- const LABEL_LEARN_LATEX = 'Learn LaTeX with a tutorial'
-
- ensureUserExists({ email: WITHOUT_PROJECTS_USER })
- ensureUserExists({ email: REGULAR_USER })
-
- describe('enabled in Pro', function () {
- if (isExcludedBySharding('PRO_CUSTOM_2')) return
- startWith({
- pro: true,
- vars: {
- OVERLEAF_PROXY_LEARN: 'true',
- },
- })
-
- it('should add a documentation entry to the nav bar', function () {
- login(REGULAR_USER)
- cy.visit('/project')
- cy.findByRole('navigation', { name: 'Primary' }).findByRole('menuitem', {
- name: 'Documentation',
- })
- })
-
- it('should display a tutorial link in the welcome page', function () {
- login(WITHOUT_PROJECTS_USER)
- cy.visit('/project')
- cy.findByRole('link', { name: LABEL_LEARN_LATEX }).should(
- 'have.attr',
- 'href',
- '/learn/latex/Learn_LaTeX_in_30_minutes'
- )
- })
-
- it('should render wiki page', function () {
- login(REGULAR_USER)
-
- cy.visit(UPLOADING_A_PROJECT_URL)
- cy.findByRole('heading', { name: 'Uploading a project' })
- cy.contains(/how to create an Overleaf project/)
- cy.findByRole('img', { name: 'Creating a new project on Overleaf' })
- .should('be.visible')
- .and((el: any) => {
- expect(el[0].naturalWidth, 'renders image').to.be.greaterThan(0)
- })
-
- cy.visit(COPYING_A_PROJECT_URL)
- cy.findByRole('heading', { name: 'Copying a project' })
- cy.findByRole('link', {
- name: '1 How to copy a project (option 1)',
- }).should('exist')
- cy.findByRole('link', {
- name: '2 How to copy a project (option 2)',
- }).should('exist')
- })
-
- it('should navigate within wiki page using table of contents', function () {
- login(REGULAR_USER)
- cy.visit(COPYING_A_PROJECT_URL)
- cy.findByRole('heading', { name: 'Copying a project' })
- cy.findByRole('link', {
- name: '2 How to copy a project (option 2)',
- }).click()
- cy.url().should('contain', COPYING_A_PROJECT_URL)
- cy.findByRole('heading', {
- name: 'How to copy a project (option 2)',
- }).should('be.visible')
- })
- })
-
- describe('disabled in Pro', function () {
- if (isExcludedBySharding('PRO_DEFAULT_1')) return
- startWith({ pro: true })
- checkDisabled()
- })
-
- describe('unavailable in CE', function () {
- if (isExcludedBySharding('CE_CUSTOM_1')) return
- startWith({
- pro: false,
- vars: {
- OVERLEAF_PROXY_LEARN: 'true',
- },
- })
-
- checkDisabled()
- })
-
- function checkDisabled() {
- it('should not add a documentation entry to the nav bar', function () {
- login(REGULAR_USER)
- cy.visit('/project')
- cy.findByRole('navigation', { name: 'Primary' })
- .findByRole('menuitem', {
- name: 'Documentation',
- })
- .should('not.exist')
- })
-
- it('should not render wiki page', function () {
- login(REGULAR_USER)
- cy.visit(COPYING_A_PROJECT_URL, {
- failOnStatusCode: false,
- })
- cy.findByRole('heading', { name: 'Not found' })
- })
-
- it('should not display a tutorial link in the welcome page', function () {
- login(WITHOUT_PROJECTS_USER)
- cy.visit('/project')
- cy.findByText(LABEL_LEARN_LATEX).should('not.exist')
- })
- }
-})
diff --git a/server-ce/test/package.json b/server-ce/test/package.json
deleted file mode 100644
index 1e16326d10..0000000000
--- a/server-ce/test/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "@overleaf/server-ce-test",
- "description": "e2e tests for Overleaf Community Edition",
- "private": true,
- "type": "module",
- "scripts": {
- "cypress:open": "cypress open --e2e --browser chrome",
- "cypress:run": "cypress run --e2e --browser chrome",
- "lint": "eslint --cache --cache-location ../../.cache/eslint/ --max-warnings 0 --format unix --ext .js,.jsx,.mjs,.ts,.tsx .",
- "lint:fix": "eslint --cache --cache-location ../../.cache/eslint/ --fix --ext .js,.jsx,.mjs,.ts,.tsx ."
- },
- "devDependencies": {
- "@isomorphic-git/lightning-fs": "^4.6.0",
- "@overleaf/promise-utils": "workspace:*",
- "@overleaf/validation-tools": "workspace:*",
- "@testing-library/cypress": "10.1.0",
- "@types/adm-zip": "^0.5.7",
- "@types/uuid": "^9.0.8",
- "adm-zip": "^0.5.12",
- "body-parser": "1.20.4",
- "cypress": "15.12.0",
- "cypress-multi-reporters": "^2.0.5",
- "express": "4.22.1",
- "isomorphic-git": "^1.33.1",
- "js-yaml": "^4.1.1",
- "mocha-junit-reporter": "^2.2.1",
- "pdfjs-dist": "^5.1.91",
- "uuid": "^9.0.1",
- "zod-validation-error": "^4.0.1"
- }
-}
diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts
deleted file mode 100644
index fb61cc9012..0000000000
--- a/server-ce/test/project-list.spec.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { ensureUserExists, login } from './helpers/login'
-import { createProject } from './helpers/project'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { v4 as uuid } from 'uuid'
-
-const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
-const REGULAR_USER = 'user@example.com'
-
-describe('Project List', function () {
- if (isExcludedBySharding('PRO_DEFAULT_2')) return
- startWith({ pro: true })
-
- const findProjectRow = (projectName: string) => {
- cy.log('find project row')
- return cy.findByText(projectName).parent().parent()
- }
-
- describe('user with no projects', function () {
- ensureUserExists({ email: WITHOUT_PROJECTS_USER })
-
- it("'Import from GitHub' is not displayed in the welcome page", function () {
- login(WITHOUT_PROJECTS_USER)
- cy.visit('/project')
- cy.findByRole('button', { name: 'Create a new project' }).click()
- cy.findByRole('menuitem', { name: 'Import from GitHub' }).should(
- 'not.exist'
- )
- })
- })
-
- describe('user with projects', function () {
- const projectName = `test-project-${uuid()}`
- ensureUserExists({ email: REGULAR_USER })
-
- before(function () {
- login(REGULAR_USER)
- createProject(projectName, { type: 'Example project', open: false })
- })
- beforeEach(function () {
- login(REGULAR_USER)
- cy.visit('/project')
- })
-
- it('Can download project sources', function () {
- findProjectRow(projectName).within(() =>
- cy.findByRole('button', { name: 'Download .zip file' }).click()
- )
-
- const zipName = projectName.replaceAll('-', '_')
- cy.task('readFileInZip', {
- pathToZip: `cypress/downloads/${zipName}.zip`,
- fileToRead: 'main.tex',
- }).should('contain', 'Your introduction goes here')
- })
-
- it('Can download project PDF', function () {
- findProjectRow(projectName).within(() =>
- cy.findByRole('button', { name: 'Download PDF' }).click()
- )
-
- const pdfName = projectName.replaceAll('-', '_')
- cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
- 'contain',
- 'Your introduction goes here'
- )
- })
-
- it('can assign and remove tags to projects', function () {
- const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
- cy.log('select project')
- cy.findByRole('checkbox', { name: `Select ${projectName}` }).check()
-
- cy.log('add tag to project')
- cy.findByRole('button', { name: 'Tags' }).click()
- cy.findByRole('menuitem', { name: 'Create new tag' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('heading', { name: 'Create new tag' })
- cy.findByLabelText('New tag name').type(`${tagName}{enter}`)
- })
- cy.findByRole('button', { name: `Select tag ${tagName}` }) // tag label in project row
-
- cy.log('remove tag')
- cy.findByRole('button', { name: `Remove tag ${tagName}` })
- .first()
- .click()
- cy.findByRole('button', { name: `Select tag ${tagName}` }).should(
- 'not.exist'
- )
- })
-
- it('can filter by tag', function () {
- cy.log('create a separate project to filter')
- const nonTaggedProjectName = `project-${uuid()}`
- createProject(nonTaggedProjectName, { open: false })
-
- cy.log('select project')
- cy.findByRole('checkbox', { name: `Select ${projectName}` }).check()
-
- cy.log('add tag to project')
- const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
- cy.findByRole('button', { name: 'Tags' }).click()
- cy.findByRole('menuitem', { name: 'Create new tag' }).click()
-
- cy.findByRole('dialog').within(() => {
- cy.findByRole('heading', { name: 'Create new tag' })
- cy.findByLabelText('New tag name').type(`${tagName}{enter}`)
- })
-
- cy.log(
- 'check the non-tagged project is filtered out after clicking the tag'
- )
- cy.findByRole('link', { name: nonTaggedProjectName }).should('exist')
- cy.findByRole('button', { name: `Select tag ${tagName}` }).click()
- cy.findByRole('link', { name: nonTaggedProjectName }).should('not.exist')
- })
-
- it('can change theme from the account menu', function () {
- cy.findByRole('menuitem', { name: 'Account' }).click()
- cy.findByLabelText('Dark').click()
- cy.get('body').should('have.attr', 'data-theme', 'default')
- cy.findByLabelText('Light').click()
- cy.get('body').should('have.attr', 'data-theme', 'light')
- })
- })
-})
diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts
deleted file mode 100644
index b09483703c..0000000000
--- a/server-ce/test/project-sharing.spec.ts
+++ /dev/null
@@ -1,540 +0,0 @@
-import { v4 as uuid } from 'uuid'
-import {
- isExcludedBySharding,
- startWith,
- reloadWith,
- STARTUP_TIMEOUT,
-} from './helpers/config'
-import { ensureUserExists, login } from './helpers/login'
-import {
- createProjectAndOpenInNewEditor,
- enableLinkSharing,
- getSpamSafeProjectName,
- openProjectByName,
- openProjectViaLinkSharingAsAnon,
- openProjectViaLinkSharingAsUser,
- shareProjectByEmailAndAcceptInviteViaDash,
- shareProjectByEmailAndAcceptInviteViaEmail,
-} from './helpers/project'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
-
-describe('Project Sharing', function () {
- if (isExcludedBySharding('PRO_CUSTOM_4')) return
- startWith({ withDataDir: true, pro: true, resetData: true })
- ensureUserExists({ email: 'user@example.com' })
-
- let projectName: string
- let recompile: () => void
- let waitForCompile: (triggerCompile: () => void) => void
-
- // Link-sharing urls have to be created before disabling link sharing.
- // We use the `beforeEach` hook to reload the server with link sharing
- // disabled **after** the initial setup which happens in the `before`
- // block. The `before` hook always runs prior to the `beforeEach` hook.
-
- // Set up retained access before disabling link sharing
- beforeWithReRunOnTestRetry(() => {
- // undo the link sharing restriction before joining via link-sharing below
- cy.wrap(reloadWith({ withDataDir: true, pro: true }), {
- timeout: STARTUP_TIMEOUT,
- })
-
- projectName = getSpamSafeProjectName()
- ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
- setupTestProject()
- })
-
- beforeEach(function () {
- // Always start with a fresh session
- cy.session([uuid()], () => {})
- })
-
- let linkSharingReadOnly: string
- let linkSharingReadAndWrite: string
-
- function setupTestProject() {
- login('user@example.com')
- waitForCompile(() => {
- createProjectAndOpenInNewEditor(projectName)
- })
-
- // Add chat message
- cy.findByRole('tab', { name: 'Chat' }).click()
- // wait for lazy loading of the chat pane
- cy.findByText('Start the conversation by saying hello or sharing an update')
- cy.get(
- 'textarea[placeholder="Send a message to your collaborators…"]'
- ).type('New Chat Message{enter}')
-
- // Get link sharing links
- enableLinkSharing().then(
- ({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
- linkSharingReadAndWrite = rw
- linkSharingReadOnly = ro
- }
- )
- }
-
- function expectContentReadOnlyAccess() {
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\maketitle'
- )
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'have.attr',
- 'contenteditable',
- 'false'
- )
- }
-
- function expectContentWriteAccess() {
- const section = `Test Section ${uuid()}`
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- // wait for the editor to finish loading
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\maketitle'
- )
- // the editor should be writable
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'have.attr',
- 'contenteditable',
- 'true'
- )
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
- })
- // should have written
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- `\\section{${section}}`
- )
- // check PDF
- recompile()
- cy.findByRole('region', { name: 'PDF preview' }).within(() => {
- cy.findByLabelText(/Page.*1/i).should('be.visible')
- cy.findByText(projectName).should('be.visible')
- })
- cy.findByRole('region', { name: 'PDF preview' }).within(() => {
- cy.findByLabelText(/Page.*1/i).should('be.visible')
- cy.contains(section)
- })
- }
-
- function expectNoAccess() {
- // try read only access link
- cy.visit(linkSharingReadOnly)
- cy.url().should('match', /\/login/)
-
- // Cypress bugs: cypress resolves the link-sharing link outside the browser, and it carries over the hash of the link-sharing link to the login page redirect (bug 1).
- // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
- // This is turn does not trigger a "page load", but rather just "scrolling", which in turn trips up the "page loaded" detection in cypress (bug 2).
- // Work around this by navigating away from the /login page in between checks.
- cy.visit('/user/password/reset')
-
- // try read and write access link
- cy.visit(linkSharingReadAndWrite)
- cy.url().should('match', /\/login/)
- }
-
- function expectChatAccess() {
- cy.findByRole('tab', { name: 'Chat' }).click()
- cy.findByText('New Chat Message')
- }
-
- function expectHistoryAccess() {
- cy.findByRole('button', { name: 'History' }).click()
- // The input is not clickable due to being visually hidden, click its label instead
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findByRole('group', {
- name: 'Show all of the project history or only labelled versions.',
- }).within(() => {
- cy.findByText('All history').click()
- })
- cy.findByRole('radio', { name: 'Labels' }).should('not.be.checked')
- cy.findByRole('radio', { name: 'All history' }).should('be.checked')
- })
- cy.findByText(/\\begin\{document}/)
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findAllByTestId('history-version-metadata-users')
- .last()
- .should('have.text', 'user')
- })
- cy.findByRole('button', { name: 'Back to editor' }).click()
- }
-
- function expectNoChatAccess() {
- cy.findByRole('button', { name: 'Layout options' }) // wait for lazy loading
- cy.findByRole('tab', { name: 'Chat' }).should('not.exist')
- }
-
- function expectNoHistoryAccess() {
- cy.findByRole('button', { name: 'Layout options' }) // wait for lazy loading
- cy.findByRole('button', { name: 'History' }).should('not.exist')
- }
-
- function expectCommentAccess() {
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\maketitle'
- )
-
- cy.findByText('\\maketitle').parent().dblclick()
-
- cy.findByRole('button', { name: 'Add comment' }).should('be.visible')
-
- cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
- }
-
- function expectNoCommentAccess() {
- cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
- 'contain.text',
- '\\maketitle'
- )
-
- cy.findByText('\\maketitle').parent().dblclick()
-
- cy.findByRole('button', { name: 'Add comment' }).should('not.exist')
- cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
- }
-
- function expectFullReadOnlyAccess() {
- expectContentReadOnlyAccess()
- expectChatAccess()
- expectHistoryAccess()
- expectNoCommentAccess()
- }
-
- function expectRestrictedReadOnlyAccess() {
- expectContentReadOnlyAccess()
- expectNoChatAccess()
- expectNoHistoryAccess()
- expectNoCommentAccess()
- }
-
- function expectFullReadAndWriteAccess() {
- expectContentWriteAccess()
- expectChatAccess()
- expectHistoryAccess()
- expectCommentAccess()
- }
-
- function expectAnonymousReadAndWriteAccess() {
- expectContentWriteAccess()
- expectChatAccess()
- expectHistoryAccess()
- expectNoCommentAccess()
- }
-
- function expectProjectDashboardEntry() {
- cy.visit('/project')
- cy.findByText(projectName)
- }
-
- function expectEditAuthoredAs(author: string) {
- cy.findByRole('button', { name: 'History' }).click()
- cy.findByRole('complementary', {
- name: 'Project history and labels',
- }).within(() => {
- cy.findAllByTestId('history-version-metadata-users')
- .first()
- .should('contain.text', author) // might have other edits in the same group
- })
- }
- describe('via email', function () {
- const email = 'collaborator-email@example.com'
- ensureUserExists({ email })
-
- beforeEach(function () {
- login('user@example.com')
- shareProjectByEmailAndAcceptInviteViaEmail(
- projectName,
- email,
- 'Viewer',
- true
- )
- })
-
- it('should grant the collaborator read access', function () {
- expectFullReadOnlyAccess()
- expectProjectDashboardEntry()
- })
- })
-
- describe('read only', function () {
- const email = 'collaborator-ro@example.com'
- ensureUserExists({ email })
-
- beforeWithReRunOnTestRetry(() => {
- login('user@example.com')
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- email,
- 'Viewer',
- true
- )
- })
-
- it('should grant the collaborator read access', function () {
- login(email)
- openProjectByName(projectName, true)
- expectFullReadOnlyAccess()
- expectProjectDashboardEntry()
- })
- })
-
- describe('read and write', function () {
- const email = 'collaborator-rw@example.com'
- ensureUserExists({ email })
-
- beforeWithReRunOnTestRetry(() => {
- login('user@example.com')
- shareProjectByEmailAndAcceptInviteViaDash(
- projectName,
- email,
- 'Editor',
- true
- )
- })
-
- it('should grant the collaborator write access', function () {
- login(email)
- openProjectByName(projectName, true)
- expectFullReadAndWriteAccess()
- expectEditAuthoredAs('You')
- expectProjectDashboardEntry()
- })
- })
-
- describe('token access', function () {
- describe('logged in', function () {
- describe('read only', function () {
- const email = 'collaborator-link-ro@example.com'
- ensureUserExists({ email })
-
- it('should grant restricted read access', function () {
- login(email)
- openProjectViaLinkSharingAsUser(
- linkSharingReadOnly,
- projectName,
- email,
- true
- )
- expectRestrictedReadOnlyAccess()
- expectProjectDashboardEntry()
- })
- })
-
- describe('read and write', function () {
- const email = 'collaborator-link-rw@example.com'
- ensureUserExists({ email })
-
- it('should grant full write access', function () {
- login(email)
- openProjectViaLinkSharingAsUser(
- linkSharingReadAndWrite,
- projectName,
- email,
- true
- )
- expectFullReadAndWriteAccess()
- expectEditAuthoredAs('You')
- expectProjectDashboardEntry()
- })
- })
- })
-
- describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () {
- describe('wrap startup', function () {
- startWith({
- pro: true,
- vars: {
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
- },
- withDataDir: true,
- })
- it('should block access', function () {
- expectNoAccess()
- })
- })
-
- describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
- startWith({
- pro: true,
- vars: {
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
- },
- withDataDir: true,
- })
- it('should block access', function () {
- expectNoAccess()
- })
- })
- })
-
- describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () {
- describe('wrap startup', function () {
- startWith({
- pro: true,
- vars: {
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
- },
- withDataDir: true,
- })
- it('should grant read access with read link', function () {
- openProjectViaLinkSharingAsAnon(linkSharingReadOnly, true)
- expectRestrictedReadOnlyAccess()
- })
-
- it('should prompt for login with write link', function () {
- cy.visit(linkSharingReadAndWrite)
- cy.url().should('match', /\/login/)
- })
- })
-
- describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
- startWith({
- pro: true,
- vars: {
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
- },
- withDataDir: true,
- })
-
- it('should grant read access with read link', function () {
- openProjectViaLinkSharingAsAnon(linkSharingReadOnly, true)
- expectRestrictedReadOnlyAccess()
- })
-
- it('should grant write access with write link', function () {
- openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite, true)
- expectAnonymousReadAndWriteAccess()
- expectEditAuthoredAs('Anonymous')
- })
- })
- })
-
- describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () {
- const email = 'collaborator-email@example.com'
- ensureUserExists({ email })
-
- const invitedEmail = 'invited-email@example.com'
- ensureUserExists({ email: invitedEmail })
-
- const retainedViewerEmail = 'collaborator-retained-viewer@example.com'
- ensureUserExists({ email: retainedViewerEmail })
-
- const retainedEditorEmail = 'collaborator-retained-editor@example.com'
- ensureUserExists({ email: retainedEditorEmail })
-
- beforeWithReRunOnTestRetry(() => {
- // Set up retained viewer access
- login(retainedViewerEmail)
- openProjectViaLinkSharingAsUser(
- linkSharingReadOnly,
- projectName,
- retainedViewerEmail,
- true
- )
-
- // Set up retained editor access
- login(retainedEditorEmail)
- openProjectViaLinkSharingAsUser(
- linkSharingReadAndWrite,
- projectName,
- retainedEditorEmail,
- true
- )
- })
-
- beforeEach(function () {
- this.timeout(STARTUP_TIMEOUT) // Increase timeout for server reload
-
- return cy.wrap(
- reloadWith({
- pro: true,
- vars: {
- OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
- OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
- OVERLEAF_DISABLE_LINK_SHARING: 'true',
- },
- withDataDir: true,
- }),
- { timeout: STARTUP_TIMEOUT }
- )
- })
-
- it('should not display link sharing in the sharing modal', function () {
- login('user@example.com')
- openProjectByName(projectName, true)
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'Share' })
- .click()
- cy.findByRole('button', { name: 'Turn on link sharing' }).should(
- 'not.exist'
- )
- })
-
- it('should block new access to read-only link shared projects', function () {
- login(email)
-
- // Test read-only link returns 404
- cy.request({
- url: linkSharingReadOnly,
- failOnStatusCode: false,
- }).then(response => {
- expect(response.status).to.eq(404)
- })
- })
-
- it('should block new access to read-write link shared projects', function () {
- login(email)
-
- // Test read-write link returns 404
- cy.request({
- url: linkSharingReadAndWrite,
- failOnStatusCode: false,
- }).then(response => {
- expect(response.status).to.eq(404)
- })
- })
-
- it('should continue to allow email sharing', function () {
- login('user@example.com')
- shareProjectByEmailAndAcceptInviteViaEmail(
- projectName,
- invitedEmail,
- 'Viewer',
- true
- )
- expectFullReadOnlyAccess()
- expectProjectDashboardEntry()
- })
-
- it('should retain read-only access when project was joined via link before link sharing was turned off', function () {
- login(retainedViewerEmail)
- openProjectByName(projectName, true)
- expectRestrictedReadOnlyAccess()
- expectProjectDashboardEntry()
- })
-
- it('should retain read-write access when project was joined via link before link sharing was turned off', function () {
- login(retainedEditorEmail)
- openProjectByName(projectName, true)
- expectFullReadAndWriteAccess()
- expectProjectDashboardEntry()
- })
- })
- })
-})
diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts
deleted file mode 100644
index 252075fefd..0000000000
--- a/server-ce/test/sandboxed-compiles.spec.ts
+++ /dev/null
@@ -1,351 +0,0 @@
-import { ensureUserExists, login } from './helpers/login'
-import { createProjectAndOpenInNewEditor } from './helpers/project'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { prepareWaitForNextCompileSlot, stopCompile } from './helpers/compile'
-import { v4 as uuid } from 'uuid'
-import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
-
-const LABEL_TEX_LIVE_VERSION = 'TeX Live version'
-
-describe('SandboxedCompiles', function () {
- const enabledVars = {
- SANDBOXED_COMPILES: 'true',
- ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: '2023,2022',
- }
-
- describe('enabled in Server Pro', function () {
- if (isExcludedBySharding('PRO_CUSTOM_2')) return
- startWith({
- pro: true,
- vars: enabledVars,
- resetData: true,
- })
- ensureUserExists({ email: 'user@example.com' })
- beforeEach(function () {
- login('user@example.com')
- })
-
- it('should offer TexLive images and switch the compiler', function () {
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('sandboxed')
- })
- cy.log('wait for compile')
- cy.findByRole('region', { name: 'PDF preview' }).should(
- 'contain.text',
- 'sandboxed'
- )
- cy.findByRole('button', { name: 'View logs' }).click()
- cy.findByLabelText('Raw logs from the LaTeX compiler').within(() => {
- cy.log('Check which compiler version was used, expect 2023')
- cy.findByRole('button', { name: 'Expand' }).click()
- cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /)
- })
-
- cy.log('Switch TeXLive version from 2023 to 2022')
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').findByRole('tab', { name: 'Compiler' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('option', { name: '2023' }).should('be.selected')
- cy.findByRole('combobox', {
- name: LABEL_TEX_LIVE_VERSION,
- }).select('2022')
- })
- cy.get('body').type('{esc}')
- cy.findByRole('dialog').should('not.exist')
- cy.log('Trigger compile with other TeX Live version')
- recompile()
-
- cy.findByRole('button', { name: 'View logs' }).click()
- cy.findByLabelText('Raw logs from the LaTeX compiler').within(() => {
- cy.log('Check which compiler version was used, expect 2022')
- cy.findByRole('button', { name: 'Expand' }).click()
- cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2022\) /)
- })
- })
-
- checkSyncTeX()
- checkXeTeX()
- checkRecompilesAfterErrors()
- checkStopCompile()
- })
-
- function checkStopCompile() {
- it('users can stop a running compile', function () {
- login('user@example.com')
- const { recompile, waitForCompile, waitForCompileRateLimitCoolOff } =
- prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('test-project')
- })
- // create an infinite loop in the main document
- // this will cause the compile to run indefinitely
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle')
- .parent()
- .type(
- '\nFirst page\\clearpage' +
- '\nSecond page' +
- '\\def\\loop{{}\\let\\next\\loop\\next}\\loop'
- )
- waitForCompileRateLimitCoolOff()
- cy.log('Start compile')
- // We need to start the compile manually because we do not want to wait for it to finish
- cy.findByRole('button', { name: 'Recompile' }).click()
- // Wait for the compile to start
- cy.findByRole('button', { name: 'Compiling…' }).should('exist')
- // Now stop the compile and kill the latex process
- stopCompile({ delay: 1000 })
- cy.findByRole('region', { name: 'PDF preview' })
- .invoke('text')
- .should('match', /Compilation cancelled/)
- // Check that the previous compile is not running in the background by
- // disabling the infinite loop and recompiling
- cy.findByText('\\def').parent().click()
- cy.findByText('\\def').parent().type('{home}disabled loop% ')
- recompile()
- cy.findByRole('region', { name: 'PDF preview' })
- .should('contain.text', 'disabled loop')
- .should('not.contain.text', 'A previous compile is still running')
- })
- }
-
- function checkSyncTeX() {
- describe('SyncTeX', function () {
- let projectName: string
- beforeEach(function () {
- projectName = `Project ${uuid()}`
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor(projectName)
- })
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(
- () => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle')
- .parent()
- .type(
- `\n\\pagebreak\n\\section{{}Section A}\n\\pagebreak\n\\section{{}Section B}\n\\pagebreak`
- )
- }
- )
- recompile()
- cy.log('wait for pdf-rendering')
- cy.findByRole('region', { name: 'PDF preview' }).findByText(projectName)
- })
-
- it('should sync to code', function () {
- cy.log('navigate to \\maketitle using double click in PDF')
- cy.findByRole('region', { name: 'PDF preview' })
- .findByText(projectName)
- .dblclick()
- cy.get('.cm-activeLine').should('have.text', '\\maketitle')
-
- cy.log('navigate to Section A using double click in PDF')
- cy.findByRole('region', { name: 'PDF preview' })
- .findByText('Section A')
- .dblclick()
- cy.get('.cm-activeLine').should('have.text', '\\section{Section A}')
-
- cy.log('navigate to Section B using arrow button')
- cy.findByTestId('pdfjs-viewer-inner')
- .should('have.prop', 'scrollTop')
- .as('start')
- cy.findByRole('region', { name: 'PDF preview' })
- .findByText('Section B')
- .scrollIntoView()
- cy.get('@start').then((start: any) => {
- waitUntilScrollingFinished(
- '[data-testid="pdfjs-viewer-inner"]',
- start
- )
- })
- // The sync button is swapped as the position in the PDF changes.
- // Cypress appears to click on a button that references a stale position.
- // Adding a cy.wait() statement is the most reliable "fix" so far :/
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.wait(1000)
- cy.findByRole('button', {
- name: 'Go to PDF location in code (Tip: double click on the PDF for best results)',
- }).click()
- cy.get('.cm-activeLine').should('have.text', '\\section{Section B}')
- })
-
- it('should sync to pdf', function () {
- cy.log('zoom in')
- cy.findByRole('button', { name: 'PDF zoom level' }).click()
- cy.findByRole('menuitem', { name: '400%' }).click()
- cy.log('scroll to top')
- cy.findByTestId('pdfjs-viewer-inner').scrollTo('top')
- waitUntilScrollingFinished('[data-testid="pdfjs-viewer-inner"]', -1).as(
- 'start'
- )
-
- cy.log('navigate to title')
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(
- () => {
- cy.findByText('\\maketitle').parent().click()
- }
- )
- cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
- cy.get('@start').then((start: any) => {
- waitUntilScrollingFinished(
- '[data-testid="pdfjs-viewer-inner"]',
- start
- )
- .as('title')
- .should('be.greaterThan', start)
- })
-
- cy.log('navigate to Section A')
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() =>
- cy.findByText('Section A').click()
- )
- cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
- cy.get('@title').then((title: any) => {
- waitUntilScrollingFinished(
- '[data-testid="pdfjs-viewer-inner"]',
- title
- )
- .as('sectionA')
- .should('be.greaterThan', title)
- })
-
- cy.log('navigate to Section B')
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() =>
- cy.findByText('Section B').click()
- )
- cy.findByRole('button', { name: 'Go to code location in PDF' }).click()
- cy.get('@sectionA').then((title: any) => {
- waitUntilScrollingFinished(
- '[data-testid="pdfjs-viewer-inner"]',
- title
- )
- .as('sectionB')
- .should('be.greaterThan', title)
- })
- })
- })
- }
-
- function checkRecompilesAfterErrors() {
- it('recompiles even if there are Latex errors', function () {
- login('user@example.com')
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('test-project')
- })
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle')
- .parent()
- .type('\n\\fakeCommand{} \n\\section{{}Test Section}')
- })
- recompile()
- recompile()
- cy.findByRole('region', { name: 'PDF preview' })
- .findByText('Test Section')
- .should('not.contain.text', 'No PDF')
- })
- }
-
- function checkXeTeX() {
- it('should be able to use XeLaTeX', function () {
- const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- createProjectAndOpenInNewEditor('XeLaTeX')
- })
- cy.log('wait for compile')
- cy.findByRole('region', { name: 'PDF preview' }).findByText('XeLaTeX')
- cy.log('Check which compiler was used, expect pdfLaTeX')
- cy.findByRole('button', { name: 'View logs' }).click()
- cy.findByLabelText('Raw logs from the LaTeX compiler').within(() => {
- cy.log('Check which compiler was used, expect pdfLaTeX')
- cy.findByRole('button', { name: 'Expand' }).click()
- cy.findByText(/This is pdfTeX/)
- })
-
- cy.log('Switch compiler to from pdfLaTeX to XeLaTeX')
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').findByRole('tab', { name: 'Compiler' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByRole('option', { name: 'pdfLaTeX' }).should('be.selected')
- cy.findByRole('combobox', {
- name: 'Compiler',
- }).select('XeLaTeX')
- })
-
- cy.get('body').type('{esc}')
- cy.findByRole('dialog').should('not.exist')
-
- cy.log('Trigger compile with other compiler')
- recompile()
-
- cy.findByRole('button', { name: 'View logs' }).click()
- cy.findByLabelText('Raw logs from the LaTeX compiler').within(() => {
- cy.log('Check which compiler was used, expect XeLaTeX')
- cy.findByRole('button', { name: 'Expand' }).click()
- cy.findByText(/This is XeTeX/)
- })
- })
- }
-
- function checkUsesDefaultCompiler() {
- beforeEach(function () {
- login('user@example.com')
- })
-
- it('should not offer TexLive images and use default compiler', function () {
- createProjectAndOpenInNewEditor('sandboxed')
- cy.log('wait for compile')
- cy.findByRole('region', { name: 'PDF preview' }).findByText('sandboxed')
-
- cy.log('Check which compiler version was used, expect 2026')
- cy.findByRole('button', { name: 'View logs' }).click()
- cy.findByLabelText('Raw logs from the LaTeX compiler').within(() => {
- cy.findByRole('button', { name: 'Expand' }).click()
- cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2026\) /)
- })
-
- cy.log('Check that there is no TeX Live version toggle')
- cy.findByRole('button', { name: 'Settings' }).click()
- cy.findByRole('dialog').findByRole('tab', { name: 'Compiler' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByText('Main document').should('exist')
- cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist')
- })
- })
- }
-
- describe('disabled in Server Pro', function () {
- if (isExcludedBySharding('PRO_DEFAULT_2')) return
- startWith({ pro: true })
- ensureUserExists({ email: 'user@example.com' })
- beforeEach(function () {
- login('user@example.com')
- })
-
- checkUsesDefaultCompiler()
- checkSyncTeX()
- checkXeTeX()
- checkRecompilesAfterErrors()
- checkStopCompile()
- })
-
- // https://github.com/overleaf/internal/issues/20216
- // eslint-disable-next-line mocha/no-pending-tests
- describe.skip('unavailable in CE', function () {
- if (isExcludedBySharding('CE_CUSTOM_1')) return
- startWith({ pro: false, vars: enabledVars, resetData: true })
- ensureUserExists({ email: 'user@example.com' })
- beforeEach(function () {
- login('user@example.com')
- })
-
- checkUsesDefaultCompiler()
- checkSyncTeX()
- checkXeTeX()
- checkRecompilesAfterErrors()
- checkStopCompile()
- })
-})
diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts
deleted file mode 100644
index dc7eaa6649..0000000000
--- a/server-ce/test/templates.spec.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { ensureUserExists, login } from './helpers/login'
-import {
- createProjectAndOpenInNewEditor,
- NEW_PROJECT_BUTTON_MATCHER,
- redirectEditorUrlWithQueryParams,
-} from './helpers/project'
-
-const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
-const ADMIN_USER = 'admin@example.com'
-const REGULAR_USER = 'user@example.com'
-const TEMPLATES_USER = 'templates@example.com'
-
-// Re-use value for "exists" and "does not exist" tests
-const LABEL_BROWSE_TEMPLATES = 'Browse templates'
-
-describe('Templates', function () {
- ensureUserExists({ email: TEMPLATES_USER })
- ensureUserExists({ email: WITHOUT_PROJECTS_USER })
-
- let OVERLEAF_TEMPLATES_USER_ID: string
- before(function () {
- login(TEMPLATES_USER)
- cy.visit('/')
- cy.get('meta[name="ol-user_id"]').then(el => {
- OVERLEAF_TEMPLATES_USER_ID = el.attr('content')!
- })
- })
-
- function varsFn() {
- return {
- OVERLEAF_TEMPLATES_USER_ID,
- OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS:
- '[{"name":"All Templates","url":"/templates/all"}]',
- }
- }
-
- describe('enabled in Server Pro', function () {
- if (isExcludedBySharding('PRO_CUSTOM_2')) return
- startWith({
- pro: true,
- varsFn,
- })
- ensureUserExists({ email: REGULAR_USER })
- ensureUserExists({ email: ADMIN_USER, isAdmin: true })
-
- it('should show templates link on welcome page', function () {
- login(WITHOUT_PROJECTS_USER)
- cy.visit('/')
- cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).click()
- cy.url().should('match', /\/templates$/)
- })
-
- it('should have templates feature', function () {
- login(TEMPLATES_USER)
- const name = `Template ${Date.now()}`
- const description = `Template Description ${Date.now()}`
-
- cy.visit('/')
- createProjectAndOpenInNewEditor(name, { type: 'Example project' }).as(
- 'templateProjectId'
- )
-
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Manage template' }).click()
-
- cy.findByLabelText('Template Description').type(description)
- cy.findByRole('button', { name: 'Publish' }).click()
- cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled')
- cy.findByRole('button', { name: 'Publish' }).should('not.exist')
- cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 })
- cy.findByRole('button', { name: 'Republish' })
-
- cy.findByRole('link', { name: 'View it in the template gallery' }).click()
- cy.url()
- .should('match', /\/templates\/[a-f0-9]{24}$/)
- .as('templateURL')
-
- cy.findByRole('heading', { level: 2 }).findByText(name)
- cy.findByText(description)
- cy.findByRole('link', { name: 'Open as Template' })
- cy.findByRole('button', { name: 'Unpublish' })
- cy.findByRole('button', { name: 'Republish' })
- cy.get('img')
- .should('have.attr', 'src')
- .and('match', /\/v\/0\//)
- cy.findByRole('button', { name: 'Republish' }).click()
- cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled')
- cy.findByRole('button', { name: 'Republish', timeout: 60_000 })
- cy.get('img', { timeout: 60_000 })
- .should('have.attr', 'src')
- .and('match', /\/v\/1\//)
-
- // custom tag
- const tagName = `${Date.now()}`
- cy.visit('/')
- cy.findByRole('checkbox', { name: `Select ${name}` }).check()
- cy.findByRole('navigation', { name: 'Project categories and tags' })
- .findByRole('button', { name: 'New tag' })
- .click()
- cy.focused().type(tagName)
- cy.findByRole('button', { name: 'Create' }).click()
- cy.findByRole('navigation', {
- name: 'Project categories and tags',
- }).should('contain', `${tagName} (1)`)
-
- // Check listing
- cy.visit('/templates')
- cy.findByRole('link', { name: tagName })
- cy.visit('/templates/all')
- cy.findByRole('heading', { name })
- cy.visit(`/templates/${tagName}`)
- cy.findByRole('heading', { name })
-
- // Unpublish via template page
- cy.get('@templateURL').then(url => cy.visit(`${url}`))
- cy.findByRole('button', { name: 'Unpublish' }).click()
- cy.url().should('match', /\/templates$/)
- cy.get('@templateURL').then(url =>
- cy.visit(`${url}`, {
- failOnStatusCode: false,
- })
- )
- cy.findByRole('heading', { name: 'Not found' })
- cy.visit('/templates/all')
- cy.findByRole('heading', { name }).should('not.exist')
- cy.visit(`/templates/${tagName}`)
- cy.findByRole('heading', { name }).should('not.exist')
-
- // Publish again
- cy.get('@templateProjectId').then(projectId =>
- cy.visit(`/project/${projectId}`)
- )
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Manage template' }).click()
- cy.findByRole('button', { name: 'Publish' }).click()
- cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 })
-
- // Should assign a new template id
- cy.findByRole('link', { name: 'View it in the template gallery' }).click()
- cy.url()
- .should('match', /\/templates\/[a-f0-9]{24}$/)
- .as('newTemplateURL')
- cy.get('@newTemplateURL').then(newURL => {
- cy.get('@templateURL').then(prevURL => {
- expect(newURL).to.match(/\/templates\/[a-f0-9]{24}$/)
- expect(prevURL).to.not.equal(newURL)
- })
- })
-
- // Open project from template
- login(REGULAR_USER)
- cy.visit('/templates')
- cy.findByRole('link', { name: tagName }).click()
- cy.findByRole('link', { name }).click()
- cy.findByRole('link', { name: 'Open as Template' }).click()
- cy.findByRole('navigation', { name: 'Project actions' }).findByText(
- /Your Paper/i
- ) // might have (1) suffix
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Word count' }).click() // wait for lazy loading
- cy.findByRole('menuitem', { name: 'Manage template' }).should('not.exist')
-
- // Check management as regular user
- cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
- cy.findByRole('link', { name: 'Open as Template' })
- cy.findByRole('button', { name: 'Unpublish' }).should('not.exist')
- cy.findByRole('button', { name: 'Republish' }).should('not.exist')
-
- // Check management as admin user
- login(ADMIN_USER)
-
- redirectEditorUrlWithQueryParams(true)
-
- cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
- cy.findByRole('link', { name: 'Open as Template' })
- cy.findByRole('button', { name: 'Unpublish' })
- cy.findByRole('button', { name: 'Republish' })
- cy.get('@templateProjectId').then(projectId =>
- cy.visit(`/project/${projectId}`)
- )
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Manage template' }).click()
- cy.findByRole('button', { name: 'Unpublish' })
-
- // Back to templates user
- login(TEMPLATES_USER)
-
- // Unpublish via editor
- cy.get('@templateProjectId').then(projectId =>
- cy.visit(`/project/${projectId}`)
- )
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Manage template' }).click()
- cy.findByRole('button', { name: 'Unpublish' }).click()
- cy.findByRole('button', { name: 'Publish' })
- cy.visit('/templates/all')
- cy.findByRole('link', { name }).should('not.exist')
-
- // check for template links, after creating the first project
- cy.visit('/')
- cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
- cy.findByRole('menuitem', { name: /All Templates/ }).should(
- 'have.attr',
- 'href',
- '/templates/all'
- )
- })
- })
-
- function checkDisabled() {
- it('should not have templates feature', function () {
- login(TEMPLATES_USER)
-
- cy.visit('/')
- createProjectAndOpenInNewEditor('maybe templates')
-
- cy.findByRole('navigation', {
- name: 'Project actions',
- })
- .findByRole('button', { name: 'File' })
- .click()
- cy.findByRole('menuitem', { name: 'Word count' }) // wait for lazy loading
- cy.findByRole('menuitem', { name: 'Manage template' }).should('not.exist')
-
- cy.visit('/templates', { failOnStatusCode: false })
- cy.findByRole('heading', { name: 'Not found' })
- cy.visit('/templates/all', { failOnStatusCode: false })
- cy.findByRole('heading', { name: 'Not found' })
-
- // check for template links, after creating the first project
- cy.visit('/')
- cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click()
- cy.findByRole('menuitem', { name: /All Templates/ }).should('not.exist')
- })
-
- it('should not show templates link on welcome page', function () {
- login(WITHOUT_PROJECTS_USER)
- cy.visit('/')
- cy.findByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }) // wait for lazy loading
- cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).should(
- 'not.exist'
- )
- })
- }
-
- describe('disabled Server Pro', function () {
- if (isExcludedBySharding('PRO_DEFAULT_2')) return
- startWith({ pro: true })
- checkDisabled()
- })
-
- describe('unavailable in CE', function () {
- if (isExcludedBySharding('CE_CUSTOM_1')) return
- startWith({
- pro: false,
- varsFn,
- })
- checkDisabled()
- })
-})
diff --git a/server-ce/test/tsconfig.json b/server-ce/test/tsconfig.json
deleted file mode 100644
index db40ae1adf..0000000000
--- a/server-ce/test/tsconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "compilerOptions": {
- "target": "esnext" /* Specify ECMAScript target version */,
- "module": "es2020" /* Specify module code generation */,
- "allowJs": true /* Allow JavaScript files to be compiled. */,
- // "checkJs": true /* Report errors in .js files. */,
- "jsx": "preserve" /* Specify JSX code generation */,
- "noEmit": true /* Do not emit outputs. */,
- "strict": true /* Enable all strict type-checking options. */,
- "moduleResolution": "node" /* Specify module resolution strategy */,
- "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
- "skipLibCheck": true /* Skip type checking of declaration files. */,
- "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
- "types": ["cypress", "node", "@testing-library/cypress"],
- "allowImportingTsExtensions": true
- },
- "include": ["**/*.ts", "**/*.js"]
-}
diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts
deleted file mode 100644
index 0938b4b51b..0000000000
--- a/server-ce/test/upgrading.spec.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { ensureUserExists, login } from './helpers/login'
-import { isExcludedBySharding, startWith } from './helpers/config'
-import { runScript } from './helpers/hostAdminClient'
-import { createProject, openProjectByName } from './helpers/project'
-import { prepareWaitForNextCompileSlot } from './helpers/compile'
-import { v4 as uuid } from 'uuid'
-
-const USER = 'user@example.com'
-const PROJECT_NAME = 'Old Project'
-
-describe('Upgrading', function () {
- if (isExcludedBySharding('PRO_CUSTOM_3')) return
-
- let recompile: () => void
- let waitForCompile: (triggerCompile: () => void) => void
-
- function testUpgrade(
- steps: {
- version: string
- vars?: Object
- newProjectButtonMatcher?: RegExp
- hook?: () => void
- }[]
- ) {
- const startOptions = steps.shift()!
-
- before(async function () {
- cy.log('Create old instance')
- })
- startWith({
- pro: true,
- version: startOptions.version,
- withDataDir: true,
- resetData: true,
- vars: startOptions.vars,
- })
- before(function () {
- cy.log('Create initial user after deleting it')
- })
- ensureUserExists({ email: USER })
- before(function () {
- cy.log('Populate old instance')
- login(USER)
- ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
- waitForCompile(() => {
- createProject(PROJECT_NAME, {
- newProjectButtonMatcher: startOptions.newProjectButtonMatcher,
- })
- })
-
- cy.log('Wait for successful compile')
- cy.findByLabelText(/Page.*1/i).findByText(PROJECT_NAME)
-
- cy.log('Increment the doc version three times')
- for (let i = 0; i < 3; i++) {
- cy.log('Add content')
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle')
- .parent()
- .type(`\n\\section{{}Old Section ${i}}`)
-
- cy.log('Trigger full flush')
- recompile()
- cy.findByRole('navigation', {
- name: 'Project Layout, Sharing, and Submission',
- })
- .findByRole('button', { name: 'Menu' })
- .click()
- cy.findByRole('link', { name: 'Source' }).click()
- cy.get('body').type('{esc}')
- }
-
- cy.log('Check compile and history')
- for (let i = 0; i < 3; i++) {
- cy.findByLabelText(/Page.*1/i).findByText(`Old Section ${i}`)
- }
- cy.findByRole('button', { name: 'History' }).click()
- for (let i = 0; i < 3; i++) {
- cy.findByText(new RegExp(`\\\\section{Old Section ${i}}`))
- }
- })
-
- for (const step of steps) {
- before(function () {
- cy.log(`Upgrade to version ${step.version}`)
-
- // Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests)
- cy.visit('/project', {})
- })
- before(async function () {
- cy.log('Graceful shutdown: flush all the things')
- this.timeout(20 * 1000)
- // Ideally we could use the container shutdown procedure, but it's too slow and unreliable for tests.
- // TODO(das7pad): adopt the below after speeding up the graceful shutdown procedure on all supported releases
- // await dockerCompose('stop', 'sharelatex')
-
- // For now, we are stuck with manually flushing things
- await runScript({
- cwd: 'services/document-updater',
- script: 'scripts/flush_all.js',
- })
- await runScript({
- cwd: 'services/project-history',
- script: 'scripts/flush_all.js',
- })
- })
- startWith({
- pro: true,
- version: step.version,
- vars: step.vars,
- withDataDir: true,
- })
-
- step.hook?.()
- }
- beforeEach(function () {
- login(USER)
- })
-
- it('should list the old project', function () {
- cy.visit('/project')
- cy.findByRole('link', { name: PROJECT_NAME })
- })
-
- it('should open the old project', function () {
- waitForCompile(() => {
- openProjectByName(PROJECT_NAME)
- })
-
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- cy.findByRole('navigation', {
- name: 'Project actions',
- }).within(() => {
- cy.findByText(PROJECT_NAME)
- })
-
- cy.log('wait for successful compile')
- cy.findByLabelText(/Page.*1/i).findByText(PROJECT_NAME)
- cy.findByLabelText(/Page.*1/i).findByText('Old Section 2')
-
- cy.log('Add more content')
- const newSection = `New Section ${uuid()}`
- cy.findByText('\\maketitle').parent().click()
- cy.findByText('\\maketitle').parent().type(`\n\\section{{}${newSection}}`)
-
- cy.log('Check compile and history')
- recompile()
- cy.findByLabelText(/Page.*1/i).findByText(newSection)
- cy.findByRole('button', { name: 'History' }).click()
- cy.findByText(/\\section\{Old Section 2}/)
- cy.findByText(new RegExp(`\\\\section\\{${newSection}}`))
- })
- }
-
- const optionsFourDotTwo = {
- version: '4.2',
- vars: {
- // Add core vars with old branding
- SHARELATEX_SITE_URL: 'http://sharelatex',
- SHARELATEX_MONGO_URL: 'mongodb://mongo/sharelatex',
- SHARELATEX_REDIS_HOST: 'redis',
- },
- newProjectButtonMatcher: /create first project/i,
- }
- const optionsBinaryFilesMigration = {
- version: '5.5.5',
- hook() {
- before(async function () {
- await runScript({
- cwd: 'services/history-v1',
- script: 'storage/scripts/back_fill_file_hash.mjs',
- args: ['--all'],
- })
- })
- },
- }
- describe('from 4.2 to latest', function () {
- testUpgrade([
- optionsFourDotTwo,
- optionsBinaryFilesMigration,
- { version: 'latest' },
- ])
- })
- describe('from 5.0 to latest', function () {
- testUpgrade([
- { version: '5.0' },
- optionsBinaryFilesMigration,
- { version: 'latest' },
- ])
- })
-})
diff --git a/services/chat/.nvmrc b/services/chat/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/chat/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/chat/Dockerfile b/services/chat/Dockerfile
deleted file mode 100644
index ca3c6a25a0..0000000000
--- a/services/chat/Dockerfile
+++ /dev/null
@@ -1,50 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/chat
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY services/chat/package.json /overleaf/services/chat/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/chat overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY services/chat/ /overleaf/services/chat/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/chat/Makefile b/services/chat/Makefile
deleted file mode 100644
index 9fa67f9e7a..0000000000
--- a/services/chat/Makefile
+++ /dev/null
@@ -1,205 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export PROJECT_NAME = chat
-BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
-HERE=$(shell pwd)
-export MONOREPO ?= $(shell cd ../../ && pwd)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/services/chat/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/chat/reports:/overleaf/services/chat/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/chat/reports:/overleaf/services/chat/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/chat
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/chat
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/chat
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/chat/buildscript.txt b/services/chat/buildscript.txt
deleted file mode 100644
index c3cbe23489..0000000000
--- a/services/chat/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-chat
---dependencies=mongo
---deploy-pipeline=chat
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=44
---public-repo=False
diff --git a/services/chat/docker-compose.ci.yml b/services/chat/docker-compose.ci.yml
deleted file mode 100644
index 0747d8ab9e..0000000000
--- a/services/chat/docker-compose.ci.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/chat/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/chat/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- depends_on:
- mongo:
- condition: service_started
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/chat/docker-compose.yml b/services/chat/docker-compose.yml
deleted file mode 100644
index 32c167cd66..0000000000
--- a/services/chat/docker-compose.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/chat/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/chat
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/chat
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/chat/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/chat
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/chat
- environment:
- RETRIES:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/clsi/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/clsi/.nycrc b/services/clsi/.nycrc
deleted file mode 100644
index e1849f18cc..0000000000
--- a/services/clsi/.nycrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extension": [".js", ".cjs", ".ts"],
- "cwd": "../../",
- "produce-source-map": true,
- "source-map": true,
- "temp-dir": "/tmp/.nyc_output"
-}
diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile
deleted file mode 100644
index 603d6ddf7a..0000000000
--- a/services/clsi/Dockerfile
+++ /dev/null
@@ -1,68 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/clsi
-COPY services/clsi/install_deps.sh /overleaf/services/clsi/
-RUN chmod 0755 ./install_deps.sh && ./install_deps.sh
-ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
-COPY services/clsi/entrypoint.sh /
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/overleaf-editor-core/package.json /overleaf/libraries/overleaf-editor-core/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/clsi/package.json /overleaf/services/clsi/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/clsi overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/clsi/ /overleaf/services/clsi/
-
-FROM app AS with-texlive
-
-RUN apt-get update \
- && apt-cache depends texlive-full | grep "Depends: " | grep -v -- "-doc" | grep -v -- "-lang-" | sed 's/Depends: //' | xargs apt-get install -y --no-install-recommends \
- && apt-get install -y --no-install-recommends fontconfig inkscape python3-pygments qpdf \
- && rm -rf /var/lib/apt/lists/*
-
-RUN mkdir -p cache compiles output \
-&& chown node:node cache compiles output
-
-CMD ["node", "--expose-gc", "app.js"]
-
-FROM app
-RUN mkdir -p cache compiles output \
-&& chown node:node cache compiles output
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/clsi/Makefile b/services/clsi/Makefile
deleted file mode 100644
index 47eae7ee8a..0000000000
--- a/services/clsi/Makefile
+++ /dev/null
@@ -1,210 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export PROJECT_NAME = clsi
-BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
-HERE=$(shell pwd)
-export MONOREPO ?= $(shell cd ../../ && pwd)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/overleaf-editor-core/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/clsi/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
- -git clean -dfX cache compiles output
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/clsi/reports:/overleaf/services/clsi/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/clsi/reports:/overleaf/services/clsi/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/clsi
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/clsi
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/clsi
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
- -docker pull us-east1-docker.pkg.dev/overleaf-ops/ol-docker/pandoc:3.9
- -docker pull us-east1-docker.pkg.dev/overleaf-ops/ol-docker/pandoc-staging:3.9
- -cd ../../ && docker build -t us-east1-docker.pkg.dev/overleaf-ops/ol-docker/pdftocairo:24.02 dockerfiles/pdftocairo
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt
deleted file mode 100644
index 690b547f74..0000000000
--- a/services/clsi/buildscript.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-clsi
---data-dirs=cache,compiles,output
---dependencies=
---env-add=DOWNLOAD_HOST=http://clsi-nginx:8080,ALLOWED_COMPILE_GROUPS=clsi-perf simple-latex-file,ENABLE_PDF_CACHING=true,PDF_CACHING_ENABLE_WORKER_POOL=true,ALLOWED_IMAGES=quay.io/sharelatex/texlive-full:2017.1 quay.io/sharelatex/texlive-full:2025.1 quay.io/sharelatex/pandoc:3.9 quay.io/sharelatex/pdftocairo:24.02,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,ENABLE_PANDOC_CONVERSIONS=true,ENABLE_PDF_CONVERSIONS=true
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=32
---public-repo=True
---test-unit-vitest=True
---tsconfig-extra-includes=vitest.config.unit.cjs
---use-large-ci-runner=True
diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml
deleted file mode 100644
index 3017b9eb78..0000000000
--- a/services/clsi/docker-compose.ci.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./reports:/overleaf/services/clsi/reports
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VITEST_NO_CACHE: true
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- DOWNLOAD_HOST: http://clsi-nginx:8080
- ALLOWED_COMPILE_GROUPS: clsi-perf simple-latex-file
- ENABLE_PDF_CACHING: true
- PDF_CACHING_ENABLE_WORKER_POOL: true
- ALLOWED_IMAGES: quay.io/sharelatex/texlive-full:2017.1 quay.io/sharelatex/texlive-full:2025.1 quay.io/sharelatex/pandoc:3.9 quay.io/sharelatex/pdftocairo:24.02
- 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
- ENABLE_PANDOC_CONVERSIONS: true
- ENABLE_PDF_CONVERSIONS: true
- volumes:
- - ./reports:/overleaf/services/clsi/reports
- - ./compiles:/overleaf/services/clsi/compiles
- - ./output:/overleaf/services/clsi/output
- - /var/run/docker.sock:/var/run/docker.sock
- depends_on:
- clsi-nginx:
- condition: service_started
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- clsi-nginx:
- image: nginx:1.28
- read_only: true
- tmpfs:
- - /tmp
- - /var/cache/nginx
- - /run
- volumes:
- - ./output:/output:ro
- - ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
- - ./tiny.pdf:/var/clsi/tiny.pdf:ro
diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml
deleted file mode 100644
index 1ea4549b20..0000000000
--- a/services/clsi/docker-compose.yml
+++ /dev/null
@@ -1,80 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/clsi/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/clsi
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/clsi
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- command: yarn run --silent test:unit
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/clsi/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/clsi
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - /var/run/docker.sock:/var/run/docker.sock
- working_dir: /overleaf/services/clsi
- environment:
- RETRIES:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- DOWNLOAD_HOST: http://clsi-nginx:8080
- ALLOWED_COMPILE_GROUPS: clsi-perf simple-latex-file
- ENABLE_PDF_CACHING: true
- PDF_CACHING_ENABLE_WORKER_POOL: true
- ALLOWED_IMAGES: quay.io/sharelatex/texlive-full:2017.1 quay.io/sharelatex/texlive-full:2025.1 quay.io/sharelatex/pandoc:3.9 quay.io/sharelatex/pdftocairo:24.02
- 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
- ENABLE_PANDOC_CONVERSIONS: true
- ENABLE_PDF_CONVERSIONS: true
- depends_on:
- clsi-nginx:
- condition: service_started
- command: yarn run --silent test:acceptance
-
- clsi-nginx:
- image: nginx:1.28
- read_only: true
- tmpfs:
- - /tmp
- - /var/cache/nginx
- - /run
- volumes:
- - ./output:/output:ro
- - ./nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
- - ./tiny.pdf:/var/clsi/tiny.pdf:ro
diff --git a/services/docstore/.nvmrc b/services/docstore/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/docstore/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/docstore/Dockerfile b/services/docstore/Dockerfile
deleted file mode 100644
index ae69304db4..0000000000
--- a/services/docstore/Dockerfile
+++ /dev/null
@@ -1,56 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/docstore
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/object-persistor/package.json /overleaf/libraries/object-persistor/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/docstore/package.json /overleaf/services/docstore/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/docstore overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/docstore/ /overleaf/services/docstore/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/docstore/Makefile b/services/docstore/Makefile
deleted file mode 100644
index 6cdcd7d322..0000000000
--- a/services/docstore/Makefile
+++ /dev/null
@@ -1,208 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export PROJECT_NAME = docstore
-BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
-HERE=$(shell pwd)
-export MONOREPO ?= $(shell cd ../../ && pwd)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/object-persistor/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/docstore/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/docstore/reports:/overleaf/services/docstore/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/docstore/reports:/overleaf/services/docstore/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/docstore
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/docstore
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/docstore
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/docstore/buildscript.txt b/services/docstore/buildscript.txt
deleted file mode 100644
index c0be66d047..0000000000
--- a/services/docstore/buildscript.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-docstore
---dependencies=mongo,gcs
---deploy-pipeline=docstore
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=32
---public-repo=True
---test-unit-vitest=True
---tsconfig-extra-includes=vitest.config.unit.cjs
diff --git a/services/docstore/docker-compose.ci.yml b/services/docstore/docker-compose.ci.yml
deleted file mode 100644
index 6ba8256375..0000000000
--- a/services/docstore/docker-compose.ci.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/docstore/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VITEST_NO_CACHE: true
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/docstore/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- depends_on:
- mongo:
- condition: service_started
- gcs:
- condition: service_healthy
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/docstore/docker-compose.yml b/services/docstore/docker-compose.yml
deleted file mode 100644
index a3789640a1..0000000000
--- a/services/docstore/docker-compose.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/docstore/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/docstore
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/docstore
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/docstore/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/docstore
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/docstore
- environment:
- RETRIES:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- gcs:
- condition: service_healthy
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/document-updater/.nvmrc b/services/document-updater/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/document-updater/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/document-updater/Dockerfile b/services/document-updater/Dockerfile
deleted file mode 100644
index 07c80ca19e..0000000000
--- a/services/document-updater/Dockerfile
+++ /dev/null
@@ -1,56 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/document-updater
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/overleaf-editor-core/package.json /overleaf/libraries/overleaf-editor-core/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/ranges-tracker/package.json /overleaf/libraries/ranges-tracker/package.json
-COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY services/document-updater/package.json /overleaf/services/document-updater/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/document-updater overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/ranges-tracker/ /overleaf/libraries/ranges-tracker/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY services/document-updater/ /overleaf/services/document-updater/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/document-updater/Makefile b/services/document-updater/Makefile
deleted file mode 100644
index b436b7a5bf..0000000000
--- a/services/document-updater/Makefile
+++ /dev/null
@@ -1,208 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export 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)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/overleaf-editor-core/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/ranges-tracker/package.json \
- $(MONOREPO)/libraries/redis-wrapper/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/services/document-updater/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/document-updater/reports:/overleaf/services/document-updater/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/document-updater/reports:/overleaf/services/document-updater/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/document-updater
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/document-updater
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/document-updater
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/document-updater/buildscript.txt b/services/document-updater/buildscript.txt
deleted file mode 100644
index a90fc19c49..0000000000
--- a/services/document-updater/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-document-updater
---dependencies=mongo,redis
---deploy-pipeline=document-updater
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=44
---public-repo=True
diff --git a/services/document-updater/docker-compose.ci.yml b/services/document-updater/docker-compose.ci.yml
deleted file mode 100644
index b091d49a9b..0000000000
--- a/services/document-updater/docker-compose.ci.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/document-updater/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/document-updater/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping="$$(redis-cli ping)" && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/document-updater/docker-compose.yml b/services/document-updater/docker-compose.yml
deleted file mode 100644
index 7c577603c0..0000000000
--- a/services/document-updater/docker-compose.yml
+++ /dev/null
@@ -1,100 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/document-updater/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/document-updater
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/document-updater
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/document-updater/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/document-updater
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/document-updater
- environment:
- RETRIES:
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping=$$(redis-cli ping) && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/filestore/.nvmrc b/services/filestore/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/filestore/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/filestore/Dockerfile b/services/filestore/Dockerfile
deleted file mode 100644
index c3445ae8a1..0000000000
--- a/services/filestore/Dockerfile
+++ /dev/null
@@ -1,51 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/filestore
-COPY services/filestore/install_deps.sh /overleaf/services/filestore/
-RUN chmod 0755 ./install_deps.sh && ./install_deps.sh
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/object-persistor/package.json /overleaf/libraries/object-persistor/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY services/filestore/package.json /overleaf/services/filestore/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/filestore overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY services/filestore/ /overleaf/services/filestore/
-
-FROM app
-RUN mkdir -p uploads template_files \
-&& chown node:node uploads template_files
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/filestore/Makefile b/services/filestore/Makefile
deleted file mode 100644
index 4cd37a5e9b..0000000000
--- a/services/filestore/Makefile
+++ /dev/null
@@ -1,205 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export PROJECT_NAME = filestore
-BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
-HERE=$(shell pwd)
-export MONOREPO ?= $(shell cd ../../ && pwd)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/object-persistor/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/services/filestore/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
- -git clean -dfX uploads template_files
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/filestore/reports:/overleaf/services/filestore/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/filestore/reports:/overleaf/services/filestore/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/filestore
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/filestore
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/filestore
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/filestore/buildscript.txt b/services/filestore/buildscript.txt
deleted file mode 100644
index 2b5a51e890..0000000000
--- a/services/filestore/buildscript.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-filestore
---data-dirs=uploads,template_files
---dependencies=s3,gcs
---deploy-pipeline=filestore-readonly
---env-add=ENABLE_CONVERSIONS=true,USE_PROM_METRICS=true,AWS_S3_USER_FILES_STORAGE_CLASS=REDUCED_REDUNDANCY,AWS_S3_USER_FILES_BUCKET_NAME=fake-user-files,AWS_S3_USER_FILES_DEK_BUCKET_NAME=fake-user-files-dek,AWS_S3_TEMPLATE_FILES_BUCKET_NAME=fake-template-files,GCS_USER_FILES_BUCKET_NAME=fake-gcs-user-files,GCS_TEMPLATE_FILES_BUCKET_NAME=fake-gcs-template-files
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=32
---public-repo=True
---test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_
---test-unit-vitest=True
---tsconfig-extra-includes=vitest.config.unit.cjs,vitest.config.acceptance.cjs
---use-large-ci-runner=True
diff --git a/services/filestore/docker-compose.ci.yml b/services/filestore/docker-compose.ci.yml
deleted file mode 100644
index 70b93bc473..0000000000
--- a/services/filestore/docker-compose.ci.yml
+++ /dev/null
@@ -1,227 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-volumes:
- minio-certs:
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/filestore/reports
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VITEST_NO_CACHE: true
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- AWS_S3_ENDPOINT: https://minio:9000
- AWS_S3_PATH_STYLE: "true"
- DELETE_OBJECTS_MD5_FALLBACK: true
- AWS_ACCESS_KEY_ID: OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY: OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- ENABLE_CONVERSIONS: true
- USE_PROM_METRICS: true
- AWS_S3_USER_FILES_STORAGE_CLASS: REDUCED_REDUNDANCY
- AWS_S3_USER_FILES_BUCKET_NAME: fake-user-files
- AWS_S3_USER_FILES_DEK_BUCKET_NAME: fake-user-files-dek
- AWS_S3_TEMPLATE_FILES_BUCKET_NAME: fake-template-files
- GCS_USER_FILES_BUCKET_NAME: fake-gcs-user-files
- GCS_TEMPLATE_FILES_BUCKET_NAME: fake-gcs-template-files
- volumes:
- - ./reports:/overleaf/services/filestore/reports
- - minio-certs:/certs
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- minio_setup:
- condition: service_completed_successfully
- gcs:
- condition: service_healthy
- user: node
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- certs:
- build:
- dockerfile_inline: |
- FROM node:24.14.1
- RUN wget -O /certgen "https://github.com/minio/certgen/releases/download/v1.3.0/certgen-linux-"`dpkg --print-architecture`
- RUN chmod +x /certgen
- volumes:
- - minio-certs:/certs
- working_dir: /certs
- entrypoint: sh
- command:
- - "-cex"
- - |
- if [ ! -f private.key ] || [ ! -f public.crt ]; then
- /certgen -host minio
- fi
- if ! openssl x509 -checkend 864000 -noout -in public.crt > /dev/null; then
- /certgen -host minio
- fi
-
- minio:
- image: minio/minio:RELEASE.2024-10-13T13-34-11Z
- command: server /data
- volumes:
- - minio-certs:/root/.minio/certs
- environment:
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- depends_on:
- certs:
- condition: service_completed_successfully
-
- minio_setup:
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- image: minio/mc:RELEASE.2024-10-08T09-37-26Z
- volumes:
- - minio-certs:/root/.mc/certs/CAs
- entrypoint: sh
- command:
- - "-cex"
- - |
- sleep 1
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
- mc mb --ignore-existing s3/fake-user-files
- mc mb --ignore-existing s3/fake-user-files-dek
- mc mb --ignore-existing s3/fake-template-files
- mc admin user add s3 \
- OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID \
- OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY
-
- echo '
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-user-files"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-user-files/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-user-files-dek"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-user-files-dek/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-template-files"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-template-files/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::random-bucket-*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::random-bucket-*"
- }
- ]
- }' > policy-filestore.json
-
- mc admin policy create s3 overleaf-filestore policy-filestore.json
- mc admin policy attach s3 overleaf-filestore \
- --user=OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/filestore/docker-compose.yml b/services/filestore/docker-compose.yml
deleted file mode 100644
index 4629a5a0cf..0000000000
--- a/services/filestore/docker-compose.yml
+++ /dev/null
@@ -1,239 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-volumes:
- minio-certs:
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/filestore/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/filestore
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/filestore
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- command: yarn run --silent test:unit
- user: node
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/filestore/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/filestore
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - minio-certs:/certs
- working_dir: /overleaf/services/filestore
- environment:
- RETRIES:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- AWS_S3_ENDPOINT: https://minio:9000
- AWS_S3_PATH_STYLE: "true"
- DELETE_OBJECTS_MD5_FALLBACK: true
- AWS_ACCESS_KEY_ID: OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY: OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- ENABLE_CONVERSIONS: true
- USE_PROM_METRICS: true
- AWS_S3_USER_FILES_STORAGE_CLASS: REDUCED_REDUNDANCY
- AWS_S3_USER_FILES_BUCKET_NAME: fake-user-files
- AWS_S3_USER_FILES_DEK_BUCKET_NAME: fake-user-files-dek
- AWS_S3_TEMPLATE_FILES_BUCKET_NAME: fake-template-files
- GCS_USER_FILES_BUCKET_NAME: fake-gcs-user-files
- GCS_TEMPLATE_FILES_BUCKET_NAME: fake-gcs-template-files
- user: node
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- minio_setup:
- condition: service_completed_successfully
- gcs:
- condition: service_healthy
- command: yarn run --silent test:acceptance
-
- certs:
- build:
- dockerfile_inline: |
- FROM node:24.14.1
- RUN wget -O /certgen "https://github.com/minio/certgen/releases/download/v1.3.0/certgen-linux-"`dpkg --print-architecture`
- RUN chmod +x /certgen
- volumes:
- - minio-certs:/certs
- working_dir: /certs
- entrypoint: sh
- command:
- - "-cex"
- - |
- if [ ! -f private.key ] || [ ! -f public.crt ]; then
- /certgen -host minio
- fi
- if ! openssl x509 -checkend 864000 -noout -in public.crt > /dev/null; then
- /certgen -host minio
- fi
-
- minio:
- image: minio/minio:RELEASE.2024-10-13T13-34-11Z
- command: server /data
- volumes:
- - minio-certs:/root/.minio/certs
- environment:
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- depends_on:
- certs:
- condition: service_completed_successfully
-
- minio_setup:
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- image: minio/mc:RELEASE.2024-10-08T09-37-26Z
- volumes:
- - minio-certs:/root/.mc/certs/CAs
- entrypoint: sh
- command:
- - "-cex"
- - |
- sleep 1
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
- mc mb --ignore-existing s3/fake-user-files
- mc mb --ignore-existing s3/fake-user-files-dek
- mc mb --ignore-existing s3/fake-template-files
- mc admin user add s3 \
- OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID \
- OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY
-
- echo '
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-user-files"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-user-files/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-user-files-dek"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-user-files-dek/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::fake-template-files"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::fake-template-files/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::random-bucket-*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::random-bucket-*"
- }
- ]
- }' > policy-filestore.json
-
- mc admin policy create s3 overleaf-filestore policy-filestore.json
- mc admin policy attach s3 overleaf-filestore \
- --user=OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/git-bridge/Dockerfile b/services/git-bridge/Dockerfile
deleted file mode 100644
index ab139ec7f9..0000000000
--- a/services/git-bridge/Dockerfile
+++ /dev/null
@@ -1,69 +0,0 @@
-# Build the a8m/envsubst binary, as it supports default values,
-# which the gnu envsubst (from gettext-base) does not.
-FROM golang:1.25.5-alpine AS envsubst_builder
-
-WORKDIR /build
-
-RUN go install github.com/a8m/envsubst/cmd/envsubst@latest
-
-FROM maven:3-amazoncorretto-21-debian AS base
-
-RUN apt-get update && apt-get install -y make git sqlite3 curl \
- && rm -rf /var/lib/apt/lists
-
-COPY --from=envsubst_builder /go/bin/envsubst /opt/envsubst
-RUN chmod +x /opt/envsubst
-
-RUN useradd --create-home node
-
-ENV MAVEN_OPTS="-Dmaven.repo.local=/home/node/repository"
-
-# Pre-resolve Maven dependencies and plugins into the base image to limit
-# downstream `mvn` invocations hitting Maven Central on every CI build.
-# Uses a stub source and test, since `dependency:go-offline` and `dependency:resolve-plugins`
-# miss lazy-loaded plugins (surefire, maven-assembly, etc.)
-COPY pom.xml /tmp/warmup/pom.xml
-RUN mkdir -p /tmp/warmup/src/main/java/stub /tmp/warmup/src/test/java/stub \
- && echo 'package stub; public class Stub {}' > /tmp/warmup/src/main/java/stub/Stub.java \
- && echo 'package stub; public class StubTest { @org.junit.Test public void noop() {} }' \
- > /tmp/warmup/src/test/java/stub/StubTest.java \
-&& mvn -B --no-transfer-progress -f /tmp/warmup/pom.xml clean package \
-&& chown -R node:node /home/node/repository \
-&& rm -rf /tmp/warmup
-
-FROM base AS builder
-
-COPY . /app
-
-WORKDIR /app
-
-RUN make package \
-# The name of the created jar contains the current version tag.
-# Rename it to a static path that can be used for copying.
-&& find /app/target \
- -name 'writelatex-git-bridge*jar-with-dependencies.jar' \
- -exec mv {} /git-bridge.jar \;
-
-FROM amazoncorretto:21-alpine
-
-RUN apk add --update --no-cache bash git sqlite procps htop net-tools jemalloc util-linux
-
-ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2
-
-RUN adduser -D node
-
-COPY --from=builder /git-bridge.jar /
-
-COPY --from=envsubst_builder /go/bin/envsubst /opt/envsubst
-RUN chmod +x /opt/envsubst
-
-COPY conf/envsubst_template.json envsubst_template.json
-COPY start.sh start.sh
-COPY server-pro-start.sh server-pro-start.sh
-
-RUN mkdir conf
-RUN chown node:node conf
-
-USER node
-
-CMD ["/start.sh"]
diff --git a/services/git-bridge/Makefile b/services/git-bridge/Makefile
deleted file mode 100644
index 7c773d8dd1..0000000000
--- a/services/git-bridge/Makefile
+++ /dev/null
@@ -1,93 +0,0 @@
-# git-bridge makefile
-
-MVN_OPTS := --no-transfer-progress
-MVN_TARGET := target/writelatex-git-bridge-1.0-SNAPSHOT-jar-with-dependencies.jar
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export COMMIT_SHA ?= $(shell git rev-parse HEAD)
-PROJECT_NAME = git-bridge
-
-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_TAG_SAFE)
-IMAGE_REPO_MAIN ?= $(IMAGE_REPO):main
-IMAGE_REPO_FINAL ?= $(IMAGE_REPO_BRANCH)-$(BUILD_NUMBER)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-runtime-conf:
- /opt/envsubst < conf/envsubst_template.json > conf/runtime.json
-
-
-run: $(MVN_TARGET) runtime-conf
- @exec java $(GIT_BRIDGE_JVM_ARGS) -jar $(MVN_TARGET) conf/runtime.json
-
-
-$(MVN_TARGET): $(shell find src -type f) pom.xml
- mvn $(MVN_OPTS) package -DskipTests
-
-build: $(MVN_TARGET)
-
-docker_build_base:
- docker build --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --cache-from $(IMAGE_REPO_BRANCH) \
- --cache-from $(IMAGE_REPO_MAIN) \
- --tag $(IMAGE_CI) \
- --target base \
- .
-
-docker_build:
- docker build --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --cache-from $(IMAGE_REPO_BRANCH) \
- --cache-from $(IMAGE_REPO_MAIN) \
- --tag $(IMAGE_REPO_BRANCH) \
- --tag $(IMAGE_REPO_FINAL) \
- .
-
-format:
- mvn $(MVN_OPTS) com.spotify.fmt:fmt-maven-plugin:check
-
-docker_format:
- docker run --rm -v $(PWD):$(PWD) -w $(PWD) --user node $(IMAGE_CI) make format
-
-format_fix:
- mvn $(MVN_OPTS) com.spotify.fmt:fmt-maven-plugin:format
-
-
-test:
- mvn $(MVN_OPTS) test
-
-docker_test:
- docker run --rm -v $(PWD):$(PWD) -w $(PWD) --user node $(IMAGE_CI) make test
-
-clean:
- mvn $(MVN_OPTS) clean
-
-
-package: clean
- mvn $(MVN_OPTS) package -DskipTests
-
-refresh_cache: refresh_cache_branch
-refresh_cache_branch:
- docker inspect $(IMAGE_REPO_BRANCH) > /dev/null && docker pull $(IMAGE_REPO_BRANCH) || true
-refresh_cache: refresh_cache_latest
-refresh_cache_latest:
- docker inspect $(IMAGE_REPO_MAIN) > /dev/null && docker pull $(IMAGE_REPO_MAIN) || true
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_REPO_BRANCH)
-
-clean_ci:
- -docker rmi -f $(IMAGE_CI) $(IMAGE_REPO_FINAL)
- -git clean -xdf .
-
-.PHONY: run package build clean test runtime-conf
diff --git a/services/history-v1/.nvmrc b/services/history-v1/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/history-v1/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/history-v1/Dockerfile b/services/history-v1/Dockerfile
deleted file mode 100644
index dee30c5f19..0000000000
--- a/services/history-v1/Dockerfile
+++ /dev/null
@@ -1,65 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/history-v1
-COPY services/history-v1/install_deps.sh /overleaf/services/history-v1/
-RUN chmod 0755 ./install_deps.sh && ./install_deps.sh
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-# fs persistor needs a writable folder as a target for the mounted volume
-RUN mkdir /buckets && chown node:node /buckets
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/object-persistor/package.json /overleaf/libraries/object-persistor/package.json
-COPY libraries/overleaf-editor-core/package.json /overleaf/libraries/overleaf-editor-core/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/history-v1/package.json /overleaf/services/history-v1/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus overleaf-editor overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/history-v1/ /overleaf/services/history-v1/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/history-v1/Makefile b/services/history-v1/Makefile
deleted file mode 100644
index b3a2bd6281..0000000000
--- a/services/history-v1/Makefile
+++ /dev/null
@@ -1,210 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export 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)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/object-persistor/package.json \
- $(MONOREPO)/libraries/overleaf-editor-core/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/redis-wrapper/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/history-v1/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/history-v1/reports:/overleaf/services/history-v1/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/history-v1/reports:/overleaf/services/history-v1/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/history-v1
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/history-v1
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/history-v1
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/history-v1/buildscript.txt b/services/history-v1/buildscript.txt
deleted file mode 100644
index b711aa44d7..0000000000
--- a/services/history-v1/buildscript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-history-v1
---dependencies=postgres,gcs,mongo,redis,s3
---deploy-pipeline=history-v1
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=44
---public-repo=False
---tsconfig-extra-includes=backup-deletion-app.mjs,backup-verifier-app.mjs,backup-worker-app.mjs,api/**/*,migrations/**/*,storage/**/*
diff --git a/services/history-v1/docker-compose.ci.yml b/services/history-v1/docker-compose.ci.yml
deleted file mode 100644
index 3c5b768489..0000000000
--- a/services/history-v1/docker-compose.ci.yml
+++ /dev/null
@@ -1,277 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-volumes:
- minio-certs:
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/history-v1/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- AWS_S3_ENDPOINT: https://minio:9000
- AWS_S3_PATH_STYLE: "true"
- DELETE_OBJECTS_MD5_FALLBACK: true
- AWS_ACCESS_KEY_ID: OVERLEAF_HISTORY_S3_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY: OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/history-v1/reports
- - minio-certs:/certs
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- postgres:
- condition: service_healthy
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- minio_setup:
- condition: service_completed_successfully
- gcs:
- condition: service_healthy
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping="$$(redis-cli ping)" && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- postgres:
- image: postgres:14
- environment:
- POSTGRES_USER: overleaf
- POSTGRES_PASSWORD: overleaf
- POSTGRES_DB: overleaf-history-v1-test
- volumes:
- - ./test/acceptance/pg-init/:/docker-entrypoint-initdb.d/
- healthcheck:
- test: pg_isready --quiet
- interval: 1s
- retries: 20
-
- certs:
- build:
- dockerfile_inline: |
- FROM node:24.14.1
- RUN wget -O /certgen "https://github.com/minio/certgen/releases/download/v1.3.0/certgen-linux-"`dpkg --print-architecture`
- RUN chmod +x /certgen
- volumes:
- - minio-certs:/certs
- working_dir: /certs
- entrypoint: sh
- command:
- - "-cex"
- - |
- if [ ! -f private.key ] || [ ! -f public.crt ]; then
- /certgen -host minio
- fi
- if ! openssl x509 -checkend 864000 -noout -in public.crt > /dev/null; then
- /certgen -host minio
- fi
-
- minio:
- image: minio/minio:RELEASE.2024-10-13T13-34-11Z
- command: server /data
- volumes:
- - minio-certs:/root/.minio/certs
- environment:
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- depends_on:
- certs:
- condition: service_completed_successfully
-
- minio_setup:
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- image: minio/mc:RELEASE.2024-10-08T09-37-26Z
- volumes:
- - minio-certs:/root/.mc/certs/CAs
- entrypoint: sh
- command:
- - "-cex"
- - |
- sleep 1
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
- mc mb --ignore-existing s3/overleaf-test-history-chunks
- mc mb --ignore-existing s3/overleaf-test-history-deks
- mc mb --ignore-existing s3/overleaf-test-history-global-blobs
- mc mb --ignore-existing s3/overleaf-test-history-project-blobs
- mc admin user add s3 \
- OVERLEAF_HISTORY_S3_ACCESS_KEY_ID \
- OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY
- echo '
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-chunks"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-chunks/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-deks"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-deks/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-global-blobs"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-global-blobs/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-project-blobs"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-project-blobs/*"
- }
- ]
- }' > policy-history.json
-
- mc admin policy create s3 overleaf-history policy-history.json
- mc admin policy attach s3 overleaf-history \
- --user=OVERLEAF_HISTORY_S3_ACCESS_KEY_ID
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/history-v1/docker-compose.yml b/services/history-v1/docker-compose.yml
deleted file mode 100644
index a92ff491ba..0000000000
--- a/services/history-v1/docker-compose.yml
+++ /dev/null
@@ -1,292 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-volumes:
- minio-certs:
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/history-v1/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/history-v1
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/history-v1
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/history-v1/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/history-v1
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - minio-certs:/certs
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/history-v1
- environment:
- RETRIES:
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- AWS_S3_ENDPOINT: https://minio:9000
- AWS_S3_PATH_STYLE: "true"
- DELETE_OBJECTS_MD5_FALLBACK: true
- AWS_ACCESS_KEY_ID: OVERLEAF_HISTORY_S3_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY: OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- GCS_API_ENDPOINT: http://gcs:9090
- GCS_PROJECT_ID: fake
- STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- postgres:
- condition: service_healthy
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- minio_setup:
- condition: service_completed_successfully
- gcs:
- condition: service_healthy
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping=$$(redis-cli ping) && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- postgres:
- image: postgres:14
- environment:
- POSTGRES_USER: overleaf
- POSTGRES_PASSWORD: overleaf
- POSTGRES_DB: overleaf-history-v1-test
- volumes:
- - ./test/acceptance/pg-init/:/docker-entrypoint-initdb.d/
- healthcheck:
- test: pg_isready --host=localhost --quiet
- interval: 1s
- retries: 20
-
- certs:
- build:
- dockerfile_inline: |
- FROM node:24.14.1
- RUN wget -O /certgen "https://github.com/minio/certgen/releases/download/v1.3.0/certgen-linux-"`dpkg --print-architecture`
- RUN chmod +x /certgen
- volumes:
- - minio-certs:/certs
- working_dir: /certs
- entrypoint: sh
- command:
- - "-cex"
- - |
- if [ ! -f private.key ] || [ ! -f public.crt ]; then
- /certgen -host minio
- fi
- if ! openssl x509 -checkend 864000 -noout -in public.crt > /dev/null; then
- /certgen -host minio
- fi
-
- minio:
- image: minio/minio:RELEASE.2024-10-13T13-34-11Z
- command: server /data
- volumes:
- - minio-certs:/root/.minio/certs
- environment:
- MINIO_ROOT_USER: MINIO_ROOT_USER
- MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD
- depends_on:
- certs:
- condition: service_completed_successfully
-
- minio_setup:
- depends_on:
- certs:
- condition: service_completed_successfully
- minio:
- condition: service_started
- image: minio/mc:RELEASE.2024-10-08T09-37-26Z
- volumes:
- - minio-certs:/root/.mc/certs/CAs
- entrypoint: sh
- command:
- - "-cex"
- - |
- sleep 1
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
- || sleep 3 && \
- mc alias set s3 https://minio:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
- mc mb --ignore-existing s3/overleaf-test-history-chunks
- mc mb --ignore-existing s3/overleaf-test-history-deks
- mc mb --ignore-existing s3/overleaf-test-history-global-blobs
- mc mb --ignore-existing s3/overleaf-test-history-project-blobs
- mc admin user add s3 \
- OVERLEAF_HISTORY_S3_ACCESS_KEY_ID \
- OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY
- echo '
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-chunks"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-chunks/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-deks"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-deks/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-global-blobs"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-global-blobs/*"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:ListBucket"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-project-blobs"
- },
- {
- "Effect": "Allow",
- "Action": [
- "s3:PutObject",
- "s3:GetObject",
- "s3:DeleteObject",
- "s3:AbortMultipartUpload",
- "s3:ListMultipartUploadParts",
- "s3:ListBucketMultipartUploads"
- ],
- "Resource": "arn:aws:s3:::overleaf-test-history-project-blobs/*"
- }
- ]
- }' > policy-history.json
-
- mc admin policy create s3 overleaf-history policy-history.json
- mc admin policy attach s3 overleaf-history \
- --user=OVERLEAF_HISTORY_S3_ACCESS_KEY_ID
-
- gcs:
- image: fsouza/fake-gcs-server:1.52.3
- command: ["--port=9090", "--scheme=http", "--external-url=http://gcs:9090"]
- healthcheck:
- test: wget --quiet --output-document=/dev/null http://localhost:9090/storage/v1/b
- interval: 1s
- retries: 20
diff --git a/services/notifications/.nvmrc b/services/notifications/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/notifications/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/notifications/Dockerfile b/services/notifications/Dockerfile
deleted file mode 100644
index 0732cf7cc9..0000000000
--- a/services/notifications/Dockerfile
+++ /dev/null
@@ -1,53 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/notifications
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/notifications/package.json /overleaf/services/notifications/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/notifications overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/notifications/ /overleaf/services/notifications/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.ts"]
-
diff --git a/services/notifications/Makefile b/services/notifications/Makefile
deleted file mode 100644
index 74236ad99a..0000000000
--- a/services/notifications/Makefile
+++ /dev/null
@@ -1,206 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export PROJECT_NAME = notifications
-BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]')
-HERE=$(shell pwd)
-export MONOREPO ?= $(shell cd ../../ && pwd)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/notifications/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/notifications/reports:/overleaf/services/notifications/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/notifications/reports:/overleaf/services/notifications/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/notifications
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/notifications
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/notifications
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/notifications/buildscript.txt b/services/notifications/buildscript.txt
deleted file mode 100644
index e15499cc5f..0000000000
--- a/services/notifications/buildscript.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-notifications
---dependencies=mongo
---deploy-pipeline=notifications
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=32
---public-repo=True
---test-acceptance-vitest=True
---test-unit-vitest=True
---ts-app=True
---tsconfig-extra-includes=vitest.config.unit.cjs,vitest.config.acceptance.cjs
diff --git a/services/notifications/docker-compose.ci.yml b/services/notifications/docker-compose.ci.yml
deleted file mode 100644
index 79931efb57..0000000000
--- a/services/notifications/docker-compose.ci.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/notifications/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VITEST_NO_CACHE: true
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/notifications/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- depends_on:
- mongo:
- condition: service_started
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/notifications/docker-compose.yml b/services/notifications/docker-compose.yml
deleted file mode 100644
index 0361fea5fc..0000000000
--- a/services/notifications/docker-compose.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/notifications/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/notifications
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/notifications
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/notifications/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/notifications
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/notifications
- environment:
- RETRIES:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/project-history/.nvmrc b/services/project-history/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/project-history/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/project-history/Dockerfile b/services/project-history/Dockerfile
deleted file mode 100644
index 2bab7e0eb9..0000000000
--- a/services/project-history/Dockerfile
+++ /dev/null
@@ -1,58 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/project-history
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/overleaf-editor-core/package.json /overleaf/libraries/overleaf-editor-core/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/project-history/package.json /overleaf/services/project-history/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/project-history overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/project-history/ /overleaf/services/project-history/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/project-history/Makefile b/services/project-history/Makefile
deleted file mode 100644
index 732aba5bad..0000000000
--- a/services/project-history/Makefile
+++ /dev/null
@@ -1,209 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export 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)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/overleaf-editor-core/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/redis-wrapper/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/project-history/package.json \
- $(MONOREPO)/tools/migrations/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/document-updater/app/js/types.ts:/overleaf/services/document-updater/app/js/types.ts --volume $(MONOREPO)/services/project-history/reports:/overleaf/services/project-history/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/document-updater/app/js/types.ts:/overleaf/services/document-updater/app/js/types.ts --volume $(MONOREPO)/services/project-history/reports:/overleaf/services/project-history/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/project-history
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/project-history
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/project-history
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/project-history/buildscript.txt b/services/project-history/buildscript.txt
deleted file mode 100644
index 5115a0c8d8..0000000000
--- a/services/project-history/buildscript.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-project-history
---dependencies=mongo,redis
---deploy-pipeline=project-history
---env-add=
---env-pass-through=
---esmock-loader=True
---node-version=24.14.1
---pipeline-owner=44
---public-repo=False
diff --git a/services/project-history/docker-compose.ci.yml b/services/project-history/docker-compose.ci.yml
deleted file mode 100644
index 24231a3a61..0000000000
--- a/services/project-history/docker-compose.ci.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/project-history/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/project-history/reports
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- user: node
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping="$$(redis-cli ping)" && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/project-history/docker-compose.yml b/services/project-history/docker-compose.yml
deleted file mode 100644
index 38f0a353d5..0000000000
--- a/services/project-history/docker-compose.yml
+++ /dev/null
@@ -1,100 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/project-history/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/project-history
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/project-history
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/project-history/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/project-history
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- working_dir: /overleaf/services/project-history
- environment:
- RETRIES:
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- mongo:
- condition: service_started
- redis_test:
- condition: service_healthy
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping=$$(redis-cli ping) && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
diff --git a/services/real-time/.nvmrc b/services/real-time/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/real-time/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/real-time/Dockerfile b/services/real-time/Dockerfile
deleted file mode 100644
index 56103a946b..0000000000
--- a/services/real-time/Dockerfile
+++ /dev/null
@@ -1,49 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/real-time
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-FROM base AS app
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/real-time/package.json /overleaf/services/real-time/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-
-RUN cd /overleaf && yarn workspaces focus @overleaf/real-time overleaf
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/real-time/ /overleaf/services/real-time/
-
-FROM app
-USER node
-
-CMD ["node", "--expose-gc", "app.js"]
diff --git a/services/real-time/Makefile b/services/real-time/Makefile
deleted file mode 100644
index adcbd7e801..0000000000
--- a/services/real-time/Makefile
+++ /dev/null
@@ -1,205 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-export BUILD_NUMBER ?= local
-export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
-export BRANCH_NAME_TAG_SAFE = $(shell echo $(BRANCH_NAME) | sed 's/\//\-\-/g' | cut -c1-96)
-export 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)
-export RETRIES ?= $(if $(CI),3,0)
-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_TAG_SAFE)-$(BUILD_NUMBER)
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/redis-wrapper/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/real-time/package.json \
- $(MONOREPO)/.yarn/patches/* \
-| sha256sum | cut -d '-' -f1)
-export MOCHA_GREP ?=
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE ?= test_acceptance_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_ACCEPTANCE = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_ACCEPTANCE) $(DOCKER_COMPOSE)
-
-COMPOSE_PROJECT_NAME_TEST_UNIT ?= test_unit_$(BUILD_DIR_NAME)
-DOCKER_COMPOSE_TEST_UNIT = \
- COMPOSE_PROJECT_NAME=$(COMPOSE_PROJECT_NAME_TEST_UNIT) $(DOCKER_COMPOSE)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-clean:
- -docker rmi $(IMAGE_CI)
- -docker rmi $(IMAGE_REPO_FINAL)
- -$(DOCKER_COMPOSE_TEST_UNIT) down --remove-orphans --rmi local --timeout 0 --volumes
- -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --remove-orphans --rmi local --timeout 0 --volumes
- -rm -rf reports/
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-RUN_LINTING_CI = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/real-time/reports:/overleaf/services/real-time/reports --volume $(MONOREPO)/.cache:/overleaf/.cache $(IMAGE_CI) yarn run --silent
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/real-time/reports:/overleaf/services/real-time/reports --volume $(MONOREPO)/.cache:/overleaf/.cache -w /overleaf $(IMAGE_CI) yarn run --silent
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(HERE):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(HERE):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-WITH_CACHE_FOLDER = \
- lint \
- lint_ci \
- lint_fix \
- format \
- format_ci \
- format_fix \
- typecheck \
- typecheck_ci \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-format:
- $(RUN_LINTING_MONOREPO) format services/real-time
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/real-time
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/real-time
-
-lint:
- $(RUN_LINTING) lint
-
-lint_ci:
- -$(RUN_LINTING_CI) lint --format json --output-file reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' reports/eslint.json
-
-lint_fix:
- $(RUN_LINTING) lint:fix
-
-typecheck:
- $(RUN_LINTING) types:check
-
-typecheck_ci:
- $(RUN_LINTING_CI) types:check
-
-test: format lint typecheck shellcheck test_unit test_acceptance
-
-test_unit:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) run $(DC_RUN_FLAGS) test_unit
-endif
-
-test_clean: test_unit_clean
-test_unit_clean:
-ifneq (,$(wildcard test/unit))
- $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0
-endif
-
-test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run
- $(MAKE) test_acceptance_clean
-
-test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug
- $(MAKE) test_acceptance_clean
-
-test_acceptance_run:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance
-endif
-
-test_acceptance_run_debug:
-ifneq (,$(wildcard test/acceptance))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 $(DC_RUN_FLAGS) test_acceptance yarn run --inspect=0.0.0.0:19999 --inspect-brk test:acceptance
-endif
-
-test_clean: test_acceptance_clean
-test_acceptance_clean:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0
-
-test_acceptance_pre_run:
-ifneq (,$(wildcard test/acceptance/js/scripts/pre-run))
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance test/acceptance/js/scripts/pre-run
-endif
-
-benchmarks:
- $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run $(DC_RUN_FLAGS) test_acceptance yarn run benchmarks
-
-build:
- docker build \
- --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CACHE) \
- --tag $(IMAGE_REPO_FINAL) \
- --cache-from $(IMAGE_CACHE) \
- --file Dockerfile \
- ../..
-
-tar:
- $(DOCKER_COMPOSE) up tar
-
-push:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-.PHONY: clean \
- format format_fix \
- lint lint_fix \
- build_types typecheck \
- lint_ci format_ci typecheck_ci \
- shellcheck shellcheck_fix \
- test test_clean test_unit test_unit_clean \
- test_acceptance test_acceptance_debug test_acceptance_pre_run \
- test_acceptance_run test_acceptance_run_debug test_acceptance_clean \
- benchmarks \
- build tar publish \
diff --git a/services/real-time/buildscript.txt b/services/real-time/buildscript.txt
deleted file mode 100644
index f82389c997..0000000000
--- a/services/real-time/buildscript.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-real-time
---dependencies=redis
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---pipeline-owner=32
---public-repo=False
---test-unit-vitest=True
---tsconfig-extra-includes=vitest.config.unit.cjs
diff --git a/services/real-time/docker-compose.ci.yml b/services/real-time/docker-compose.ci.yml
deleted file mode 100644
index f96e3cb87f..0000000000
--- a/services/real-time/docker-compose.ci.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- user: node
- volumes:
- - ./reports:/overleaf/services/real-time/reports
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- command: yarn run test:unit:_run
- environment:
- CI:
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VITEST_NO_CACHE: true
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- depends_on:
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- environment:
- CI:
- RETRIES:
- REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_HOST: mongo
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- volumes:
- - ./reports:/overleaf/services/real-time/reports
- depends_on:
- redis_test:
- condition: service_healthy
- user: node
- command: yarn run test:acceptance
-
- tar:
- build: .
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./:/tmp/build/
- command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs .
- user: root
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping="$$(redis-cli ping)" && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
diff --git a/services/real-time/docker-compose.yml b/services/real-time/docker-compose.yml
deleted file mode 100644
index 678d96e22d..0000000000
--- a/services/real-time/docker-compose.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run bin/update_build_scripts from
-# https://github.com/overleaf/internal/
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/real-time/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/real-time
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- working_dir: /overleaf/services/real-time
- environment:
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- command: yarn run --silent test:unit
- user: node
- depends_on:
- redis_test:
- condition: service_healthy
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/real-time/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/real-time
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- working_dir: /overleaf/services/real-time
- environment:
- RETRIES:
- REDIS_HOST: redis_test
- HISTORY_REDIS_HOST: redis_test
- QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_REDIS_HOST: redis_test
- ANALYTICS_QUEUES_BQ_SINK_REDIS_HOST: redis_test
- MONGO_CONNECTION_STRING: mongodb://mongo/test-overleaf
- POSTGRES_HOST: postgres
- MOCHA_GREP: ${MOCHA_GREP}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- user: node
- depends_on:
- redis_test:
- condition: service_healthy
- command: yarn run --silent test:acceptance
-
- redis_test:
- image: redis:7.4.8
- healthcheck:
- test: ping=$$(redis-cli ping) && [ "$$ping" = 'PONG' ]
- interval: 1s
- retries: 20
diff --git a/services/web/.github/prompts/await-migration.prompt.md b/services/web/.github/prompts/await-migration.prompt.md
deleted file mode 100644
index 26ee1ee114..0000000000
--- a/services/web/.github/prompts/await-migration.prompt.md
+++ /dev/null
@@ -1,389 +0,0 @@
----
-mode: 'agent'
-description: 'Generate a clear code explanation with examples'
----
-
-# Improved Async/Await Migration Instructions
-
-Based on lessons learned from PR #28840 and reviewer feedback, these comprehensive instructions address the original migration requirements while preventing common issues.
-
-## Core Migration Principles
-
-### 1. Function Signature Transformation
-
-- Convert callback-style functions to async/await
-- Remove callback parameters from function signatures
-- Add `async` keyword to function declarations
-- Replace `return callback(err, result)` with `throw err` or `return result`
-- Replace `return callback()` with `return` (or `return undefined`)
-
-### 2. Error Handling Patterns
-
-#### OError.tag Usage - CRITICAL UPDATE
-
-**DO NOT** wrap simple operations in try/catch just to tag errors with OError. With async/await, the stack trace is preserved automatically, making OError.tag less necessary for basic error propagation.
-
-```javascript
-// OLD (callback style) - OError.tag was needed
-callback(err) => {
- OError.tag(err, 'description', { context })
- return callback(err)
-}
-
-// BAD (unnecessary with async/await)
-try {
- await operation()
-} catch (err) {
- throw OError.tag(err, 'description', { context })
-}
-
-// GOOD (let errors propagate naturally)
-await operation()
-
-// ONLY use OError.tag when adding meaningful context or transforming errors
-try {
- await complexOperation()
-} catch (err) {
- if (err.code === 'SPECIFIC_ERROR') {
- throw OError.tag(err, 'meaningful context about why this failed', {
- important_context: value
- })
- }
- throw err // let other errors propagate unchanged
-}
-```
-
-### 3. Concurrency Considerations - CRITICAL
-
-#### Sequential vs Parallel Operations
-
-**Be extremely cautious when converting from serial to parallel operations.** The original code's choice of sequential processing is often intentional.
-
-```javascript
-// OLD - Sequential processing (often intentional)
-Async.mapSeries(items, processItem, callback)
-
-// BAD - Unbounded parallel processing
-await Promise.all(items.map(processItem))
-
-// BETTER - Keep sequential if unsure about resource limits
-for (const item of items) {
- await processItem(item)
-}
-
-// GOOD - Controlled batch processing for performance
-const BATCH_SIZE = 10
-for (let i = 0; i < items.length; i += BATCH_SIZE) {
- const batch = items.slice(i, i + BATCH_SIZE)
- await Promise.all(batch.map(processItem))
-}
-
-// IDEAL - Use Redis MGET for multiple key retrieval
-// Instead of: Promise.all(keys.map(k => redis.get(k)))
-const values = await redis.mget(keys)
-```
-
-#### Database/Redis Operation Guidelines
-
-- **Never** send unbounded parallel requests to databases
-- **Prefer** sequential processing for database operations unless there's a specific performance need
-- **Consider** batch operations (like Redis MGET/MSET) for multiple operations
-- **Implement** maximum concurrency limits when parallel processing is necessary
-
-### 4. Background Operations
-
-#### Fire-and-Forget Pattern
-
-When operations were called in the background (with empty callbacks), preserve this behavior:
-
-```javascript
-// OLD - Background operation with ignored callback
-someOperation(user, function () {}) // errors swallowed
-
-// GOOD - Preserve background behavior
-someOperation(user).catch(err => {
- logger.error({ err }, 'Failed to run background operation')
-})
-
-// Or if truly fire-and-forget:
-someOperation(user).catch(() => {}) // explicitly ignore errors
-```
-
-### 5. Module Export Patterns
-
-#### Using callbackifyAll for Dual API
-
-```javascript
-const { callbackifyAll } = require('@overleaf/promise-utils')
-
-const MyModule = {
- async myMethod(param) {
- // async implementation
- },
-}
-
-const moduleExports = {
- ...callbackifyAll(MyModule), // callback API
- promises: MyModule, // promise API
-}
-
-module.exports = moduleExports
-```
-
-#### Internal Method Stubbing (for testing)
-
-**Only** add method binding patterns when tests need to stub internal method calls:
-
-```javascript
-// ONLY if tests need to stub internal calls to _internalMethod
-MyModule._internalMethod = (...args) => moduleExports._internalMethod(...args)
-```
-
-**Do NOT** expose internal methods at the top level - they should be accessible via `moduleExports.promises._internalMethod`.
-
-### 6. Test Migration Patterns
-
-#### Async Test Conversion
-
-```javascript
-// OLD
-it('should do something', function (done) {
- MyModule.method(param, function (err, result) {
- expect(err).to.not.exist
- expect(result).to.equal(expected)
- done()
- })
-})
-
-// NEW
-it('should do something', async function () {
- const result = await MyModule.promises.method(param)
- expect(result).to.equal(expected)
-})
-```
-
-#### Mock/Stub Patterns
-
-```javascript
-// For Redis or database mocks, ensure method chaining works
-beforeEach(function () {
- redis.multi = sinon.stub().returns({
- sadd: sinon.stub().returnsThis(),
- pexpire: sinon.stub().returnsThis(),
- exec: sinon.stub().resolves(),
- })
-})
-```
-
-### 7. Specific Redis Patterns
-
-#### Multi-Transaction Operations
-
-```javascript
-// Correct pattern for Redis multi operations
-const multi = redis.multi()
-multi.sadd(key, value)
-multi.pexpire(key, ttl)
-await multi.exec()
-```
-
-#### Single vs Multiple Key Operations
-
-```javascript
-// BAD - Multiple individual operations
-const values = await Promise.all(keys.map(k => redis.get(k)))
-
-// GOOD - Use batch operations when available
-const values = await redis.mget(keys)
-```
-
-## Migration Checklist
-
-### Before Starting
-
-- [ ] Understand the original code's concurrency patterns
-- [ ] Identify any background operations that should remain non-blocking
-- [ ] Check if Redis batch operations can replace individual operations
-- [ ] Look for internal method calls that might need test stubbing
-
-### During Migration
-
-- [ ] Convert function signatures (remove callbacks, add async)
-- [ ] Replace callback patterns with await
-- [ ] Handle early returns properly
-- [ ] Preserve sequential processing unless there's a clear performance benefit
-- [ ] Keep background operations non-blocking
-- [ ] Avoid unnecessary OError.tag wrapping
-- [ ] Update JSDoc comments to remove callback parameters
-
-### After Migration
-
-- [ ] Run comprehensive tests (fix Docker/environment issues if needed)
-- [ ] Verify all background operations still work correctly
-- [ ] Check that internal method calls can be stubbed if needed
-- [ ] Ensure database operations don't overwhelm resources
-- [ ] Validate error handling preserves meaningful context
-- [ ] **Remove all decaffeinate artifacts from both implementation AND test files**
-- [ ] Add explanatory comments for any non-obvious technical patterns
-- [ ] Avoid selfRef patterns - use module exports routing instead
-
-### Test Migration
-
-- [ ] **Run tests EARLY and OFTEN during migration process**
-- [ ] Convert test functions to async
-- [ ] Update assertion patterns
-- [ ] Fix mock/stub configurations for chained operations (Redis multi, etc.)
-- [ ] Verify all test scenarios still pass
-- [ ] Remove duplicate or unnecessary mock setups
-- [ ] Clean up decaffeinate comments from test files
-- [ ] Ensure internal method stubs work through promises interface
-
-## Critical Lessons from Real Migration Experience
-
-### 1. Testing Environment Issues
-
-**ALWAYS run tests early and often during migration.** Don't wait until the end.
-
-Common test running problems:
-
-- Docker containers may need cleanup: `docker system prune -f`
-- Use specific test grep patterns: `MOCHA_GREP="ModuleName" make test_unit_app`
-- Mock objects must return proper objects for chaining (e.g., `multi()` must return `{method: stub().returnsThis(), ...}`)
-
-### 2. Method Stubbing for Internal Calls
-
-When methods call other methods internally, tests may need to stub those calls:
-
-```javascript
-// If methodA() calls methodB() internally and tests need to verify this:
-// DON'T do this - creates unnecessary complexity:
-const selfRef = { ... }; // BAD pattern
-
-// DO this - route through the module exports interface:
-moduleExports.promises.methodB(params).catch(err => {
- logger.error({ err }, 'Failed to run background operation')
-})
-
-// Add a brief comment explaining the routing pattern:
-// Route through moduleExports so tests can stub this call
-```
-
-### 3. Avoid the selfRef Pattern
-
-The `selfRef` pattern should be avoided - it's a code smell that indicates better module structure is needed:
-
-```javascript
-// BAD - selfRef pattern
-const selfRef = {}
-selfRef.methodA = async function() {
- await selfRef.methodB() // circular reference
-}
-
-// GOOD - route through module exports when stubbing is needed
-async methodA() {
- await moduleExports.promises.methodB() // testable
-}
-```
-
-### 4. Complete Decaffeinate Cleanup
-
-**Remove ALL legacy CoffeeScript artifacts** - this is a required part of the migration.
-
-Look for and remove patterns like these (exact format may vary):
-
-```javascript
-/* eslint-disable */
-// TODO: This file was created by bulk-decaffeinate.
-
-/* eslint-disable
- camelcase,
- n/handle-callback-err,
- max-len,
- no-return-assign,
- no-unused-vars,
-*/
-
-// Fix any style issues and re-enable lint.
-
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-```
-
-**Search patterns to look for:**
-
-- Comments containing "bulk-decaffeinate" or "decaffeinate"
-- Large `/* eslint-disable */` blocks at the top of files
-- Comments about "Fix any style issues and re-enable lint"
-- "decaffeinate suggestions" comment blocks
-
-Check **both implementation AND test files** for these artifacts.
-
-### 5. Add Explanatory Comments for Non-Obvious Code
-
-When you need to write "ugly" code for unavoidable technical reasons, add a brief comment explaining why:
-
-```javascript
-// Background operation - preserve fire-and-forget behavior
-// Route through moduleExports so tests can stub this call
-moduleExports.promises._checkSessions(user).catch(err => {
- logger.error({ err }, 'Failed to check sessions in background')
-})
-```
-
-This prevents future developers from "refactoring" the code and breaking functionality.
-
-### 6. Simplify OError.tag() Usage
-
-**With async/await, many OError.tag() wrappers can be removed.** OError.tag() was primarily used to preserve stack traces across callback boundaries, but async/await handles this automatically.
-
-```javascript
-// BEFORE - callback era (needed OError.tag for stack traces)
-try {
- await redis.multi().sadd(key, value).exec()
-} catch (err) {
- throw OError.tag(err, 'error adding to redis set', { key })
-}
-
-// AFTER - async/await preserves stack traces naturally
-await redis.multi().sadd(key, value).exec()
-```
-
-**Keep OError.tag() only when:**
-
-- Adding meaningful context that aids debugging
-- Transforming low-level errors into domain-specific errors
-- The wrapper adds significant value beyond just a descriptive message
-
-**Remove OError.tag() when:**
-
-- The error message doesn't add meaningful context
-- The tag message just restates what the code obviously does
-- Stack trace preservation was the only benefit
-
-## Common Pitfalls to Avoid
-
-1. **Over-parallelization**: Don't convert all sequential operations to parallel
-2. **Unnecessary error wrapping**: Don't wrap every operation in try/catch just for OError.tag
-3. **Breaking background operations**: Maintain fire-and-forget behavior where intended
-4. **Exposing internal methods incorrectly**: Use the promises interface, not top-level exports
-5. **Resource exhaustion**: Be mindful of database connection limits and Redis performance
-6. **Test mock complexity**: Keep mocks simple and targeted to what's actually needed
-7. **Using selfRef patterns**: Always route through module exports instead
-8. **Forgetting decaffeinate cleanup**: Remove all legacy comments and eslint disables
-9. **Not running tests early**: Run tests frequently during migration, not just at the end
-10. **Missing explanatory comments**: Add brief comments for non-obvious technical patterns
-
-## Success Metrics
-
-- All existing tests pass without modification (except for async conversion)
-- No new resource exhaustion issues under load
-- Background operations continue to work as intended
-- Error messages and logging remain informative
-- Internal method stubbing works correctly for testing
-- Code is cleaner and more maintainable than before
-
-These instructions should be applied systematically, with careful consideration of the specific context and requirements of each module being migrated.
diff --git a/services/web/.nvmrc b/services/web/.nvmrc
deleted file mode 100644
index 8e35034890..0000000000
--- a/services/web/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-24.14.1
diff --git a/services/web/.nycrc b/services/web/.nycrc
deleted file mode 100644
index 9efcbec3b0..0000000000
--- a/services/web/.nycrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extension": [".js", ".cjs", ".mjs", ".ts"],
- "cwd": "../../",
- "produce-source-map": true,
- "source-map": true,
- "temp-dir": "/tmp/.nyc_output"
-}
diff --git a/services/web/.storybook/index.html.m4 b/services/web/.storybook/index.html.m4
deleted file mode 100644
index 1908abebeb..0000000000
--- a/services/web/.storybook/index.html.m4
+++ /dev/null
@@ -1,78 +0,0 @@
-divert(`-1')
-define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')')
-define(`_arg1', `$1')
-define(`_foreach', `ifelse(`$2', `()', `',
- `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')')
-divert`'dnl
-
-
-
- Overleaf Storybook builds
-
-
-
-
- Overleaf Storybook builds
- Branches:
-
- foreach(DIR, (LIST),
- -
- DIR/
-
-
-
-
- )
-
-
- Last updated on syscmd(date)dnl
- for ifdef(`BRANCH_NAME',BRANCH_NAME,unknown branch)
- (build history).
-
-
-
diff --git a/services/web/.storybook/main.ts b/services/web/.storybook/main.ts
deleted file mode 100644
index 462e56cfb8..0000000000
--- a/services/web/.storybook/main.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-// This file has been automatically migrated to valid ESM format by Storybook.
-import { fileURLToPath } from 'node:url'
-import { createRequire } from 'node:module'
-import { defineMain } from '@storybook/react-webpack5/node'
-import path, { dirname } from 'node:path'
-import MiniCssExtractPlugin from 'mini-css-extract-plugin'
-
-const __filename = fileURLToPath(import.meta.url)
-const __dirname = dirname(__filename)
-const require = createRequire(import.meta.url)
-
-const rootDir = path.resolve(__dirname, '..')
-
-// NOTE: must be set before webpack config is imported
-process.env.OVERLEAF_CONFIG = path.join(rootDir, 'config/settings.webpack.js')
-
-function getAbsolutePath(value: string): any {
- return path.dirname(require.resolve(path.join(value, 'package.json')))
-}
-
-// Make sure that babel-macros are re-evaluated after changing the modules config
-// Import this after setting process.env.OVERLEAF_CONFIG
-const invalidateBabelCacheIfNeeded = require('../frontend/macros/invalidate-babel-cache-if-needed')
-invalidateBabelCacheIfNeeded()
-
-export default defineMain({
- core: {
- disableTelemetry: true,
- },
-
- staticDirs: [path.join(rootDir, 'public')],
-
- stories: [
- path.join(rootDir, 'frontend/stories/**/*.stories.{js,jsx,ts,tsx}'),
- path.join(rootDir, 'modules/**/stories/**/*.stories.{js,jsx,ts,tsx}'),
- path.join(rootDir, 'frontend/stories/**/*.mdx'),
- path.join(rootDir, 'modules/**/stories/**/*.mdx'),
- ],
-
- addons: [
- getAbsolutePath('@storybook/addon-links'),
- getAbsolutePath('@storybook/addon-a11y'),
- getAbsolutePath('@storybook/addon-designs'),
- getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
- {
- name: getAbsolutePath('@storybook/addon-styling-webpack'),
- options: {
- rules: [
- {
- test: /\.css$/,
- use: [
- { loader: MiniCssExtractPlugin.loader },
- { loader: 'css-loader' },
- ],
- },
- {
- // Pass Sass files through sass-loader/css-loader/mini-css-extract-
- // plugin (note: run in reverse order)
- test: /\.s[ac]ss$/,
- use: [
- // Allows the CSS to be extracted to a separate .css file
- { loader: MiniCssExtractPlugin.loader },
- // Resolves any CSS dependencies (e.g. url())
- { loader: 'css-loader' },
- // Resolve relative paths sensibly in SASS
- { loader: 'resolve-url-loader' },
- {
- // Runs autoprefixer on CSS via postcss
- loader: 'postcss-loader',
- options: {
- postcssOptions: {
- plugins: ['autoprefixer'],
- },
- },
- },
- // Compiles Sass to CSS
- {
- loader: 'sass-loader',
- options: { sourceMap: true }, // sourceMap: true is required for resolve-url-loader
- },
- ],
- },
- ],
- plugins: [new MiniCssExtractPlugin()],
- },
- },
- getAbsolutePath('@storybook/addon-docs'),
- ],
-
- framework: {
- name: getAbsolutePath('@storybook/react-webpack5'),
- options: {},
- },
-
- babel: (options: Record) => {
- return {
- ...options,
- plugins: [
- ['@babel/plugin-proposal-decorators', { legacy: true }],
- // ensure that TSX files are transformed before other plugins run
- ['@babel/plugin-transform-typescript', { isTSX: true }],
- ...(options.plugins ?? []),
- ],
- }
- },
-
- webpackFinal: storybookConfig => {
- return {
- ...storybookConfig,
- resolve: {
- ...storybookConfig.resolve,
- fallback: {
- ...storybookConfig.resolve?.fallback,
- fs: false,
- os: false,
- module: false,
- tty: require.resolve('tty-browserify'),
- },
- extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.json'],
- alias: {
- ...storybookConfig.resolve?.alias,
- // custom prefixes for import paths
- '@': path.join(rootDir, 'frontend/js/'),
- '@modules': path.join(rootDir, 'modules/'),
- '@ol-types': path.join(rootDir, 'types/'),
- '@ol-storybook': path.join(rootDir, '.storybook/'),
- '@wf': path.join(
- rootDir,
- 'modules/writefull/frontend/js/integration/src/'
- ),
- },
- },
- module: {
- ...storybookConfig.module,
- rules: (storybookConfig.module?.rules ?? []).concat(
- {
- test: /\.wasm$/,
- type: 'asset/resource',
- generator: {
- filename: 'js/[name]-[contenthash][ext]',
- },
- },
- {
- // Disable webpack's `new URL()` processing for pdfjs-dist worker
- // files, so that webpack does not try to resolve qcms_bg.wasm and
- // openjpeg.wasm (which live in pdfjs-dist/wasm/, not build/).
- // The main webpack build relies on a `/* webpackIgnore: true */`
- // comment (via yarn patch) to achieve the same effect, but
- // storybook's babel pipeline may strip comments from the 1.9 MB
- // worker file (babel `compact: "auto"` removes comments for files
- // > 500 KB), so we disable URL parsing for these files instead.
- test: /pdfjs-dist.*pdf\.worker.*\.m?js$/,
- parser: { javascript: { url: false } },
- }
- ),
- },
- }
- },
-})
diff --git a/services/web/.storybook/manager.css b/services/web/.storybook/manager.css
deleted file mode 100644
index 7a99a82e64..0000000000
--- a/services/web/.storybook/manager.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.sidebar-container a[title='Overleaf'] {
- max-width: 100px;
-}
diff --git a/services/web/.storybook/manager.ts b/services/web/.storybook/manager.ts
deleted file mode 100644
index 63601e9c22..0000000000
--- a/services/web/.storybook/manager.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { addons } from 'storybook/manager-api'
-import { create } from 'storybook/theming/create'
-
-import './manager.css'
-
-import brandImage from '../public/img/ol-brand/overleaf.svg'
-
-const theme = create({
- base: 'light',
- brandTitle: 'Overleaf',
- brandUrl: 'https://www.overleaf.com',
- brandImage,
-})
-
-addons.setConfig({ theme })
diff --git a/services/web/.storybook/preview.tsx b/services/web/.storybook/preview.tsx
deleted file mode 100644
index 414b70ac89..0000000000
--- a/services/web/.storybook/preview.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import { definePreview } from '@storybook/react-webpack5'
-import addonA11y from '@storybook/addon-a11y'
-import addonDesigns from '@storybook/addon-designs'
-import addonDocs from '@storybook/addon-docs'
-import addonLinks from '@storybook/addon-links'
-
-// Storybook does not (currently) support async loading of "stories". Therefore
-// the strategy in frontend/js/i18n.ts does not work (because we cannot wait on
-// the promise to resolve).
-// Therefore we have to use the synchronous method for configuring
-// react-i18next. Because this, we can only hard-code a single language.
-import i18n from 'i18next'
-import { initReactI18next } from 'react-i18next'
-// @ts-ignore
-import en from '../../../services/web/locales/en.json'
-
-function resetMeta() {
- window.metaAttributesCache = new Map()
- window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
- window.metaAttributesCache.set('ol-capabilities', ['chat'])
- window.metaAttributesCache.set('ol-compileSettings', {
- compileTimeout: 20,
- })
- window.metaAttributesCache.set('ol-ExposedSettings', {
- adminEmail: 'placeholder@example.com',
- appName: 'Overleaf',
- cookieDomain: '.overleaf.stories',
- dropboxAppName: 'Overleaf-Stories',
- emailConfirmationDisabled: false,
- enableSubscriptions: true,
- hasAffiliationsFeature: false,
- hasLinkUrlFeature: true,
- hasLinkedProjectFileFeature: true,
- hasLinkedProjectOutputFileFeature: true,
- hasSamlFeature: true,
- ieeeBrandId: 15,
- isOverleaf: true,
- labsEnabled: true,
- maxEntitiesPerProject: 10,
- maxUploadSize: 5 * 1024 * 1024,
- recaptchaDisabled: {
- invite: true,
- login: true,
- passwordReset: true,
- register: true,
- addEmail: true,
- },
- sentryAllowedOriginRegex: '',
- siteUrl: 'http://localhost',
- templateLinks: [],
- textExtensions: [
- 'tex',
- 'latex',
- 'sty',
- 'cls',
- 'bst',
- 'bib',
- 'bibtex',
- 'txt',
- 'tikz',
- 'mtx',
- 'rtex',
- 'md',
- 'asy',
- 'lbx',
- 'bbx',
- 'cbx',
- 'm',
- 'lco',
- 'dtx',
- 'ins',
- 'ist',
- 'def',
- 'clo',
- 'ldf',
- 'rmd',
- 'lua',
- 'gv',
- 'mf',
- 'lhs',
- 'lean',
- 'lean4',
- 'hs',
- 'mk',
- 'xmpdata',
- 'cfg',
- 'rnw',
- 'ltx',
- 'inc',
- ],
- editableFilenames: ['latexmkrc', '.latexmkrc', 'makefile', 'gnumakefile'],
- validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
- fileIgnorePattern:
- '**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}',
- projectUploadTimeout: 12000,
- })
-}
-
-i18n.use(initReactI18next).init({
- lng: 'en',
-
- // still using the v3 plural suffixes
- compatibilityJSON: 'v3',
-
- resources: {
- en: { translation: en },
- },
-
- react: {
- useSuspense: false,
- transSupportBasicHtmlNodes: false,
- },
-
- interpolation: {
- prefix: '__',
- suffix: '__',
- unescapeSuffix: 'HTML',
- skipOnVariables: true,
- escapeValue: false,
- defaultVariables: {
- appName: 'Overleaf',
- },
- },
-})
-
-export default definePreview({
- addons: [addonA11y(), addonDesigns(), addonDocs(), addonLinks()],
- parameters: {
- // Automatically mark prop-types like onClick, onToggle, etc as Storybook
- // "actions", so that they are logged in the Actions pane at the bottom of the
- // viewer
- actions: { argTypesRegex: '^on.*' },
- docs: {
- story: {
- // render stories in iframes, to isolate modals
- inline: false,
- },
- },
- options: {
- storySort: {
- method: 'alphabetical',
- order: [
- 'Storybook Guideline',
- ['Foundations', 'Storybook builds', 'Feature Flags'],
- 'Shared',
- ],
- },
- },
- },
- globalTypes: {
- theme: {
- name: 'Theme',
- description: 'Editor theme',
- defaultValue: 'main-',
- toolbar: {
- icon: 'circlehollow',
- items: [
- { value: 'main-', title: 'Default' },
- { value: 'main-light-', title: 'Light' },
- ],
- },
- },
- },
- loaders: [
- async () => {
- return {
- mainStyle: await import(
- // @ts-ignore
- `!!to-string-loader!css-loader!resolve-url-loader!sass-loader!../../../services/web/frontend/stylesheets/main-style.scss`
- ),
- }
- },
- ],
- decorators: [
- (Story, context) => {
- const { mainStyle } = context.loaded
-
- resetMeta()
-
- return (
-
- {mainStyle && }
-
-
- )
- },
- ],
-})
-
-// Populate meta for top-level access in modules on import
-resetMeta()
diff --git a/services/web/.storybook/utils/figma-design-url.ts b/services/web/.storybook/utils/figma-design-url.ts
deleted file mode 100644
index 570b7cd88e..0000000000
--- a/services/web/.storybook/utils/figma-design-url.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/** Creates design parameters for a Storybook story conditionally.
- * Helper function to generate design parameters based on the presence of a Figma access token.
- * The benefit of the token is that it allows component inspection directly in Storybook.
- * If token is not available, it defaults to a basic Figma URL.
- * Token can be generated in your Figma account settings.
- * To copy URL: In your Figma file, click to select the specific component frame you want to display in Storybook.
- * It's important to select the outer frame of the component, not just a single layer inside it.
- */
-
-export const figmaDesignUrl = (url: string) => {
- const accessToken = process.env.STORYBOOK_FIGMA_ACCESS_TOKEN
-
- const designConfig = accessToken
- ? { type: 'figspec', url, accessToken }
- : { type: 'figma', url }
-
- return {
- design: designConfig,
- }
-}
diff --git a/services/web/.storybook/utils/with-split-tests.tsx b/services/web/.storybook/utils/with-split-tests.tsx
deleted file mode 100644
index 80a7690891..0000000000
--- a/services/web/.storybook/utils/with-split-tests.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { Meta } from '@storybook/react-webpack5'
-import _ from 'lodash'
-import { SplitTestContext } from '@/shared/context/split-test-context'
-
-export const defaultSplitTestsArgTypes = {
- // to be able to use this utility, you need to add the argTypes for each split test in this object
- // Check the original implementation for an example: https://github.com/overleaf/internal/pull/17809
-}
-
-export const withSplitTests = (
- story: Meta,
- splitTests: (keyof ArgTypes)[] = [],
- /** @deprecated For demo purposes only. Add actual split tests in defaultSplitTestsArgTypes */
- _splitTestsArgTypes?: ArgTypes
-): Meta => {
- const splitTestsArgTypes = _splitTestsArgTypes ?? defaultSplitTestsArgTypes
- return {
- ...story,
- argTypes: { ...story.argTypes, ..._.pick(splitTestsArgTypes, splitTests) },
- decorators: [
- (Story, { args }) => {
- const splitTestVariants = _.pick(args, splitTests)
- const value = { splitTestVariants, splitTestInfo: {} }
- return (
-
-
-
- )
- },
- ...(story.decorators
- ? Array.isArray(story.decorators)
- ? story.decorators
- : [story.decorators]
- : []),
- ],
- }
-}
diff --git a/services/web/.stylelintrc.json b/services/web/.stylelintrc.json
deleted file mode 100644
index 83ca4692de..0000000000
--- a/services/web/.stylelintrc.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": ["stylelint-config-standard-scss"],
- "plugins": [
- "./stylelint-rules/no-themed-vars-in-root.mjs",
- "./stylelint-rules/themed-custom-property-suffix.mjs"
- ],
- "rules": {
- "function-url-quotes": null,
- "no-descending-specificity": null,
- "scss/at-extend-no-missing-placeholder": null,
- "scss/operator-no-newline-after": null,
- "property-no-vendor-prefix": [true, { "ignoreProperties": ["mask-image"] }],
- "overleaf/no-themed-vars-in-root": true
- },
- "overrides": [
- {
- "files": ["frontend/stylesheets/abstracts/themes-common-variables.scss"],
- "rules": {
- "overleaf/themed-custom-property-suffix": true
- }
- }
- ]
-}
diff --git a/services/web/.vscode/settings.json b/services/web/.vscode/settings.json
deleted file mode 100644
index d8655e6ffb..0000000000
--- a/services/web/.vscode/settings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "files.exclude": {
- "node_modules": true,
- "data": true
- },
- "cSpell.words": ["docstore", "Tpds", "Writefull"]
-}
diff --git a/services/web/Dockerfile b/services/web/Dockerfile
deleted file mode 100644
index 47ed96ed0b..0000000000
--- a/services/web/Dockerfile
+++ /dev/null
@@ -1,150 +0,0 @@
-# This file was auto-generated, do not edit it directly.
-# Instead run internal$ services/web/build_scripts
-
-FROM node:24.14.1 AS base
-
-# Corepack setup, shared between all the images.
-ENV PATH="/overleaf/node_modules/.bin:$PATH"
-ENV COREPACK_HOME=/opt/corepack
-RUN corepack enable && corepack install -g yarn@4.14.1
-ENV COREPACK_ENABLE_NETWORK=0
-
-WORKDIR /overleaf/services/web
-
-# Google Cloud Storage needs a writable $HOME/.config for resumable uploads
-# (see https://googleapis.dev/nodejs/storage/latest/File.html#createWriteStream)
-RUN mkdir /home/node/.config && chown node:node /home/node/.config
-
-RUN mkdir -p /overleaf/services/web/data/dumpFolder \
- && mkdir -p /overleaf/services/web/data/logs \
- && mkdir -p /overleaf/services/web/data/pdf \
- && mkdir -p /overleaf/services/web/data/uploads \
- && mkdir -p /overleaf/services/web/data/zippedProjects \
- && mkdir -p /overleaf/services/web/data/projectHistories \
- && chmod -R 0755 /overleaf/services/web/data \
- && chown -R node:node /overleaf/services/web/data
-
-# Add intermediate certificate for prism.optica.org
-COPY services/web/certs/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem /usr/local/share/ca-certificates/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt
-RUN update-ca-certificates
-
-# the deps image is used for caching yarn workspaces focus
-FROM base AS deps-prod
-
-# Pyodide wheel bundle (~370 MB). Version + SHA-256 are pinned in the fetch
-# script; keep that in sync with the pyodide dep in services/web/package.json.
-COPY services/web/scripts/fetch-pyodide-packages.mjs /overleaf/services/web/scripts/fetch-pyodide-packages.mjs
-RUN cd /overleaf/services/web && node scripts/fetch-pyodide-packages.mjs
-
-COPY package.json yarn.lock .yarnrc.yml /overleaf/
-COPY libraries/access-token-encryptor/package.json /overleaf/libraries/access-token-encryptor/package.json
-COPY libraries/eslint-plugin/package.json /overleaf/libraries/eslint-plugin/package.json
-COPY libraries/fetch-utils/package.json /overleaf/libraries/fetch-utils/package.json
-COPY libraries/logger/package.json /overleaf/libraries/logger/package.json
-COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json
-COPY libraries/mongo-utils/package.json /overleaf/libraries/mongo-utils/package.json
-COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json
-COPY libraries/object-persistor/package.json /overleaf/libraries/object-persistor/package.json
-COPY libraries/overleaf-editor-core/package.json /overleaf/libraries/overleaf-editor-core/package.json
-COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
-COPY libraries/ranges-tracker/package.json /overleaf/libraries/ranges-tracker/package.json
-COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json
-COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
-COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
-COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
-COPY services/web/package.json /overleaf/services/web/package.json
-COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
-COPY .yarn/patches/ /overleaf/.yarn/patches/
-
-RUN cd /overleaf && NODE_ENV=production yarn workspaces focus --production @overleaf/web
-
-FROM deps-prod AS deps
-
-ENV CYPRESS_INSTALL_BINARY=0
-
-COPY tsconfig.backend.json /overleaf/
-RUN cd /overleaf && yarn workspaces focus @overleaf/web overleaf
-
-
-# the dev is suitable for running tests
-FROM deps AS dev
-
-ARG SENTRY_RELEASE
-ENV SENTRY_RELEASE=$SENTRY_RELEASE
-COPY libraries/access-token-encryptor/ /overleaf/libraries/access-token-encryptor/
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/ranges-tracker/ /overleaf/libraries/ranges-tracker/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/web/ /overleaf/services/web/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-# Build the latex parser
-RUN cd /overleaf/services/web && yarn run 'lezer-latex:generate'
-
-# The module folders are owned as root. The node user cannot create them. Create them in advance.
-RUN cd /overleaf/services/web && make create_module_Makefiles
-
-USER node
-
-
-# the webpack image has deps+src+webpack artifacts
-FROM dev AS webpack
-USER root
-RUN OVERLEAF_CONFIG=/overleaf/services/web/config/settings.webpack.js nice yarn run webpack:production
-
-
-# intermediate image for removing source maps ahead of copying into final production image
-FROM webpack AS webpack-no-sourcemaps
-RUN nice find /overleaf/services/web/public -name '*.js.map' -delete
-
-
-# copy source code and precompile pug images
-FROM deps-prod AS pug
-COPY libraries/access-token-encryptor/ /overleaf/libraries/access-token-encryptor/
-COPY libraries/eslint-plugin/ /overleaf/libraries/eslint-plugin/
-COPY libraries/fetch-utils/ /overleaf/libraries/fetch-utils/
-COPY libraries/logger/ /overleaf/libraries/logger/
-COPY libraries/metrics/ /overleaf/libraries/metrics/
-COPY libraries/mongo-utils/ /overleaf/libraries/mongo-utils/
-COPY libraries/o-error/ /overleaf/libraries/o-error/
-COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
-COPY libraries/overleaf-editor-core/ /overleaf/libraries/overleaf-editor-core/
-COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
-COPY libraries/ranges-tracker/ /overleaf/libraries/ranges-tracker/
-COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/
-COPY libraries/settings/ /overleaf/libraries/settings/
-COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
-COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
-COPY services/web/ /overleaf/services/web/
-COPY tools/migrations/ /overleaf/tools/migrations/
-
-# Omit Server Pro/CE specific scripts from SaaS image
-RUN rm /overleaf/services/web/modules/server-ce-scripts -rf
-RUN OVERLEAF_CONFIG=/overleaf/services/web/config/settings.overrides.saas.js nice yarn run precompile-pug
-
-
-# the web image with only production dependencies but no webpack production build, for development
-FROM pug AS app-only
-USER node
-CMD ["node", "--expose-gc", "app.mjs"]
-
-
-# the final production image, with webpack production build but without source maps
-FROM pug AS app
-ARG SENTRY_RELEASE
-ENV SENTRY_RELEASE=$SENTRY_RELEASE
-ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt
-COPY --from=webpack-no-sourcemaps /overleaf/services/web/public /overleaf/services/web/public
-USER node
-CMD ["node", "--expose-gc", "app.mjs"]
diff --git a/services/web/Dockerfile.frontend b/services/web/Dockerfile.frontend
deleted file mode 100644
index 39614e412d..0000000000
--- a/services/web/Dockerfile.frontend
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM node:24.14.1
-
-# Install Google Chrome
-RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
-RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
-RUN apt-get update && apt-get install -y google-chrome-stable
diff --git a/services/web/Dockerfile.frontend.ci b/services/web/Dockerfile.frontend.ci
deleted file mode 100644
index 463cc45a0c..0000000000
--- a/services/web/Dockerfile.frontend.ci
+++ /dev/null
@@ -1,11 +0,0 @@
-ARG PROJECT_NAME
-ARG BRANCH_NAME_TAG_SAFE
-ARG BUILD_NUMBER
-
-FROM ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
-
-USER root
-
-RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
- echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
- apt-get update && apt-get install -y google-chrome-stable
diff --git a/services/web/Makefile b/services/web/Makefile
deleted file mode 100644
index fe18056ca0..0000000000
--- a/services/web/Makefile
+++ /dev/null
@@ -1,802 +0,0 @@
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-export RETRIES ?= $(if $(CI),3,0)
-
-export BUILD_NUMBER ?= local
-ifeq ($(BRANCH_NAME),)
- export BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
-endif
-export BRANCH_NAME_TAG_SAFE := $(shell echo "$(BRANCH_NAME)" | sed 's/\//\-\-/g' | cut -c1-96)
-
-.PHONY: print-branch-tag-safe
-print-branch-tag-safe:
- @echo $(BRANCH_NAME_TAG_SAFE)
-
-ifeq ($(COMMIT_SHA),)
- export COMMIT_SHA := $(shell git rev-parse HEAD)
-endif
-export PROJECT_NAME = web
-BUILD_DIR_NAME := web
-PWD := $(shell pwd)
-ifeq ($(MONOREPO),)
- export MONOREPO := $(shell cd ../../ && pwd)
-endif
-
-export OVERLEAF_CONFIG ?= /overleaf/services/web/test/acceptance/config/settings.test.saas.js
-export BASE_CONFIG ?= ${OVERLEAF_CONFIG}
-
-CFG_SAAS=/overleaf/services/web/test/acceptance/config/settings.test.saas.js
-CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server-ce.js
-CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js
-
-export MOCHA_GREP ?=
-DOCKER_COMPOSE := docker compose ${DOCKER_COMPOSE_FLAGS}
-
-# Common flags for docker compose run.
-# --rm Tear down test runner container
-# --build Make sure that we use the latest base image
-# --quiet-build Hide build output
-ifeq ($(CI),true)
- DC_RUN_FLAGS := --rm
-else
- DC_RUN_FLAGS := --rm --build --quiet-build
-endif
-
-MODULE_DIRS := $(shell find modules -mindepth 1 -maxdepth 1 -type d -not -name '.git' )
-MODULE_MAKEFILES := $(MODULE_DIRS:=/Makefile)
-MODULE_NAME=$(shell basename $(MODULE))
-
-create_module_Makefiles: $(MODULE_MAKEFILES)
-$(MODULE_MAKEFILES): Makefile.module
- cp Makefile.module $@ || diff Makefile.module $@
-
-MODULE_APP_SRC_DIRS := $(shell find modules -mindepth 3 -maxdepth 3 -type d -path '*/app/src')
-MODULE_TSCONFIGS := $(MODULE_APP_SRC_DIRS:=/tsconfig.json)
-
-create_module_tsconfigs: $(MODULE_TSCONFIGS)
-$(MODULE_TSCONFIGS): tsconfig.module.json
- cp tsconfig.module.json $@ || diff tsconfig.module.json $@
-
-#
-# Clean
-#
-
-SHARD_PROJECT_NAMES = \
- unit_test_$(BUILD_DIR_NAME) \
- unit_test_all_$(BUILD_DIR_NAME) \
- unit_test_esm_$(BUILD_DIR_NAME) \
- unit_test_esm_watch_$(BUILD_DIR_NAME) \
- unit_test_mocha_$(BUILD_DIR_NAME) \
- unit_test_parallel_$(BUILD_DIR_NAME) \
- unit_test_parallel_make_$(BUILD_DIR_NAME) \
- acceptance_test_saas_$(BUILD_DIR_NAME) \
- acceptance_test_server_ce_$(BUILD_DIR_NAME) \
- acceptance_test_server_pro_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_1_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_2_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_3_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_4_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_saas_5_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_server_ce_$(BUILD_DIR_NAME) \
- acceptance_test_modules_merged_server_pro_$(BUILD_DIR_NAME) \
- frontend_test_$(BUILD_DIR_NAME) \
- test_frontend_ct_$(BUILD_DIR_NAME) \
- test_frontend_ct_core_other_$(BUILD_DIR_NAME) \
- test_frontend_ct_core_features_$(BUILD_DIR_NAME) \
- test_frontend_ct_modules_$(BUILD_DIR_NAME) \
- test_frontend_ct_editor_other_$(BUILD_DIR_NAME) \
- test_frontend_ct_editor_visual_$(BUILD_DIR_NAME) \
- tar_$(BUILD_DIR_NAME) \
-
-CLEAN_SHARDS=$(addprefix clean/,$(SHARD_PROJECT_NAMES))
-clean: $(CLEAN_SHARDS)
- -docker run --rm --volume /dev/shm:/dev/shm --user root $(IMAGE_CI) rm -rf /dev/shm/overleaf
- -docker rmi --force $(IMAGE_CI) $(IMAGE_CI)-dev $(IMAGE_CI)-pug $(IMAGE_CI)-webpack $(IMAGE_REPO_FINAL)
- -docker compose down --remove-orphans --rmi local --timeout 0 --volumes
- -git clean -dfX data/
-
-$(CLEAN_SHARDS): clean/%:
- -COMPOSE_PROJECT_NAME=$* docker compose down --remove-orphans --rmi local --timeout 0 --volumes
-
-clean_ci:
- $(DOCKER_COMPOSE) down -v -t 0
- docker container list | grep 'days ago' | cut -d ' ' -f 1 - | xargs -r docker container stop
- docker image prune -af --filter "until=48h"
- docker network prune -f
-
-#
-# Tests
-#
-
-test: test_unit test_acceptance test_frontend test_writefull
-
-test_module: test_unit_module test_acceptance_module
-
-#
-# Unit tests
-#
-
-test_unit: test_unit_all
-test_unit_all: export COMPOSE_PROJECT_NAME=unit_test_all_$(BUILD_DIR_NAME)
-test_unit_all: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit yarn run test:unit:all
-
-test_unit_all_silent: export COMPOSE_PROJECT_NAME=unit_test_all_$(BUILD_DIR_NAME)
-test_unit_all_silent: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit yarn run test:unit:all:silent
-
-test_unit_app: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME)
-test_unit_app: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run --name unit_test_$(BUILD_DIR_NAME) --rm test_unit
-
-test_unit_parallel: export JUNIT_ROOT_SUITE_NAME = ESM unit tests - parallel
-test_unit_parallel: export COMPOSE_PROJECT_NAME=unit_test_parallel_$(BUILD_DIR_NAME)
-test_unit_parallel: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit yarn run test:unit:parallel
-
-test_unit_sequential: export JUNIT_ROOT_SUITE_NAME = ESM unit tests - sequential
-test_unit_sequential: export COMPOSE_PROJECT_NAME=unit_test_sequential_$(BUILD_DIR_NAME)
-test_unit_sequential: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit yarn run test:unit:sequential
-
-test_unit_watch: export COMPOSE_PROJECT_NAME=unit_test_watch_$(BUILD_DIR_NAME)
-test_unit_watch: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit yarn run test:unit:watch
-
-TEST_SUITES = $(sort $(filter-out \
- $(wildcard test/unit/src/helpers/*), \
- $(wildcard test/unit/src/*/*)))
-
-MOCHA_CMD_LINE = \
- mocha \
- --exit \
- --file test/unit/bootstrap.js \
- --grep="${MOCHA_GREP}" \
- --reporter spec \
- --timeout 25000 \
-
-.PHONY: $(TEST_SUITES)
-$(TEST_SUITES):
- $(MOCHA_CMD_LINE) $@
-
-J ?= 1
-test_unit_app_parallel_gnu_make: $(TEST_SUITES)
-test_unit_app_parallel_gnu_make_docker: export COMPOSE_PROJECT_NAME = \
- unit_test_parallel_make_$(BUILD_DIR_NAME)
-test_unit_app_parallel_gnu_make_docker: mongo_migrations_for_tests
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit \
- make test_unit_app_parallel_gnu_make --output-sync -j $(J)
-
-TEST_UNIT_MODULES = $(MODULE_DIRS:=/test_unit)
-$(TEST_UNIT_MODULES): %/test_unit: %/Makefile
-test_unit_modules: mongo_migrations_for_tests $(TEST_UNIT_MODULES)
-
-test_unit_module: mongo_migrations_for_tests
- $(MAKE) modules/$(MODULE_NAME)/test_unit
-
-mongo_migrations_for_tests:
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) --workdir /overleaf/tools/migrations test_unit yarn run migrations migrate -t 'saas & !nonblocking'
-
-#
-# Frontend tests
-#
-
-test_frontend_jsdom: export JUNIT_ROOT_SUITE_NAME = JSDOM frontend tests
-test_frontend_jsdom:
- COMPOSE_PROJECT_NAME=frontend_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_frontend_jsdom
- COMPOSE_PROJECT_NAME=frontend_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
-
-test_frontend: test_frontend_jsdom test_frontend_ct
-
-#
-# Frontend component tests in Cypress
-#
-# Local development: use $ make test_frontend_ct
-#
-TEST_FRONTEND_CT_VARIANTS = \
- test_frontend_ct \
- test_frontend_ct_core_other \
- test_frontend_ct_core_features \
- test_frontend_ct_modules \
- test_frontend_ct_editor_other \
- test_frontend_ct_editor_visual \
-
-TEST_FRONTEND_CT_TARGETS = \
- $(TEST_FRONTEND_CT_VARIANTS) \
- build_test_frontend_ct \
- test_frontend_ct_ui \
-
-$(TEST_FRONTEND_CT_TARGETS): export USER_UID=$(shell id -u)
-$(TEST_FRONTEND_CT_TARGETS): export USER_GID=$(shell id -g)
-
-test_frontend_ct_ui:
- docker compose -f docker-compose.cypress.yml run $(DC_RUN_FLAGS) cypress run cypress:open-ct -- --browser chrome
-
-#
-# Writefull tests
-#
-
-test_writefull: export JUNIT_ROOT_SUITE_NAME = Writefull tests
-test_writefull:
- COMPOSE_PROJECT_NAME=writefull_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_writefull
- COMPOSE_PROJECT_NAME=writefull_test_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
-
-# Note: The below cypress targets are for CI only
-build_test_frontend_ct:
- $(DOCKER_COMPOSE) build test_frontend_ct
- docker run --rm --volume /dev/shm:/dev/shm --user root $(IMAGE_CI) bash -ec 'for path in /overleaf/services/web/cypress/results /overleaf/services/web/cypress/support /overleaf/services/web/.cache; do mkdir -p $$path; chown -R node:node $$path; done && tar -cC / overleaf | tar -xC /dev/shm'
-
-test_frontend_ct_core_other: export CYPRESS_DOWNLOADS=./cypress/downloads/core
-test_frontend_ct_core_other: export CYPRESS_RESULTS=./cypress/results/core
-test_frontend_ct_core_other: export CYPRESS_SPEC_PATTERN=./test/frontend/**/*.spec.{js,jsx,ts,tsx}
-test_frontend_ct_core_other: export CYPRESS_EXCLUDE_SPEC_PATTERN=./test/frontend/features/**/*.spec.{js,jsx,ts,tsx}
-
-test_frontend_ct_core_features: export CYPRESS_DOWNLOADS=./cypress/downloads/core
-test_frontend_ct_core_features: export CYPRESS_RESULTS=./cypress/results/core
-test_frontend_ct_core_features: export CYPRESS_SPEC_PATTERN=./test/frontend/features/**/*.spec.{js,jsx,ts,tsx}
-test_frontend_ct_core_features: export CYPRESS_EXCLUDE_SPEC_PATTERN=./test/frontend/features/source-editor/**/*.spec.{js,jsx,ts,tsx}
-
-test_frontend_ct_modules: export CYPRESS_DOWNLOADS=./cypress/downloads/modules
-test_frontend_ct_modules: export CYPRESS_RESULTS=./cypress/results/modules
-test_frontend_ct_modules: export CYPRESS_SPEC_PATTERN=./modules/**/test/frontend/**/*.spec.{js,jsx,ts,tsx}
-
-test_frontend_ct_editor_other: export CYPRESS_DOWNLOADS=./cypress/downloads/editor_other
-test_frontend_ct_editor_other: export CYPRESS_RESULTS=./cypress/results/editor_other
-test_frontend_ct_editor_other: export CYPRESS_SPEC_PATTERN=./test/frontend/features/source-editor/**/*.spec.{js,jsx,ts,tsx}
-test_frontend_ct_editor_other: export CYPRESS_EXCLUDE_SPEC_PATTERN=./test/frontend/features/source-editor/components/codemirror-editor-visual*.spec.{js,jsx,ts,tsx}
-
-test_frontend_ct_editor_visual: export CYPRESS_DOWNLOADS=./cypress/downloads/editor_visual
-test_frontend_ct_editor_visual: export CYPRESS_RESULTS=./cypress/results/editor_visual
-test_frontend_ct_editor_visual: export CYPRESS_SPEC_PATTERN=./test/frontend/features/source-editor/components/codemirror-editor-visual*.spec.{js,jsx,ts,tsx}
-
-$(TEST_FRONTEND_CT_VARIANTS):
- COMPOSE_PROJECT_NAME=$@_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_frontend_ct
- COMPOSE_PROJECT_NAME=$@_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
-
-#
-# Acceptance tests
-#
-
-test_acceptance: test_acceptance_app test_acceptance_modules
-test_acceptance_saas: test_acceptance_app_saas test_acceptance_modules_merged_saas
-test_acceptance_server_ce: test_acceptance_app_server_ce test_acceptance_modules_merged_server_ce
-test_acceptance_server_pro: test_acceptance_app_server_pro test_acceptance_modules_merged_server_pro
-
-TEST_ACCEPTANCE_APP := \
- test_acceptance_app_saas \
- test_acceptance_app_server_ce \
- test_acceptance_app_server_pro \
-
-ifeq ($(DEBUG),true)
-TEST_ACCEPTANCE_DEBUG_PORT := -p 127.0.0.1:19999:19999
-TEST_ACCEPTANCE_YARN_SCRIPT := yarn run --silent test:acceptance:app:debug
-else
-TEST_ACCEPTANCE_DEBUG_PORT :=
-TEST_ACCEPTANCE_YARN_SCRIPT := yarn run --silent test:acceptance:app
-endif
-
-test_acceptance_app: $(TEST_ACCEPTANCE_APP)
-test_acceptance_app_saas: export JUNIT_ROOT_SUITE_NAME = SaaS app acceptance tests
-test_acceptance_app_saas: export COMPOSE_PROJECT_NAME=acceptance_test_saas_$(BUILD_DIR_NAME)
-test_acceptance_app_saas: export OVERLEAF_CONFIG=$(CFG_SAAS)
-test_acceptance_app_server_ce: export JUNIT_ROOT_SUITE_NAME = Server CE app acceptance tests
-test_acceptance_app_server_ce: export COMPOSE_PROJECT_NAME=acceptance_test_server_ce_$(BUILD_DIR_NAME)
-test_acceptance_app_server_ce: export OVERLEAF_CONFIG=$(CFG_SERVER_CE)
-test_acceptance_app_server_pro: export JUNIT_ROOT_SUITE_NAME = Server Pro app acceptance tests
-test_acceptance_app_server_pro: export COMPOSE_PROJECT_NAME=acceptance_test_server_pro_$(BUILD_DIR_NAME)
-test_acceptance_app_server_pro: export OVERLEAF_CONFIG=$(CFG_SERVER_PRO)
-
-$(TEST_ACCEPTANCE_APP):
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) $(TEST_ACCEPTANCE_DEBUG_PORT) test_acceptance $(TEST_ACCEPTANCE_YARN_SCRIPT)
- $(DOCKER_COMPOSE) down -v -t 0
-
-# We are using _make magic_ for turning these file-targets into calls to
-# sub-Makefiles in the individual modules.
-# These sub-Makefiles need to be kept in sync with the template, hence we
-# add a dependency on each modules Makefile and cross-link that to the
-# template at the very top of this file.
-# Example: `web$ make modules/server-ce-scripts/test_acceptance_server_ce`
-# Description: Run the acceptance tests of the server-ce-scripts module in an
-# Overleaf Community Edition Environment.
-# Break down:
-# Target: modules/server-ce-scripts/test_acceptance_server_ce
-# -> depends on modules/server-ce-scripts/Makefile
-# -> add environment variable BASE_CONFIG=$(CFG_SERVER_CE)
-# -> BASE_CONFIG=/overleaf/services/web/test/acceptance/config/settings.test.server-ce.js
-# -> automatic target: `make -C server-ce-scripts test_acceptance_server_ce`
-# -> automatic target: run `make test_acceptance_server_ce` in module
-# Target: modules/server-ce-scripts/Makefile
-# -> depends on Makefile.module
-# -> automatic target: copies the file when changed
-TEST_ACCEPTANCE_MODULES = $(MODULE_DIRS:=/test_acceptance)
-$(TEST_ACCEPTANCE_MODULES): %/test_acceptance: %/Makefile
-$(TEST_ACCEPTANCE_MODULES): modules/%/test_acceptance:
- $(MAKE) test_acceptance_module MODULE_NAME=$*
-
-TEST_ACCEPTANCE_MODULES_SAAS = $(MODULE_DIRS:=/test_acceptance_saas)
-$(TEST_ACCEPTANCE_MODULES_SAAS): %/test_acceptance_saas: %/Makefile
-$(TEST_ACCEPTANCE_MODULES_SAAS): export BASE_CONFIG = $(CFG_SAAS)
-
-# This line adds `/test_acceptance_saas` suffix to all items in $(MODULE_DIRS).
-TEST_ACCEPTANCE_MODULES_SERVER_CE = $(MODULE_DIRS:=/test_acceptance_server_ce)
-# This line adds a dependency on the modules Makefile.
-$(TEST_ACCEPTANCE_MODULES_SERVER_CE): %/test_acceptance_server_ce: %/Makefile
-# This line adds the environment variable BASE_CONFIG=$(CFG_SERVER_CE) to all
-# invocations of `web$ make modules/foo/test_acceptance_server_ce`.
-$(TEST_ACCEPTANCE_MODULES_SERVER_CE): export BASE_CONFIG = $(CFG_SERVER_CE)
-
-TEST_ACCEPTANCE_MODULES_SERVER_PRO = $(MODULE_DIRS:=/test_acceptance_server_pro)
-$(TEST_ACCEPTANCE_MODULES_SERVER_PRO): %/test_acceptance_server_pro: %/Makefile
-$(TEST_ACCEPTANCE_MODULES_SERVER_PRO): export BASE_CONFIG = $(CFG_SERVER_PRO)
-
-CLEAN_TEST_ACCEPTANCE_MODULES = $(MODULE_DIRS:=/clean_test_acceptance)
-$(CLEAN_TEST_ACCEPTANCE_MODULES): %/clean_test_acceptance: %/Makefile
-clean_test_acceptance_modules: $(CLEAN_TEST_ACCEPTANCE_MODULES)
-clean_ci: clean_test_acceptance_modules
-
-test_acceptance_module_noop:
- @echo
- @echo Module '$(MODULE_NAME)' does not run in ${LABEL}.
- @echo
-
-TEST_ACCEPTANCE_MODULE_MAYBE_IN := \
- test_acceptance_module_maybe_in_saas \
- test_acceptance_module_maybe_in_server_ce \
- test_acceptance_module_maybe_in_server_pro \
-
-test_acceptance_module: $(TEST_ACCEPTANCE_MODULE_MAYBE_IN)
-test_acceptance_module_maybe_in_saas: export BASE_CONFIG=$(CFG_SAAS)
-test_acceptance_module_maybe_in_server_ce: export BASE_CONFIG=$(CFG_SERVER_CE)
-test_acceptance_module_maybe_in_server_pro: export BASE_CONFIG=$(CFG_SERVER_PRO)
-
-# We need to figure out whether the module is loaded in a given environment.
-# This information is stored in the (base-)settings.
-# We get the full list of modules and check for a matching module entry.
-# Either the grep will find and emit the module, or exits with code 1, which
-# we handle with a fallback to a noop make target.
-# Run the node command in a docker compose container which provides the needed
-# yarn dependencies (from disk in dev-env or from the CI image in CI).
-# Pick the test_unit service which is very light-weight -- the test_acceptance
-# service would start mongo/redis.
-$(TEST_ACCEPTANCE_MODULE_MAYBE_IN): test_acceptance_module_maybe_in_%:
- $(MAKE) $(shell \
- OVERLEAF_CONFIG=$(BASE_CONFIG) \
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_unit \
- yarn node test/acceptance/getModuleTargets test_acceptance_$* \
- | grep -e /$(MODULE_NAME)/ || echo test_acceptance_module_noop LABEL=$* \
- )
-
-# See docs for test_acceptance_server_ce how this works.
-test_acceptance_module_saas: export BASE_CONFIG = $(CFG_SAAS)
-test_acceptance_module_saas:
- $(MAKE) modules/$(MODULE_NAME)/test_acceptance_saas
-
-test_acceptance_module_server_ce: export BASE_CONFIG = $(CFG_SERVER_CE)
-test_acceptance_module_server_ce:
- $(MAKE) modules/$(MODULE_NAME)/test_acceptance_server_ce
-
-test_acceptance_module_server_pro: export BASE_CONFIG = $(CFG_SERVER_PRO)
-test_acceptance_module_server_pro:
- $(MAKE) modules/$(MODULE_NAME)/test_acceptance_server_pro
-
-# See docs for test_acceptance_server_ce how this works.
-TEST_ACCEPTANCE_MODULES_MERGED_INNER = $(MODULE_DIRS:=/test_acceptance_merged_inner)
-$(TEST_ACCEPTANCE_MODULES_MERGED_INNER): %/test_acceptance_merged_inner: %/Makefile
-test_acceptance_modules_merged_inner:
- $(MAKE) $(shell \
- OVERLEAF_CONFIG=$(BASE_CONFIG) \
- yarn node test/acceptance/getModuleTargets test_acceptance_merged_inner \
- )
-
-# inner loop for running saas tests in parallel
-no_more_targets:
-
-# If we ever have more than 50 modules, we need to add _6 targets to all the places and have it START at 51.
-test_acceptance_modules_merged_inner_1: export START=1
-test_acceptance_modules_merged_inner_2: export START=11
-test_acceptance_modules_merged_inner_3: export START=21
-test_acceptance_modules_merged_inner_4: export START=31
-test_acceptance_modules_merged_inner_5: export START=41
-TEST_ACCEPTANCE_MODULES_MERGED_INNER_SPLIT = \
- test_acceptance_modules_merged_inner_1 \
- test_acceptance_modules_merged_inner_2 \
- test_acceptance_modules_merged_inner_3 \
- test_acceptance_modules_merged_inner_4 \
- test_acceptance_modules_merged_inner_5 \
-
-# The node script prints one module per line.
-# Using tail and head we skip over the first n=START entries and print the last 10.
-# Finally we check with grep for any targets in a batch and print a fallback if none were found.
-$(TEST_ACCEPTANCE_MODULES_MERGED_INNER_SPLIT):
- $(MAKE) $(shell \
- OVERLEAF_CONFIG=$(BASE_CONFIG) \
- yarn node test/acceptance/getModuleTargets test_acceptance_merged_inner \
- | tail -n+$(START) | head -n 10 \
- | grep -e . || echo no_more_targets \
- )
-
-# See docs for test_acceptance_server_ce how this works.
-test_acceptance_modules_merged_saas: export JUNIT_ROOT_SUITE_NAME = SaaS modules acceptance tests
-test_acceptance_modules_merged_saas: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_saas: export BASE_CONFIG = $(CFG_SAAS)
-
-test_acceptance_modules_merged_server_ce: export JUNIT_ROOT_SUITE_NAME = Server CE modules acceptance tests
-test_acceptance_modules_merged_server_ce: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_server_ce_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_server_ce: export BASE_CONFIG = $(CFG_SERVER_CE)
-
-test_acceptance_modules_merged_server_pro: export JUNIT_ROOT_SUITE_NAME = Server Pro modules acceptance tests
-test_acceptance_modules_merged_server_pro: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_server_pro_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_server_pro: export BASE_CONFIG = $(CFG_SERVER_PRO)
-
-# All these variants run the same command.
-# Each target has a different set of environment defined above.
-TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS = \
- test_acceptance_modules_merged_saas \
- test_acceptance_modules_merged_server_ce \
- test_acceptance_modules_merged_server_pro \
-
-$(TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS):
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_acceptance yarn exec make test_acceptance_modules_merged_inner
- $(DOCKER_COMPOSE) down -v -t 0
-
-# outer loop for running saas tests in parallel
-TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS = \
- test_acceptance_modules_merged_saas_1 \
- test_acceptance_modules_merged_saas_2 \
- test_acceptance_modules_merged_saas_3 \
- test_acceptance_modules_merged_saas_4 \
- test_acceptance_modules_merged_saas_5 \
-
-test_acceptance_modules_merged_saas_1: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_1_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_saas_2: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_2_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_saas_3: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_3_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_saas_4: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_4_$(BUILD_DIR_NAME)
-test_acceptance_modules_merged_saas_5: export COMPOSE_PROJECT_NAME = \
- acceptance_test_modules_merged_saas_5_$(BUILD_DIR_NAME)
-$(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): export BASE_CONFIG = $(CFG_SAAS)
-$(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): export JUNIT_ROOT_SUITE_NAME = SaaS modules acceptance tests
-
-$(TEST_ACCEPTANCE_MODULES_MERGED_SPLIT_SAAS): test_acceptance_modules_merged_saas_%:
- $(DOCKER_COMPOSE) run $(DC_RUN_FLAGS) test_acceptance yarn exec make test_acceptance_modules_merged_inner_$*
- $(DOCKER_COMPOSE) down -v -t 0
-
-test_acceptance_modules: $(TEST_ACCEPTANCE_MODULES_MERGED_VARIANTS)
-
-#
-# CI tests
-#
-
-rebase_coverage_reports:
- sed -i 's_path="/overleaf/_path="_' data/coverage/*/clover.xml
-
-# Create the bundle with full paths inside the monorepo to make it easier to organize them later.
-bundle_coverage_reports:
- cd ../../ && tar -czf services/web/data/coverage.tar.gz services/web/data/coverage/*/clover.xml
-
-ci:
- MOCHA_ARGS="--reporter tap" \
- $(MAKE) test
-
-#
-# Lint & format
-#
-
-# Run the linting commands in the scope of the monorepo.
-# Eslint and prettier (plus some configs) are on the root.
-RUN_LINTING_CI_MONOREPO = docker run --rm --volume $(MONOREPO)/.editorconfig:/overleaf/.editorconfig --volume $(MONOREPO)/eslint.config.mjs:/overleaf/eslint.config.mjs --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume $(MONOREPO)/tsconfig.backend.json:/overleaf/tsconfig.backend.json --volume $(MONOREPO)/services/web/data/reports:/overleaf/services/web/data/reports --volume $(MONOREPO)/.cache/:/overleaf/.cache/ -w /overleaf $(IMAGE_CI) yarn run --silent
-RUN_LINTING = ../../bin/run -w /overleaf/services/$(PROJECT_NAME) monorepo yarn run --silent
-RUN_LINTING_MONOREPO = ../../bin/run monorepo yarn run --silent
-
-ORG_PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-RUN_LINT_FORMAT ?= \
- docker run --rm --env BRANCH_NAME --env CI --env COMMIT_SHA --env MONOREPO --env PATH=/overleaf/node_modules/.bin:$(ORG_PATH) --volume $(MONOREPO)/.prettierignore:/overleaf/.prettierignore --volume $(MONOREPO)/.prettierrc.cjs:/overleaf/.prettierrc.cjs --volume ${PWD}/data/reports:/overleaf/services/web/data/reports --volume $(MONOREPO)/.cache/:/overleaf/.cache/ ${IMAGE_CI}
-
-NODE_MODULES_PATH := ${PATH}:${PWD}/node_modules/.bin:/overleaf/services/web/node_modules/.bin
-WITH_NODE_MODULES_PATH = \
- format_backend \
- format_frontend \
- format_misc \
- format_styles \
- format_test_app_unit \
- format_test_app_rest \
- format_test_modules \
- $(TEST_SUITES) \
-
-$(WITH_NODE_MODULES_PATH): export PATH=$(NODE_MODULES_PATH)
-
-WITH_CACHE_FOLDER = \
- lint_eslint \
- lint_stylelint \
- lint_in_docker \
- format_js \
- format_ci \
- format_fix \
- format_styles \
- format_styles_fix \
- format_pug \
- format_pug_fix \
- format_jenkins \
- format_jenkins_fix \
- format_in_docker \
-
-$(WITH_CACHE_FOLDER): ../../.cache/prettier/
-../../.cache/prettier/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/eslint/
-../../.cache/eslint/:
- mkdir -p $@
-
-$(WITH_CACHE_FOLDER): ../../.cache/stylelint/
-../../.cache/stylelint/:
- mkdir -p $@
-
-lint: lint_eslint
-lint_eslint:
-ifeq ($(CI),true)
- -yarn run lint --format json --output-file data/reports/eslint.json
- sed -i 's_"filePath":"/overleaf_"filePath":"$(MONOREPO)_g' data/reports/eslint.json
-else
- $(RUN_LINTING) lint
-endif
-
-lint_fix: lint_eslint_fix
-lint_eslint_fix:
- $(RUN_LINTING) lint:fix
-
-lint: lint_stylelint
-lint_stylelint:
-ifeq ($(CI),true)
- -yarn run lint:styles --formatter json --output-file data/reports/stylelint.json
- sed -i 's_"source":"/overleaf_"source":"$(MONOREPO)_g' data/reports/stylelint.json
-else
- $(RUN_LINTING) lint:styles
-endif
-
-lint_fix: lint_stylelint_fix
-lint_stylelint_fix:
- $(RUN_LINTING) lint:styles:fix
-
-lint: lint_pug
-lint_pug:
- bin/lint_pug_templates
-
-lint: lint_locales
-lint_locales:
- bin/lint_locales
-
-lint: check_extracted_translations
-check_extracted_translations:
- bin/check_extracted_translations
-
-cleanup_unused_locales:
- node scripts/translations/cleanupUnusedLocales.js
-
-lint: lint_flag_res_send_usage
-lint_flag_res_send_usage:
- bin/lint_flag_res_send_usage
-
-lint: lint_overleafModuleImports
-lint_overleafModuleImports:
- node scripts/check_overleafModuleImports.mjs
-
-lint: typecheck_frontend
-typecheck_frontend:
-ifeq ($(CI),true)
- yarn run --silent type-check
-else
- $(RUN_LINTING) type-check
-endif
-
-lint: typecheck_backend
-typecheck_backend:
-ifeq ($(CI),true)
- yarn run --silent type-check:backend
-else
- $(RUN_LINTING) type-check:backend
-endif
-
-lint_in_docker:
- $(RUN_LINT_FORMAT) make lint -j2 --output-sync
-
-format: format_js
-format_js:
- $(RUN_LINTING_MONOREPO) format services/web
-
-format_ci:
- $(RUN_LINTING_CI_MONOREPO) format services/web
-
-format_fix:
- $(RUN_LINTING_MONOREPO) format:fix services/web
-
-format_styles:
- $(RUN_LINTING_MONOREPO) format:styles
-
-format_styles_fix:
- $(RUN_LINTING_MONOREPO) format:styles:fix
-
-format_pug:
- $(RUN_LINTING_MONOREPO) format:pug
-
-format_pug_fix:
- $(RUN_LINTING_MONOREPO) format:pug:fix
-
-format_jenkins:
- $(RUN_LINTING_MONOREPO) format:jenkins
-
-format_jenkins_fix:
- $(RUN_LINTING_MONOREPO) format:jenkins:fix
-
-format_in_docker:
- $(RUN_LINT_FORMAT) bash -c 'cd /overleaf && yarn run format services/web'
-
-SHELLCHECK_OPTS = \
- --shell=bash \
- --external-sources
-SHELLCHECK_COLOR := $(if $(CI),--color=never,--color)
-SHELLCHECK_FILES := { git ls-files "*.sh" -z; git grep -Plz "\A\#\!.*bash"; } | sort -zu
-
-shellcheck:
- @$(SHELLCHECK_FILES) | xargs -0 -r docker run --rm -v $(PWD):/mnt -w /mnt \
- koalaman/shellcheck:stable $(SHELLCHECK_OPTS) $(SHELLCHECK_COLOR)
-
-shellcheck_fix:
- @$(SHELLCHECK_FILES) | while IFS= read -r -d '' file; do \
- diff=$$(docker run --rm -v $(PWD):/mnt -w /mnt koalaman/shellcheck:stable $(SHELLCHECK_OPTS) --format=diff "$$file" 2>/dev/null); \
- if [ -n "$$diff" ] && ! echo "$$diff" | patch -p1 >/dev/null 2>&1; then echo "\033[31m$$file\033[0m"; \
- elif [ -n "$$diff" ]; then echo "$$file"; \
- else echo "\033[2m$$file\033[0m"; fi \
- done
-
-#
-# Build & publish
-#
-
-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_TAG_SAFE)-$(BUILD_NUMBER)
-# A shared scratch tag that is used by all the pipelines for pushing the layers of the production image ASAP from a parallel step.
-# We do not want to make the image available before all the tests have passed.
-# Using a single tag avoids generating cruft in our docker repository / AR.
-IMAGE_SCRATCH ?= $(IMAGE_REPO):do-not-use-this-tag-for-deploys--it-is-used-for-early-pushes-in-ci
-IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
- $(MONOREPO)/package.json \
- $(MONOREPO)/yarn.lock \
- $(MONOREPO)/libraries/access-token-encryptor/package.json \
- $(MONOREPO)/libraries/eslint-plugin/package.json \
- $(MONOREPO)/libraries/fetch-utils/package.json \
- $(MONOREPO)/libraries/logger/package.json \
- $(MONOREPO)/libraries/metrics/package.json \
- $(MONOREPO)/libraries/mongo-utils/package.json \
- $(MONOREPO)/libraries/o-error/package.json \
- $(MONOREPO)/libraries/object-persistor/package.json \
- $(MONOREPO)/libraries/overleaf-editor-core/package.json \
- $(MONOREPO)/libraries/promise-utils/package.json \
- $(MONOREPO)/libraries/ranges-tracker/package.json \
- $(MONOREPO)/libraries/redis-wrapper/package.json \
- $(MONOREPO)/libraries/settings/package.json \
- $(MONOREPO)/libraries/stream-utils/package.json \
- $(MONOREPO)/libraries/validation-tools/package.json \
- $(MONOREPO)/services/web/package.json \
- $(MONOREPO)/services/web/scripts/fetch-pyodide-packages.mjs \
- $(MONOREPO)/patches/* \
-| sha256sum | cut -d '-' -f1)
-
-export SENTRY_RELEASE ?= ${COMMIT_SHA}
-
-build_deps:
- docker build --pull \
- --build-arg BUILDKIT_INLINE_CACHE=1 \
- --cache-from $(IMAGE_CACHE) \
- --tag $(IMAGE_CACHE) \
- --target deps \
- --file Dockerfile \
- ../..
-
-build_dev:
- docker build \
- --build-arg SENTRY_RELEASE \
- --tag $(IMAGE_CI) \
- --tag $(IMAGE_CI)-dev \
- --target dev \
- --file Dockerfile \
- ../..
-
-build_webpack:
- $(MAKE) build_webpack_once \
- || $(MAKE) build_webpack_once
-
-build_webpack_once:
- docker build \
- --build-arg SENTRY_RELEASE \
- --tag $(IMAGE_CI)-webpack \
- --target webpack \
- --file Dockerfile \
- ../..
-
-build_pug:
- docker build \
- --build-arg SENTRY_RELEASE \
- --tag $(IMAGE_CI)-pug \
- --target pug \
- --file Dockerfile \
- ../..
-
-build:
- docker build \
- --build-arg SENTRY_RELEASE \
- --tag $(IMAGE_REPO_FINAL) \
- --tag $(IMAGE_SCRATCH) \
- --target app \
- --file Dockerfile \
- ../..
-
-publish:
- docker push $(IMAGE_REPO_FINAL)
-
-push_branch:
- docker push $(IMAGE_CACHE)
-
-push_scratch:
- docker push $(IMAGE_SCRATCH)
-
-SENTRY_IMAGE=getsentry/sentry-cli:2.16.1
-sentry_prefetch:
- docker pull $(SENTRY_IMAGE)
-
-sentry_upload:
- docker run --rm \
- --volume $(MONOREPO):$(MONOREPO) --workdir $(PWD) \
- --user "$(shell id -u):$(shell id -g)" \
- --env BRANCH_NAME --env SENTRY_RELEASE \
- --entrypoint bin/sentry_upload \
- $(SENTRY_IMAGE)
-
-tar:
- COMPOSE_PROJECT_NAME=tar_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) run --rm tar
- COMPOSE_PROJECT_NAME=tar_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) down -v -t 0
-
-build_storybook:
- yarn run lezer-latex:generate
- mkdir -p data/storybook/
- rm -rf data/storybook/$(BRANCH_NAME)
- yarn run build-storybook --output-dir data/storybook/$(BRANCH_NAME) --loglevel warn --quiet
-
-storybook_index:
- mkdir -p data/storybook/
- m4 \
- -DLIST="$(shell gsutil ls "${BUCKET}/" \
- | sed -E "s@^${BUCKET}/([^/]+)/@\1@" \
- | grep -v -e index.html \
- | sort \
- | tr '\n' ,)" \
- -DBRANCH_NAME="${BRANCH_NAME}" .storybook/index.html.m4 \
- > data/storybook/index.html
-
-MODULE_TARGETS = \
- $(TEST_ACCEPTANCE_MODULES_SAAS) \
- $(TEST_ACCEPTANCE_MODULES_SERVER_CE) \
- $(TEST_ACCEPTANCE_MODULES_SERVER_PRO) \
- $(TEST_ACCEPTANCE_MODULES_MERGED_INNER) \
- $(CLEAN_TEST_ACCEPTANCE_MODULES) \
- $(TEST_UNIT_MODULES) \
-
-$(MODULE_TARGETS):
- $(MAKE) -C $(dir $@) $(notdir $@) BUILD_DIR_NAME=$(BUILD_DIR_NAME)
-
-.PHONY:
- $(MODULE_TARGETS) \
- compile_modules compile_modules_full clean_ci \
- test test_module mongo_migrations_for_tests test_unit test_unit_app \
- test_unit_modules test_unit_module test_frontend_jsdom \
- test_acceptance test_acceptance_app test_acceptance_modules \
- test_acceptance_module ci format format_fix lint \
- shellcheck shellcheck_fix \
- build publish tar
diff --git a/services/web/Makefile.module b/services/web/Makefile.module
deleted file mode 100644
index 3a40b73d80..0000000000
--- a/services/web/Makefile.module
+++ /dev/null
@@ -1,74 +0,0 @@
-BUILD_DIR_NAME ?= web
-ifeq ($(BRANCH_NAME),)
- export BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
-endif
-export BRANCH_NAME_TAG_SAFE := $(shell echo "$(BRANCH_NAME)" | sed 's/\//\-\-/g' | cut -c1-96)
-export MODULE_NAME := $(notdir $(shell pwd))
-export MODULE_DIR := modules/$(MODULE_NAME)
-export PROJECT_NAME = web
-export RETRIES ?= $(if $(CI),3,0)
-export MOCHA_GREP ?=
-
-export OVERLEAF_CONFIG = /overleaf/services/web/$(MODULE_DIR)/test/acceptance/config/settings.test.js
-export BASE_CONFIG ?= /overleaf/services/web/test/acceptance/config/settings.test.saas.js
-
-CFG_SAAS=/overleaf/services/web/test/acceptance/config/settings.test.saas.js
-CFG_SERVER_CE=/overleaf/services/web/test/acceptance/config/settings.test.server-ce.js
-CFG_SERVER_PRO=/overleaf/services/web/test/acceptance/config/settings.test.server-pro.js
-
-DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
-DOCKER_COMPOSE := cd ../../ && docker compose ${DOCKER_COMPOSE_FLAGS}
-
-ifeq (,$(wildcard test/unit))
-test_unit:
-
-else
-test_unit: export COMPOSE_PROJECT_NAME=unit_test_$(BUILD_DIR_NAME)_$(MODULE_NAME)
-test_unit:
- ${DOCKER_COMPOSE} run --rm test_unit yarn test:unit:run_dir ${MOCHA_ARGS} $(MODULE_DIR)/test/unit/src
- ${DOCKER_COMPOSE} down
-
-endif
-
-ALL_TEST_ACCEPTANCE_VARIANTS := \
- test_acceptance \
- test_acceptance_saas \
- test_acceptance_server_ce \
- test_acceptance_server_pro \
-
-ifeq (,$(wildcard test/acceptance))
-$(ALL_TEST_ACCEPTANCE_VARIANTS) test_acceptance_merged_inner:
- @echo
- @echo Module $(MODULE_NAME) does not have acceptance tests.
- @echo
-
-clean_test_acceptance:
-
-else
-ifeq ($(DEBUG),true)
-TEST_ACCEPTANCE_DEBUG_PORT := -p 127.0.0.1:19999:19999
-TEST_ACCEPTANCE_NPM_SCRIPT := test:acceptance:debug:run_dir
-else
-TEST_ACCEPTANCE_DEBUG_PORT :=
-TEST_ACCEPTANCE_NPM_SCRIPT := test:acceptance:run_dir
-endif
-
-test_acceptance_saas: export BASE_CONFIG = $(CFG_SAAS)
-test_acceptance_server_ce: export BASE_CONFIG = $(CFG_SERVER_CE)
-test_acceptance_server_pro: export BASE_CONFIG = $(CFG_SERVER_PRO)
-
-$(ALL_TEST_ACCEPTANCE_VARIANTS): export COMPOSE_PROJECT_NAME=acceptance_test_$(BUILD_DIR_NAME)_$(MODULE_NAME)
-$(ALL_TEST_ACCEPTANCE_VARIANTS):
- $(MAKE) --no-print-directory clean_test_acceptance
- ${DOCKER_COMPOSE} run --rm $(TEST_ACCEPTANCE_DEBUG_PORT) test_acceptance yarn $(TEST_ACCEPTANCE_NPM_SCRIPT) -- ${MOCHA_ARGS} $(MODULE_DIR)/test/acceptance/src
- $(MAKE) --no-print-directory clean_test_acceptance
-
-test_acceptance_merged_inner:
- cd ../../ && \
- yarn test:acceptance:run_dir -- ${MOCHA_ARGS} $(MODULE_DIR)/test/acceptance/src
-
-clean_test_acceptance: export COMPOSE_PROJECT_NAME=acceptance_test_$(BUILD_DIR_NAME)_$(MODULE_NAME)
-clean_test_acceptance:
- ${DOCKER_COMPOSE} down -v -t 0
-
-endif
diff --git a/services/web/bin/cdn_upload b/services/web/bin/cdn_upload
deleted file mode 100755
index 52281e4dd0..0000000000
--- a/services/web/bin/cdn_upload
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/bash
-set -eEu
-
-function upload_into_bucket() {
- bucket=$1
-
- # stylesheets
- bin/cdn_upload_batch 'text/css' "$bucket" '.css' \
- -x '.+(?&2 'WARN: %d local file(s) not available in bucket %s:\n' \
- ${#missing_from_bucket[@]} "$bucket"
- printf >&2 ' - %s\n' "${missing_from_bucket[@]}"
- return 1
- fi
-}
-
-# Upload to staging CDN if branch is either 'main' or 'staging-main'
-if [[ "$BRANCH_NAME" == "main" ]] || [[ "$BRANCH_NAME" == "staging-main" ]]; then
- tar --directory=/tmp/ -xf build.tar
-
- # delete source maps
- find /tmp/public -name '*.js.map' -delete
-
- bin/compress_assets
-
- upload_into_bucket "$CDN_STAG" &&
- verify_upload_into_bucket "$CDN_STAG" || exit 3 &
- pid_staging=$! # record pid of the detached process "upload && verify || exit 3")
-
- pid_production=
- # Only upload to production CDN if branch is
- if [[ "$BRANCH_NAME" == "main" ]]; then
- upload_into_bucket "$CDN_PROD" &&
- verify_upload_into_bucket "$CDN_PROD" || exit 4 &
- pid_production=$! # record pid of the detached process "upload && verify || exit 4")
- fi
-
- wait "$pid_staging" # wait for staging upload to finish, wait(1) will exit if the upload failed
- if [[ -n "$pid_production" ]]; then
- wait "$pid_production" # wait for production upload to finish (if started), wait(1) will exit if the upload failed
- fi
-fi
diff --git a/services/web/bin/cdn_upload_batch b/services/web/bin/cdn_upload_batch
deleted file mode 100755
index 606e2a92cc..0000000000
--- a/services/web/bin/cdn_upload_batch
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/bash
-set -e
-
-content_type=$1
-bucket=$2
-text_extension=$3
-shift 3
-content_type_options=""
-if [[ "$content_type" != "-" ]]; then
- content_type_options="-h Content-Type:${content_type};charset=utf-8"
-fi
-
-# DOCS for gsutil -- it does not have long command line flags!
-## global flags
-# -h NAME:VALUE add header, can occur multiples times
-# -m upload with multiple threads
-## rsync flags
-# -c use checksums for determining changed files (mtime is not stable)
-# -r traverse into directories recursively
-# -x Python regex for excluding files from the sync
-if [[ "$text_extension" == "-" || $(find /tmp/public -type f -name "*$text_extension" | wc -l) != "0" ]]; then
- # Potentially skip upload of non-compressed .js/.css files.
- # shellcheck disable=SC2086
- gsutil \
- -h "Cache-Control:public, max-age=31536000" \
- ${content_type_options} \
- -m \
- rsync \
- -c \
- -r \
- "$@" \
- "/tmp/public/" \
- "${bucket}/public/"
-fi
-
-# shellcheck disable=SC2086
-gsutil \
- -h "Cache-Control:public, max-age=31536000" \
- -h "Content-Encoding:gzip" \
- ${content_type_options} \
- -m \
- rsync \
- -c \
- -r \
- "$@" \
- "/tmp/compressed/public/" \
- "${bucket}/public/"
diff --git a/services/web/bin/check_extracted_translations b/services/web/bin/check_extracted_translations
deleted file mode 100755
index 569af5ccf0..0000000000
--- a/services/web/bin/check_extracted_translations
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# Ensure all locales used in the frontend are tracked
-OUTPUT=data/dumpFolder/i18next-scanner
-trap 'rm -rf "$OUTPUT"' EXIT
-npx i18next-scanner --output "$OUTPUT"
-ACTUAL=frontend/extracted-translations.json
-EXPECTED="$OUTPUT/frontend/extracted-translations.json"
-if ! diff "$ACTUAL" "$EXPECTED"; then
- cat <&2
-
-services/web/frontend/extracted-translations.json is not up-to-date.
-
----
-Try running:
-
- internal$ bin/run web npm run extract-translations
-
----
-MSG
- exit 1
-fi
diff --git a/services/web/bin/compress_assets b/services/web/bin/compress_assets
deleted file mode 100755
index e50f0a3389..0000000000
--- a/services/web/bin/compress_assets
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env bash
-set -e -o pipefail
-
-SCRIPT_PATH=$(realpath "${BASH_SOURCE[0]}")
-SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
-
-pushd /tmp
-
-find public/ -type d | sed 's!^!compressed/!' | xargs mkdir --parents
-
-find public/ -type f -print0 \
-| xargs --null --max-args 20 --max-procs 10 "$SCRIPT_DIR/compress_batch_of_assets"
-
-popd
diff --git a/services/web/bin/compress_batch_of_assets b/services/web/bin/compress_batch_of_assets
deleted file mode 100755
index 4a650da4b8..0000000000
--- a/services/web/bin/compress_batch_of_assets
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-for file in "$@"; do
- file_gzipped="compressed/$file"
-
- gzip -9 --no-name --stdout "$file" > "$file_gzipped"
-
- before=$(stat -c%s "$file")
- after=$(stat -c%s "$file_gzipped")
- if [[ "$after" -ge "$before" ]]; then
- rm "$file_gzipped"
- else
- rm "$file"
- fi
-done
diff --git a/services/web/bin/copy_external_pages b/services/web/bin/copy_external_pages
deleted file mode 100755
index db94156481..0000000000
--- a/services/web/bin/copy_external_pages
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-set -e
-
-# Branding
-cp app/views/external/robots.txt public/robots.txt
-cp app/views/external/googlebdb0f8f7f4a17241.html public/googlebdb0f8f7f4a17241.html
diff --git a/services/web/bin/lint_flag_res_send_usage b/services/web/bin/lint_flag_res_send_usage
deleted file mode 100755
index a4ff1f5f62..0000000000
--- a/services/web/bin/lint_flag_res_send_usage
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/bash
-
-set -e
-
-POTENTIAL_SEND_USAGE=$(\
- grep \
- --files-with-matches \
- --recursive \
- app.mjs \
- app/ \
- modules/*/app \
- test/acceptance/ \
- modules/*/test/acceptance/ \
- --regex "\.send\b" \
- --regex "\bsend(" \
-)
-HELPER_MODULE="app/src/infrastructure/Response.mjs"
-MOCK_MODULE="test/acceptance/src/mocks/MockResponse.mjs"
-if [[ "$POTENTIAL_SEND_USAGE" == "$HELPER_MODULE" ]] || [[ "$file" == "$MOCK_MODULE" ]]; then
- exit 0
-fi
-
-for file in ${POTENTIAL_SEND_USAGE}; do
- if [[ "$file" == "$HELPER_MODULE" ]] || [[ "$file" == "$MOCK_MODULE" ]]; then
- continue
- fi
-
- cat <&2
-
-ERROR: $file contains a potential use of 'res.send'.
-
----
-$(grep -n -C 3 "$file" --regex "\.send\b" --regex "\bsend(")
----
-
-Using 'res.send' is prone to introducing XSS vulnerabilities.
-
-Consider using 'res.json' or one of the helpers in $HELPER_MODULE.
-
-If this is a false-positive, consider using a more specific name than 'send'
- for your newly introduced function.
-
-Links:
- - https://github.com/overleaf/internal/issues/6268
-
-MSG
- exit 1
-done
diff --git a/services/web/bin/lint_locales b/services/web/bin/lint_locales
deleted file mode 100755
index dc931b7cd2..0000000000
--- a/services/web/bin/lint_locales
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-set -e
-
-# Ensure all locales are still in use
-node scripts/translations/cleanupUnusedLocales.js --check
-
-# Ensure all locales use the same variables
-node scripts/translations/checkVariables.js --ignore-orphaned-translations
diff --git a/services/web/bin/lint_pug_templates b/services/web/bin/lint_pug_templates
deleted file mode 100755
index 6f255d9745..0000000000
--- a/services/web/bin/lint_pug_templates
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-
-set -e
-
-TEMPLATES_EXTENDING_META_BLOCK=$(\
- grep \
- --files-with-matches \
- --recursive app/views modules/*/app/views \
- --regex 'block append meta' \
- --regex 'block prepend meta' \
- --regex 'append meta' \
- --regex 'prepend meta' \
-)
-
-for file in ${TEMPLATES_EXTENDING_META_BLOCK}; do
- if ! grep "$file" --quiet --extended-regexp -e 'extends .+layout'; then
- cat <&2
-
-ERROR: $file is a partial template and extends 'block meta'.
-
-Using block append/prepend in a partial will duplicate the block contents into
- the due to a bug in pug.
-Putting meta tags in the can lead to Angular XSS.
-
-You will need to refactor the partial and move the block into the top level
- page template that extends the global layout.pug.
-
-MSG
- exit 1
- fi
-done
diff --git a/services/web/bin/push-translations-changes.sh b/services/web/bin/push-translations-changes.sh
deleted file mode 100755
index ba24757052..0000000000
--- a/services/web/bin/push-translations-changes.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-set -e
-
-SCRIPT_PATH=$(realpath "${BASH_SOURCE[0]}")
-SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
-WEB_DIR=$(dirname "$SCRIPT_DIR")
-
-cd "$WEB_DIR"
-
-if [[ $(git status --porcelain=2 locales/) ]]; then
- git add locales/*
- git commit -m "auto update translation"
- # Switch the cloudbuild clone from https to ssh authentication.
- git remote set-url --push origin git@github.com:overleaf/internal.git
- git push origin "HEAD:$BRANCH_NAME"
-else
- echo 'No changes'
-fi
diff --git a/services/web/bin/routes b/services/web/bin/routes
deleted file mode 100755
index 857d182256..0000000000
--- a/services/web/bin/routes
+++ /dev/null
@@ -1,130 +0,0 @@
-#! /usr/bin/env node
-
-const acorn = require('acorn')
-const acornWalk = require('acorn-walk')
-const fs = require('fs')
-const _ = require('lodash')
-const { glob } = require('glob')
-const escodegen = require('escodegen')
-const print = console.log
-
-const Methods = new Set([
- 'get',
- 'head',
- 'post',
- 'put',
- 'delete',
- 'connect',
- 'options',
- 'trace',
- 'patch',
-])
-
-const isMethod = str => {
- return Methods.has(str)
-}
-
-// Check if the expression is a call on a router, return data about it, or null
-const routerCall = callExpression => {
- const callee = callExpression.callee
- const property = callee.property
- const args = callExpression.arguments
- if (!callee.object || !callee.object.name) {
- return false
- }
- const routerName = callee.object.name
- if (
- // Match known names for the Express routers: app, webRouter, whateverRouter, etc...
- isMethod(property.name) &&
- (routerName === 'app' || routerName.match('^.*[rR]outer$'))
- ) {
- return {
- routerName,
- method: property.name,
- args,
- }
- } else {
- return null
- }
-}
-
-const formatMethodCall = expression => {
- return escodegen.generate(expression, { format: { compact: true } })
-}
-
-const parseAndPrintRoutesSync = path => {
- const content = fs.readFileSync(path)
- // Walk the AST (Abstract Syntax Tree)
- acornWalk.simple(
- acorn.parse(content, { sourceType: 'module', ecmaVersion: 2020 }),
- {
- // We only care about call expression ( like `a.b()` )
- CallExpression(node) {
- const call = routerCall(node)
- if (call) {
- const firstArg = _.first(call.args)
- try {
- print(
- ` ${formatRouterName(call.routerName)}\t .${call.method} \t: ${
- firstArg.value
- } => ${call.args.slice(1).map(formatMethodCall).join(' => ')}`
- )
- } catch (e) {
- print('>> Error')
- print(e)
- print(JSON.stringify(call))
- process.exit(1)
- }
- }
- },
- }
- )
-}
-
-const routerNameMapping = {
- privateApiRouter: 'privateApi',
- publicApiRouter: 'publicApi',
-}
-const formatRouterName = name => {
- return routerNameMapping[name] || name
-}
-
-const main = async () => {
- // Take an optional filter to apply to file names
- const filter = process.argv[2] || null
-
- if (filter && (filter === '--help' || filter === 'help')) {
- print('')
- print(' Usage: bin/routes [filter]')
- print(' Examples:')
- print(' bin/routes')
- print(' bin/routes GitBridge')
- print('')
- process.exit(0)
- }
-
- // Find all routers
- const files = await glob('*[rR]outer.*js', { matchBase: true })
- for (const file of files) {
- if (file.match('^node_modules.*$') || file.match('.*/public/.*')) {
- continue
- }
- // Restrict to the filter (if filter is present)
- if (filter && !file.match(`.*${filter}.*`)) {
- continue
- }
- print(`[${file}]`)
- try {
- parseAndPrintRoutesSync(file)
- } catch (_e) {
- print('>> Error parsing file')
- }
- }
-}
-
-if (require.main === module) {
- main().then(() => process.exit(0)).catch(() => {
- console.error(err)
- process.exit(1)
- })
-}
diff --git a/services/web/bin/run b/services/web/bin/run
deleted file mode 100755
index f0af921c79..0000000000
--- a/services/web/bin/run
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-pushd ..
-bin/run "$*"
-RV=$?
-popd || exit 1
-
-exit $RV
diff --git a/services/web/bin/sentry_upload b/services/web/bin/sentry_upload
deleted file mode 100755
index 45a2a9336a..0000000000
--- a/services/web/bin/sentry_upload
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/sh
-set -e
-
-echo "BRANCH_NAME=$BRANCH_NAME"
-echo "SENTRY_RELEASE=$SENTRY_RELEASE"
-
-if [[ "$BRANCH_NAME" == "main" ]]; then
- rm -rf sentry_upload
- mkdir sentry_upload
- tar --directory sentry_upload -xf build.tar
- cd sentry_upload/public
-
- sentry-cli releases new "$SENTRY_RELEASE"
- sentry-cli releases set-commits --auto "$SENTRY_RELEASE"
- sentry-cli sourcemaps upload --release="$SENTRY_RELEASE" .
- sentry-cli releases finalize "$SENTRY_RELEASE"
-
- cd ../..
- rm -rf sentry_upload
-else
- echo "Not on main branch. Skipping sentry upload."
-fi
diff --git a/services/web/bin/test_acceptance_run_dir b/services/web/bin/test_acceptance_run_dir
deleted file mode 100755
index 151af59120..0000000000
--- a/services/web/bin/test_acceptance_run_dir
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-declare -a mocha_prefix=()
-
-if [[ "$COVERAGE_ACCEPTANCE_TESTS" == "true" ]]; then
- name=$(echo "$BASE_CONFIG $OVERLEAF_CONFIG" "$@" | sha256sum)
- dir=data/coverage/mocha-acceptance-${name}
- echo "Enabling coverage. Writing into ${dir}"
- mocha_prefix+=("c8" "--reporter=clover" "--all" "--include={app.mjs,app/**/*.{mjs,js},modules/*/index.mjs,modules/*/app/**/*.{mjs,js}}" "--exclude=app/src/Features/Metadata/packageMapping.mjs" "--report-dir=${dir}")
-fi
-
-"${mocha_prefix[@]}" mocha --recursive --timeout 25000 --grep="$MOCHA_GREP" --require test/acceptance/bootstrap.js --retries="$RETRIES" "$@"
diff --git a/services/web/bin/test_unit_run_dir b/services/web/bin/test_unit_run_dir
deleted file mode 100755
index 02e5a03f5b..0000000000
--- a/services/web/bin/test_unit_run_dir
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-declare -a vitest_args=("$@")
-
-if [[ -n "$MOCHA_GREP" ]]; then
- vitest_args+=("--testNamePattern" "$MOCHA_GREP")
-fi
-
-if [[ -n "$VITEST_NO_CACHE" ]]; then
- echo "Disabling cache for vitest."
- vitest_args+=("--no-cache")
-fi
-
-echo "Running unit tests in directory: $*"
-
-exec yarn run vitest run "${vitest_args[@]}"
diff --git a/services/web/buildscript.txt b/services/web/buildscript.txt
deleted file mode 100644
index 375e696a54..0000000000
--- a/services/web/buildscript.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-web
---dependencies=mongo
---docker-repos=us-east1-docker.pkg.dev/overleaf-ops/ol-docker
---env-add=
---env-pass-through=
---esmock-loader=False
---node-version=24.14.1
---public-repo=False
diff --git a/services/web/certs/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem b/services/web/certs/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem
deleted file mode 100644
index f06ba91adf..0000000000
--- a/services/web/certs/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt.pem
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
-MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh
-bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD
-ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV
-cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy
-FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc
-3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8
-osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT
-zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud
-EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G
-A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd
-BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG
-CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG
-NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH
-Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t
-L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC
-ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG
-9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t
-wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS
-slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R
-bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4
-chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN
-JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==
------END CERTIFICATE-----
diff --git a/services/web/data/.gitignore b/services/web/data/.gitignore
deleted file mode 100644
index 8790881c38..0000000000
--- a/services/web/data/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# ignore everything in this directory
-*
-# Except this file
-!.gitignore
diff --git a/services/web/data/coverage/.gitignore b/services/web/data/coverage/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/dumpFolder/.gitignore b/services/web/data/dumpFolder/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/logs/.gitignore b/services/web/data/logs/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/pdfs/.gitignore b/services/web/data/pdfs/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/projectHistories/.gitignore b/services/web/data/projectHistories/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/reports/.gitignore b/services/web/data/reports/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/uploads/.gitignore b/services/web/data/uploads/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/data/zippedProjects/.gitignore b/services/web/data/zippedProjects/.gitignore
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/services/web/docker-compose.ci.yml b/services/web/docker-compose.ci.yml
deleted file mode 100644
index 3353b33e8a..0000000000
--- a/services/web/docker-compose.ci.yml
+++ /dev/null
@@ -1,180 +0,0 @@
-volumes:
- data:
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- logging:
- driver: local
- user: node
- volumes:
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ./data/coverage:/overleaf/services/web/data/coverage
- - ./data/reports:/overleaf/services/web/data/reports
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:unit:app
- working_dir: /overleaf/services/web
- env_file: docker-compose.common.env
- environment:
- CI:
- MODULE_NAME:
- JUNIT_ROOT_SUITE_NAME:
- BASE_CONFIG:
- OVERLEAF_CONFIG:
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- VITEST_NO_CACHE: true
- COVERAGE_UNIT_TESTS:
- NO_COLOR: true
- MONOREPO:
- depends_on:
- - redis_test
- - mongo
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- logging:
- driver: local
- working_dir: /overleaf/services/web
- env_file: docker-compose.common.env
- environment:
- CI:
- MODULE_NAME:
- JUNIT_ROOT_SUITE_NAME:
- BASE_CONFIG:
- OVERLEAF_CONFIG:
- REDIS_HOST: redis_test
- COVERAGE_ACCEPTANCE_TESTS:
- NO_COLOR: true
- MONOREPO:
- RETRIES:
- extra_hosts:
- - "www.overleaf.test:127.0.0.1"
- volumes:
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ./data/coverage:/overleaf/services/web/data/coverage
- - ./data/reports:/overleaf/services/web/data/reports
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run test:acceptance:app
- depends_on:
- - redis_test
- - mongo
- - saml
- - ldap
- tmpfs:
- - /overleaf/services/web/data:uid=1000,gid=1000
-
- test_frontend_jsdom:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- volumes:
- - ./data/reports:/overleaf/services/web/data/reports
- logging:
- driver: local
- user: node
- working_dir: /overleaf/services/web
- command: yarn run test:frontend
- environment:
- NODE_OPTIONS: "--unhandled-rejections=strict"
- CI:
- JUNIT_ROOT_SUITE_NAME:
- NO_COLOR: true
-
- test_frontend_ct:
- build:
- context: ../../dockerfiles/cypress
- args:
- - USER_UID
- - USER_GID
- logging:
- driver: local
- working_dir: /overleaf/services/web
- environment:
- CI:
- CYPRESS_DOWNLOADS:
- CYPRESS_RESULTS:
- CYPRESS_SPEC_PATTERN:
- CYPRESS_EXCLUDE_SPEC_PATTERN:
- NO_COLOR: true
- volumes:
- - /dev/shm/overleaf:/overleaf
- - ./data/reports:/overleaf/services/web/data/reports
- tmpfs:
- - /overleaf/services/web/node_modules/.cache:uid=1000,gid=1000
- entrypoint: yarn
- command:
- - "run"
- - "cypress:run-ct"
-
- test_writefull:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER
- logging:
- driver: local
- user: node
- working_dir: /overleaf/services/web
- command: yarn run test:writefull
- volumes:
- - ./data/reports:/overleaf/services/web/data/reports
- environment:
- CI:
- JUNIT_ROOT_SUITE_NAME:
- NODE_OPTIONS: "--unhandled-rejections=strict"
- NO_COLOR: true
-
- tar:
- image: ci/$PROJECT_NAME:$BRANCH_NAME_TAG_SAFE-$BUILD_NUMBER-webpack
- volumes:
- - ./:/tmp/build/
- command: tar -cf /tmp/build/build.tar public/
- user: root
-
- redis_test:
- image: redis:7.4.8
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- ldap:
- image: rroemhild/test-openldap:1.1
- logging:
- driver: none
- # Workaround large memory allocation (using the max-open-files-limit as socket buffer scale).
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63877553
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63871343
- command:
- - "bash"
- - "-c"
- - "ulimit -n 1024 && exec bash /run.sh"
-
- saml:
- image: us-east1-docker.pkg.dev/overleaf-ops/ol-docker/saml-test
- logging:
- driver: none
- environment:
- SAML_BASE_URL_PATH: "http://saml/simplesaml/"
- SAML_TEST_SP_ENTITY_ID: "overleaf-test-saml"
- SAML_TEST_SP_LOCATION: "http://www.overleaf.test:23000/saml/callback"
diff --git a/services/web/docker-compose.common.env b/services/web/docker-compose.common.env
deleted file mode 100644
index bb7d3d8438..0000000000
--- a/services/web/docker-compose.common.env
+++ /dev/null
@@ -1,55 +0,0 @@
-ADMIN_PRIVILEGE_AVAILABLE=true
-BCRYPT_ROUNDS=1
-WEB_PORT=23000
-REDIS_HOST=redis
-QUEUES_REDIS_HOST=redis
-ANALYTICS_QUEUES_REDIS_HOST=redis
-MONGO_URL=mongodb://mongo/test-overleaf
-OVERLEAF_ALLOW_PUBLIC_ACCESS=true
-LINKED_URL_PROXY=http://127.0.0.1:6543
-ENABLED_LINKED_FILE_TYPES=url,project_file,project_output_file,mendeley,zotero
-NODE_ENV=test
-NODE_OPTIONS=--unhandled-rejections=strict
-LOCK_MANAGER_MAX_LOCK_WAIT_TIME=30000
-COOKIE_DOMAIN=.overleaf.test
-ADMIN_URL=http://admin.overleaf.test
-PUBLIC_URL=http://www.overleaf.test:23000
-HTTP_TEST_HOST=www.overleaf.test
-OT_JWT_AUTH_KEY="very secret key"
-EXTERNAL_AUTH=none
-RECAPTCHA_ENDPOINT=http://127.0.0.1:2222/recaptcha/api/siteverify
-# Server-Pro LDAP
-OVERLEAF_LDAP_URL=ldap://ldap:389
-OVERLEAF_LDAP_SEARCH_BASE=ou=people,dc=planetexpress,dc=com
-OVERLEAF_LDAP_SEARCH_FILTER="(uid={{username}})"
-OVERLEAF_LDAP_BIND_DN=cn=admin,dc=planetexpress,dc=com
-OVERLEAF_LDAP_BIND_CREDENTIALS=GoodNewsEveryone
-OVERLEAF_LDAP_EMAIL_ATT=mail
-OVERLEAF_LDAP_NAME_ATT=cn
-OVERLEAF_LDAP_LAST_NAME_ATT=sn
-OVERLEAF_LDAP_UPDATE_USER_DETAILS_ON_LOGIN=true
-OVERLEAF_LDAP_IS_ADMIN_ATT=ou
-OVERLEAF_LDAP_IS_ADMIN_ATT_VALUE=Staff
-# Server-Pro SAML
-OVERLEAF_SAML_ENTRYPOINT=http://saml/simplesaml/saml2/idp/SSOService.php
-OVERLEAF_SAML_CALLBACK_URL=http://saml/saml/callback
-OVERLEAF_SAML_ISSUER=overleaf-test-saml
-OVERLEAF_SAML_IDENTITY_SERVICE_NAME="SAML Test Server"
-OVERLEAF_SAML_EMAIL_FIELD=email
-OVERLEAF_SAML_FIRST_NAME_FIELD=givenName
-OVERLEAF_SAML_LAST_NAME_FIELD=sn
-OVERLEAF_SAML_UPDATE_USER_DETAILS_ON_LOGIN=true
-OVERLEAF_SAML_IS_ADMIN_FIELD=sn
-OVERLEAF_SAML_IS_ADMIN_FIELD_VALUE=Brookes
-# simplesaml cert from https://github.com/overleaf/google-ops/tree/master/docker-images/saml-test/var-simplesamlphp/cert
-OVERLEAF_SAML_CERT=MIIDXTCCAkWgAwIBAgIJAOvOeQ4xFTzsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMTE1MTQxMjU5WhcNMjYxMTE1MTQxMjU5WjBFMQswCQYDVQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCT6MBe5G9VoLU8MfztOEbUhnwLp17ak8eFUqxqeXkkqtWB0b/cmIBU3xoQoO3dIF8PBzfqehqfYVhrNt/TFgcmDfmJnPJRL1RJWMW3VmiP5odJ3LwlkKbZpkeT3wZ8HEJIR1+zbpxiBNkbd2GbdR1iumcsHzMYX1A2CBj+ZMV5VijC+K4P0e9c05VsDEUtLmfeAasJAiumQoVVgAe/BpiXjICGGewa6EPFI7mKkifIRKOGxdRESwZZjxP30bI31oDN0cgKqIgSJtJ9nfCn9jgBMBkQHu42WMuaWD4jrGd7+vYdX+oIfArs9aKgAH5kUGhGdew2R9SpBefrhbNxG8QIDAQABo1AwTjAdBgNVHQ4EFgQU+aSojSyyLChP/IpZcafvSdhj7KkwHwYDVR0jBBgwFoAU+aSojSyyLChP/IpZcafvSdhj7KkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABl3+OOVLBWMKs6PjA8lPuloWDNzSr3v76oUcHqAb+cfbucjXrOVsS9RJ0X9yxvCQyfM9FfY43DbspnN3izYhdvbJD8kKLNf0LA5st+ZxLfy0ACyL2iyAwICaqndqxAjQYplFAHmpUiu1DiHckyBPekokDJd+ze95urHMOsaGS5RWPoKJVE0bkaAeZCmEu0NNpXRSBiuxXSTeSAJfv6kyE/rkdhzUKyUl/cGQFrsVYfAFQVA+W6CKOh74ErSEzSHQQYndl7nD33snD/YqdU1ROxV6aJzLKCg+sdj+wRXSP2u/UHnM4jW9TGJfhO42jzL6WVuEvr9q4l7zWzUQKKKhtQ==
-# DEVICE_HISTORY_SECRET has been generated using:
-# NOTE: crypto.generateKeySync was added in v15, v16 is the next LTS release.
-# $ docker run --rm node:24.14.1 --print 'require("crypto").generateKeySync("aes", { length: 256 }).export().toString("hex")'
-DEVICE_HISTORY_SECRET=1b46e6cdf72db02845da06c9517c9cfbbfa0d87357479f4e1df3ce160bd54807
-QUEUE_PROCESSING_ENABLED=true
-CURRENT_IMAGE_NAME=texlive-full:2025.1
-ALLOWED_IMAGE_NAMES=[{"imageName":"texlive-full:2026.1","imageDesc":"2026", "alphaOnly": true},{"imageName":"texlive-full:2025.1","imageDesc":"2025"},{"imageName":"wl_texlive:2018.1","imageDesc":"2018"}]
-ENABLE_PANDOC_CONVERSIONS=true
-GOOGLE_VERTEX_PROJECT=overleaf-dev
-GOOGLE_VERTEX_LOCATION=global
diff --git a/services/web/docker-compose.cypress.yml b/services/web/docker-compose.cypress.yml
deleted file mode 100644
index 47b7bf523b..0000000000
--- a/services/web/docker-compose.cypress.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-services:
- cypress:
- build:
- context: ../../dockerfiles/cypress
- args:
- - USER_UID
- - USER_GID
- volumes:
- - ../../:/overleaf
- - /tmp/.X11-unix:/tmp/.X11-unix
- - ${XAUTHORITY:-/dev/null}:/home/node/.Xauthority
- working_dir: /overleaf/services/web
- entrypoint: yarn
- command: run cypress:open-ct
- environment:
- VERBOSE_LOGGING:
- DISPLAY: ${DISPLAY:-:0}
-# DEBUG: cypress.*
diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml
deleted file mode 100644
index be82f0432e..0000000000
--- a/services/web/docker-compose.yml
+++ /dev/null
@@ -1,191 +0,0 @@
-volumes:
- data:
-
-services:
- test_unit:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/web
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- - ../../tsconfig.backend.json:/overleaf/tsconfig.backend.json
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- working_dir: /overleaf/services/web
- env_file: docker-compose.common.env
- environment:
- BASE_CONFIG:
- OVERLEAF_CONFIG:
- CI:
- MODULE_NAME:
- JUNIT_ROOT_SUITE_NAME:
- COVERAGE_UNIT_TESTS: ${COVERAGE_UNIT_TESTS:-}
- MOCHA_GREP: ${MOCHA_GREP:-}
- LOG_LEVEL: ${LOG_LEVEL:-}
- NODE_ENV: test
- NODE_OPTIONS: "--unhandled-rejections=strict"
- REDIS_HOST: redis_test
- MONOREPO:
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:unit:app
- user: node
- depends_on:
- - mongo
- - redis_test
-
- test_acceptance:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/web
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../bin/shared/wait_for_it:/overleaf/bin/shared/wait_for_it
- - ../../tools/migrations:/overleaf/tools/migrations
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- user: node
- working_dir: /overleaf/services/web
- env_file: docker-compose.common.env
- environment:
- BASE_CONFIG:
- OVERLEAF_CONFIG:
- CI:
- MODULE_NAME:
- JUNIT_ROOT_SUITE_NAME:
- COVERAGE_ACCEPTANCE_TESTS: ${COVERAGE_ACCEPTANCE_TESTS:-}
- MOCHA_GREP: ${MOCHA_GREP:-}
- LOG_LEVEL: ${LOG_LEVEL:-}
- MONGO_SERVER_SELECTION_TIMEOUT: 600000
- MONGO_SOCKET_TIMEOUT: 300000
- REDIS_HOST: redis_test
- MONOREPO:
- RETRIES:
- # OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
-
- extra_hosts:
- - "www.overleaf.test:127.0.0.1"
- depends_on:
- - redis_test
- - mongo
- - saml
- - ldap
- entrypoint: /overleaf/bin/shared/wait_for_it mongo:27017 --timeout=60 --
- command: yarn run --silent test:acceptance:app
-
- test_frontend_jsdom:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/web
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- working_dir: /overleaf/services/web
- environment:
- CI:
- JUNIT_ROOT_SUITE_NAME:
- MOCHA_GREP: ${MOCHA_GREP}
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VERBOSE_LOGGING:
- command: yarn run --silent test:frontend
- user: node
-
- test_frontend_ct:
- build:
- context: ../../dockerfiles/cypress
- args:
- - USER_UID
- - USER_GID
- volumes:
- - .:/overleaf/services/web
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- working_dir: /overleaf/services/web
- environment:
- VERBOSE_LOGGING:
- CI:
- JUNIT_ROOT_SUITE_NAME:
- CYPRESS_SPEC_PATTERN: ${CYPRESS_SPEC_PATTERN:-}
- CYPRESS_EXCLUDE_SPEC_PATTERN: ${CYPRESS_EXCLUDE_SPEC_PATTERN:-}
- entrypoint: npm
- command:
- - "run"
- - "cypress:run-ct"
-
- test_writefull:
- build:
- context: ../..
- dockerfile: services/web/Dockerfile
- target: base
- volumes:
- - .:/overleaf/services/web
- - ../../node_modules:/overleaf/node_modules
- - ../../libraries:/overleaf/libraries
- - ../../package.json:/overleaf/package.json
- - ../../.yarnrc.yml:/overleaf/.yarnrc.yml
- - ../../yarn.lock:/overleaf/yarn.lock
- - ../../.yarn:/overleaf/.yarn
- working_dir: /overleaf/services/web
- environment:
- CI:
- JUNIT_ROOT_SUITE_NAME:
- NODE_OPTIONS: "--unhandled-rejections=strict"
- VERBOSE_LOGGING:
- command: yarn run --silent test:writefull
- user: node
-
- redis_test:
- image: redis:7.4.8
-
- mongo:
- image: mongo:8.0.11
- command: --replSet overleaf --notablescan
- volumes:
- - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
- - ../../bin/shared/mongodb-docker-entrypoint-wait.sh:/mongodb-docker-entrypoint-wait.sh
- entrypoint: /mongodb-docker-entrypoint-wait.sh
- environment:
- MONGO_INITDB_DATABASE: sharelatex
- extra_hosts:
- # Required when using the automatic database setup for initializing the
- # replica set. This override is not needed when running the setup after
- # starting up mongo.
- - mongo:127.0.0.1
-
- ldap:
- image: rroemhild/test-openldap:1.1
- # Workaround large memory allocation (using the max-open-files-limit as socket buffer scale).
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63877553
- # REF: https://github.com/moby/moby/issues/8231#issuecomment-63871343
- command:
- - "bash"
- - "-c"
- - "ulimit -n 1024 && exec bash /run.sh"
-
- saml:
- image: us-east1-docker.pkg.dev/overleaf-ops/ol-docker/saml-test
- environment:
- SAML_BASE_URL_PATH: "http://saml/simplesaml/"
- SAML_TEST_SP_ENTITY_ID: "overleaf-test-saml"
- SAML_TEST_SP_LOCATION: "http://www.overleaf.test:23000/saml/callback"
diff --git a/services/web/eslint.config.mjs b/services/web/eslint.config.mjs
deleted file mode 100644
index a94477242d..0000000000
--- a/services/web/eslint.config.mjs
+++ /dev/null
@@ -1,988 +0,0 @@
-// Shared ESLint flat config for the Overleaf web service.
-//
-// Mirrors what services/web/.eslintrc.js used to provide before the
-// flat-config migration.
-
-import { defineConfig, globalIgnores } from 'eslint/config'
-import tsParser from '@typescript-eslint/parser'
-import overleaf from '@overleaf/eslint-plugin'
-import globals from 'globals'
-import mocha from 'eslint-plugin-mocha'
-import chaiExpect from 'eslint-plugin-chai-expect'
-import chaiFriendly from 'eslint-plugin-chai-friendly'
-import vitest from '@vitest/eslint-plugin'
-import unicorn from 'eslint-plugin-unicorn'
-import cypress from 'eslint-plugin-cypress/flat'
-import testingLibrary from 'eslint-plugin-testing-library'
-import jsxA11Y from 'eslint-plugin-jsx-a11y'
-import reactPlugin from 'eslint-plugin-react'
-import reactHooks from 'eslint-plugin-react-hooks'
-import storybook from 'eslint-plugin-storybook'
-import importPlugin from 'eslint-plugin-import'
-import n from 'eslint-plugin-n'
-import promise from 'eslint-plugin-promise'
-import typescriptEslint from '@typescript-eslint/eslint-plugin'
-import js from '@eslint/js'
-import json from '@eslint/json'
-import prettier from 'eslint-config-prettier/flat'
-import { fixupPluginRules } from '@eslint/compat'
-
-// eslint-plugin-import ships its configs in eslintrc shape; wrap with
-// fixupPluginRules so its rules work in flat config.
-const importPluginFixed = fixupPluginRules(importPlugin)
-
-// eslint-plugin-react 7.x still calls the removed `context.getFilename()`
-// API; wrap with fixupPluginRules so the compat shim translates it under
-// ESLint v10. Build a derivative flat-recommended config that references
-// the wrapped plugin instead of the original.
-const react = fixupPluginRules(reactPlugin)
-const reactFlatRecommended = {
- ...reactPlugin.configs.flat.recommended,
- plugins: { react },
-}
-
-import _ from 'lodash'
-import confusingBrowserGlobals from 'confusing-browser-globals'
-
-// Type-aware linting is expensive: setting `parserOptions.project` makes
-// @typescript-eslint build a full TypeScript program from tsconfig.backend.json
-// on every ESLint invocation, even when linting a single file.
-// Set ESLINT_FAST=1 to skip type-aware checks; the full lint still runs in CI.
-const TYPE_AWARE = process.env.ESLINT_FAST !== '1'
-
-export default defineConfig([
- // Declare which file extensions ESLint should consider in this workspace.
- // Replaces the previous `--ext .js,.jsx,.mjs,.ts,.tsx` CLI flag, which is
- // silently ignored under ESLint v9 + flat config.
- {
- files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
- },
- {
- // Scope JS/TS-specific config to JS/TS files only so that built-in
- // rules like `no-irregular-whitespace` aren't applied to JSON files
- // linted via @eslint/json elsewhere in this config.
- files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
-
- languageOptions: {
- parser: tsParser,
- // Default to node globals for all backend-style files; the
- // frontend block below replaces with browser globals where
- // appropriate. Matches what the old monorepo-root .eslintrc
- // used to provide via env: { node: true }, plus the three
- // browser globals eslint-config-standard@17 added universally.
- globals: {
- ...globals.node,
- document: 'readonly',
- navigator: 'readonly',
- window: 'readonly',
- },
- },
-
- // Match v8 default: don't flag stale `eslint-disable` directives.
- linterOptions: {
- reportUnusedDisableDirectives: 'off',
- },
-
- extends: [
- js.configs.recommended,
- typescriptEslint.configs['flat/recommended'],
- storybook.configs['flat/recommended'],
- ],
-
- plugins: {
- '@overleaf': overleaf,
- '@typescript-eslint': typescriptEslint,
- import: importPluginFixed,
- // Registered (no rules enabled) so that pre-existing
- // `eslint-disable n/handle-callback-err` (etc.) directives in
- // the source still resolve to a real rule name. Under v8 these
- // came in transitively via eslint-config-standard.
- n,
- promise,
- react,
- 'react-hooks': reactHooks,
- 'jsx-a11y': jsxA11Y,
- 'testing-library': testingLibrary,
- cypress,
- },
-
- settings: {
- // Tell eslint-plugin-react to detect which version of React we are using
- react: {
- version: 'detect',
- },
- },
-
- rules: {
- 'no-constant-binary-expression': 'error',
- 'no-restricted-globals': ['error', ...confusingBrowserGlobals],
-
- // do not allow importing of implicit dependencies.
- 'import/no-extraneous-dependencies': 'error',
-
- '@overleaf/prefer-kebab-url': 'error',
- '@overleaf/require-cio-snake-case-properties': 'error',
-
- // disable some TypeScript rules
- '@typescript-eslint/no-var-requires': 'off',
- '@typescript-eslint/no-unused-vars': 'off',
- '@typescript-eslint/no-empty-function': 'off',
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/no-this-alias': 'off',
- '@typescript-eslint/no-non-null-assertion': 'off',
- '@typescript-eslint/ban-ts-comment': 'off',
-
- 'no-use-before-define': 'off',
-
- '@typescript-eslint/no-use-before-define': [
- 'error',
- {
- functions: false,
- classes: false,
- variables: false,
- },
- ],
-
- // The following three overrides preserve v8 behaviour the source
- // tree inherited from eslint-config-standard@17. See the matching
- // block in libraries/eslint-config/index.mjs for context.
- 'no-unused-vars': [
- 'error',
- {
- args: 'none',
- caughtErrors: 'none',
- ignoreRestSiblings: true,
- vars: 'all',
- },
- ],
- 'no-redeclare': ['error', { builtinGlobals: false }],
- 'no-empty': ['error', { allowEmptyCatch: true }],
-
- // ESLint v10's eslint:recommended added these three rules. Disable
- // to preserve the v9 zero-error baseline; revisit in a follow-up
- // cleanup PR.
- 'no-unassigned-vars': 'off',
- 'no-useless-assignment': 'off',
- 'preserve-caught-error': 'off',
- },
- },
- // NOTE: changing paths may require updating them in the Makefile too.
- {
- // Node
- files: [
- '**/app/src/**/*.{js,mjs}',
- 'app.{js,mjs}',
- 'i18next-scanner.config.js',
- 'scripts/**/*.{js,mjs}',
- 'webpack.config*.js',
- ],
-
- languageOptions: {
- globals: {
- ...globals.node,
- },
- },
- },
- {
- // Test specific rules
- files: ['**/test/**/*.{js,jsx,mjs,cjs,ts,tsx}'],
- ignores: ['**/test/unit/src/**/*.test.mjs', 'test/unit/bootstrap.mjs'], // exclude vitest files
-
- plugins: {
- mocha,
- 'chai-expect': chaiExpect,
- 'chai-friendly': chaiFriendly,
- },
-
- languageOptions: {
- globals: {
- ...globals.mocha,
- },
- },
-
- rules: {
- // mocha-specific rules
- 'mocha/handle-done-callback': 'error',
- 'mocha/no-exclusive-tests': 'error',
- 'mocha/no-global-tests': 'error',
- 'mocha/no-identical-title': 'error',
- 'mocha/no-nested-tests': 'error',
- 'mocha/no-pending-tests': 'error',
- 'mocha/no-mocha-arrows': 'error',
-
- // Swap the no-unused-expressions rule with a more chai-friendly one
- 'no-unused-expressions': 'off',
- 'chai-friendly/no-unused-expressions': 'error',
-
- // chai-specific rules
- 'chai-expect/missing-assertion': 'error',
- 'chai-expect/terminating-properties': 'error',
-
- // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
- // we don't enforce this at the top-level - just in tests to manage `this` scope
- // based on mocha's context mechanism
- 'mocha/prefer-arrow-callback': 'error',
-
- '@typescript-eslint/no-unused-expressions': 'off',
- },
- },
- {
- files: ['**/test/unit/src/**/*.test.mjs', 'test/unit/bootstrap.mjs'],
-
- languageOptions: {
- globals: {
- ...globals.jest, // best match for vitest API etc.
- },
- },
-
- plugins: {
- '@vitest': vitest,
- 'chai-expect': chaiExpect,
- 'chai-friendly': chaiFriendly, // still using chai for now
- },
-
- rules: {
- // vitest-specific rules
- '@vitest/no-focused-tests': 'error',
- '@vitest/no-disabled-tests': 'error',
-
- // Swap the no-unused-expressions rule with a more chai-friendly one
- 'no-unused-expressions': 'off',
- 'chai-friendly/no-unused-expressions': 'error',
-
- // chai-specific rules
- 'chai-expect/missing-assertion': 'error',
- 'chai-expect/terminating-properties': 'error',
- '@typescript-eslint/no-unused-expressions': 'off',
- '@overleaf/require-vi-doMock-valid-path': 'error',
- },
- },
- {
- // ES specific rules
- files: [
- '**/app/src/**/*.mjs',
- 'modules/*/index.mjs',
- 'app.mjs',
- 'scripts/**/*.mjs',
- 'migrations/**/*.mjs',
- '**/test/acceptance/src/**/*.mjs',
- '**/test/unit/src/**/*.mjs',
- ],
-
- ignores: [
- // migration template file
- 'migrations/lib/template.mjs',
- ],
-
- languageOptions: {
- sourceType: 'module',
- parserOptions: {},
- },
-
- plugins: {
- unicorn,
- },
-
- rules: {
- 'import/no-unresolved': [
- 'error',
- {
- // eslint-plugin-import does not support exports directive in package.json
- // https://github.com/import-js/eslint-plugin-import/issues/1810
- ignore: ['^p-queue$'],
- },
- ],
-
- 'import/named': 'error',
- 'import/default': 'error',
-
- 'import/extensions': [
- 'error',
- 'ignorePackages',
- {
- js: 'always',
- mjs: 'always',
- },
- ],
-
- 'unicorn/prefer-module': 'error',
- 'unicorn/prefer-node-protocol': 'error',
- },
- },
- {
- // Backend specific rules
- files: ['**/app/src/**/*.{js,mjs}', 'app.{js,mjs}'],
-
- languageOptions: {
- parserOptions: TYPE_AWARE
- ? {
- tsconfigRootDir: import.meta.dirname,
- project: './tsconfig.backend.json',
- }
- : {},
- },
-
- rules: {
- // do not allow importing of implicit dependencies.
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- // do not allow importing of devDependencies.
- devDependencies: false,
- },
- ],
-
- 'no-restricted-syntax': [
- 'error',
- // do not allow node-fetch in backend code
- {
- selector:
- "CallExpression[callee.name='require'] > .arguments[value='node-fetch']",
- message:
- 'Requiring node-fetch is not allowed in production services, please use fetch-utils.',
- },
- // mongoose populate must set fields to populate
- {
- selector:
- "CallExpression[callee.property.name='populate'][arguments.length<2]",
- message:
- "Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead",
- },
- // Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
- message:
- 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
- },
- // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
- message:
- "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
- },
- // Catch incorrect usage of `await db.collection.find()`
- {
- selector:
- "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
- message:
- 'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.',
- },
- ],
-
- '@typescript-eslint/no-floating-promises': TYPE_AWARE
- ? [
- 'error',
- {
- checkThenables: true,
- },
- ]
- : 'off',
- },
- },
- {
- // Backend scripts specific rules
- files: ['**/scripts/**/*.{js,mjs}'],
-
- rules: {
- 'no-restricted-syntax': [
- 'error',
- // Require `new` when constructing ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']",
- message:
- 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`',
- },
- // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade)
- {
- selector:
- "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child",
- message:
- "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead",
- },
- // Catch incorrect usage of `await db.collection.find()`
- {
- selector:
- "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']",
- message:
- 'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.',
- },
- ],
- },
- },
- {
- // Insist on using Script Runner for new scripts. Old scripts should be
- // converted to use Script Runner in future, but are excluded for now.
- rules: {
- '@overleaf/require-script-runner': 'error',
- },
-
- files: ['**/scripts/**/*.mjs'], // ESM only
-
- ignores: [
- 'modules/admin-roles/scripts/import_admin_role_assignments.mjs',
- 'modules/admin-roles/scripts/remove_admin_role_from_user.mjs',
- 'modules/admin-roles/scripts/remove_admin_roles_from_non_admins.mjs',
- 'modules/admin-roles/scripts/utils.mjs',
- 'modules/institutions/scripts/apply_policy_to_institution.mjs',
- 'modules/server-ce-scripts/scripts/change-compile-timeout.mjs',
- 'modules/server-ce-scripts/scripts/check-mongodb.mjs',
- 'modules/server-ce-scripts/scripts/check-redis.mjs',
- 'modules/server-ce-scripts/scripts/check-texlive-images.mjs',
- 'modules/server-ce-scripts/scripts/create-user.mjs',
- 'modules/server-ce-scripts/scripts/delete-user.mjs',
- 'modules/server-ce-scripts/scripts/export-user-projects.mjs',
- 'modules/server-ce-scripts/scripts/migrate-user-emails.mjs',
- 'modules/server-ce-scripts/scripts/rename-tag.mjs',
- 'modules/server-ce-scripts/scripts/transfer-all-projects-to-user.mjs',
- 'modules/server-ce-scripts/scripts/upgrade-user-features.mjs',
- 'modules/subscriptions/scripts/backfill_user_last_trial.mjs',
- 'scripts/add_feature_override.mjs',
- 'scripts/add_subscription_members_csv.mjs',
- 'scripts/analytics/helpers/GoogleBigQueryHelper.mjs',
- 'scripts/attach_dangling_comments_to_doc.mjs',
- 'scripts/backfill_mixpanel_user_properties.mjs',
- 'scripts/backfill_project_image_name.mjs',
- 'scripts/backfill_user_properties.mjs',
- 'scripts/backfill_users_sso_attribute.mjs',
- 'scripts/bench_bcrypt.mjs',
- 'scripts/check_institution_users.mjs',
- 'scripts/check_overleafModuleImports.mjs',
- 'scripts/check_saml_emails.mjs',
- 'scripts/clear_feedback_collection.mjs',
- 'scripts/clear_sessions_set_must_reconfirm.mjs',
- 'scripts/count_files_in_projects.mjs',
- 'scripts/count_project_size.mjs',
- 'scripts/create_oauth_personal_access_token.mjs',
- 'scripts/create_project.mjs',
- 'scripts/deactivate_projects.mjs',
- 'scripts/delete-duplicate-splittest-versions/delete_test_dupes.mjs',
- 'scripts/delete-orphaned-docs/delete-orphaned-docs.mjs',
- 'scripts/delete_dangling_comments.mjs',
- 'scripts/delete_orphaned_chat_threads.mjs',
- 'scripts/delete_orphaned_data_helper.mjs',
- 'scripts/delete_subscriptions.mjs',
- 'scripts/devcontainer_setup.mjs',
- 'scripts/e2e_test_setup.mjs',
- 'scripts/ensure_affiliations.mjs',
- 'scripts/esm-check-migration.mjs',
- 'scripts/example/script_for_migration.mjs',
- 'scripts/fix_collaborator_refs_null.mjs',
- 'scripts/fix_comment_id.mjs',
- 'scripts/helpers/chunkArray.mjs',
- 'scripts/helpers/env_variable_helper.mjs',
- 'scripts/inst_table.mjs',
- 'scripts/invalidate_tokens.mjs',
- 'scripts/ip_matcher_ranges.mjs',
- 'scripts/learn/checkSanitize/checkSanitizeOptions.mjs',
- 'scripts/learn/checkSanitize/scrape.mjs',
- 'scripts/lezer-latex/benchmark.mjs',
- 'scripts/lezer-latex/print-tree.mjs',
- 'scripts/lezer-latex/random.mjs',
- 'scripts/lezer-latex/run.mjs',
- 'scripts/lezer-latex/test-incremental-parser.mjs',
- 'scripts/mark_migration.mjs',
- 'scripts/marketing-exports/error-assistant-export.mjs',
- 'scripts/marketing-exports/export.mjs',
- 'scripts/marketing-exports/linked-papers-users.mjs',
- 'scripts/marketing-exports/papers-export.mjs',
- 'scripts/marketing-exports/writefull-export.mjs',
- 'scripts/oauth/upgrade_token_scopes.mjs',
- 'scripts/plan-prices/plans.mjs',
- 'scripts/process_lapsed_reconfirmations.mjs',
- 'scripts/purge_non_logged_in_sessions.mjs',
- 'scripts/recurly/generate_recurly_prices.mjs',
- 'scripts/recurly/get_paypal_accounts_csv.mjs',
- 'scripts/recurly/recurly_prices.mjs',
- 'scripts/recurly/resync_recurly_state_single_subscription.mjs',
- 'scripts/recurly/resync_subscriptions.mjs',
- 'scripts/recurly/set_manually_collected_subscriptions.mjs',
- 'scripts/refresh_features.mjs',
- 'scripts/regenerate_duplicate_referral_ids.mjs',
- 'scripts/remove_deleted_users_from_token_access_refs.mjs',
- 'scripts/remove_email.mjs',
- 'scripts/remove_user_enrollment.mjs',
- 'scripts/sso_id_migration_check.mjs',
- 'scripts/stress_test.mjs',
- 'scripts/suspend_users.mjs',
- 'scripts/sync-user-entitlements/sync-user-entitlements.mjs',
- 'scripts/update_project_image_name.mjs',
- 'scripts/user-export/analytics.mjs',
- 'scripts/user-export/fs.mjs',
- 'scripts/user-export/http.mjs',
- 'scripts/user-export/observer.mjs',
- 'scripts/user-export/options.mjs',
- 'scripts/user-export/project.mjs',
- 'scripts/user-export/scrubber.mjs',
- 'scripts/user-export/stream.mjs',
- 'scripts/user-export/user.mjs',
- 'scripts/validate-data-of-model.mjs',
- ],
- },
- {
- // Cypress specific rules
- files: [
- 'cypress/**/*.{js,jsx,ts,tsx}',
- '**/test/frontend/**/*.spec.{js,jsx,ts,tsx}',
- ],
-
- plugins: {
- cypress,
- },
-
- rules: {
- ...cypress.configs.recommended.rules,
- },
- },
- {
- // Frontend test specific rules
- files: ['**/frontend/**/*.test.{js,jsx,ts,tsx}'],
-
- plugins: {
- 'testing-library': testingLibrary,
- },
-
- rules: {
- ...testingLibrary.configs['flat/react'].rules,
- 'testing-library/no-await-sync-events': 'off',
- 'testing-library/no-await-sync-queries': 'off',
- 'testing-library/no-container': 'off',
- 'testing-library/no-node-access': 'off',
- 'testing-library/no-render-in-lifecycle': 'off',
- 'testing-library/no-wait-for-multiple-assertions': 'off',
- 'testing-library/no-wait-for-side-effects': 'off',
- 'testing-library/prefer-query-by-disappearance': 'off',
- 'testing-library/prefer-screen-queries': 'off',
- 'testing-library/render-result-naming-convention': 'off',
- },
- },
- {
- // Frontend specific rules
- files: [
- '**/frontend/js/**/*.{js,jsx,ts,tsx}',
- '**/frontend/stories/**/*.{js,jsx,ts,tsx}',
- '**/*.stories.{js,jsx,ts,tsx}',
- '**/test/frontend/**/*.{js,jsx,ts,tsx}',
- '**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}',
- ],
-
- languageOptions: {
- globals: {
- ...globals.browser,
- __webpack_public_path__: true,
- $: true,
- ga: true,
- },
-
- sourceType: 'module',
- parserOptions: {},
- },
-
- plugins: {
- react,
- 'react-hooks': reactHooks,
- 'jsx-a11y': jsxA11Y,
- },
-
- rules: {
- ...reactFlatRecommended.rules,
- ...reactHooks.configs['recommended-latest'].rules,
- ...jsxA11Y.flatConfigs.recommended.rules,
- 'react-hooks/exhaustive-deps': [
- 'warn',
- {
- additionalHooks: '(useCommandProvider)',
- },
- ],
- // TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed.
- // START: inline standard-react rules
- // "react/jsx-no-bind": ["error", {
- // "allowArrowFunctions": true,
- // "allowBind": false,
- // "ignoreRefs": true
- // },],
- 'react/no-did-update-set-state': 'error',
- 'react/no-unused-prop-types': 'error',
- 'react/prop-types': 'error',
- '@overleaf/no-generated-editor-themes': 'error',
- // "react/react-in-jsx-scope": "error",
- // END: inline standard-react rules
-
- // eslint-plugin-react 7.37 enabled `react/no-unescaped-entities`
- // in plugin:react/recommended. v8 (with 7.32) didn't. Disable to
- // match v8 -- the source uses literal apostrophes and quotes in
- // JSX freely.
- 'react/no-unescaped-entities': 'off',
-
- 'react/no-unknown-property': [
- 'error',
- {
- ignore: ['dnd-container', 'dropdown-toggle'],
- },
- ],
-
- 'react/jsx-no-target-blank': [
- 'error',
- {
- allowReferrer: true,
- },
- ],
-
- // Prevent usage of legacy string refs
- 'react/no-string-refs': 'error',
-
- // Prevent curly braces around strings (as they're unnecessary)
- 'react/jsx-curly-brace-presence': [
- 'error',
- {
- props: 'never',
- children: 'never',
- },
- ],
-
- // Don't import React for JSX; the JSX runtime is added by a Babel plugin
- 'react/react-in-jsx-scope': 'off',
- 'react/jsx-uses-react': 'off',
-
- // Allow functions as JSX props
- 'react/jsx-no-bind': 'off', // TODO: fix occurrences and re-enable this
-
- // Fix conflict between prettier & standard by overriding to prefer
- // double quotes
- 'jsx-quotes': ['error', 'prefer-double'],
-
- // Override weird behaviour of jsx-a11y label-has-for (says labels must be
- // nested *and* have for/id attributes)
- 'jsx-a11y/label-has-for': [
- 'error',
- {
- required: {
- some: ['nesting', 'id'],
- },
- },
- ],
-
- // Require .jsx or .tsx file extension when using JSX
- 'react/jsx-filename-extension': [
- 'error',
- {
- extensions: ['.jsx', '.tsx'],
- },
- ],
-
- 'no-restricted-syntax': [
- 'error',
- // prohibit direct calls to methods of window.localStorage
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
- message:
- 'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
- },
- ],
-
- 'no-unused-vars': 'off',
-
- '@typescript-eslint/no-unused-vars': [
- 'error',
- {
- args: 'after-used',
- argsIgnorePattern: '^_',
- ignoreRestSiblings: false,
- caughtErrors: 'none',
- vars: 'all',
- varsIgnorePattern: '^_',
- },
- ],
- },
- },
- {
- // Sorting for Meta
- files: ['frontend/js/utils/meta.ts'],
-
- rules: {
- '@typescript-eslint/member-ordering': [
- 'error',
- {
- interfaces: {
- order: 'alphabetically',
- },
- },
- ],
- },
- },
- {
- // React component specific rules
- //
- files: [
- '**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}',
- '**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}',
- ],
-
- rules: {
- '@overleaf/no-unnecessary-trans': 'error',
- '@overleaf/should-unescape-trans': 'error',
- '@overleaf/require-loading-label': 'error',
-
- // https://astexplorer.net/
- 'no-restricted-syntax': [
- 'error',
- // prohibit direct calls to methods of window.location
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='location']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit assignment to window.location
- {
- selector:
- "AssignmentExpression[left.object.name='window'][left.property.name='location']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit assignment to window.location.href
- {
- selector:
- "AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']",
- message:
- 'Modify location via useLocation instead of calling window.location methods directly',
- },
- // prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed
- {
- selector: 'Literal[regex.pattern=/\\(\\?<[!=]/]',
- message: 'Lookbehind is not supported in older Safari versions.',
- },
- // prohibit direct calls to methods of window.localStorage
- // NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config
- {
- selector:
- "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']",
- message:
- 'Modify location via customLocalStorage instead of calling window.localStorage methods directly',
- },
- ],
- },
- },
- // React + TypeScript-specific rules
- {
- files: ['**/*.tsx'],
-
- rules: {
- 'react/prop-types': 'off',
- 'no-undef': 'off',
- },
- },
- // TypeScript-specific rules
- {
- files: ['**/*.ts'],
-
- rules: {
- 'no-undef': 'off',
- },
- },
- // JavaScript-specific rules
- {
- files: ['**/*.js'],
-
- rules: {
- '@typescript-eslint/no-require-imports': 'off',
- },
- },
-
- {
- files: ['scripts/ukamf/*.js'],
- rules: {
- // Do not allow importing of any dependencies unless specified in either
- // - web/package.json
- // - web/scripts/ukamf/package.json
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- packageDir: ['.', 'scripts/ukamf'],
- },
- ],
- },
- },
- {
- files: ['scripts/learn/checkSanitize/*.js'],
-
- rules: {
- // The checkSanitize script is used in the dev-env only.
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- devDependencies: true,
- packageDir: ['.', '../../'],
- },
- ],
- },
- },
- {
- files: [
- // Backend: Use @overleaf/logger
- // Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging
- '**/app/**/*.{js,cjs,mjs}',
- 'app.{js,mjs}',
- 'modules/*/*.{js,mjs}',
- // Frontend: Prefer debugConsole over bare console
- // Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend
- '**/frontend/**/*.{js,jsx,ts,tsx}',
- // Tests
- '**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}',
- ],
-
- ignores: [
- // Allow console logs in scripts
- '**/scripts/**/*.js',
- // Allow console logs in stories
- '**/stories/**/*.{js,jsx,ts,tsx}',
- // Workers do not have access to the search params for enabling ?debug=true.
- // self.location.url is the URL of the worker script.
- '**/*.worker.{js,ts}',
- ],
-
- rules: {
- 'no-console': 'error',
- },
- },
- {
- files: ['**/*.worker.{js,ts}'],
-
- rules: {
- 'no-restricted-globals': [
- 'error',
- ..._.difference(
- Object.keys({
- ...globals.browser,
- ...globals.node,
- }),
- Object.keys(globals.worker)
- ),
- ],
- },
- },
- {
- // The writefull module ships from upstream as a vendored
- // integration; its style/test conventions differ from the rest
- // of services/web. Under v8 these files lint-passed in CI even
- // though the surface area would normally trip several rules --
- // suggesting either historical exemption or pre-existing CI
- // tolerance. To match the user-reported v8 zero-error baseline
- // without touching the imported source, disable the rules that
- // fire here.
- files: ['modules/writefull/**/*.{js,jsx,ts,tsx,mjs,cjs}'],
- rules: {
- 'no-console': 'off',
- '@typescript-eslint/no-unused-vars': 'off',
- 'no-unused-vars': 'off',
- 'react/jsx-curly-brace-presence': 'off',
- 'react/jsx-no-target-blank': 'off',
- 'react/no-unused-prop-types': 'off',
- 'react/no-deprecated': 'off',
- 'react-hooks/rules-of-hooks': 'off',
- '@overleaf/no-generated-editor-themes': 'off',
- // jsx-a11y/* fires on many writefull components. Disable the
- // ones that surface here to match the v8 zero-error baseline.
- 'jsx-a11y/no-static-element-interactions': 'off',
- 'jsx-a11y/click-events-have-key-events': 'off',
- 'jsx-a11y/no-autofocus': 'off',
- 'jsx-a11y/label-has-for': 'off',
- 'jsx-a11y/role-supports-aria-props': 'off',
- 'jsx-a11y/anchor-is-valid': 'off',
- 'jsx-a11y/interactive-supports-focus': 'off',
- },
- },
- {
- // ESLint v9's prefer-const analysis on TypeScript destructuring
- // (`let { foo, bar } = ...`) fires where v8 did not, even with
- // identical `destructuring: 'all'` options. Disable to match v8
- // behaviour on the existing TS source -- the actual let-vs-const
- // intent is preserved by the source code.
- files: ['**/*.{ts,tsx}'],
- rules: {
- 'prefer-const': 'off',
- },
- },
- {
- // eslint-plugin-testing-library bumped from 7.1 to 7.5 in the
- // migration; the newer release enabled / tightened several rules
- // that fire on pre-existing test code. Disable to preserve v8
- // behaviour; revisit in a follow-up cleanup PR.
- files: ['**/frontend/**/*.test.{js,jsx,ts,tsx}'],
- rules: {
- 'testing-library/no-debugging-utils': 'off',
- 'testing-library/prefer-presence-queries': 'off',
- 'testing-library/no-manual-cleanup': 'off',
- },
- },
- {
- // Frontend test files import chai's `use()` and call it at module
- // top level; eslint-plugin-react-hooks v5 heuristically treats any
- // `use*` name as a React Hook and flags this as rules-of-hooks.
- // Pre-existing v8 behaviour (react-hooks v4) was more conservative;
- // disable for test files to match.
- // react/no-deprecated also fires on test files using
- // ReactDOM.unmountComponentAtNode and similar React-18-deprecated
- // APIs; v8 effectively tolerated these (the user-reported zero
- // baseline). Disable for test files.
- files: [
- '**/test/frontend/**/*.{js,jsx,ts,tsx}',
- '**/frontend/**/*.test.{js,jsx,ts,tsx}',
- '**/frontend/**/*.spec.{js,jsx,ts,tsx}',
- ],
- rules: {
- 'react-hooks/rules-of-hooks': 'off',
- 'react/no-deprecated': 'off',
- },
- },
- {
- // eslint.config.mjs itself imports eslint plugins as devDependencies;
- // allow that since this file is part of the tooling, not the app.
- files: ['eslint.config.mjs'],
- rules: {
- 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
- },
- },
- // eslint-config-prettier disables stylistic rules that conflict with
- // prettier formatting. Placed last so it overrides rules pulled in by
- // preceding configs (eslint:recommended, @typescript-eslint, react,
- // jsx-a11y, ...).
- prettier,
- {
- // Lint locale JSON files for typographic and i18n conventions.
- files: ['locales/*.json'],
- language: 'json/json',
- plugins: { json, '@overleaf': overleaf },
- rules: {
- '@overleaf/no-consecutive-spaces-in-locales': 'error',
- '@overleaf/no-straight-apostrophes-in-locales': 'error',
- '@overleaf/sorted-keys-in-locales': 'error',
- '@overleaf/locale-variables-match-en': 'error',
- '@overleaf/no-orphan-locale-keys': 'error',
- },
- },
- {
- files: ['locales/fr.json'],
- language: 'json/json',
- plugins: { json, '@overleaf': overleaf },
- rules: {
- '@overleaf/french-typography-in-locales': 'error',
- },
- },
- globalIgnores([
- '**/data/',
- 'scripts/translations/.cache/',
- '**/node_modules',
- 'frontend/js/vendor',
- 'modules/**/frontend/js/vendor',
- 'public/',
- 'frontend/js/features/source-editor/lezer-latex/latex.mjs',
- 'frontend/js/features/source-editor/lezer-latex/latex.terms.mjs',
- 'frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs',
- 'frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs',
- 'frontend/js/features/source-editor/hunspell/wasm/hunspell.mjs',
- ]),
-])
diff --git a/services/web/frontend/stories/chat.stories.jsx b/services/web/frontend/stories/chat.stories.jsx
deleted file mode 100644
index 73b207ac74..0000000000
--- a/services/web/frontend/stories/chat.stories.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import ChatPane from '../js/features/chat/components/chat-pane'
-import useFetchMock from './hooks/use-fetch-mock'
-import { generateMessages } from './fixtures/chat-messages'
-import { ScopeDecorator } from './decorators/scope'
-import { UserProvider } from '@/shared/context/user-context'
-
-export const Conversation = args => {
- useFetchMock(fetchMock => {
- fetchMock.get(/messages/, generateMessages(35)).post(/messages/, {})
- })
-
- return
-}
-
-export const NoMessages = args => {
- useFetchMock(fetchMock => {
- fetchMock.get(/messages/, [])
- })
-
- return
-}
-
-export const Loading = args => {
- useFetchMock(fetchMock => {
- fetchMock.get(/messages/, generateMessages(6), {
- delay: 1000 * 10,
- })
- })
-
- return
-}
-
-export const LoadingError = args => {
- useFetchMock(fetchMock => {
- fetchMock.get(/messages/, 500)
- })
-
- return
-}
-
-export default {
- title: 'Editor / Chat',
- component: ChatPane,
- argTypes: {
- resetUnreadMessages: { action: 'resetUnreadMessages' },
- },
- args: {
- resetUnreadMessages: () => {},
- },
- decorators: [
- ScopeDecorator,
- Story => (
-
-
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/clone-project-modal.stories.jsx b/services/web/frontend/stories/clone-project-modal.stories.jsx
deleted file mode 100644
index 843a18f0be..0000000000
--- a/services/web/frontend/stories/clone-project-modal.stories.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import useFetchMock from './hooks/use-fetch-mock'
-import CloneProjectModal from '../js/features/clone-project-modal/components/clone-project-modal'
-import { ScopeDecorator } from './decorators/scope'
-
-export const Success = args => {
- useFetchMock(fetchMock => {
- fetchMock.post(
- 'express:/project/:projectId/clone',
- { status: 200 },
- { delay: 250 }
- )
- })
-
- return
-}
-
-export const GenericErrorResponse = args => {
- useFetchMock(fetchMock => {
- fetchMock.post(
- 'express:/project/:projectId/clone',
- { status: 500 },
- { delay: 250 }
- )
- })
-
- return
-}
-
-export const SpecificErrorResponse = args => {
- useFetchMock(fetchMock => {
- fetchMock.post(
- 'express:/project/:projectId/clone',
- { status: 400, body: 'The project name is not valid' },
- { delay: 250 }
- )
- })
-
- return
-}
-
-export default {
- title: 'Editor / Modals / Clone Project',
- component: CloneProjectModal,
- args: {
- show: true,
- projectName: 'Project 1',
- projectTags: [
- {
- _id: 'tag-1',
- name: 'Category 1',
- color: '#c0ffee',
- },
- ],
- },
- argTypes: {
- handleHide: { action: 'close modal' },
- openProject: { action: 'open project' },
- handleAfterCloned: { action: 'after cloned' },
- },
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/contact-us-modal.stories.tsx b/services/web/frontend/stories/contact-us-modal.stories.tsx
deleted file mode 100644
index 061ef8997b..0000000000
--- a/services/web/frontend/stories/contact-us-modal.stories.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { ComponentProps, useCallback, useState } from 'react'
-import useFetchMock from './hooks/use-fetch-mock'
-import ContactUsModal from '../../modules/support/frontend/js/components/contact-us-modal'
-import fixedHelpSuggestionSearch from '../../modules/support/test/frontend/util/fixed-help-suggestion-search'
-import { ScopeDecorator } from './decorators/scope'
-import OLButton from '@/shared/components/ol/ol-button'
-
-type ContactUsModalProps = ComponentProps
-
-function GenericContactUsModal(args: ContactUsModalProps) {
- useFetchMock(fetchMock => {
- fetchMock.post('/support', { status: 200 }, { delay: 1000 })
- })
-
- return (
-
- )
-}
-
-export const Generic = (args: ContactUsModalProps) => (
-
-)
-
-const ContactUsModalWithRequestError = (args: ContactUsModalProps) => {
- useFetchMock(fetchMock => {
- fetchMock.post('/support', { status: 404 }, { delay: 250 })
- })
-
- return (
-
- )
-}
-
-export const RequestError = (args: ContactUsModalProps) => (
-
-)
-
-const ContactUsModalWithAcknowledgement = (
- args: Omit
-) => {
- useFetchMock(fetchMock => {
- fetchMock.post('/support', { status: 200 }, { delay: 1000 })
- })
-
- const [show, setShow] = useState(false)
-
- const hideModal = useCallback((event?: Event) => {
- event?.preventDefault()
- setShow(false)
- }, [])
-
- return (
- <>
- setShow(true)}>Contact Us
-
- >
- )
-}
-
-export const WithAcknowledgement = (args: ContactUsModalProps) => {
- const { show: _show, handleHide: _handleHide, ...rest } = args
- return
-}
-
-export default {
- title: 'Shared / Modals / Contact Us',
- component: ContactUsModal,
- args: {
- show: true,
- handleHide: () => {},
- autofillProjectUrl: true,
- },
- argTypes: {
- handleHide: { action: 'close modal' },
- },
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/decorators/ide-redesign.tsx b/services/web/frontend/stories/decorators/ide-redesign.tsx
deleted file mode 100644
index d0708790d8..0000000000
--- a/services/web/frontend/stories/decorators/ide-redesign.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Decorator } from '@storybook/react-webpack5'
-
-export const IdeRedesign: Decorator = Story => (
-
-
-
-)
diff --git a/services/web/frontend/stories/decorators/scope.tsx b/services/web/frontend/stories/decorators/scope.tsx
deleted file mode 100644
index 85785743d0..0000000000
--- a/services/web/frontend/stories/decorators/scope.tsx
+++ /dev/null
@@ -1,199 +0,0 @@
-import React, { FC, useEffect, useState } from 'react'
-import { User, UserId } from '../../../types/user'
-import { Project } from '../../../types/project'
-import {
- mockBuildFile,
- mockCompile,
- mockCompileError,
-} from '../fixtures/compile'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { useMeta } from '../hooks/use-meta'
-import SocketIOShim, { SocketIOMock } from '@/ide/connection/SocketIoShim'
-import { IdeContext } from '@/shared/context/ide-context'
-import { IdeReactContext } from '@/features/ide-react/context/ide-react-context'
-import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter'
-import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/react-scope-value-store'
-import { ReactScopeEventEmitter } from '@/features/ide-react/scope-event-emitter/react-scope-event-emitter'
-import { ConnectionContext } from '@/features/ide-react/context/connection-context'
-import { Socket } from '@/features/ide-react/connection/types/socket'
-import { ConnectionState } from '@/features/ide-react/connection/types/connection-state'
-import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
-
-const scopeWatchers: [string, (value: any) => void][] = []
-
-export const user: User = {
- id: 'story-user' as UserId,
- email: 'story-user@example.com',
- allowedFreeTrial: true,
- features: { dropbox: true, symbolPalette: true },
-}
-
-const project: Project = {
- _id: '63e21c07946dd8c76505f85a',
- name: 'A Project',
- features: { mendeley: true, zotero: true, referencesSearch: true },
- tokens: {},
- owner: {
- _id: 'a-user',
- email: 'stories@overleaf.com',
- },
- members: [],
- invites: [],
- rootDocId: '5e74f1a7ce17ae0041dfd056',
- rootFolder: [
- {
- _id: 'root-folder-id',
- name: 'rootFolder',
- docs: [
- { _id: 'test-file-id', name: 'testfile.tex' },
- { _id: 'test-bib-file-id', name: 'testsources.bib' },
- ],
- fileRefs: [{ _id: 'test-image-id', name: 'frog.jpg', hash: '42' }],
- folders: [],
- },
- ],
-}
-
-const socket = new SocketIOShim.SocketShimNoop(
- new SocketIOMock()
-) as unknown as Socket
-
-const initializeMetaTags = () => {
- // window.metaAttributesCache is reset in preview.tsx
- window.metaAttributesCache.set('ol-user', user)
- window.metaAttributesCache.set('ol-project_id', project._id)
- window.metaAttributesCache.set(
- 'ol-gitBridgePublicBaseUrl',
- 'https://git.stories.com'
- )
-}
-
-type ScopeDecoratorOptions = {
- mockCompileOnLoad: boolean
- providers?: Record
-}
-
-export const ScopeDecorator = (
- Story: any,
- opts: ScopeDecoratorOptions = { mockCompileOnLoad: true },
- meta: Record = {}
-) => {
- initializeMetaTags()
-
- // mock compile on load
- useFetchMock(fetchMock => {
- if (opts.mockCompileOnLoad) {
- mockCompile(fetchMock)
- mockCompileError(fetchMock)
- mockBuildFile(fetchMock)
- }
- })
-
- // clear scopeWatchers on unmount
- useEffect(() => {
- return () => {
- scopeWatchers.length = 0
- }
- }, [])
-
- // set values on window.metaAttributesCache (created in initialize, above)
- useMeta(meta)
-
- return (
-
-
-
- )
-}
-
-const ConnectionProvider: FC = ({ children }) => {
- const [value] = useState(() => {
- const connectionState: ConnectionState = {
- readyState: WebSocket.OPEN,
- forceDisconnected: false,
- inactiveDisconnect: false,
- reconnectAt: null,
- forcedDisconnectDelay: 0,
- lastConnectionAttempt: 0,
- error: '',
- }
- return {
- socket,
- connectionState,
- isConnected: true,
- isStillReconnecting: false,
- secondsUntilReconnect: () => 0,
- tryReconnectNow: () => {},
- registerUserActivity: () => {},
- closeConnection: () => {},
- getSocketDebuggingInfo: () => ({
- client_id: 'fakeClientId',
- transport: 'fakeTransport',
- publicId: 'fakePublicId',
- lastUserActivity: 0,
- connectionState,
- externalHeartbeat: {
- currentStart: 0,
- lastSuccess: 0,
- lastLatency: 0,
- },
- }),
- }
- })
-
- return (
-
- {children}
-
- )
-}
-
-const IdeReactProvider: FC = ({ children }) => {
- const projectId = 'project-123'
- const [startedFreeTrial, setStartedFreeTrial] = useState(false)
-
- const [ideReactContextValue] = useState(() => ({
- projectId,
- eventEmitter: new IdeEventEmitter(),
- startedFreeTrial,
- setStartedFreeTrial,
- reportError: () => {},
- projectJoined: true,
- permissionsLevel: 'owner' as const,
- setPermissionsLevel: () => {},
- setOutOfSync: () => {},
- }))
-
- const [ideContextValue] = useState(() => {
- const scopeEventEmitter = new ReactScopeEventEmitter(new IdeEventEmitter())
- const unstableStore = new ReactScopeValueStore()
-
- window.overleaf = {
- ...window.overleaf,
- unstable: {
- ...window.overleaf?.unstable,
- store: unstableStore,
- },
- }
-
- return {
- socket,
- scopeEventEmitter,
- unstableStore,
- }
- })
-
- return (
-
-
- {children}
-
-
- )
-}
diff --git a/services/web/frontend/stories/deprecated-browser.stories.tsx b/services/web/frontend/stories/deprecated-browser.stories.tsx
deleted file mode 100644
index c353cf1b72..0000000000
--- a/services/web/frontend/stories/deprecated-browser.stories.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Meta, StoryObj } from '@storybook/react-webpack5'
-import { DeprecatedBrowser } from '@/shared/components/deprecated-browser'
-
-const meta: Meta = {
- title: 'Project List / Deprecated Browser',
- component: DeprecatedBrowser,
-}
-
-export default meta
-
-type Story = StoryObj
-
-export const Notification: Story = {}
diff --git a/services/web/frontend/stories/docs/cloud-builds.mdx b/services/web/frontend/stories/docs/cloud-builds.mdx
deleted file mode 100644
index 8d0520c760..0000000000
--- a/services/web/frontend/stories/docs/cloud-builds.mdx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Meta } from '@storybook/addon-docs/blocks'
-
-
-
-# Cloud Builds
-
-Storybook builds can be used to share your development work. They can be found at [https://storybook.dev-overleaf.com/](https://storybook.dev-overleaf.com/).
-
-The naming scheme is predictable, i.e.: `https://storybook.dev-overleaf.com//`.
-
-# Triggers
-
-Storybook builds are automatically triggered for the main branch. For ad hoc builds of feature branches, the commits must be pushed to branches prefixed with `storybook-`.
-
-For example, you may temporarily push your branch to a `storybook-` branch: `git push origin my-example-feature:storybook-my-example-feature`
-
-You can delete it shortly after: `git push origin :storybook-my-example-feature`
-
-Alternatively, **if you already have a branch**, but you would like to create a Storybook, you can manually trigger the `storybook-push-trigger` on your branch via [Google Cloud Build triggers](https://console.cloud.google.com/cloud-build/triggers?project=overleaf-dev).
-
-Storybook builds take about 10 minutes to finish.
-
-**Source:** [Storybook in the developer manual](https://manual.dev-overleaf.com/development/code/storybook/)
diff --git a/services/web/frontend/stories/docs/feature-flags.mdx b/services/web/frontend/stories/docs/feature-flags.mdx
deleted file mode 100644
index ec0cf32d24..0000000000
--- a/services/web/frontend/stories/docs/feature-flags.mdx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks'
-import * as FormatCurrency from './format-currency.stories'
-
-
-
-# Feature flags
-
-You can wrap your story with the `withSplitTests` utility to add split test variants to your Storybook stories, so you can toggle feature flags directly in the Storybook UI. See [withSplitTests on GitHub](https://github.com/overleaf/internal/blob/main/services/web/.storybook/utils/with-split-tests.tsx).
-
-1. Define your split test argTypes:
- Add your split test configurations to the `splitTestsArgTypes` object.
-
-```js
-export const splitTestsArgTypes = {
- 'local-ccy-format': {
- description: 'Use local currency formatting',
- control: { type: 'radio' },
- options: ['default', 'enabled'],
- },
-}
-```
-
-2. Wrap your story with `withSplitTests`.
-
-Import `withSplitTests` and `Meta` from '@storybook/react-webpack5' in your stories.
-
-```js
-export default {
- ...config,
- ...withSplitTests(config, ['local-ccy-format']),
-}
-```
-
-## Example
-
-Resulting stories will have added controls to define the variants of the split tests.
-
-
-
-
-
diff --git a/services/web/frontend/stories/docs/format-currency.stories.tsx b/services/web/frontend/stories/docs/format-currency.stories.tsx
deleted file mode 100644
index da0d0de7ca..0000000000
--- a/services/web/frontend/stories/docs/format-currency.stories.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react'
-import { useSplitTest } from '@/shared/context/split-test-context'
-import { withSplitTests } from '../../../.storybook/utils/with-split-tests'
-
-const FormatCurrency = () => {
- const { variant } = useSplitTest('local-ccy-format')
- const formatCurrency = (amount: number, narrowSymbol: boolean) => {
- return variant === 'enabled'
- ? new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD',
- currencyDisplay: narrowSymbol ? 'narrowSymbol' : 'name',
- }).format(amount)
- : `USD ${amount}`
- }
- return (
-
- Variant: {JSON.stringify(variant)}
-
- Long: {formatCurrency(1234.56, false)}
-
- Short: {formatCurrency(1234.56, true)}
-
- )
-}
-
-const config = {
- title: 'Storybook Guideline / Feature Flags', // Must match MDX title exactly
- component: FormatCurrency,
-}
-
-export default {
- ...config,
- ...withSplitTests(config, ['local-ccy-format'], {
- 'local-ccy-format': {
- description: 'Use local currency formatting',
- control: { type: 'radio' as const },
- options: ['default', 'enabled'],
- },
- }),
- tags: ['!dev'], // hides in the sidebar
-}
-
-export const Story = {}
diff --git a/services/web/frontend/stories/docs/foundations.mdx b/services/web/frontend/stories/docs/foundations.mdx
deleted file mode 100644
index d764e65adf..0000000000
--- a/services/web/frontend/stories/docs/foundations.mdx
+++ /dev/null
@@ -1,256 +0,0 @@
-import {
- Meta,
- ColorItem,
- ColorPalette,
- Title,
- Typeset,
-} from '@storybook/addon-docs/blocks'
-import colors from '../../stylesheets/foundations/tokens/colors.json'
-import typography from '../../stylesheets/foundations/tokens/typography.json'
-import borderRadius from '../../stylesheets/foundations/tokens/borderRadius.json'
-import spacing from '../../stylesheets/foundations/tokens/spacing.json'
-
-
-
-# Foundations
-
-Foundations in UX design are the basic rules and core elements, like colors, fonts, and spacing, that ensure consistency across the design.
-
-
- These palettes are generated from our token files. The tokens were exported
- from Figma and split into separate files to categorise:{' '}
-
- Token files on GitHub
-
-
-
-**Table of Contents**
-
-- [Primitive colors](#primitive-colors)
-- [Background colors](#background-colors)
-- [Content colors](#content-colors)
-- [Border colors](#border-colors)
-- [Link colors](#link-colors)
-- [Special colors](#special-colors)
-- [Font weight](#font-weight)
-- [Font size](#font-size)
-- [Border radius](#border-radius)
-- [Spacing](#spacing)
-
-# Colors
-
-
-
-## Primitive colors
-
-
- {Object.entries(colors.PrimitiveColor).map(([name, color]) => (
-
- ))}
-
-
-## Background colors
-
-Colors used for page and component backgrounds.
-
-
- {Object.entries(colors)
- .filter(([name]) => name.startsWith('bg-'))
- .map(([name, colors]) => (
-
- ))}
-
-
-## Content colors
-
-Colors used for text and icons.
-
-
- {Object.entries(colors)
- .filter(([name]) => name.startsWith('content-'))
- .map(([name, colors]) => (
-
- ))}
-
-
-## Border colors
-
-Colors used for borders and dividers.
-
-
- {Object.entries(colors)
- .filter(([name]) => name.startsWith('border-'))
- .map(([name, colors]) => (
-
- ))}
-
-
-## Link colors
-
-Colors used for hyperlink states.
-
-
- {Object.entries(colors)
- .filter(([name]) => name.startsWith('link-'))
- .map(([name, colors]) => (
-
- ))}
-
-
-## Special colors
-
-Colors used for specific UI elements like color pickers.
-
-
- {Object.entries(colors)
- .filter(([name]) => name.startsWith('special-'))
- .map(([name, colors]) => (
-
- ))}
-
-
-# Typography
-
-
-
-## Font weight
-
-
-
-{Object.entries(typography.Fontweight).map(([name, typography]) => (
-
-
- {name}
-
-
-))}
-
-## Font size
-
-
-
-{Object.entries(typography['Fontsize']).map(([name, typography]) => {
-const mixins = typography.$mixin ? [].concat(typography.$mixin) : [];
-
-return (
-
-
-
-{name}
-{mixins.map(mixin => (
- / @mixin {mixin}
-))}
-
-
-
-);
-})}
-
-## Border radius
-
-
-
-
-
-
- | Variable name |
- Value |
- Example |
-
-
-
- {Object.entries(borderRadius['BorderRadius']).map(
- ([name, borderRadius]) => (
-
- |
- {name}
- |
- {borderRadius.$value}px |
-
-
- |
-
- )
- )}
-
-
-
-## Spacing
-
-
-
-
-
-
- | Variable Name |
- Value |
- Example |
-
-
-
- {Object.entries(spacing.Spacing)
- .filter(([, spacing]) => spacing.$value > 0)
- .map(([name, spacing]) => (
-
- |
- {name}
- |
- {spacing.$value}px |
-
-
- |
-
- ))}
-
-
diff --git a/services/web/frontend/stories/editor-switch.stories.jsx b/services/web/frontend/stories/editor-switch.stories.jsx
deleted file mode 100644
index 2db4255768..0000000000
--- a/services/web/frontend/stories/editor-switch.stories.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import EditorSwitch from '../js/features/source-editor/components/editor-switch'
-import { ScopeDecorator } from './decorators/scope'
-
-export default {
- title: 'Editor / Switch',
- component: EditorSwitch,
- decorators: [ScopeDecorator],
-}
-
-export const Switcher = () => {
- return
-}
diff --git a/services/web/frontend/stories/editor/pdf-compile-button.stories.tsx b/services/web/frontend/stories/editor/pdf-compile-button.stories.tsx
deleted file mode 100644
index d83aa2f3a6..0000000000
--- a/services/web/frontend/stories/editor/pdf-compile-button.stories.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { FC } from 'react'
-import type { Meta } from '@storybook/react-webpack5'
-import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button'
-import { ScopeDecorator } from '../decorators/scope'
-import { CompileContext } from '@/shared/context/local-compile-context'
-import { DetachCompileContext } from '@/shared/context/detach-compile-context'
-
-export const CompileButton: FC = (props: CompileContext) => (
-
-
-
-)
-
-const args: Partial = {
- autoCompile: false,
- compiling: false,
- draft: false,
- hasChanges: false,
- stopOnFirstError: false,
- stopOnValidationError: false,
- animateCompileDropdownArrow: false,
-}
-
-const meta: Meta = {
- title: 'Editor / Toolbar / Compile Button',
- component: CompileButton,
- // @ts-ignore
- decorators: [ScopeDecorator],
- argTypes: {
- startCompile: { action: 'startCompile' },
- setAutoCompile: { action: 'setAutoCompile' },
- setCompiling: { action: 'setCompiling' },
- setDraft: { action: 'setDraft' },
- setStopOnFirstError: { action: 'setStopOnFirstError' },
- setError: { action: 'setError' },
- setHasLintingError: { action: 'setHasLintingError' },
- setPosition: { action: 'setPosition' },
- setStopOnValidationError: { action: 'setStopOnValidationError' },
- recompileFromScratch: { action: 'recompileFromScratch' },
- stopCompile: { action: 'stopCompile' },
- setAnimateCompileDropdownArrow: {
- action: 'setAnimateCompileDropdownArrow',
- },
- },
- args,
-}
-
-export default meta
diff --git a/services/web/frontend/stories/editor/synctex-toasts.stories.tsx b/services/web/frontend/stories/editor/synctex-toasts.stories.tsx
deleted file mode 100644
index f934e8c2bc..0000000000
--- a/services/web/frontend/stories/editor/synctex-toasts.stories.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Meta, StoryObj } from '@storybook/react-webpack5'
-import { OLToast } from '@/shared/components/ol/ol-toast'
-import { SynctexFileErrorToast } from '@/features/pdf-preview/components/synctex-toasts'
-
-const meta = {
- title: 'Editor/ Synctex File Error Toast',
- component: SynctexFileErrorToast,
- decorators: [
- Story => (
-
- ),
- ],
-} satisfies Meta
-
-export default meta
-type Story = StoryObj
-
-export const WithoutFile = {
- args: { data: {} },
-} satisfies Story
-
-export const WithFile = {
- args: { data: { filePath: 'references.bbl' } },
-} satisfies Story
diff --git a/services/web/frontend/stories/file-tree.stories.jsx b/services/web/frontend/stories/file-tree.stories.jsx
deleted file mode 100644
index 51621cb094..0000000000
--- a/services/web/frontend/stories/file-tree.stories.jsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import { rootFolderBase } from './fixtures/file-tree-base'
-import { rootFolderLimit } from './fixtures/file-tree-limit'
-import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
-import FileTreeError from '../js/features/file-tree/components/file-tree-error'
-import useFetchMock from './hooks/use-fetch-mock'
-import { ScopeDecorator } from './decorators/scope'
-import { useScope } from './hooks/use-scope'
-import { useIdeContext } from '@/shared/context/ide-context'
-
-const MOCK_DELAY = 2000
-
-const DEFAULT_PROJECT = {
- _id: '123abc',
- name: 'Some Project',
- rootDocId: '5e74f1a7ce17ae0041dfd056',
- rootFolder: rootFolderBase,
-}
-
-function defaultSetupMocks(fetchMock, socket) {
- fetchMock
- .post(
- /\/project\/\w+\/(file|doc|folder)\/\w+\/rename/,
- (path, req) => {
- const body = JSON.parse(req.body)
- const entityId = path.match(/([^/]+)\/rename$/)[1]
- socket.emitToClient('reciveEntityRename', entityId, body.name)
- return 204
- },
-
- {
- delay: MOCK_DELAY,
- }
- )
- .post(
- /\/project\/\w+\/folder/,
- (_path, req) => {
- const body = JSON.parse(req.body)
- const newFolder = {
- folders: [],
- fileRefs: [],
- docs: [],
- _id: Math.random().toString(16).replace(/0\./, 'random-test-id-'),
- name: body.name,
- }
- socket.emitToClient('reciveNewFolder', body.parent_folder_id, newFolder)
- return newFolder
- },
- {
- delay: MOCK_DELAY,
- }
- )
- .delete(
- /\/project\/\w+\/(file|doc|folder)\/\w+/,
- path => {
- const entityId = path.match(/[^/]+$/)[0]
- socket.emitToClient('removeEntity', entityId)
- return 204
- },
- {
- delay: MOCK_DELAY,
- }
- )
- .post(/\/project\/\w+\/(file|doc|folder)\/\w+\/move/, (path, req) => {
- const body = JSON.parse(req.body)
- const entityId = path.match(/([^/]+)\/move/)[1]
- socket.emitToClient('reciveEntityMove', entityId, body.folder_id)
- return 204
- })
-}
-
-export const FullTree = args => {
- const { socket } = useIdeContext()
- useFetchMock(fetchMock => defaultSetupMocks(fetchMock, socket))
-
- useScope({
- project: DEFAULT_PROJECT,
- permissionsLevel: 'owner',
- })
-
- return
-}
-
-export const ReadOnly = args => {
- useScope({
- project: DEFAULT_PROJECT,
- permissionsLevel: 'readOnly',
- })
-
- return
-}
-
-export const Disconnected = args => {
- useScope({
- project: DEFAULT_PROJECT,
- permissionsLevel: 'owner',
- })
-
- return
-}
-Disconnected.args = { isConnected: false }
-
-export const NetworkErrors = args => {
- useFetchMock(fetchMock => {
- fetchMock
- .post(/\/project\/\w+\/folder/, 500, {
- delay: MOCK_DELAY,
- })
- .post(/\/project\/\w+\/(file|doc|folder)\/\w+\/rename/, 500, {
- delay: MOCK_DELAY,
- })
- .post(/\/project\/\w+\/(file|doc|folder)\/\w+\/move/, 500, {
- delay: MOCK_DELAY,
- })
- .delete(/\/project\/\w+\/(file|doc|folder)\/\w+/, 500, {
- delay: MOCK_DELAY,
- })
- })
-
- useScope({
- project: DEFAULT_PROJECT,
- permissionsLevel: 'owner',
- })
-
- return
-}
-
-export const FallbackError = args => {
- useScope({
- project: DEFAULT_PROJECT,
- })
-
- return
-}
-
-export const FilesLimit = args => {
- const { socket } = useIdeContext()
- useFetchMock(fetchMock => defaultSetupMocks(fetchMock, socket))
-
- useScope({
- project: {
- ...DEFAULT_PROJECT,
- rootFolder: rootFolderLimit,
- },
- permissionsLevel: 'owner',
- })
-
- return
-}
-
-export default {
- title: 'Editor / File Tree',
- component: FileTreeRoot,
- args: {
- setStartedFreeTrial: () => {
- console.log('started free trial')
- },
- refProviders: {},
- setRefProviderEnabled: provider => {
- console.log(`ref provider ${provider} enabled`)
- },
- isConnected: true,
- },
- argTypes: {
- onInit: { action: 'onInit' },
- onSelect: { action: 'onSelect' },
- },
- decorators: [
- ScopeDecorator,
- Story => (
- <>
-
-
- >
- ),
- ],
-}
diff --git a/services/web/frontend/stories/file-view/file-view.stories.jsx b/services/web/frontend/stories/file-view/file-view.stories.jsx
deleted file mode 100644
index 80c4363c9d..0000000000
--- a/services/web/frontend/stories/file-view/file-view.stories.jsx
+++ /dev/null
@@ -1,204 +0,0 @@
-import FileView from '../../js/features/file-view/components/file-view'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { ScopeDecorator } from '../decorators/scope'
-
-const bodies = {
- latex: `\\documentclass{article}
-\\begin{document}
-First document. This is a simple example, with no
-extra parameters or packages included.
-\\end{document}`,
- bibtex: `@book{latexcompanion,
- author = "Michel Goossens and Frank Mittelbach and Alexander Samarin",
- title = "The \\LaTeX\\ Companion",
- year = "1993",
- publisher = "Addison-Wesley",
- address = "Reading, Massachusetts"
-}`,
- text: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`,
-}
-
-const setupFetchMock = fetchMock => {
- return fetchMock
- .head('express:/project/:project_id/blob/:hash', {
- status: 201,
- headers: { 'Content-Length': 10000 },
- })
- .post('express:/project/:project_id/linked_file/:file_id/refresh', {
- status: 204,
- })
- .post('express:/project/:project_id/references/indexAll', {
- status: 204,
- })
-}
-
-const fileData = {
- id: 'file-id',
- name: 'file.tex',
- hash: 'c0ffee',
- created: new Date().toISOString(),
-}
-
-export const FileFromUrl = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.latex,
- })
- )
-
- return
-}
-FileFromUrl.args = {
- file: {
- ...fileData,
- linkedFileData: {
- url: 'https://example.com/source-file.tex',
- provider: 'url',
- },
- },
-}
-
-export const FileFromProjectWithLinkableProjectId = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.latex,
- })
- )
-
- return
-}
-FileFromProjectWithLinkableProjectId.args = {
- file: {
- ...fileData,
- linkedFileData: {
- source_project_id: 'source-project-id',
- source_entity_path: '/source-file.tex',
- provider: 'project_file',
- },
- },
-}
-
-export const FileFromProjectWithoutLinkableProjectId = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.latex,
- })
- )
-
- return
-}
-FileFromProjectWithoutLinkableProjectId.args = {
- file: {
- ...fileData,
- linkedFileData: {
- v1_source_doc_id: 'v1-source-id',
- source_entity_path: '/source-file.tex',
- provider: 'project_file',
- },
- },
-}
-
-export const FileFromProjectOutputWithLinkableProject = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.latex,
- })
- )
-
- return
-}
-FileFromProjectOutputWithLinkableProject.args = {
- file: {
- ...fileData,
- linkedFileData: {
- source_project_id: 'source_project_id',
- source_output_file_path: '/source-file.tex',
- provider: 'project_output_file',
- },
- },
-}
-
-export const FileFromProjectOutputWithoutLinkableProjectId = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.latex,
- })
- )
-
- return
-}
-FileFromProjectOutputWithoutLinkableProjectId.args = {
- file: {
- ...fileData,
- linkedFileData: {
- v1_source_doc_id: 'v1-source-id',
- source_output_file_path: '/source-file.tex',
- provider: 'project_output_file',
- },
- },
-}
-
-export const ImageFile = args => {
- useFetchMock(setupFetchMock) // NOTE: can't mock img src request
-
- return
-}
-ImageFile.storyName = 'Image File (Error)'
-ImageFile.args = {
- file: {
- ...fileData,
- id: '60097ca20454610027c442a8',
- name: 'file.jpg',
- linkedFileData: {
- source_project_id: 'source_project_id',
- source_entity_path: '/source-file.jpg',
- provider: 'project_file',
- },
- },
-}
-
-export const TextFile = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
- body: bodies.text,
- })
- )
- return
-}
-TextFile.args = {
- file: {
- ...fileData,
- linkedFileData: {
- source_project_id: 'source-project-id',
- source_entity_path: '/source-file.txt',
- provider: 'project_file',
- },
- name: 'file.txt',
- },
-}
-
-export const UploadedFile = args => {
- useFetchMock(fetchMock =>
- setupFetchMock(fetchMock).head('express:/project/:project_id/blob/:hash', {
- status: 500,
- })
- )
- return
-}
-UploadedFile.storyName = 'Uploaded File (Error)'
-UploadedFile.args = {
- file: {
- ...fileData,
- linkedFileData: null,
- name: 'file.jpg',
- },
-}
-
-export default {
- title: 'Editor / FileView',
- component: FileView,
- argTypes: {
- storeReferencesKeys: { action: 'store references keys' },
- },
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/fixtures/chat-messages.js b/services/web/frontend/stories/fixtures/chat-messages.js
deleted file mode 100644
index 78b91fa8f0..0000000000
--- a/services/web/frontend/stories/fixtures/chat-messages.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const ONE_MINUTE = 60 * 1000
-
-const user = {
- id: 'fake_user',
- first_name: 'mortimer',
- email: 'fake@example.com',
-}
-
-const user2 = {
- id: 'another_fake_user',
- first_name: 'leopold',
- email: 'another_fake@example.com',
-}
-
-let nextMessageId = 1
-
-export function generateMessages(count) {
- const messages = []
- let timestamp = new Date().getTime() // newest message goes first
- for (let i = 0; i <= count; i++) {
- const author = Math.random() > 0.5 ? user : user2
- // modify the timestamp so the previous message has 70% chances to be within 5 minutes from
- // the current one, for grouping purposes
- timestamp -= (4.3 + Math.random()) * ONE_MINUTE
-
- messages.push({
- id: '' + nextMessageId++,
- content: `message #${i}`,
- user: author,
- timestamp,
- })
- }
- return messages
-}
diff --git a/services/web/frontend/stories/fixtures/compile.js b/services/web/frontend/stories/fixtures/compile.js
deleted file mode 100644
index b0303e1f81..0000000000
--- a/services/web/frontend/stories/fixtures/compile.js
+++ /dev/null
@@ -1,220 +0,0 @@
-import examplePdf from './storybook-example.pdf'
-import { cloneDeep } from 'lodash'
-
-export const dispatchDocChanged = () => {
- window.dispatchEvent(
- new CustomEvent('doc:changed', { detail: { doc_id: 'foo' } })
- )
-}
-
-export const outputFiles = [
- {
- path: 'output.pdf',
- build: '1234-5678',
- url: '/build/output.pdf',
- type: 'pdf',
- },
- {
- path: 'output.bbl',
- build: '1234-5678',
- url: '/build/output.bbl',
- type: 'bbl',
- },
- {
- path: 'output.bib',
- build: '1234-5678',
- url: '/build/output.bib',
- type: 'bib',
- },
- {
- path: 'example.txt',
- build: '1234-5678',
- url: '/build/example.txt',
- type: 'txt',
- },
- {
- path: 'output.log',
- build: '1234-5678',
- url: '/build/output.log',
- type: 'log',
- },
- {
- path: 'output.blg',
- build: '1234-5678',
- url: '/build/output.blg',
- type: 'blg',
- },
-]
-
-export const mockCompile = (fetchMock, delay = 1000) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- {
- body: {
- status: 'success',
- clsiServerId: 'foo',
- compileGroup: 'priority',
- pdfDownloadDomain: '',
- outputFiles: cloneDeep(outputFiles),
- },
- },
- { delay }
- )
-
-export const mockCompileError = (fetchMock, status = 'success', delay = 1000) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- {
- body: {
- status,
- clsiServerId: 'foo',
- compileGroup: 'priority',
- },
- },
- { delay, overwriteRoutes: true }
- )
-
-export const mockCompileValidationIssues = (
- fetchMock,
- validationProblems,
- delay = 1000
-) =>
- fetchMock.post(
- 'express:/project/:projectId/compile',
- () => {
- return {
- body: {
- status: 'validation-problems',
- validationProblems,
- clsiServerId: 'foo',
- compileGroup: 'priority',
- },
- }
- },
- { delay }
- )
-
-export const mockClearCache = fetchMock =>
- fetchMock.delete('express:/project/:projectId/output', 204, {
- delay: 1000,
- })
-
-export const mockBuildFile = fetchMock =>
- fetchMock.get('express:/build/:file', ({ url }) => {
- const { pathname } = new URL(url, 'https://example.com')
-
- switch (pathname) {
- case '/build/output.blg':
- return 'This is BibTeX, Version 4.0' // FIXME
-
- case '/build/output.log':
- return `
-The LaTeX compiler output
- * With a lot of details
-
-Wrapped in an HTML element with
- preformatted text which is to be presented exactly
- as written in the HTML file
-
- (whitespace included™)
-
-The text is typically rendered using a non-proportional ("monospace") font.
-
-LaTeX Font Info: External font \`cmex10' loaded for size
-(Font) <7> on input line 18.
-LaTeX Font Info: External font \`cmex10' loaded for size
-(Font) <5> on input line 18.
-! Undefined control sequence.
- \\Zlpha
-
- main.tex, line 23
-
-`
-
- case '/build/output.pdf':
- return new Promise(resolve => {
- const xhr = new XMLHttpRequest()
- xhr.addEventListener('load', () => {
- resolve({
- status: 200,
- headers: {
- 'Content-Length': xhr.getResponseHeader('Content-Length'),
- 'Content-Type': xhr.getResponseHeader('Content-Type'),
- },
- body: xhr.response,
- })
- })
- xhr.open('GET', examplePdf)
- xhr.responseType = 'arraybuffer'
- xhr.send()
- })
-
- default:
- console.log(pathname)
- return 404
- }
- })
-
-const mockHighlights = [
- {
- page: 1,
- h: 85.03936,
- v: 509.999878,
- width: 441.921265,
- height: 8.855677,
- },
- {
- page: 1,
- h: 85.03936,
- v: 486.089539,
- width: 441.921265,
- height: 8.855677,
- },
- {
- page: 1,
- h: 85.03936,
- v: 498.044708,
- width: 441.921265,
- height: 8.855677,
- },
- {
- page: 1,
- h: 85.03936,
- v: 521.955078,
- width: 441.921265,
- height: 8.855677,
- },
-]
-
-export const mockEventTracking = fetchMock =>
- fetchMock.get('express:/event/:event', 204)
-
-export const mockValidPdf = fetchMock =>
- fetchMock.get('express:/build/output.pdf', () => {
- return new Promise(resolve => {
- const xhr = new XMLHttpRequest()
- xhr.addEventListener('load', () => {
- resolve({
- status: 200,
- headers: {
- 'Content-Length': xhr.getResponseHeader('Content-Length'),
- 'Content-Type': xhr.getResponseHeader('Content-Type'),
- 'Accept-Ranges': 'bytes',
- },
- body: xhr.response,
- })
- })
- xhr.open('GET', examplePdf)
- xhr.responseType = 'arraybuffer'
- xhr.send()
- })
- })
-
-export const mockSynctex = fetchMock =>
- fetchMock
- .get('express:/project/:projectId/sync/code', () => {
- return { pdf: cloneDeep(mockHighlights) }
- })
- .get('express:/project/:projectId/sync/pdf', () => {
- return { code: [{ file: 'main.tex', line: 100 }] }
- })
diff --git a/services/web/frontend/stories/fixtures/contacts.js b/services/web/frontend/stories/fixtures/contacts.js
deleted file mode 100644
index 10c0348ed0..0000000000
--- a/services/web/frontend/stories/fixtures/contacts.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export const contacts = [
- // user with edited name
- {
- type: 'user',
- email: 'test-user@example.com',
- first_name: 'Test',
- last_name: 'User',
- name: 'Test User',
- },
- // user with default name (email prefix)
- {
- type: 'user',
- email: 'test@example.com',
- first_name: 'test',
- },
- // no last name
- {
- type: 'user',
- first_name: 'Eratosthenes',
- email: 'eratosthenes@example.com',
- },
- // more users
- {
- type: 'user',
- first_name: 'Claudius',
- last_name: 'Ptolemy',
- email: 'ptolemy@example.com',
- },
- {
- type: 'user',
- first_name: 'Abd al-Rahman',
- last_name: 'Al-Sufi',
- email: 'al-sufi@example.com',
- },
- {
- type: 'user',
- first_name: 'Nicolaus',
- last_name: 'Copernicus',
- email: 'copernicus@example.com',
- },
-]
diff --git a/services/web/frontend/stories/fixtures/document.ts b/services/web/frontend/stories/fixtures/document.ts
deleted file mode 100644
index ad2995a4dc..0000000000
--- a/services/web/frontend/stories/fixtures/document.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export function mockDocument(text: string) {
- return {
- doc_id: 'story-doc',
- getSnapshot: () => text,
- hasBufferedOps: () => false,
- }
-}
-
-export const document = {
- tex: `\\documentclass{article}
-
-% Language setting
-% Replace \`english' with e.g. \`spanish' to change the document language
-\\usepackage[english]{babel}
-
-% Set page size and margins
-% Replace \`letterpaper' with\`a4paper' for UK/EU standard size
-\\usepackage[letterpaper,top=2cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
-
-% Useful packages
-\\usepackage{amsmath}
-\\usepackage{graphicx}
-\\usepackage[colorlinks=true, allcolors=blue]{hyperref}
-
-\\title{Your Paper}
-\\author{You}
-
-\\begin{document}
-\\maketitle
-
-\\begin{abstract}
-Your abstract.
-\\end{abstract}
-
-\\section{Introduction}
-
-Your introduction goes here! Simply start writing your document and use the Recompile button to view the updated PDF preview. Examples of commonly used commands and features are listed below, to help you get started.
-
-Once you're familiar with the editor, you can find various project setting in the Overleaf menu, accessed via the button in the very top left of the editor. To view tutorials, user guides, and further documentation, please visit our \\href{https://www.overleaf.com/learn}{help library}, or head to our plans page to \\href{https://www.overleaf.com/user/subscription/plans}{choose your plan}.`,
-}
diff --git a/services/web/frontend/stories/fixtures/file-tree-base.js b/services/web/frontend/stories/fixtures/file-tree-base.js
deleted file mode 100644
index e3237030c5..0000000000
--- a/services/web/frontend/stories/fixtures/file-tree-base.js
+++ /dev/null
@@ -1,44 +0,0 @@
-export const rootFolderBase = [
- {
- _id: '5e74f1a7ce17ae0041dfd054',
- name: 'rootFolder',
- folders: [
- {
- _id: '5f638e58b652df0026c5c8f5',
- name: 'a folder',
- folders: [
- {
- _id: '5f956f62700e19000177daa0',
- name: 'sub folder',
- folders: [],
- fileRefs: [],
- docs: [],
- },
- ],
- fileRefs: [
- { _id: '5cffb9d93da45d3995d05362', name: 'file-in-a-folder.pdf' },
- ],
- docs: [
- { _id: '5f46786322d556004e72a555', name: 'doc-in-a-folder.tex' },
- ],
- },
- {
- _id: '5f638e68b652df0026c5c8f6',
- name: 'another folder',
- folders: [],
- fileRefs: [],
- docs: [],
- },
- ],
- fileRefs: [{ _id: '5f11c78e0924770027412a67', name: 'univers.jpg' }],
- docs: [
- { _id: '5e74f1a7ce17ae0041dfd056', name: 'main.tex' },
- { _id: '5f46789522d556004e72a556', name: 'perso.bib' },
- {
- _id: '5da532e29019e800015321c6',
- name: 'zotero.bib',
- linkedFileData: { provider: 'zotero' },
- },
- ],
- },
-]
diff --git a/services/web/frontend/stories/fixtures/file-tree-limit.js b/services/web/frontend/stories/fixtures/file-tree-limit.js
deleted file mode 100644
index b403405b65..0000000000
--- a/services/web/frontend/stories/fixtures/file-tree-limit.js
+++ /dev/null
@@ -1,52 +0,0 @@
-const FILE_PER_FOLDER = 2
-const DOC_PER_FOLDER = 3
-const FOLDER_PER_FOLDER = 2
-const MAX_DEPTH = 7
-
-function fakeId() {
- return Math.random().toString(16).replace(/0\./, 'random-test-id-')
-}
-
-function makeFileRefs(path) {
- const fileRefs = []
-
- for (let index = 0; index < FILE_PER_FOLDER; index++) {
- fileRefs.push({ _id: fakeId(), name: `${path}-file-${index}.jpg` })
- }
- return fileRefs
-}
-
-function makeDocs(path) {
- const docs = []
-
- for (let index = 0; index < DOC_PER_FOLDER; index++) {
- docs.push({ _id: fakeId(), name: `${path}-doc-${index}.tex` })
- }
- return docs
-}
-
-function makeFolders(path, depth = 0) {
- const folders = []
-
- for (let index = 0; index < FOLDER_PER_FOLDER; index++) {
- const folderPath = `${path}-folder-${index}`
- folders.push({
- _id: fakeId(),
- name: folderPath,
- folders: depth < MAX_DEPTH ? makeFolders(folderPath, depth + 1) : [],
- fileRefs: makeFileRefs(folderPath),
- docs: makeDocs(folderPath),
- })
- }
- return folders
-}
-
-export const rootFolderLimit = [
- {
- _id: fakeId(),
- name: 'rootFolder',
- folders: makeFolders('root'),
- fileRefs: makeFileRefs('root'),
- docs: makeDocs('root'),
- },
-]
diff --git a/services/web/frontend/stories/fixtures/project.ts b/services/web/frontend/stories/fixtures/project.ts
deleted file mode 100644
index 9cc54c4fc4..0000000000
--- a/services/web/frontend/stories/fixtures/project.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Project } from '../../../types/project'
-
-export const project: Project = {
- _id: '63e21c07946dd8c76505f85a',
- name: 'A Project',
- features: {
- collaborators: -1, // unlimited
- },
- publicAccesLevel: 'private',
- tokens: {
- readOnly: 'ro-token',
- readAndWrite: 'rw-token',
- },
- owner: {
- _id: 'project-owner',
- email: 'stories@overleaf.com',
- },
- members: [
- {
- _id: 'viewer-member',
- type: 'user',
- privileges: 'readOnly',
- name: 'Viewer User',
- email: 'viewer@example.com',
- },
- {
- _id: 'author-member',
- type: 'user',
- privileges: 'readAndWrite',
- name: 'Author User',
- email: 'author@example.com',
- },
- ],
- invites: [
- {
- _id: 'test-invite-1',
- privileges: 'readOnly',
- name: 'Invited Viewer',
- email: 'invited-viewer@example.com',
- },
- {
- _id: 'test-invite-2',
- privileges: 'readAndWrite',
- name: 'Invited Author',
- email: 'invited-author@example.com',
- },
- ],
-}
diff --git a/services/web/frontend/stories/fixtures/storybook-example.pdf b/services/web/frontend/stories/fixtures/storybook-example.pdf
deleted file mode 100644
index 4c2cc8512d..0000000000
Binary files a/services/web/frontend/stories/fixtures/storybook-example.pdf and /dev/null differ
diff --git a/services/web/frontend/stories/history/document-diff-viewer.stories.tsx b/services/web/frontend/stories/history/document-diff-viewer.stories.tsx
deleted file mode 100644
index fa8014a157..0000000000
--- a/services/web/frontend/stories/history/document-diff-viewer.stories.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { ScopeDecorator } from '../decorators/scope'
-import DocumentDiffViewer from '../../js/features/history/components/diff-view/document-diff-viewer'
-import React from 'react'
-import { Highlight } from '../../js/features/history/services/types/doc'
-
-const highlights: Highlight[] = [
- {
- type: 'addition',
- range: { from: 3, to: 10 },
- hue: 200,
- label: 'Added by Wombat on Monday',
- },
- {
- type: 'deletion',
- range: { from: 15, to: 25 },
- hue: 62,
- label: 'Deleted by Duck on Monday',
- },
- {
- type: 'addition',
- range: { from: 100, to: 400 },
- hue: 200,
- label: 'Added by Wombat on Friday',
- },
- {
- type: 'deletion',
- range: { from: 564, to: 565 },
- hue: 200,
- label: 'Deleted by Wombat on Friday',
- },
- {
- type: 'addition',
- range: { from: 1770, to: 1780 },
- hue: 200,
- label: 'Added by Wombat on Tuesday',
- },
-]
-
-const doc = `\\documentclass{article}
-
-% Language setting
-% Replace \`english' with e.g. \`spanish' to change the document language
-\\usepackage[english]{babel}
-
-% Set page size and margins
-% Replace \`letterpaper' with \`a4paper' for UK/EU standard size
-\\usepackage[letterpaper,top=2cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
-
-% Useful packages
-\\usepackage{amsmath}
-\\usepackage{graphicx}
-\\usepackage[colorlinks=true, allcolors=blue]{hyperref}
-
-\\title{Your Paper}
-\\author{You}
-
-\\begin{document}
-\\maketitle
-
-\\begin{abstract}
-Your abstract.
-\\end{abstract}
-
-\\section{Introduction}
-
-Your introduction goes here! Simply start writing your document and use the Recompile button to view the updated PDF preview. Examples of commonly used commands and features are listed below, to help you get started.
-
-Once you're familiar with the editor, you can find various project settings in the Overleaf menu, accessed via the button in the very top left of the editor. To view tutorials, user guides, and further documentation, please visit our \\href{https://www.overleaf.com/learn}{help library}, or head to our plans page to \\href{https://www.overleaf.com/user/subscription/plans}{choose your plan}.
-
-\\begin{enumerate}
- \\item The labels consists of sequential numbers
- \\begin{itemize}
- \\item The individual entries are indicated with a black dot, a so-called bullet
- \\item The text in the entries may be of any length
- \\begin{description}
- \\item[Note:] I would like to describe something here
- \\item[Caveat!] And give a warning
- \\end{description}
- \\end{itemize}
- \\item The numbers starts at 1 with each use of the \\text{enumerate} environment
-\\end{enumerate}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-\\bibliographystyle{alpha}
-\\bibliography{sample}
-
-\\end{document}`
-
-export default {
- title: 'History / Document Diff Viewer',
- component: DocumentDiffViewer,
- args: { doc, highlights },
- argTypes: {
- doc: {
- table: { disable: true },
- },
- highlights: {
- table: { disable: true },
- },
- },
- decorators: [
- ScopeDecorator,
- (Story: React.ComponentType) => (
-
-
-
- ),
- ],
-}
-
-export const Highlights = (
- args: React.ComponentProps
-) => {
- return
-}
-
-export const ScrollToFirstHighlight = (
- args: React.ComponentProps
-) => {
- const lastHighlightOnly = args.highlights.slice(-1)
- return
-}
diff --git a/services/web/frontend/stories/history/history-version.stories.tsx b/services/web/frontend/stories/history/history-version.stories.tsx
deleted file mode 100644
index 65c2a58d60..0000000000
--- a/services/web/frontend/stories/history/history-version.stories.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import HistoryVersionComponent from '../../js/features/history/components/change-list/history-version'
-import { ScopeDecorator } from '../decorators/scope'
-import { HistoryProvider } from '../../js/features/history/context/history-context'
-import { disableControlsOf } from '../utils/arg-types'
-
-const update = {
- fromV: 3,
- toV: 4,
- meta: {
- users: [
- {
- first_name: 'john.doe',
- last_name: '',
- email: 'john.doe@test.com',
- id: '631710ab1094c5002647184e',
- },
- ],
- start_ts: 1681220036419,
- end_ts: 1681220036419,
- },
- labels: [
- {
- id: '643561cdfa2b2beac88f0024',
- comment: 'tag-1',
- version: 4,
- user_id: '123',
- created_at: '2023-04-11T13:34:05.856Z',
- user_display_name: 'john.doe',
- },
- {
- id: '643561d1fa2b2beac88f0025',
- comment: 'tag-2',
- version: 4,
- user_id: '123',
- created_at: '2023-04-11T13:34:09.280Z',
- user_display_name: 'john.doe',
- },
- ],
- pathnames: [],
- project_ops: [{ add: { pathname: 'name.tex' }, atV: 3 }],
-}
-
-export const HistoryVersion = (
- args: React.ComponentProps
-) => {
- return (
-
-
-
- )
-}
-
-export default {
- title: 'History / Change list',
- component: HistoryVersionComponent,
- args: {
- update,
- currentUserId: '1',
- projectId: '123',
- comparing: false,
- faded: false,
- showDivider: false,
- selectionState: false,
- setSelection: () => {},
- dropdownOpen: false,
- dropdownActive: false,
- setActiveDropdownItem: () => {},
- closeDropdownForItem: () => {},
- },
- argTypes: {
- ...disableControlsOf(
- 'update',
- 'currentUserId',
- 'projectId',
- 'setSelection',
- 'dropdownOpen',
- 'dropdownActive',
- 'setActiveDropdownItem',
- 'closeDropdownForItem'
- ),
- },
- decorators: [
- ScopeDecorator,
- (Story: React.ComponentType) => (
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/history/label-version.stories.tsx b/services/web/frontend/stories/history/label-version.stories.tsx
deleted file mode 100644
index a72bab27a8..0000000000
--- a/services/web/frontend/stories/history/label-version.stories.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import LabelListItemComponent from '../../js/features/history/components/change-list/label-list-item'
-import { ScopeDecorator } from '../decorators/scope'
-import { HistoryProvider } from '../../js/features/history/context/history-context'
-import { disableControlsOf } from '../utils/arg-types'
-
-const labels = [
- {
- id: '643561cdfa2b2beac88f0024',
- comment: 'tag-1',
- version: 1,
- user_id: '123',
- created_at: '2023-04-11T13:34:05.856Z',
- user_display_name: 'john.doe',
- },
- {
- id: '643561d1fa2b2beac88f0025',
- comment: 'tag-2',
- version: 1,
- user_id: '123',
- created_at: '2023-04-11T13:34:09.280Z',
- user_display_name: 'john.doe',
- },
-]
-
-export const LabelVersion = (
- args: React.ComponentProps
-) => {
- return (
-
-
-
- )
-}
-
-export default {
- title: 'History / Change list',
- component: LabelListItemComponent,
- args: {
- labels,
- version: 1,
- currentUserId: '1',
- projectId: '123',
- comparing: false,
- selectionState: false,
- selectable: false,
- setSelection: () => {},
- dropdownOpen: false,
- dropdownActive: false,
- setActiveDropdownItem: () => {},
- closeDropdownForItem: () => {},
- },
- argTypes: {
- ...disableControlsOf(
- 'labels',
- 'version',
- 'currentUserId',
- 'projectId',
- 'setSelection',
- 'dropdownOpen',
- 'dropdownActive',
- 'setActiveDropdownItem',
- 'closeDropdownForItem'
- ),
- },
- decorators: [
- ScopeDecorator,
- (Story: React.ComponentType) => (
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/history/toggle-switch.stories.tsx b/services/web/frontend/stories/history/toggle-switch.stories.tsx
deleted file mode 100644
index 5fe673db55..0000000000
--- a/services/web/frontend/stories/history/toggle-switch.stories.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useState } from 'react'
-import ToggleSwitchComponent from '../../js/features/history/components/change-list/toggle-switch'
-import { ScopeDecorator } from '../decorators/scope'
-import { HistoryProvider } from '../../js/features/history/context/history-context'
-
-export const HistoryAndLabelsToggleSwitch = () => {
- const [labelsOnly, setLabelsOnly] = useState(false)
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'History / Change list',
- component: ToggleSwitchComponent,
- argTypes: {
- labelsOnly: {
- table: {
- disable: true,
- },
- },
- setLabelsOnly: {
- table: {
- disable: true,
- },
- },
- },
- decorators: [
- ScopeDecorator,
- (Story: React.ComponentType) => (
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/hooks/use-fetch-mock.tsx b/services/web/frontend/stories/hooks/use-fetch-mock.tsx
deleted file mode 100644
index 304d9e0273..0000000000
--- a/services/web/frontend/stories/hooks/use-fetch-mock.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useLayoutEffect } from 'react'
-import fetchMock from 'fetch-mock'
-
-/**
- * Run callback to mock fetch routes, call removeRoutes() and unmockGlobal() when unmounted
- */
-export default function useFetchMock(
- callback: (value: typeof fetchMock) => void
-) {
- fetchMock.mockGlobal()
-
- useLayoutEffect(() => {
- fetchMock.mockGlobal()
- callback(fetchMock)
- return () => {
- fetchMock.removeRoutes()
- fetchMock.unmockGlobal()
- }
- }, [callback])
-}
diff --git a/services/web/frontend/stories/hooks/use-meta.tsx b/services/web/frontend/stories/hooks/use-meta.tsx
deleted file mode 100644
index 0ebbe9f29f..0000000000
--- a/services/web/frontend/stories/hooks/use-meta.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { PartialMeta } from '@/utils/meta'
-
-/**
- * Set values on window.metaAttributesCache, for use in Storybook stories
- */
-export const useMeta = (meta: PartialMeta) => {
- for (const [key, value] of Object.entries(meta)) {
- window.metaAttributesCache.set(key as keyof PartialMeta, value)
- }
-}
diff --git a/services/web/frontend/stories/hooks/use-scope.tsx b/services/web/frontend/stories/hooks/use-scope.tsx
deleted file mode 100644
index f2450a38a4..0000000000
--- a/services/web/frontend/stories/hooks/use-scope.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { merge } from 'lodash'
-import { useLayoutEffect, useRef } from 'react'
-
-/**
- * Merge properties with the scope object, for use in Storybook stories
- */
-export const useScope = (scope: Record) => {
- const scopeRef = useRef(null)
- if (scopeRef.current === null) {
- scopeRef.current = scope
- }
-
- useLayoutEffect(() => {
- if (scopeRef.current) {
- for (const [path, value] of Object.entries(scopeRef.current)) {
- let existingValue: typeof value | undefined
- try {
- existingValue = window.overleaf.unstable.store.get(path)
- } catch {
- // allowed not to exist
- }
- if (typeof existingValue === 'object' && typeof value === 'object') {
- window.overleaf.unstable.store.set(path, merge(existingValue, value))
- } else {
- window.overleaf.unstable.store.set(path, value)
- }
- }
- }
- }, [])
-}
diff --git a/services/web/frontend/stories/hotkeys-modal.stories.tsx b/services/web/frontend/stories/hotkeys-modal.stories.tsx
deleted file mode 100644
index 026624ceef..0000000000
--- a/services/web/frontend/stories/hotkeys-modal.stories.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ComponentProps } from 'react'
-import HotkeysModal from '../js/features/hotkeys-modal/components/hotkeys-modal'
-
-type HotkeysModalProps = ComponentProps
-
-export const ReviewEnabled = (args: HotkeysModalProps) => {
- return
-}
-
-export const ReviewDisabled = (args: HotkeysModalProps) => {
- return
-}
-
-export const MacModifier = (args: HotkeysModalProps) => {
- return
-}
-
-export default {
- title: 'Editor / Modals / Hotkeys',
- component: HotkeysModal,
- args: {
- animation: false,
- show: true,
- isMac: false,
- trackChangesVisible: true,
- },
- argTypes: {
- handleHide: { action: 'handleHide' },
- },
-}
diff --git a/services/web/frontend/stories/loading/loading.stories.tsx b/services/web/frontend/stories/loading/loading.stories.tsx
deleted file mode 100644
index 3ec57f0a1f..0000000000
--- a/services/web/frontend/stories/loading/loading.stories.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { LoadingUI } from '@/features/ide-react/components/loading'
-import { EditorProviders } from '../../../test/frontend/helpers/editor-providers'
-import { PartialMeta } from '@/utils/meta'
-
-const meta: Meta = {
- title: 'Loading Page / Loading',
- component: LoadingUI,
- argTypes: {
- errorCode: {
- control: 'select',
- options: [
- '',
- 'io-not-loaded',
- 'unable-to-join',
- 'i18n-error',
- 'unhandled-error-code',
- ],
- },
- progress: { control: { type: 'range', min: 0, max: 100 } },
- },
-}
-
-export default meta
-
-type Story = StoryObj
-
-const errorMessages = {
- translationIoNotLoaded: 'Could not connect to the WebSocket server',
- translationLoadErrorMessage: 'Could not load translations',
- translationUnableToJoin: 'Could not connect to collaboration server',
-}
-
-export const LoadingPage: Story = {
- render: args => {
- for (const [key, value] of Object.entries(errorMessages)) {
- window.metaAttributesCache.set(`ol-${key}` as keyof PartialMeta, value)
- }
- return (
-
-
-
- )
- },
-}
diff --git a/services/web/frontend/stories/modals/create-file/create-file-modal-decorator.jsx b/services/web/frontend/stories/modals/create-file/create-file-modal-decorator.jsx
deleted file mode 100644
index a70a507436..0000000000
--- a/services/web/frontend/stories/modals/create-file/create-file-modal-decorator.jsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { useEffect } from 'react'
-import FileTreeContext from '../../../js/features/file-tree/components/file-tree-context'
-import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
-import FileTreeCreateFormProvider from '../../../js/features/file-tree/contexts/file-tree-create-form'
-import { useFileTreeActionable } from '../../../js/features/file-tree/contexts/file-tree-actionable'
-import PropTypes from 'prop-types'
-
-const defaultFileTreeContextProps = {
- refProviders: { mendeley: false, zotero: false },
- setRefProviderEnabled: provider => {
- console.log(`ref provider ${provider} enabled`)
- },
- setStartedFreeTrial: () => {
- console.log('started free trial')
- },
- initialSelectedEntityId: 'entity-1',
- onSelect: () => {
- console.log('selected')
- },
-}
-
-export const mockCreateFileModalFetch = fetchMock =>
- fetchMock
- .get('path:/user/projects', {
- projects: [
- {
- _id: 'project-1',
- name: 'Project One',
- },
- {
- _id: 'project-2',
- name: 'Project Two',
- },
- ],
- })
- .get('path:/mendeley/groups', {
- groups: [
- {
- id: 'group-1',
- name: 'Group One',
- },
- {
- id: 'group-2',
- name: 'Group Two',
- },
- ],
- })
- .get('path:/zotero/groups', {
- groups: [
- {
- id: 'group-1',
- name: 'Group One',
- },
- {
- id: 'group-2',
- name: 'Group Two',
- },
- ],
- })
- .get('express:/project/:projectId/entities', {
- entities: [
- {
- path: '/foo.tex',
- },
- {
- path: '/bar.tex',
- },
- ],
- })
- .post('express:/project/:projectId/doc', (path, req) => {
- console.log({ path, req })
- return 204
- })
- .post('express:/project/:projectId/upload', (path, req) => {
- console.log({ path, req })
- return 204
- })
- .post('express:/project/:projectId/linked_file', (path, req) => {
- console.log({ path, req })
- return 204
- })
-
-export const createFileModalDecorator =
- (fileTreeContextProps = {}, createMode = 'doc') =>
- // eslint-disable-next-line react/display-name
- Story => {
- return (
-
-
-
-
-
-
-
-
-
- )
- }
-
-function OpenCreateFileModal({ children, createMode }) {
- const { startCreatingFile } = useFileTreeActionable()
-
- useEffect(() => {
- startCreatingFile(createMode)
- }, [createMode]) // eslint-disable-line react-hooks/exhaustive-deps
-
- return <>{children}>
-}
-OpenCreateFileModal.propTypes = {
- createMode: PropTypes.string,
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- ]).isRequired,
-}
diff --git a/services/web/frontend/stories/modals/create-file/create-file-modal-footer.stories.jsx b/services/web/frontend/stories/modals/create-file/create-file-modal-footer.stories.jsx
deleted file mode 100644
index 224850f494..0000000000
--- a/services/web/frontend/stories/modals/create-file/create-file-modal-footer.stories.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- ModalFooterDecorator,
- ModalContentDecorator,
-} from '../modal-decorators'
-import { FileTreeModalCreateFileFooterContent } from '../../../js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer'
-
-export const Valid = args =>
-
-export const Invalid = args => (
-
-)
-Invalid.args = {
- valid: false,
-}
-
-export const Inflight = args => (
-
-)
-Inflight.args = {
- inFlight: true,
-}
-
-export const FileLimitWarning = args => (
-
-)
-FileLimitWarning.args = {
- fileCount: {
- status: 'warning',
- value: 1990,
- limit: 2000,
- },
-}
-
-export const FileLimitError = args => (
-
-)
-FileLimitError.args = {
- fileCount: {
- status: 'error',
- value: 2000,
- limit: 2000,
- },
-}
-
-export default {
- title: 'Editor / Modals / Create File / Footer',
- component: FileTreeModalCreateFileFooterContent,
- args: {
- fileCount: {
- status: 'success',
- limit: 10,
- value: 1,
- },
- valid: true,
- inFlight: false,
- newFileCreateMode: 'doc',
- },
- argTypes: {
- cancel: { action: 'cancel' },
- },
- decorators: [ModalFooterDecorator, ModalContentDecorator],
-}
diff --git a/services/web/frontend/stories/modals/create-file/create-file-modal.stories.jsx b/services/web/frontend/stories/modals/create-file/create-file-modal.stories.jsx
deleted file mode 100644
index ba5ad45d22..0000000000
--- a/services/web/frontend/stories/modals/create-file/create-file-modal.stories.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import {
- createFileModalDecorator,
- mockCreateFileModalFetch,
-} from './create-file-modal-decorator'
-import FileTreeModalCreateFile from '../../../js/features/file-tree/components/modals/file-tree-modal-create-file'
-import useFetchMock from '../../hooks/use-fetch-mock'
-import { ScopeDecorator } from '../../decorators/scope'
-import { useScope } from '../../hooks/use-scope'
-import getMeta from '@/utils/meta'
-
-export const MinimalFeatures = args => {
- useFetchMock(mockCreateFileModalFetch)
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasLinkUrlFeature: false,
- hasLinkedProjectFileFeature: false,
- hasLinkedProjectOutputFileFeature: false,
- })
-
- return
-}
-MinimalFeatures.decorators = [createFileModalDecorator()]
-
-export const WithExtraFeatures = args => {
- useFetchMock(mockCreateFileModalFetch)
-
- getMeta('ol-ExposedSettings').hasLinkUrlFeature = true
-
- return
-}
-WithExtraFeatures.decorators = [
- createFileModalDecorator({
- refProviders: { mendeley: true, zotero: true },
- }),
-]
-
-export const ErrorImportingFileFromExternalURL = args => {
- useFetchMock(fetchMock => {
- mockCreateFileModalFetch(fetchMock)
-
- fetchMock.post('express:/project/:projectId/linked_file', 500)
- })
-
- getMeta('ol-ExposedSettings').hasLinkUrlFeature = true
-
- return
-}
-ErrorImportingFileFromExternalURL.decorators = [createFileModalDecorator()]
-
-export const ErrorImportingFileFromReferenceProvider = args => {
- useFetchMock(fetchMock => {
- mockCreateFileModalFetch(fetchMock)
-
- fetchMock.post('express:/project/:projectId/linked_file', 500)
- })
-
- return
-}
-ErrorImportingFileFromReferenceProvider.decorators = [
- createFileModalDecorator({
- refProviders: { mendeley: true, zotero: true },
- }),
-]
-
-export const FileLimitReached = args => {
- useFetchMock(mockCreateFileModalFetch)
-
- useScope({
- project: {
- rootFolder: {
- _id: 'root-folder-id',
- name: 'rootFolder',
- docs: Array.from({ length: 10 }, (_, index) => ({
- _id: `entity-${index}`,
- })),
- fileRefs: [],
- folders: [],
- },
- },
- })
-
- return
-}
-FileLimitReached.decorators = [createFileModalDecorator()]
-
-export default {
- title: 'Editor / Modals / Create File',
- component: FileTreeModalCreateFile,
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/modals/create-file/create-file-name-input.stories.jsx b/services/web/frontend/stories/modals/create-file/create-file-name-input.stories.jsx
deleted file mode 100644
index 8f68e2d7b8..0000000000
--- a/services/web/frontend/stories/modals/create-file/create-file-name-input.stories.jsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import FileTreeCreateNameInput from '../../../js/features/file-tree/components/file-tree-create/file-tree-create-name-input'
-import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
-import {
- BlockedFilenameError,
- DuplicateFilenameError,
-} from '../../../js/features/file-tree/errors'
-import { ModalBodyDecorator, ModalContentDecorator } from '../modal-decorators'
-
-export const DefaultLabel = args => (
-
-
-
-)
-
-export const CustomLabel = args => (
-
-
-
-)
-CustomLabel.args = {
- label: 'File Name in this Project',
-}
-
-export const FocusName = args => (
-
-
-
-)
-FocusName.args = {
- focusName: true,
-}
-
-export const CustomPlaceholder = args => (
-
-
-
-)
-CustomPlaceholder.args = {
- placeholder: 'Enter a file name…',
-}
-
-export const DuplicateError = args => (
-
-
-
-)
-DuplicateError.args = {
- error: new DuplicateFilenameError(),
-}
-
-export const BlockedError = args => (
-
-
-
-)
-BlockedError.args = {
- error: new BlockedFilenameError(),
-}
-
-export default {
- title: 'Editor / Modals / Create File / File Name Input',
- component: FileTreeCreateNameInput,
- decorators: [ModalBodyDecorator, ModalContentDecorator],
- args: {
- inFlight: false,
- },
-}
diff --git a/services/web/frontend/stories/modals/create-file/error-message.stories.jsx b/services/web/frontend/stories/modals/create-file/error-message.stories.jsx
deleted file mode 100644
index 867a2b9e74..0000000000
--- a/services/web/frontend/stories/modals/create-file/error-message.stories.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import ErrorMessage from '../../../js/features/file-tree/components/file-tree-create/error-message'
-import { FetchError } from '../../../js/infrastructure/fetch-json'
-import {
- BlockedFilenameError,
- DuplicateFilenameError,
- InvalidFilenameError,
-} from '../../../js/features/file-tree/errors'
-
-export const KeyedErrors = () => {
- return (
- <>
-
-
-
-
- {/* */}
-
- >
- )
-}
-
-export const FetchStatusErrors = () => {
- return (
- <>
-
-
-
-
- >
- )
-}
-
-export const FetchDataErrors = () => {
- return (
- <>
-
-
- >
- )
-}
-
-export const SpecificClassErrors = () => {
- return (
- <>
-
-
-
-
- >
- )
-}
-
-export default {
- title: 'Editor / Modals / Create File / Error Message',
- component: ErrorMessage,
-}
diff --git a/services/web/frontend/stories/modals/modal-decorators.jsx b/services/web/frontend/stories/modals/modal-decorators.jsx
deleted file mode 100644
index 53b304673a..0000000000
--- a/services/web/frontend/stories/modals/modal-decorators.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Wrap modal content in modal classes, without modal behaviours
- */
-
-export function ModalContentDecorator(Story) {
- return (
-
- )
-}
-
-export function ModalBodyDecorator(Story) {
- return (
-
-
-
- )
-}
-
-export function ModalFooterDecorator(Story) {
- return (
-
-
-
- )
-}
diff --git a/services/web/frontend/stories/modals/unsaved-docs-locked-modal.stories.tsx b/services/web/frontend/stories/modals/unsaved-docs-locked-modal.stories.tsx
deleted file mode 100644
index 929164e5d5..0000000000
--- a/services/web/frontend/stories/modals/unsaved-docs-locked-modal.stories.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Meta, StoryObj } from '@storybook/react-webpack5'
-import { UnsavedDocsLockedAlert } from '@/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert'
-import { ScopeDecorator } from '../decorators/scope'
-
-export default {
- title: 'Editor / Modals / Unsaved Docs Locked',
- component: UnsavedDocsLockedAlert,
- decorators: [Story => ScopeDecorator(Story)],
-} satisfies Meta
-
-type Story = StoryObj
-
-export const Locked: Story = {}
diff --git a/services/web/frontend/stories/online-users.stories.tsx b/services/web/frontend/stories/online-users.stories.tsx
deleted file mode 100644
index 035c8dcfa0..0000000000
--- a/services/web/frontend/stories/online-users.stories.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Meta } from '@storybook/react-webpack5'
-import { OnlineUser } from '@/features/ide-react/context/online-users-context'
-import { OnlineUsersWidget } from '@/features/editor-navigation-toolbar/components/online-users-widget'
-
-const NAMES = [
- 'Alice',
- 'Bob',
- 'Charlie',
- 'Dave',
- 'Erin',
- 'Frank',
- 'Grace',
- 'Heidi',
- 'Ivan',
- 'Judy',
- 'Mallory',
- 'Niaj',
- 'Olivia',
- 'Peggy',
- 'Rupert',
-]
-
-const generateUser = (_: any, index: number): OnlineUser => {
- const name = NAMES[index % NAMES.length]
- return {
- user_id: `user_${'b'.repeat(index)}`,
- name,
- id: `user-${index}`,
- email: `${name.toLowerCase()}@example.com`,
- }
-}
-
-export const OnlineUsers = ({ users }: { users: number }) => {
- const generatedUsers = Array.from({ length: users }, generateUser)
- return (
-
- {}) as any}
- />
-
- )
-}
-
-const meta: Meta = {
- title: 'Editor / Online Users Widget',
- args: {
- users: 6,
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/outline.stories.jsx b/services/web/frontend/stories/outline.stories.jsx
deleted file mode 100644
index 03ed349e12..0000000000
--- a/services/web/frontend/stories/outline.stories.jsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import OutlinePane from '../js/features/outline/components/outline-pane'
-import { ScopeDecorator } from './decorators/scope'
-
-export const Basic = args =>
-Basic.args = {
- outline: [{ line: 1, title: 'Hello', level: 1 }],
-}
-
-export const Nested = args =>
-Nested.args = {
- outline: [
- {
- line: 1,
- title: 'Section',
- level: 1,
- children: [
- {
- line: 2,
- title: 'Subsection',
- level: 2,
- children: [
- {
- line: 3,
- title: 'Subsubsection',
- level: 3,
- },
- ],
- },
- ],
- },
- ],
-}
-
-export const NoSections = args =>
-NoSections.args = {}
-
-export const NonTexFile = args =>
-NonTexFile.args = {
- isTexFile: false,
-}
-
-export const PartialResult = args =>
-PartialResult.args = {
- isPartial: true,
-}
-
-export default {
- title: 'Editor / Outline',
- component: OutlinePane,
- argTypes: {
- jumpToLine: { action: 'jumpToLine' },
- onToggle: { action: 'onToggle' },
- toggleExpanded: { action: 'toggleExpanded' },
- },
- args: {
- isTexFile: true,
- outline: [],
- expanded: true,
- },
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/pdf-log-entry.stories.tsx b/services/web/frontend/stories/pdf-log-entry.stories.tsx
deleted file mode 100644
index 95a3e33fa9..0000000000
--- a/services/web/frontend/stories/pdf-log-entry.stories.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import PdfLogEntry from '@/features/pdf-preview/components/pdf-log-entry'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { ruleIds } from '@/ide/human-readable-logs/HumanReadableLogsHints'
-import { ScopeDecorator } from './decorators/scope'
-import { useMeta } from './hooks/use-meta'
-import { FC, ReactNode } from 'react'
-import { EditorViewContext } from '@/features/ide-react/context/editor-view-context'
-import { EditorView } from '@codemirror/view'
-import { LogEntry } from '@/features/pdf-preview/util/types'
-
-const fakeSourceLocation = {
- file: 'file.tex',
- line: 12,
- column: 5,
-}
-
-const fakeLogEntry: LogEntry = {
- key: 'fake',
- ruleId: 'hint_misplaced_alignment_tab_character',
- message: 'Fake message',
- messageComponent: 'Fake message component',
- content: 'Fake content',
- type: 'Error: ',
- level: 'error',
- contentDetails: ['Fake detail 1', 'Fake detail 2'],
- file: 'fake.tex',
- line: 12,
- column: 5,
- raw: 'Fake raw',
-}
-
-const fakeArgs = {
- headerTitle: 'PDF Preview',
- formattedContent: 'This is a log entry',
- level: 'error' as const,
- extraInfoURL: 'https://example.com',
- showSourceLocationLink: true,
- rawContent: 'This is a raw log entry',
- contentDetails: ['detail 1', 'detail 2'],
- ruleId: 'hint_misplaced_alignment_tab_character' as const,
- sourceLocation: fakeSourceLocation,
- logEntry: fakeLogEntry,
- logType: 'Fake type',
-}
-
-const meta: Meta = {
- title: 'Editor / PDF Preview / Logs',
- component: PdfLogEntry,
- // @ts-ignore
- decorators: [ScopeDecorator],
- argTypes: {
- ruleId: { control: 'select', options: [...ruleIds, 'other'] },
- },
- args: fakeArgs,
-}
-
-export default meta
-
-type Story = StoryObj
-
-const MockEditorViewProvider: FC = ({ children }) => {
- const value = {
- view: new EditorView({
- doc: '\\begin{document',
- }),
- setView: () => {},
- }
-
- return (
-
- {children}
-
- )
-}
-
-const Provider: FC> = ({
- children,
-}) => {
- useMeta({ 'ol-showAiFeatures': true })
- return (
-
- {children}
-
- )
-}
-
-export const PdfLogEntryWithControls: Story = {
- render: args => (
-
-
-
- ),
-}
diff --git a/services/web/frontend/stories/pdf-preview-error-boundary.stories.jsx b/services/web/frontend/stories/pdf-preview-error-boundary.stories.jsx
deleted file mode 100644
index 6ea6b4b3ab..0000000000
--- a/services/web/frontend/stories/pdf-preview-error-boundary.stories.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import PdfPreviewErrorBoundaryFallback from '../js/features/pdf-preview/components/pdf-preview-error-boundary-fallback'
-import { ScopeDecorator } from './decorators/scope'
-
-export default {
- title: 'Editor / PDF Preview / Error Boundary',
- component: PdfPreviewErrorBoundaryFallback,
- decorators: [ScopeDecorator],
-}
-
-export const PreviewErrorBoundary = () => {
- return
-}
-
-export const PdfErrorBoundary = () => {
- return
-}
-
-export const LogsErrorBoundary = () => {
- return
-}
diff --git a/services/web/frontend/stories/pdf-preview-messages.stories.jsx b/services/web/frontend/stories/pdf-preview-messages.stories.jsx
deleted file mode 100644
index b25b9d121b..0000000000
--- a/services/web/frontend/stories/pdf-preview-messages.stories.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ScopeDecorator } from './decorators/scope'
-import { useLocalCompileContext } from '../js/shared/context/local-compile-context'
-import { useEffect } from 'react'
-import { PdfPreviewMessages } from '../js/features/pdf-preview/components/pdf-preview-messages'
-import CompileTimeWarningUpgradePrompt from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt'
-
-export default {
- title: 'Editor / PDF Preview / Messages',
- component: PdfPreviewMessages,
- decorators: [ScopeDecorator],
-}
-
-export const Dismissible = () => {
- const { setShowCompileTimeWarning } = useLocalCompileContext()
-
- useEffect(() => {
- setShowCompileTimeWarning(true)
- }, [setShowCompileTimeWarning])
-
- return (
-
- )
-}
diff --git a/services/web/frontend/stories/pdf-preview-messages.stories.tsx b/services/web/frontend/stories/pdf-preview-messages.stories.tsx
deleted file mode 100644
index d04c8ff67a..0000000000
--- a/services/web/frontend/stories/pdf-preview-messages.stories.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { ScopeDecorator } from './decorators/scope'
-import { PdfPreviewMessages } from '@/features/pdf-preview/components/pdf-preview-messages'
-import { CompileTimeWarningUpgradePromptInner } from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner'
-
-export default {
- title: 'Editor / PDF Preview / Messages',
- component: PdfPreviewMessages,
- decorators: [
- ScopeDecorator,
- (Story: any) => (
-
- ),
- ],
-}
-
-export const CompileTimeoutWarningActive = (args: any) => {
- return
-}
-CompileTimeoutWarningActive.argTypes = {
- handleDismissWarning: { action: 'dismiss warning' },
-}
diff --git a/services/web/frontend/stories/pdf-preview.stories.jsx b/services/web/frontend/stories/pdf-preview.stories.jsx
deleted file mode 100644
index bff6253911..0000000000
--- a/services/web/frontend/stories/pdf-preview.stories.jsx
+++ /dev/null
@@ -1,375 +0,0 @@
-import { useCallback, useMemo, useState } from 'react'
-import useFetchMock from './hooks/use-fetch-mock'
-import OLButton from '@/shared/components/ol/ol-button'
-import PdfPreviewPane from '../js/features/pdf-preview/components/pdf-preview-pane'
-import PdfPreview from '../js/features/pdf-preview/components/pdf-preview'
-import PdfFileList from '../js/features/pdf-preview/components/pdf-file-list'
-import { buildFileList } from '../js/features/pdf-preview/util/file-list'
-import PdfLogsViewer from '../js/features/pdf-preview/components/pdf-logs-viewer'
-import PdfPreviewError from '../js/features/pdf-preview/components/pdf-preview-error'
-import PdfPreviewHybridToolbar from '../js/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
-import { useDetachCompileContext as useCompileContext } from '../js/shared/context/detach-compile-context'
-import {
- dispatchDocChanged,
- mockBuildFile,
- mockClearCache,
- mockCompile,
- mockCompileError,
- mockCompileValidationIssues,
- mockEventTracking,
- outputFiles,
-} from './fixtures/compile'
-import { cloneDeep } from 'lodash'
-import { ScopeDecorator } from './decorators/scope'
-import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider'
-import {
- Dropdown,
- DropdownMenu,
-} from '@/shared/components/dropdown/dropdown-menu'
-
-export default {
- title: 'Editor / PDF Preview',
- component: PdfPreview,
- subcomponents: {
- PdfPreviewHybridToolbar,
- PdfFileList,
- PdfPreviewError,
- },
- decorators: [ScopeDecorator],
-}
-
-export const Interactive = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock)
- mockBuildFile(fetchMock)
- mockClearCache(fetchMock)
- })
-
- const Inner = () => {
- const context = useCompileContext()
-
- const { setHasLintingError } = context
-
- const toggleLintingError = useCallback(() => {
- setHasLintingError(value => !value)
- }, [setHasLintingError])
-
- const values = useMemo(() => {
- const entries = Object.entries(context).sort((a, b) => {
- return a[0].localeCompare(b[0])
- })
-
- const values = { boolean: [], other: [] }
-
- for (const entry of entries) {
- const type = typeof entry[1]
-
- if (type === 'boolean') {
- values.boolean.push(entry)
- } else if (type !== 'function') {
- values.other.push(entry)
- }
- }
-
- return values
- }, [context])
-
- return (
-
-
-
-
-
- {values.boolean.map(([key, value]) => {
- return (
-
- | {value ? '🟢' : '🔴'} |
- {key} |
-
- )
- })}
-
-
-
-
-
-
- trigger doc change
-
-
- toggle linting error
-
-
-
-
-
-
-
-
- {values.other.map(([key, value]) => {
- return (
-
- |
- {key}
- |
-
-
- {JSON.stringify(value, null, 2)}
-
- |
-
- )
- })}
-
-
-
-
-
- )
- }
-
- return (
-
- )
-}
-
-const compileStatuses = [
- 'autocompile-backoff',
- 'clear-cache',
- 'clsi-maintenance',
- 'compile-in-progress',
- 'exited',
- 'failure',
- 'generic',
- 'project-too-large',
- 'rate-limited',
- 'success',
- 'terminated',
- 'timedout',
- 'too-recently-compiled',
- 'unavailable',
- 'validation-problems',
- 'foo',
-]
-
-export const CompileError = () => {
- const [status, setStatus] = useState('success')
-
- useFetchMock(fetchMock => {
- mockCompileError(fetchMock, status, 0)
- mockBuildFile(fetchMock)
- })
-
- const Inner = () => {
- const { startCompile } = useCompileContext()
-
- const handleStatusChange = useCallback(
- event => {
- setStatus(event.target.value)
- window.setTimeout(() => {
- startCompile()
- }, 0)
- },
- [startCompile]
- )
-
- return (
-
-
-
- )
- }
-
- return (
- <>
-
-
- >
- )
-}
-
-const compileErrors = [
- 'autocompile-backoff',
- 'clear-cache',
- 'clsi-maintenance',
- 'compile-in-progress',
- 'exited',
- 'failure',
- 'generic',
- 'project-too-large',
- 'rate-limited',
- 'success',
- 'terminated',
- 'timedout',
- 'too-recently-compiled',
- 'unavailable',
- 'validation-problems',
- 'foo',
-]
-
-export const DisplayError = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock)
- })
-
- return (
-
- {compileErrors.map(error => (
-
- ))}
-
- )
-}
-
-export const HybridToolbar = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock, 500)
- mockBuildFile(fetchMock)
- mockEventTracking(fetchMock)
- })
-
- return (
-
- )
-}
-
-export const FileList = () => {
- const fileList = useMemo(() => {
- return buildFileList(cloneDeep(outputFiles), {})
- }, [])
-
- return (
-
-
-
-
-
- )
-}
-
-export const Logs = () => {
- useFetchMock(fetchMock => {
- mockCompileError(fetchMock, 400, 0)
- mockBuildFile(fetchMock)
- mockClearCache(fetchMock)
- })
-
- return (
-
- )
-}
-
-const validationProblems = {
- sizeCheck: {
- resources: [
- { path: 'foo/bar', kbSize: 76221 },
- { path: 'bar/baz', kbSize: 2342 },
- ],
- },
- mainFile: true,
- conflictedPaths: [
- {
- path: 'foo/bar',
- },
- {
- path: 'foo/baz',
- },
- ],
-}
-
-export const ValidationIssues = () => {
- useFetchMock(fetchMock => {
- mockCompileValidationIssues(fetchMock, validationProblems, 0)
- mockBuildFile(fetchMock)
- })
-
- return
-}
diff --git a/services/web/frontend/stories/pdf-viewer.stories.jsx b/services/web/frontend/stories/pdf-viewer.stories.jsx
deleted file mode 100644
index b527d02612..0000000000
--- a/services/web/frontend/stories/pdf-viewer.stories.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import useFetchMock from './hooks/use-fetch-mock'
-import PdfSynctexControls from '../js/features/pdf-preview/components/pdf-synctex-controls'
-import PdfViewer from '../js/features/pdf-preview/components/pdf-viewer'
-import {
- mockBuildFile,
- mockCompile,
- mockSynctex,
- mockValidPdf,
-} from './fixtures/compile'
-import { useEffect, Suspense } from 'react'
-import { ScopeDecorator } from './decorators/scope'
-import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider'
-
-export default {
- title: 'Editor / PDF Viewer',
- component: PdfViewer,
- decorators: [ScopeDecorator],
-}
-
-export const Interactive = () => {
- useFetchMock(fetchMock => {
- mockCompile(fetchMock)
- mockBuildFile(fetchMock)
- mockValidPdf(fetchMock)
- mockSynctex(fetchMock)
- })
-
- useEffect(() => {
- window.dispatchEvent(
- new CustomEvent(`cursor:editor:update`, {
- detail: { row: 10, position: 10 },
- })
- )
- }, [])
-
- return (
-
- )
-}
diff --git a/services/web/frontend/stories/project-list/add-affiliation.stories.tsx b/services/web/frontend/stories/project-list/add-affiliation.stories.tsx
deleted file mode 100644
index 75adc967e1..0000000000
--- a/services/web/frontend/stories/project-list/add-affiliation.stories.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import AddAffiliation from '../../js/features/project-list/components/add-affiliation'
-import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
-import getMeta from '@/utils/meta'
-
-export const Add = (args: any) => {
- Object.assign(getMeta('ol-ExposedSettings'), {
- isOverleaf: true,
- })
- window.metaAttributesCache.set('ol-userAffiliations', [])
- useFetchMock(fetchMock => {
- fetchMock.post(/\/api\/project/, {
- projects: projectsData,
- totalSize: projectsData.length,
- })
- })
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Affiliation',
- component: AddAffiliation,
-}
diff --git a/services/web/frontend/stories/project-list/color-picker.stories.tsx b/services/web/frontend/stories/project-list/color-picker.stories.tsx
deleted file mode 100644
index b7141c2098..0000000000
--- a/services/web/frontend/stories/project-list/color-picker.stories.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ColorPicker } from '../../js/features/project-list/components/color-picker/color-picker'
-import { ColorPickerProvider } from '../../js/features/project-list/context/color-picker-context'
-
-export const Select = (args: any) => {
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Color Picker',
- component: ColorPicker,
-}
diff --git a/services/web/frontend/stories/project-list/compile-and-download-project-pdf.stories.tsx b/services/web/frontend/stories/project-list/compile-and-download-project-pdf.stories.tsx
deleted file mode 100644
index 53fa31cdb0..0000000000
--- a/services/web/frontend/stories/project-list/compile-and-download-project-pdf.stories.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import ProjectListTable from '../../js/features/project-list/components/table/project-list-table'
-import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
-
-export const Successful = (args: any) => {
- window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
- useFetchMock(fetchMock => {
- fetchMock.post(/\/api\/project/, {
- projects: projectsData,
- totalSize: projectsData.length,
- })
- fetchMock.post(
- /\/compile/,
- {
- status: 'success',
- compileGroup: 'standard',
- clsiServerId: 'server-1',
- outputFiles: [{ path: 'output.pdf', build: '123-321' }],
- },
- {
- delay: 1_000,
- }
- )
- })
-
- return (
-
-
-
- )
-}
-
-export const Failure = (args: any) => {
- window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
- useFetchMock(fetchMock => {
- fetchMock.post(/\/api\/project/, {
- projects: projectsData,
- totalSize: projectsData.length,
- })
- fetchMock.post(
- /\/compile/,
- { status: 'failure', outputFiles: [] },
- { delay: 1_000 }
- )
- })
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / PDF download',
- component: ProjectListTable,
- decorators: [
- (Story: any) => (
-
-
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/project-list/current-plan-widget.stories.tsx b/services/web/frontend/stories/project-list/current-plan-widget.stories.tsx
deleted file mode 100644
index be9a3b1cb1..0000000000
--- a/services/web/frontend/stories/project-list/current-plan-widget.stories.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import CurrentPlanWidget from '../../js/features/project-list/components/current-plan-widget/current-plan-widget'
-
-export const FreePlan = (args: any) => {
- window.metaAttributesCache.set('ol-usersBestSubscription', {
- type: 'free',
- })
-
- return
-}
-
-export const PaidPlanTrialLastDay = (args: any) => {
- window.metaAttributesCache.set('ol-usersBestSubscription', {
- type: 'individual',
- remainingTrialDays: 1,
- plan: {
- name: 'Individual',
- },
- subscription: {
- name: 'Example Name',
- },
- })
-
- return
-}
-
-export const PaidPlanRemainingDays = (args: any) => {
- window.metaAttributesCache.set('ol-usersBestSubscription', {
- type: 'individual',
- remainingTrialDays: 5,
- plan: {
- name: 'Individual',
- },
- subscription: {
- name: 'Example Name',
- },
- })
-
- return
-}
-
-export const PaidPlanActive = (args: any) => {
- window.metaAttributesCache.set('ol-usersBestSubscription', {
- type: 'individual',
- plan: {
- name: 'Individual',
- },
- subscription: {
- name: 'Example Name',
- },
- })
-
- return
-}
-
-export const PausedPlan = (args: any) => {
- window.metaAttributesCache.set('ol-usersBestSubscription', {
- type: 'individual',
- plan: {
- name: 'Individual',
- },
- subscription: {
- name: 'Example Name',
- recurlyStatus: {
- state: 'paused',
- },
- },
- })
-
- return
-}
-
-export default {
- title: 'Project List / Current Plan Widget',
- component: CurrentPlanWidget,
-}
diff --git a/services/web/frontend/stories/project-list/helpers/emails.ts b/services/web/frontend/stories/project-list/helpers/emails.ts
deleted file mode 100644
index 4fadd3ca11..0000000000
--- a/services/web/frontend/stories/project-list/helpers/emails.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { merge, cloneDeep } from 'lodash'
-import { type FetchMock } from 'fetch-mock'
-import { UserEmailData } from '../../../../types/user-email'
-import {
- Institution,
- Notification,
-} from '../../../../types/project/dashboard/notification'
-import { DeepPartial, DeepReadonly } from '../../../../types/utils'
-import { Project } from '../../../../types/project/dashboard/api'
-import getMeta from '@/utils/meta'
-
-const MOCK_DELAY = 1000
-
-const fakeInstitutionData = {
- email: 'email@example.com',
- institutionEmail: 'institution@example.com',
- institutionId: 123,
- institutionName: 'Abc Institution',
- requestedEmail: 'requested@example.com',
-} as DeepReadonly
-
-export const fakeReconfirmationUsersData = {
- affiliation: {
- institution: {
- ssoEnabled: false,
- ssoBeta: false,
- name: 'Abc Institution',
- },
- },
- samlProviderId: 'Saml Provider',
- email: 'reconfirmation-email@overleaf.com',
- default: false,
-} as DeepReadonly
-
-export function defaultSetupMocks(fetchMock: FetchMock) {
- // at least one project is required to show some notifications
- const projects = [{}] as Project[]
- fetchMock.post(/\/api\/project/, {
- status: 200,
- body: {
- projects,
- totalSize: projects.length,
- },
- })
-}
-
-export function setDefaultMeta() {
- Object.assign(getMeta('ol-ExposedSettings'), {
- emailConfirmationDisabled: false,
- samlInitPath: '/fakeSaml',
- appName: 'Overleaf',
- })
- window.metaAttributesCache.set('ol-notificationsInstitution', [])
- window.metaAttributesCache.set('ol-userEmails', [])
-}
-
-export function errorsMocks(fetchMock: FetchMock) {
- defaultSetupMocks(fetchMock)
- fetchMock.post(/\/user\/emails\/*/, 500, { delay: MOCK_DELAY })
- fetchMock.post(
- /\/project\/[A-Za-z0-9]+\/invite\/token\/[A-Za-z0-9]+\/accept/,
- 500,
- { delay: MOCK_DELAY }
- )
-}
-
-export function setInstitutionMeta(institutionData: Partial) {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-notificationsInstitution', [
- merge(cloneDeep(fakeInstitutionData), institutionData),
- ])
-}
-
-export function institutionSetupMocks(fetchMock: FetchMock) {
- defaultSetupMocks(fetchMock)
- fetchMock.delete(/\/notifications\/*/, 200, { delay: MOCK_DELAY })
-}
-
-export function setCommonMeta(notificationData: DeepPartial) {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-notifications', [notificationData])
-}
-
-export function commonSetupMocks(fetchMock: FetchMock) {
- defaultSetupMocks(fetchMock)
- fetchMock.post(
- /\/project\/[A-Za-z0-9]+\/invite\/token\/[A-Za-z0-9]+\/accept/,
- 200,
- { delay: MOCK_DELAY }
- )
-}
-
-export function setReconfirmationMeta() {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
-}
-
-export function reconfirmationSetupMocks(fetchMock: FetchMock) {
- defaultSetupMocks(fetchMock)
-}
-
-export function setReconfirmAffiliationMeta() {
- setDefaultMeta()
- window.metaAttributesCache.set(
- 'ol-reconfirmedViaSAML',
- fakeReconfirmationUsersData.samlProviderId
- )
-}
-
-export function reconfirmAffiliationSetupMocks(fetchMock: FetchMock) {
- defaultSetupMocks(fetchMock)
- fetchMock.post(/\/api\/project/, {
- status: 200,
- body: {
- projects: [{}],
- totalSize: 0,
- },
- })
-}
diff --git a/services/web/frontend/stories/project-list/inr-banner.stories.tsx b/services/web/frontend/stories/project-list/inr-banner.stories.tsx
deleted file mode 100644
index a59cf12492..0000000000
--- a/services/web/frontend/stories/project-list/inr-banner.stories.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import INRBanner from '@/features/project-list/components/notifications/ads/inr-banner'
-
-export const Default = () => {
- return
-}
-
-export default {
- title: 'Project List / INR Banner',
- component: INRBanner,
-}
diff --git a/services/web/frontend/stories/project-list/new-project-button.stories.tsx b/services/web/frontend/stories/project-list/new-project-button.stories.tsx
deleted file mode 100644
index fcb19deb4c..0000000000
--- a/services/web/frontend/stories/project-list/new-project-button.stories.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import NewProjectButton from '@/features/project-list/components/new-project-button'
-import { ProjectListProvider } from '@/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import getMeta from '@/utils/meta'
-import { SplitTestProvider } from '@/shared/context/split-test-context'
-
-const templateLinks = [
- {
- name: 'Journal articles',
- url: '/gallery/tagged/academic-journal',
- },
- {
- name: 'Books',
- url: '/gallery/tagged/book',
- },
- {
- name: 'Formal letters',
- url: '/gallery/tagged/formal-letter',
- },
- {
- name: 'Assignments',
- url: '/gallery/tagged/homework',
- },
- {
- name: 'Posters',
- url: '/gallery/tagged/poster',
- },
- {
- name: 'Presentations',
- url: '/gallery/tagged/presentation',
- },
- {
- name: 'Reports',
- url: '/gallery/tagged/report',
- },
- {
- name: 'CVs and résumés',
- url: '/gallery/tagged/cv',
- },
- {
- name: 'Theses',
- url: '/gallery/tagged/thesis',
- },
- {
- name: 'view_all',
- url: '/latex/templates',
- },
-]
-
-export const Success = () => {
- Object.assign(getMeta('ol-ExposedSettings'), {
- templateLinks,
- })
-
- useFetchMock(fetchMock => {
- fetchMock.post(
- 'express:/project/new',
- {
- status: 200,
- body: {
- project_id: '123',
- },
- },
- { delay: 250 }
- )
- })
-
- return (
-
-
-
-
-
- )
-}
-
-export const Error = () => {
- Object.assign(getMeta('ol-ExposedSettings'), {
- templateLinks,
- })
-
- useFetchMock(fetchMock => {
- fetchMock.post(
- 'express:/project/new',
- {
- status: 400,
- body: {
- message: 'Something went horribly wrong!',
- },
- },
- { delay: 250 }
- )
- })
-
- return (
-
-
-
-
-
- )
-}
-
-export default {
- title: 'Project List / New project Button',
- component: NewProjectButton,
-}
diff --git a/services/web/frontend/stories/project-list/notifications.stories.tsx b/services/web/frontend/stories/project-list/notifications.stories.tsx
deleted file mode 100644
index ea00f84681..0000000000
--- a/services/web/frontend/stories/project-list/notifications.stories.tsx
+++ /dev/null
@@ -1,355 +0,0 @@
-import UserNotifications from '../../js/features/project-list/components/notifications/user-notifications'
-import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import {
- commonSetupMocks,
- errorsMocks,
- fakeReconfirmationUsersData,
- institutionSetupMocks,
- reconfirmAffiliationSetupMocks,
- reconfirmationSetupMocks,
- setCommonMeta,
- setInstitutionMeta,
- setReconfirmAffiliationMeta,
- setReconfirmationMeta,
-} from './helpers/emails'
-import { useMeta } from '../hooks/use-meta'
-import { SplitTestProvider } from '@/shared/context/split-test-context'
-import React, { ComponentType } from 'react'
-
-export const ProjectInvite = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- templateKey: 'notification_project_invite',
- messageOpts: {
- projectId: '123',
- projectName: 'Abc Project',
- userName: 'fakeUser',
- token: 'abcdef',
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const ProjectInviteNetworkError = (args: any) => {
- useFetchMock(errorsMocks)
- setCommonMeta({
- templateKey: 'notification_project_invite',
- messageOpts: {
- projectId: '123',
- projectName: 'Abc Project',
- userName: 'fakeUser',
- token: 'abcdef',
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const Wfh2020UpgradeOffer = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'wfh_2020_upgrade_offer',
- })
-
- return (
-
-
-
- )
-}
-
-export const IPMatchedAffiliationSsoEnabled = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_ip_matched_affiliation',
- messageOpts: {
- university_name: 'Abc University',
- institutionId: '456',
- ssoEnabled: true,
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const IPMatchedAffiliationSsoDisabled = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_ip_matched_affiliation',
- messageOpts: {
- university_name: 'Abc University',
- institutionId: '456',
- ssoEnabled: false,
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const TpdsFileLimit = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_tpds_file_limit',
- messageOpts: {
- projectName: 'Abc Project',
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const DropBoxDuplicateProjectNames = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_dropbox_duplicate_project_names',
- messageOpts: {
- projectName: 'Abc Project',
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const DropBoxUnlinkedDueToLapsedReconfirmation = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
- })
-
- useMeta({
- 'ol-user': { features: {} },
- })
-
- return (
-
-
-
- )
-}
-
-export const NotificationGroupInvitation = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_group_invitation',
- messageOpts: {
- inviterName: 'John Doe',
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const NotificationGroupInvitationCancelSubscription = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({
- _id: 1,
- templateKey: 'notification_group_invitation',
- messageOpts: {
- inviterName: 'John Doe',
- },
- })
-
- window.metaAttributesCache.set('ol-hasIndividualPaidSubscription', true)
-
- return (
-
-
-
- )
-}
-
-export const NonSpecificMessage = (args: any) => {
- useFetchMock(commonSetupMocks)
- setCommonMeta({ _id: 1, html: 'Non specific message' })
-
- return (
-
-
-
- )
-}
-
-export const InstitutionSsoAvailable = (args: any) => {
- useFetchMock(institutionSetupMocks)
- setInstitutionMeta({
- _id: 1,
- templateKey: 'notification_institution_sso_available',
- })
-
- return (
-
-
-
- )
-}
-
-export const InstitutionSsoLinked = (args: any) => {
- useFetchMock(institutionSetupMocks)
- setInstitutionMeta({
- _id: 1,
- templateKey: 'notification_institution_sso_linked',
- })
-
- return (
-
-
-
- )
-}
-
-export const InstitutionSsoNonCanonical = (args: any) => {
- useFetchMock(institutionSetupMocks)
- setInstitutionMeta({
- _id: 1,
- templateKey: 'notification_institution_sso_non_canonical',
- })
-
- return (
-
-
-
- )
-}
-
-export const InstitutionSsoAlreadyRegistered = (args: any) => {
- useFetchMock(institutionSetupMocks)
- setInstitutionMeta({
- _id: 1,
- templateKey: 'notification_institution_sso_already_registered',
- })
-
- return (
-
-
-
- )
-}
-
-export const InstitutionSsoError = (args: any) => {
- useFetchMock(institutionSetupMocks)
- setInstitutionMeta({
- templateKey: 'notification_institution_sso_error',
- error: {
- message: 'message',
- translatedMessage: 'Translated Message',
- tryAgain: true,
- },
- })
-
- return (
-
-
-
- )
-}
-
-export const ResendConfirmationEmail = (args: any) => {
- useFetchMock(reconfirmationSetupMocks)
- setReconfirmationMeta()
-
- return (
-
-
-
- )
-}
-
-export const ResendConfirmationEmailNetworkError = (args: any) => {
- useFetchMock(errorsMocks)
- setReconfirmationMeta()
-
- return (
-
-
-
- )
-}
-
-export const ReconfirmAffiliation = (args: any) => {
- useFetchMock(reconfirmAffiliationSetupMocks)
- setReconfirmAffiliationMeta()
- window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
- fakeReconfirmationUsersData,
- ])
-
- return (
-
-
-
- )
-}
-
-export const ReconfirmAffiliationNetworkError = (args: any) => {
- useFetchMock(errorsMocks)
- setReconfirmAffiliationMeta()
- window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
- fakeReconfirmationUsersData,
- ])
-
- return (
-
-
-
- )
-}
-
-export const ReconfirmedAffiliationSuccess = (args: any) => {
- useFetchMock(reconfirmAffiliationSetupMocks)
- setReconfirmAffiliationMeta()
- window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Notifications',
- component: UserNotifications,
- decorators: [
- (Story: ComponentType) => (
-
-
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx b/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx
deleted file mode 100644
index 5bbcbcf8fb..0000000000
--- a/services/web/frontend/stories/project-list/notifications/confirm-email.stories.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { SplitTestProvider } from '@/shared/context/split-test-context'
-import UserNotifications from '../../../js/features/project-list/components/notifications/user-notifications'
-import { ProjectListProvider } from '../../../js/features/project-list/context/project-list-context'
-import { useMeta } from '../../hooks/use-meta'
-import useFetchMock from '../../hooks/use-fetch-mock'
-import ConfirmEmailNotification from '@/features/project-list/components/notifications/groups/confirm-email'
-
-export const ConfirmEmail = (args: any) => {
- useMeta({
- 'ol-userEmails': [
- {
- email: 'erika.mustermann+unconfirmed-primary@example.com',
- default: true,
- },
- { email: 'erika.mustermann+unconfirmed@example.com' },
- {
- email: 'erika.mustermann+untrusted@example.com',
- lastConfirmedAt: '2019-01-01',
- confirmedAt: '2019-01-01',
- },
- {
- email: 'erika.mustermann+mit@example.com',
- affiliation: {
- institution: {
- id: 123,
- name: 'Massachusetts Institute of Technology',
- confirmed: true,
- commonsAccount: true,
- },
- },
- },
- ],
- 'ol-user': { signUpDate: '2021-01-01' },
- 'ol-usersBestSubscription': { type: 'free' },
- 'ol-prefetchedProjectsBlob': { totalSize: 20 },
- })
- useFetchMock(fetchMock => {
- fetchMock.post('/user/emails/send-confirmation-code', args.statusCode, {
- delay: 500,
- })
- fetchMock.post('/user/emails/confirm-code', args.statusCode, {
- delay: 500,
- })
- fetchMock.post('/user/emails/resend-confirmation-code', args.statusCode, {
- delay: 500,
- })
- })
- return (
-
-
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Notifications',
- component: ConfirmEmailNotification,
- args: {
- statusCode: 200,
- },
- argTypes: {
- statusCode: { type: 'select', options: [200, 400, 429] },
- },
-}
diff --git a/services/web/frontend/stories/project-list/project-list.stories.tsx b/services/web/frontend/stories/project-list/project-list.stories.tsx
deleted file mode 100644
index af058548cb..0000000000
--- a/services/web/frontend/stories/project-list/project-list.stories.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import ProjectListTable from '../../js/features/project-list/components/table/project-list-table'
-import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import {
- copyableProject,
- projectsData,
-} from '../../../test/frontend/features/project-list/fixtures/projects-data'
-import { useMeta } from '../hooks/use-meta'
-import { tags } from '../../../test/frontend/features/project-list/fixtures/tags-data'
-import { v4 as uuid } from 'uuid'
-
-const MOCK_DELAY = 500
-
-export const Interactive = (args: any) => {
- window.metaAttributesCache.set('ol-user_id', '624333f147cfd8002622a1d3')
- useFetchMock(fetchMock => {
- fetchMock.post(
- /\/api\/project/,
- { projects: projectsData, totalSize: projectsData.length },
- { delay: MOCK_DELAY }
- )
- fetchMock.post(
- 'express:/project/:projectId/clone',
- () => ({
- project_id: uuid(),
- name: copyableProject.name,
- lastUpdated: new Date().toISOString(),
- owner: {
- _id: copyableProject.owner?.id,
- email: copyableProject.owner?.id,
- first_name: copyableProject.owner?.firstName,
- last_name: copyableProject.owner?.lastName,
- },
- }),
- { delay: MOCK_DELAY }
- )
- })
-
- useMeta({
- 'ol-tags': tags,
- })
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Project Table',
- component: ProjectListTable,
- decorators: [
- (Story: any) => (
-
-
-
- ),
- ],
-}
diff --git a/services/web/frontend/stories/project-list/project-search.stories.tsx b/services/web/frontend/stories/project-list/project-search.stories.tsx
deleted file mode 100644
index cf39be2285..0000000000
--- a/services/web/frontend/stories/project-list/project-search.stories.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import SearchForm from '../../js/features/project-list/components/search-form'
-import { ProjectListProvider } from '../../js/features/project-list/context/project-list-context'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { projectsData } from '../../../test/frontend/features/project-list/fixtures/projects-data'
-
-export const Search = (args: any) => {
- useFetchMock(fetchMock => {
- fetchMock.post(/\/api\/project/, {
- projects: projectsData,
- totalSize: projectsData.length,
- })
- })
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Project List / Project Search',
- component: SearchForm,
- args: {
- inputValue: '',
- },
-}
diff --git a/services/web/frontend/stories/project-list/survey-widget-ds-nav.stories.tsx b/services/web/frontend/stories/project-list/survey-widget-ds-nav.stories.tsx
deleted file mode 100644
index 8e9e0d1a40..0000000000
--- a/services/web/frontend/stories/project-list/survey-widget-ds-nav.stories.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
-
-export const Survey = (args: any) => {
- localStorage.clear()
- window.metaAttributesCache.set('ol-survey', {
- name: 'my-survey',
- title: 'To help shape the future of Overleaf',
- text: 'Click here!',
- cta: 'Let’s go!',
- url: 'https://example.com/my-survey',
- })
-
- return
-}
-
-export const UndefinedSurvey = (args: any) => {
- localStorage.clear()
-
- return
-}
-
-export const EmptySurvey = (args: any) => {
- localStorage.clear()
- window.metaAttributesCache.set('ol-survey', {})
-
- return
-}
-
-export default {
- title: 'Project List / Survey Widget',
- component: SurveyWidgetDsNav,
-}
diff --git a/services/web/frontend/stories/project-list/system-messages.stories.tsx b/services/web/frontend/stories/project-list/system-messages.stories.tsx
deleted file mode 100644
index d97ca4537c..0000000000
--- a/services/web/frontend/stories/project-list/system-messages.stories.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import SystemMessages from '@/shared/components/system-messages'
-import useFetchMock from '../hooks/use-fetch-mock'
-import { type FetchMock } from 'fetch-mock'
-
-export const SystemMessage = (args: any) => {
- useFetchMock((fetchMock: FetchMock) => {
- fetchMock.get(/\/system\/messages/, [
- {
- _id: 1,
- content: `
- Closing this message will mark it as hidden.
- Remove it from the local storage to make it appear again.
- `,
- },
- {
- _id: 'protected',
- content: 'A protected message content - cannot be closed',
- },
- ])
- })
-
- return
-}
-
-export const TranslationMessage = (args: any) => {
- useFetchMock((fetchMock: FetchMock) => {
- fetchMock.get(/\/system\/messages/, [])
- })
-
- window.metaAttributesCache.set('ol-suggestedLanguage', {
- url: '/dev/null',
- lngName: 'German',
- imgUrl: 'https://flagcdn.com/w40/de.png',
- })
-
- return
-}
-
-export default {
- title: 'Project List / System Messages',
- component: SystemMessages,
-}
diff --git a/services/web/frontend/stories/settings/account-info.stories.jsx b/services/web/frontend/stories/settings/account-info.stories.jsx
deleted file mode 100644
index 9375b8dacf..0000000000
--- a/services/web/frontend/stories/settings/account-info.stories.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import AccountInfoSection from '../../js/features/settings/components/account-info-section'
-import { setDefaultMeta, defaultSetupMocks } from './helpers/account-info'
-import { UserProvider } from '../../js/shared/context/user-context'
-import getMeta from '@/utils/meta'
-
-export const Success = args => {
- setDefaultMeta()
- useFetchMock(defaultSetupMocks)
-
- return (
-
-
-
- )
-}
-
-export const ReadOnly = args => {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', true)
- window.metaAttributesCache.set('ol-shouldAllowEditingDetails', false)
-
- return (
-
-
-
- )
-}
-
-export const NoEmailInput = args => {
- setDefaultMeta()
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasAffiliationsFeature: true,
- })
- useFetchMock(defaultSetupMocks)
-
- return (
-
-
-
- )
-}
-
-export const Error = args => {
- setDefaultMeta()
- useFetchMock(fetchMock => fetchMock.post(/\/user\/settings/, 500))
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Account Settings / Account Info',
- component: AccountInfoSection,
-}
diff --git a/services/web/frontend/stories/settings/add-email-input.stories.tsx b/services/web/frontend/stories/settings/add-email-input.stories.tsx
deleted file mode 100644
index e62e370f03..0000000000
--- a/services/web/frontend/stories/settings/add-email-input.stories.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import useFetchMock from './../hooks/use-fetch-mock'
-import Input from '../../js/features/settings/components/emails/add-email/input'
-
-export const EmailInput = (args: any) => {
- useFetchMock(fetchMock =>
- fetchMock.get(/\/institutions\/domains/, [
- {
- hostname: 'autocomplete.edu',
- university: { id: 123, name: 'Auto Complete University' },
- },
- ])
- )
- return (
- <>
-
-
-
- Use autocomplete.edu as domain to trigger an autocomplete
-
- >
- )
-}
-
-export default {
- title: 'Account Settings / Emails and Affiliations',
- component: Input,
- argTypes: {
- onChange: { action: 'change' },
- },
-}
diff --git a/services/web/frontend/stories/settings/beta-program.stories.jsx b/services/web/frontend/stories/settings/beta-program.stories.jsx
deleted file mode 100644
index 38f42cbd66..0000000000
--- a/services/web/frontend/stories/settings/beta-program.stories.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import BetaProgramSection from '../../js/features/settings/components/beta-program-section'
-import { UserProvider } from '../../js/shared/context/user-context'
-
-export const SectionNotEnrolled = args => {
- window.metaAttributesCache.set('ol-user', { betaProgram: false })
-
- return (
-
-
-
- )
-}
-
-export const SectionEnrolled = args => {
- window.metaAttributesCache.set('ol-user', { betaProgram: true })
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Account Settings / Beta program',
- component: BetaProgramSection,
-}
diff --git a/services/web/frontend/stories/settings/emails.stories.jsx b/services/web/frontend/stories/settings/emails.stories.jsx
deleted file mode 100644
index bccc1f0014..0000000000
--- a/services/web/frontend/stories/settings/emails.stories.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import EmailsSection from '../../js/features/settings/components/emails-section'
-import useFetchMock from './../hooks/use-fetch-mock'
-import {
- setDefaultMeta,
- setReconfirmationMeta,
- defaultSetupMocks,
- reconfirmationSetupMocks,
- errorsMocks,
- emailLimitSetupMocks,
-} from './helpers/emails'
-
-export const EmailsList = args => {
- useFetchMock(defaultSetupMocks)
- setDefaultMeta()
-
- return
-}
-
-export const EmailLimitList = args => {
- useFetchMock(emailLimitSetupMocks)
- setDefaultMeta()
-
- return
-}
-
-export const ReconfirmationEmailsList = args => {
- useFetchMock(reconfirmationSetupMocks)
- setReconfirmationMeta()
-
- return
-}
-
-export const NetworkErrors = args => {
- useFetchMock(errorsMocks)
- setDefaultMeta()
-
- return
-}
-
-export default {
- title: 'Account Settings / Emails and Affiliations',
- component: EmailsSection,
-}
diff --git a/services/web/frontend/stories/settings/helpers/account-info.js b/services/web/frontend/stories/settings/helpers/account-info.js
deleted file mode 100644
index 47a65be772..0000000000
--- a/services/web/frontend/stories/settings/helpers/account-info.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import getMeta from '@/utils/meta'
-
-const MOCK_DELAY = 1000
-
-export function defaultSetupMocks(fetchMock) {
- fetchMock.post(/\/user\/settings/, 200, {
- delay: MOCK_DELAY,
- })
-}
-
-export function setDefaultMeta() {
- window.metaAttributesCache.set('ol-user', {
- ...window.metaAttributesCache.get('ol-user'),
- email: 'sherlock@holmes.co.uk',
- first_name: 'Sherlock',
- last_name: 'Holmes',
- })
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasAffiliationsFeature: false,
- })
- window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false)
- window.metaAttributesCache.set('ol-shouldAllowEditingDetails', true)
-}
diff --git a/services/web/frontend/stories/settings/helpers/emails.js b/services/web/frontend/stories/settings/helpers/emails.js
deleted file mode 100644
index 5e7fe284e9..0000000000
--- a/services/web/frontend/stories/settings/helpers/emails.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import getMeta from '@/utils/meta'
-
-const MOCK_DELAY = 1000
-
-const fakeUsersData = [
- {
- affiliation: {
- institution: {
- confirmed: true,
- id: 1,
- name: 'Overleaf',
- },
- licence: 'pro_plus',
- },
- confirmedAt: '2022-03-09T10:59:44.139Z',
- email: 'foo@overleaf.com',
- default: true,
- emailHasInstitutionLicence: true,
- },
- {
- confirmedAt: '2022-03-10T10:59:44.139Z',
- email: 'bar@overleaf.com',
- default: false,
- },
- {
- affiliation: {
- institution: {
- confirmed: true,
- id: 2,
- name: 'Overleaf',
- },
- licence: 'pro_plus',
- department: 'Art & Art History',
- role: 'Postdoc',
- },
- email: 'baz@overleaf.com',
- default: false,
- },
- {
- email: 'qux@overleaf.com',
- default: false,
- },
-]
-const fakeReconfirmationUsersData = [
- {
- affiliation: {
- institution: {
- confirmed: true,
- isUniversity: true,
- id: 4,
- name: 'Reconfirmable Email Highlighted',
- },
- licence: 'pro_plus',
- inReconfirmNotificationPeriod: true,
- },
- email: 'reconfirmation-highlighted@overleaf.com',
- confirmedAt: '2022-03-09T10:59:44.139Z',
- default: false,
- },
- {
- affiliation: {
- institution: {
- confirmed: true,
- isUniversity: true,
- id: 4,
- name: 'Reconfirmable Emails Primary',
- },
- licence: 'pro_plus',
- inReconfirmNotificationPeriod: true,
- },
- email: 'reconfirmation-nonsso@overleaf.com',
- confirmedAt: '2022-03-09T10:59:44.139Z',
- default: true,
- },
- {
- affiliation: {
- institution: {
- confirmed: true,
- ssoEnabled: true,
- isUniversity: true,
- id: 3,
- name: 'Reconfirmable SSO',
- },
- licence: 'pro_plus',
- inReconfirmNotificationPeriod: true,
- },
- email: 'reconfirmation-sso@overleaf.com',
- confirmedAt: '2022-03-09T10:59:44.139Z',
- samlProviderId: 'reconfirmation-sso-provider-id',
- default: false,
- },
- {
- affiliation: {
- institution: {
- confirmed: true,
- isUniversity: true,
- ssoEnabled: true,
- id: 5,
- name: 'Reconfirmed SSO',
- },
- licence: 'pro_plus',
- },
- confirmedAt: '2022-03-09T10:59:44.139Z',
- email: 'sso@overleaf.com',
- samlProviderId: 'sso-reconfirmed-provider-id',
- default: false,
- },
-]
-
-const fakeInstitutions = [
- {
- id: 9326,
- name: 'Unknown',
- country_code: 'al',
- departments: ['New department'],
- },
-]
-const fakeInstitution = {
- id: 123,
- name: 'test',
- country_code: 'de',
- departments: [],
- team_id: null,
-}
-const bazFakeInstitution = {
- id: 2,
- name: 'Baz',
- country_code: 'de',
- departments: ['Custom department 1', 'Custom department 2'],
- team_id: null,
-}
-
-const fakeInstitutionDomain1 = [
- {
- university: {
- id: 1234,
- ssoEnabled: true,
- name: 'Auto Complete University',
- },
- hostname: 'autocomplete.edu',
- confirmed: true,
- },
-]
-
-const fakeInstitutionDomain2 = [
- {
- university: {
- id: 5678,
- ssoEnabled: false,
- name: 'Fake Auto Complete University',
- },
- hostname: 'fake-autocomplete.edu',
- confirmed: true,
- },
-]
-
-export function defaultSetupMocks(fetchMock) {
- fetchMock
- .get(/\/user\/emails/, fakeUsersData, { delay: MOCK_DELAY })
- .get(/\/institutions\/list\/2/, bazFakeInstitution, { delay: MOCK_DELAY })
- .get(/\/institutions\/list\/\d+/, fakeInstitution, { delay: MOCK_DELAY })
- .get(/\/institutions\/list\?country_code=.*/, fakeInstitutions, {
- delay: MOCK_DELAY,
- })
- .get(/\/institutions\/domains\?hostname=a/, fakeInstitutionDomain1)
- .get(/\/institutions\/domains\?hostname=f/, fakeInstitutionDomain2)
- .get(/\/institutions\/domains/, [])
- .post(/\/user\/emails\/*/, 200, {
- delay: MOCK_DELAY,
- })
-}
-
-export function reconfirmationSetupMocks(fetchMock) {
- defaultSetupMocks(fetchMock)
- fetchMock.get(/\/user\/emails/, fakeReconfirmationUsersData, {
- delay: MOCK_DELAY,
- })
-}
-
-export function emailLimitSetupMocks(fetchMock) {
- const userData = []
- for (let i = 0; i < 10; i++) {
- userData.push({ email: `example${i}@overleaf.com` })
- }
- defaultSetupMocks(fetchMock)
- fetchMock.get(/\/user\/emails/, userData, {
- delay: MOCK_DELAY,
- })
-}
-
-export function errorsMocks(fetchMock) {
- fetchMock
- .get(/\/user\/emails/, fakeUsersData, { delay: MOCK_DELAY })
- .post(/\/user\/emails\/*/, 500)
-}
-
-export function setDefaultMeta() {
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasAffiliationsFeature: true,
- hasSamlFeature: true,
- samlInitPath: 'saml/init',
- })
-}
-
-export function setReconfirmationMeta() {
- setDefaultMeta()
- window.metaAttributesCache.set(
- 'ol-reconfirmationRemoveEmail',
- 'reconfirmation-highlighted@overleaf.com'
- )
- window.metaAttributesCache.set(
- 'ol-reconfirmedViaSAML',
- 'sso-reconfirmed-provider-id'
- )
-}
diff --git a/services/web/frontend/stories/settings/helpers/leave.js b/services/web/frontend/stories/settings/helpers/leave.js
deleted file mode 100644
index 8b88b635ab..0000000000
--- a/services/web/frontend/stories/settings/helpers/leave.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import getMeta from '@/utils/meta'
-
-const MOCK_DELAY = 1000
-
-export function defaultSetupMocks(fetchMock) {
- fetchMock.post(/\/user\/delete/, 200, {
- delay: MOCK_DELAY,
- })
-}
-
-export function setDefaultMeta() {
- window.metaAttributesCache.set('ol-usersEmail', 'user@primary.com')
- Object.assign(getMeta('ol-ExposedSettings'), {
- isOverleaf: true,
- })
- window.metaAttributesCache.set('ol-hasPassword', true)
-}
diff --git a/services/web/frontend/stories/settings/helpers/linking.js b/services/web/frontend/stories/settings/helpers/linking.js
deleted file mode 100644
index de2733393f..0000000000
--- a/services/web/frontend/stories/settings/helpers/linking.js
+++ /dev/null
@@ -1,78 +0,0 @@
-const MOCK_DELAY = 1000
-
-export function defaultSetupMocks(fetchMock) {
- fetchMock
- .post('/user/oauth-unlink', 200, { delay: MOCK_DELAY })
- .get(
- 'express:/user/tpds/queues',
- { tpdsToWeb: 0, webToTpds: 0 },
- { delay: MOCK_DELAY }
- )
-}
-
-export function setDefaultMeta() {
- window.metaAttributesCache.set('ol-user', {
- ...window.metaAttributesCache.get('ol-user'),
- features: { github: true, dropbox: true, mendeley: false, zotero: false },
- refProviders: {
- mendeley: true,
- zotero: true,
- },
- })
- window.metaAttributesCache.set('ol-github', { enabled: false })
- window.metaAttributesCache.set('ol-dropbox', { registered: true })
- window.metaAttributesCache.set('ol-thirdPartyIds', {
- collabratec: 'collabratec-id',
- google: 'google-id',
- })
-
- window.metaAttributesCache.set('ol-oauthProviders', {
- collabratec: {
- descriptionKey: 'linked_collabratec_description',
- descriptionOptions: { appName: 'Overleaf' },
- name: 'IEEE Collabratec®',
- hideWhenNotLinked: true,
- linkPath: '/collabratec/auth/link',
- },
- google: {
- descriptionKey: 'login_with_service',
- descriptionOptions: { service: 'Google' },
- name: 'Google',
- linkPath: '/auth/google',
- },
- orcid: {
- descriptionKey: 'oauth_orcid_description',
- descriptionOptions: {
- link: '/blog/434',
- appName: 'Overleaf',
- },
- name: 'ORCID',
- linkPath: '/auth/orcid',
- },
- })
- window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
- window.metaAttributesCache.delete('ol-ssoErrorMessage')
-}
-
-export function setPersonalAccessTokensMeta() {
- function generateToken(_id) {
- const oneYearFromNow = new Date()
- oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1)
-
- const tokenHasBeenUsed = Math.random() > 0.5
-
- return {
- _id,
- accessTokenPartial: 'olp_abc' + _id,
- createdAt: new Date(),
- accessTokenExpiresAt: oneYearFromNow,
- lastUsedAt: tokenHasBeenUsed ? new Date() : undefined,
- }
- }
- const tokens = []
- for (let i = 0; i < 6; i++) {
- tokens.push(generateToken(i))
- }
-
- window.metaAttributesCache.set('ol-personalAccessTokens', tokens)
-}
diff --git a/services/web/frontend/stories/settings/helpers/password.js b/services/web/frontend/stories/settings/helpers/password.js
deleted file mode 100644
index d41dab1a02..0000000000
--- a/services/web/frontend/stories/settings/helpers/password.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import getMeta from '@/utils/meta'
-
-const MOCK_DELAY = 1000
-
-export function defaultSetupMocks(fetchMock) {
- fetchMock.post(
- /\/user\/password\/update/,
- {
- status: 200,
- body: {
- message: {
- type: 'success',
- email: 'tim.alby@overleaf.com',
- text: 'Password changed',
- },
- },
- },
- {
- delay: MOCK_DELAY,
- }
- )
-}
-
-export function setDefaultMeta() {
- Object.assign(getMeta('ol-ExposedSettings'), {
- isOverleaf: true,
- })
- window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false)
- window.metaAttributesCache.set('ol-hasPassword', true)
- window.metaAttributesCache.set('ol-passwordStrengthOptions', {
- length: {
- min: 2,
- },
- })
-}
diff --git a/services/web/frontend/stories/settings/leave.stories.jsx b/services/web/frontend/stories/settings/leave.stories.jsx
deleted file mode 100644
index b7ca02836c..0000000000
--- a/services/web/frontend/stories/settings/leave.stories.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import LeaveModal from '../../js/features/settings/components/leave/modal'
-import LeaveSection from '../../js/features/settings/components/leave-section'
-import { setDefaultMeta, defaultSetupMocks } from './helpers/leave'
-
-export const Section = args => {
- useFetchMock(defaultSetupMocks)
- setDefaultMeta()
-
- return
-}
-Section.component = LeaveSection
-Section.parameters = { controls: { include: [], hideNoControlsWarning: true } }
-
-export const ModalSuccess = args => {
- setDefaultMeta()
- useFetchMock(defaultSetupMocks)
-
- return
-}
-
-export const ModalWithoutPassword = args => {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-hasPassword', false)
- useFetchMock(defaultSetupMocks)
-
- return
-}
-
-export const ModalAuthError = args => {
- setDefaultMeta()
- useFetchMock(fetchMock => {
- fetchMock.post(/\/user\/delete/, 403)
- })
-
- return
-}
-
-export const ModalServerError = args => {
- setDefaultMeta()
- useFetchMock(fetchMock => {
- fetchMock.post(/\/user\/delete/, 500)
- })
-
- return
-}
-
-export const ModalSubscriptionError = args => {
- setDefaultMeta()
- useFetchMock(fetchMock => {
- fetchMock.post(/\/user\/delete/, {
- status: 422,
- body: {
- error: 'SubscriptionAdminDeletionError',
- },
- })
- })
-
- return
-}
-
-export default {
- title: 'Account Settings / Leave',
- component: LeaveModal,
- args: {
- isOpen: true,
- },
- argTypes: {
- handleClose: { action: 'handleClose' },
- },
-}
diff --git a/services/web/frontend/stories/settings/linking.stories.jsx b/services/web/frontend/stories/settings/linking.stories.jsx
deleted file mode 100644
index 0799ce0bea..0000000000
--- a/services/web/frontend/stories/settings/linking.stories.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import LinkingSection from '../../js/features/settings/components/linking-section'
-import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
-import { UserProvider } from '../../js/shared/context/user-context'
-import { SSOProvider } from '../../js/features/settings/context/sso-context'
-import { ScopeDecorator } from '../decorators/scope'
-import { useEffect } from 'react'
-import { useMeta } from '../hooks/use-meta'
-
-const MOCK_DELAY = 1000
-
-export const Section = args => {
- useFetchMock(defaultSetupMocks)
- setDefaultMeta()
-
- return (
-
-
-
-
-
- )
-}
-
-export const SectionAllUnlinked = args => {
- useFetchMock(defaultSetupMocks)
-
- useMeta({
- 'ol-thirdPartyIds': {},
- 'ol-user': {
- features: { github: true, dropbox: true, mendeley: true, zotero: true },
- refProviders: {
- mendeley: false,
- zotero: false,
- },
- },
- 'ol-github': { enabled: false },
- 'ol-dropbox': { registered: false },
- })
-
- useEffect(() => {
- setDefaultMeta()
- }, [])
-
- return (
-
-
-
-
-
- )
-}
-
-export const SectionSSOErrors = args => {
- useFetchMock(fetchMock =>
- fetchMock.post('/user/oauth-unlink', 500, { delay: MOCK_DELAY })
- )
- setDefaultMeta()
- window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
- window.metaAttributesCache.set(
- 'ol-ssoErrorMessage',
- 'Account already linked to another Overleaf user'
- )
-
- return (
-
-
-
-
-
- )
-}
-
-export const SectionProjetSyncSuccess = args => {
- useFetchMock(defaultSetupMocks)
- setDefaultMeta()
- window.metaAttributesCache.set('ol-github', { enabled: true })
- window.metaAttributesCache.set(
- 'ol-projectSyncSuccessMessage',
- 'Thanks, we’ve successfully linked your GitHub account to Overleaf. You can now export your Overleaf projects to GitHub, or import projects from your GitHub repositories.'
- )
-
- return (
-
-
-
-
-
- )
-}
-
-export default {
- title: 'Account Settings / Linking',
- component: LinkingSection,
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/settings/newsletter.stories.jsx b/services/web/frontend/stories/settings/newsletter.stories.jsx
deleted file mode 100644
index f0fffb6a35..0000000000
--- a/services/web/frontend/stories/settings/newsletter.stories.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import NewsletterSection from '../../js/features/settings/components/newsletter-section'
-
-export const Section = args => {
- return
-}
-
-export default {
- title: 'Account Settings / Newsletter',
- component: NewsletterSection,
-}
diff --git a/services/web/frontend/stories/settings/page.stories.jsx b/services/web/frontend/stories/settings/page.stories.jsx
deleted file mode 100644
index 53a4830430..0000000000
--- a/services/web/frontend/stories/settings/page.stories.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import SettingsPageRoot from '../../js/features/settings/components/root'
-import {
- setDefaultMeta as setDefaultLeaveMeta,
- defaultSetupMocks as defaultSetupLeaveMocks,
-} from './helpers/leave'
-import {
- setDefaultMeta as setDefaultAccountInfoMeta,
- defaultSetupMocks as defaultSetupAccountInfoMocks,
-} from './helpers/account-info'
-import {
- setDefaultMeta as setDefaultPasswordMeta,
- defaultSetupMocks as defaultSetupPasswordMocks,
-} from './helpers/password'
-import {
- setDefaultMeta as setDefaultEmailsMeta,
- defaultSetupMocks as defaultSetupEmailsMocks,
-} from './helpers/emails'
-import {
- setDefaultMeta as setDefaultLinkingMeta,
- defaultSetupMocks as defaultSetupLinkingMocks,
- setPersonalAccessTokensMeta,
-} from './helpers/linking'
-import { UserProvider } from '../../js/shared/context/user-context'
-import { ScopeDecorator } from '../decorators/scope'
-import getMeta from '@/utils/meta'
-
-export const Overleaf = args => {
- setDefaultLeaveMeta()
- setDefaultAccountInfoMeta()
- setDefaultPasswordMeta()
- setDefaultEmailsMeta()
- setDefaultLinkingMeta()
- useFetchMock(fetchMock => {
- defaultSetupLeaveMocks(fetchMock)
- defaultSetupAccountInfoMocks(fetchMock)
- defaultSetupPasswordMocks(fetchMock)
- defaultSetupEmailsMocks(fetchMock)
- defaultSetupLinkingMocks(fetchMock)
- })
-
- return (
-
-
-
- )
-}
-
-export const OverleafWithAccessTokens = args => {
- setPersonalAccessTokensMeta()
- return Overleaf(args)
-}
-
-export const ServerPro = args => {
- setDefaultAccountInfoMeta()
- setDefaultPasswordMeta()
- setPersonalAccessTokensMeta()
- useFetchMock(fetchMock => {
- defaultSetupAccountInfoMocks(fetchMock)
- defaultSetupPasswordMocks(fetchMock)
- })
-
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasAffiliationsFeature: false,
- isOverleaf: false,
- })
- window.metaAttributesCache.set('ol-hideLinkingWidgets', true)
- window.metaAttributesCache.delete('ol-oauthProviders')
-
- return (
-
-
-
- )
-}
-
-export default {
- title: 'Account Settings / Full Page',
- component: SettingsPageRoot,
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/settings/password.stories.jsx b/services/web/frontend/stories/settings/password.stories.jsx
deleted file mode 100644
index 46811176f5..0000000000
--- a/services/web/frontend/stories/settings/password.stories.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import useFetchMock from '../hooks/use-fetch-mock'
-import PasswordSection from '../../js/features/settings/components/password-section'
-import { setDefaultMeta, defaultSetupMocks } from './helpers/password'
-import getMeta from '@/utils/meta'
-
-export const Success = args => {
- setDefaultMeta()
- useFetchMock(defaultSetupMocks)
-
- return
-}
-
-export const ManagedExternally = args => {
- setDefaultMeta()
- Object.assign(getMeta('ol-ExposedSettings'), {
- isOverleaf: false,
- })
- window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', true)
- useFetchMock(defaultSetupMocks)
-
- return
-}
-
-export const NoExistingPassword = args => {
- setDefaultMeta()
- window.metaAttributesCache.set('ol-hasPassword', false)
- useFetchMock(defaultSetupMocks)
-
- return
-}
-
-export const Error = args => {
- setDefaultMeta()
- useFetchMock(fetchMock =>
- fetchMock.post(/\/user\/password\/update/, {
- status: 400,
- body: {
- message: 'Your old password is wrong',
- },
- })
- )
-
- return
-}
-
-export default {
- title: 'Account Settings / Password',
- component: PasswordSection,
-}
diff --git a/services/web/frontend/stories/settings/sessions.stories.jsx b/services/web/frontend/stories/settings/sessions.stories.jsx
deleted file mode 100644
index 668ed08b6a..0000000000
--- a/services/web/frontend/stories/settings/sessions.stories.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import SessionsSection from '../../js/features/settings/components/sessions-section'
-
-export const Section = args => {
- return
-}
-
-export default {
- title: 'Account Settings / Sessions',
- component: SessionsSection,
-}
diff --git a/services/web/frontend/stories/settings/sso-alert.stories.tsx b/services/web/frontend/stories/settings/sso-alert.stories.tsx
deleted file mode 100644
index e07bdb0dcc..0000000000
--- a/services/web/frontend/stories/settings/sso-alert.stories.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import EmailsSection from '../../js/features/settings/components/emails-section'
-import { SSOAlert } from '../../js/features/settings/components/emails/sso-alert'
-
-export const Info = () => {
- window.metaAttributesCache.set('ol-institutionLinked', {
- universityName: 'Overleaf University',
- })
- return
-}
-
-export const InfoWithEntitlement = () => {
- window.metaAttributesCache.set('ol-institutionLinked', {
- universityName: 'Overleaf University',
- hasEntitlement: true,
- })
- return
-}
-
-export const NonCanonicalEmail = () => {
- window.metaAttributesCache.set('ol-institutionLinked', {
- universityName: 'Overleaf University',
- })
- window.metaAttributesCache.set(
- 'ol-institutionEmailNonCanonical',
- 'user@example.com'
- )
- return
-}
-
-export const Error = () => {
- window.metaAttributesCache.set('ol-samlError', {
- translatedMessage: 'There was an Error',
- })
- return
-}
-
-export const ErrorTranslated = () => {
- window.metaAttributesCache.set('ol-samlError', {
- translatedMessage: 'Translated Error Message',
- message: 'There was an Error',
- })
- return
-}
-
-export const ErrorWithTryAgain = () => {
- window.metaAttributesCache.set('ol-samlError', {
- message: 'There was an Error',
- tryAgain: true,
- })
- return
-}
-
-export default {
- title: 'Account Settings / SSO Alerts',
- component: EmailsSection,
-}
diff --git a/services/web/frontend/stories/share-project-modal.stories.jsx b/services/web/frontend/stories/share-project-modal.stories.jsx
deleted file mode 100644
index 5c6d3949f0..0000000000
--- a/services/web/frontend/stories/share-project-modal.stories.jsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import ShareProjectModal from '../js/features/share-project-modal/components/share-project-modal'
-import useFetchMock from './hooks/use-fetch-mock'
-import { useScope } from './hooks/use-scope'
-import { ScopeDecorator } from './decorators/scope'
-import { contacts } from './fixtures/contacts'
-import { project } from './fixtures/project'
-
-export const LinkSharingOff = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'private',
- },
- })
-
- return
-}
-
-export const LinkSharingOn = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'tokenBased',
- },
- })
-
- return
-}
-
-export const LinkSharingLoading = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'tokenBased',
- },
- })
-
- return
-}
-
-export const NonProjectOwnerLinkSharingOff = args => {
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'private',
- },
- })
-
- return
-}
-
-export const NonProjectOwnerLinkSharingOn = args => {
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'tokenBased',
- },
- })
-
- return
-}
-
-export const RestrictedTokenMember = args => {
- // Override isRestrictedTokenMember to be true
- // Currently this is necessary because the context value is set from window,
- // however in the future we should change this to set via props
- window.metaAttributesCache.set('ol-isRestrictedTokenMember', true)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'tokenBased',
- },
- })
-
- return
-}
-
-export const LegacyLinkSharingReadAndWrite = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'readAndWrite',
- },
- })
-
- return
-}
-
-export const LegacyLinkSharingReadOnly = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- publicAccesLevel: 'readOnly',
- },
- })
-
- return
-}
-
-export const LimitedCollaborators = args => {
- useFetchMock(setupFetchMock)
-
- useScope({
- project: {
- ...args.project,
- features: {
- ...args.project.features,
- collaborators: 3,
- },
- },
- })
-
- return
-}
-
-export default {
- title: 'Editor / Modals / Share Project',
- component: ShareProjectModal,
- args: {
- show: true,
- animation: false,
- user: {},
- project: {
- ...project,
- owner: {
- ...project.owner,
- _id: 'story-user',
- },
- },
- },
- argTypes: {
- handleHide: { action: 'hide' },
- },
- decorators: [ScopeDecorator],
-}
-
-function setupFetchMock(fetchMock) {
- const delay = 1000
-
- fetchMock
- // list contacts
- .get('express:/user/contacts', { contacts }, { delay })
- // access tokens
- .get(
- 'express:/project/:projectId/tokens',
- { tokens: project.tokens },
- { delay }
- )
- // change privacy setting
- .post('express:/project/:projectId/settings/admin', 200, { delay })
- // update project member (e.g. set privilege level)
- .put('express:/project/:projectId/users/:userId', 200, { delay })
- // remove project member
- .delete('express:/project/:projectId/users/:userId', 200, { delay })
- // transfer ownership
- .post('express:/project/:projectId/transfer-ownership', 200, {
- delay,
- })
- // send invite
- .post('express:/project/:projectId/invite', 200, { delay })
- // delete invite
- .delete('express:/project/:projectId/invite/:inviteId', 204, {
- delay,
- })
- // resend invite
- .post('express:/project/:projectId/invite/:inviteId/resend', 200, {
- delay,
- })
- // send analytics event
- .post('express:/event/:key', 200)
-}
diff --git a/services/web/frontend/stories/shared/autocomplete.stories.tsx b/services/web/frontend/stories/shared/autocomplete.stories.tsx
deleted file mode 100644
index 1b63735cb3..0000000000
--- a/services/web/frontend/stories/shared/autocomplete.stories.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import { Meta, StoryObj } from '@storybook/react-webpack5'
-import { useState } from 'react'
-import OLAutocomplete from '@/shared/components/ol/ol-autocomplete'
-
-type Args = React.ComponentProps
-
-const sampleItems = [
- { value: 'react', label: 'React' },
- { value: 'vue', label: 'Vue' },
- { value: 'angular', label: 'Angular' },
- { value: 'svelte', label: 'Svelte' },
- { value: 'ember', label: 'Ember' },
- { value: 'backbone', label: 'Backbone' },
- { value: 'preact', label: 'Preact' },
- { value: 'nextjs', label: 'Next.js' },
- { value: 'nuxtjs', label: 'Nuxt.js' },
- { value: 'gatsby', label: 'Gatsby' },
-]
-
-const programmingLanguages = [
- { value: 'javascript', label: 'JavaScript' },
- { value: 'typescript', label: 'TypeScript' },
- { value: 'python', label: 'Python' },
- { value: 'java', label: 'Java' },
- { value: 'cpp', label: 'C++' },
- { value: 'csharp', label: 'C#' },
- { value: 'go', label: 'Go' },
- { value: 'rust', label: 'Rust' },
- { value: 'ruby', label: 'Ruby' },
- { value: 'php', label: 'PHP' },
- { value: 'swift', label: 'Swift' },
- { value: 'kotlin', label: 'Kotlin' },
- { value: 'dart', label: 'Dart' },
-]
-
-const countries = [
- { value: 'us', label: 'United States' },
- { value: 'uk', label: 'United Kingdom' },
- { value: 'ca', label: 'Canada' },
- { value: 'au', label: 'Australia' },
- { value: 'de', label: 'Germany' },
- { value: 'fr', label: 'France' },
- { value: 'es', label: 'Spain' },
- { value: 'it', label: 'Italy' },
- { value: 'nl', label: 'Netherlands' },
- { value: 'be', label: 'Belgium' },
-]
-
-const entryTypeItems = [
- { value: 'article', label: 'Article', group: 'Most common' },
- { value: 'review', label: 'Review', group: 'Most common' },
- { value: 'book', label: 'Book', group: 'Most common' },
- { value: 'conference', label: 'Conference paper', group: 'Most common' },
- { value: 'thesis', label: 'Thesis', group: 'Most common' },
- { value: 'mastersthesis', label: "Master's thesis", group: 'Most common' },
- { value: 'phdthesis', label: 'PhD thesis', group: 'Most common' },
- { value: 'bookchapter', label: 'Book chapter', group: 'Book types' },
- { value: 'booksection', label: 'Book section', group: 'Book types' },
- { value: 'bookpart', label: 'Book part', group: 'Book types' },
- { value: 'editedbook', label: 'Edited book', group: 'Book types' },
- { value: 'referencebook', label: 'Reference book', group: 'Book types' },
- { value: 'proceedings', label: 'Conference proceedings', group: 'Other' },
- { value: 'journalarticle', label: 'Journal article', group: 'Other' },
- { value: 'techreport', label: 'Technical report', group: 'Other' },
- { value: 'preprint', label: 'Preprint', group: 'Other' },
- { value: 'patent', label: 'Patent', group: 'Other' },
- { value: 'manuscript', label: 'Manuscript', group: 'Other' },
- { value: 'unpublished', label: 'Unpublished', group: 'Other' },
-]
-
-function InteractiveAutocomplete(args: Args) {
- const [value, setValue] = useState('')
-
- return (
-
-
-
- Current value: {value || '(empty)'}
-
-
- )
-}
-
-export const Default: StoryObj = {
- render: args => ,
- args: {
- items: sampleItems,
- label: 'Select a framework',
- placeholder: 'Type to search...',
- showLabel: true,
- allowCreate: true,
- },
-}
-
-export const WithoutCreateOption: StoryObj = {
- render: args => ,
- args: {
- items: programmingLanguages,
- label: 'Programming language',
- placeholder: 'Choose a language',
- showLabel: true,
- allowCreate: false,
- },
-}
-
-export const AllowCreatePredicate: StoryObj = {
- render: args => ,
- args: {
- items: programmingLanguages,
- label: 'Programming language (custom rule)',
- placeholder: 'Try typing java or script',
- showLabel: true,
- allowCreate: value => {
- const normalized = value.trim().toLowerCase()
- return (
- normalized.length >= 4 &&
- !normalized.includes('script') &&
- normalized !== 'java'
- )
- },
- },
-}
-
-export const HiddenLabel: StoryObj = {
- render: args => ,
- args: {
- items: countries,
- label: 'Country',
- placeholder: 'Select your country',
- showLabel: false,
- allowCreate: true,
- },
-}
-
-export const CustomCreatePrefix: StoryObj = {
- render: args => ,
- args: {
- items: [
- { value: 'alpha', label: 'Project Alpha' },
- { value: 'beta', label: 'Project Beta' },
- { value: 'gamma', label: 'Project Gamma' },
- ],
- label: 'Project name',
- placeholder: 'Select or create a project',
- showLabel: true,
- allowCreate: true,
- createOptionPrefix: '➕ Create new project:',
- },
-}
-
-export const Disabled: StoryObj = {
- render: args => ,
- args: {
- items: sampleItems,
- label: 'Framework (disabled)',
- placeholder: 'Cannot interact',
- showLabel: true,
- disabled: true,
- },
-}
-
-export const SmallList: StoryObj = {
- render: args => ,
- args: {
- items: [
- { value: 'option1', label: 'Option 1' },
- { value: 'option2', label: 'Option 2' },
- { value: 'option3', label: 'Option 3' },
- ],
- label: 'Choose an option',
- placeholder: 'Type or select...',
- showLabel: true,
- allowCreate: true,
- },
-}
-
-export const GroupedItems: StoryObj = {
- render: args => ,
- args: {
- items: entryTypeItems,
- label: 'Entry type',
- placeholder: 'Enter entry type',
- showLabel: true,
- allowCreate: true,
- },
-}
-
-export const GroupedWithoutCreate: StoryObj = {
- render: args => ,
- args: {
- items: [
- { value: 'react', label: 'React', group: 'Frontend' },
- { value: 'vue', label: 'Vue', group: 'Frontend' },
- { value: 'angular', label: 'Angular', group: 'Frontend' },
- { value: 'svelte', label: 'Svelte', group: 'Frontend' },
- { value: 'nodejs', label: 'Node.js', group: 'Backend' },
- { value: 'django', label: 'Django', group: 'Backend' },
- { value: 'rails', label: 'Ruby on Rails', group: 'Backend' },
- { value: 'spring', label: 'Spring Boot', group: 'Backend' },
- { value: 'reactnative', label: 'React Native', group: 'Mobile' },
- { value: 'flutter', label: 'Flutter', group: 'Mobile' },
- { value: 'swift', label: 'Swift', group: 'Mobile' },
- { value: 'kotlin', label: 'Kotlin', group: 'Mobile' },
- ],
- label: 'Technology',
- placeholder: 'Search technologies...',
- showLabel: true,
- allowCreate: false,
- },
-}
-
-export const FuzzySearch: StoryObj = {
- render: args => ,
- args: {
- items: [
- { value: 'javascript', label: 'JavaScript' },
- { value: 'typescript', label: 'TypeScript' },
- { value: 'python', label: 'Python' },
- { value: 'rust', label: 'Rust' },
- { value: 'golang', label: 'Go' },
- ],
- label: 'Language (fuzzy search)',
- placeholder: 'Try typo searches, e.g. javasript or pyton',
- showLabel: true,
- allowCreate: true,
- useFuzzySearch: true,
- },
-}
-
-export const GroupedFuzzySearch: StoryObj = {
- render: args => ,
- args: {
- items: entryTypeItems,
- label: 'Grouped entry type (fuzzy search)',
- placeholder: 'Try typo searches, e.g. confernce or theis',
- showLabel: true,
- allowCreate: true,
- useFuzzySearch: true,
- },
-}
-
-export const ExpandUp: StoryObj = {
- render: args => ,
- args: {
- items: sampleItems,
- label: 'Framework (expand up)',
- placeholder: 'Dropdown expands upward',
- showLabel: true,
- allowCreate: true,
- expandUp: true,
- },
-}
-
-export const ExpandUpGrouped: StoryObj = {
- render: args => ,
- args: {
- items: entryTypeItems,
- label: 'Entry type (expand up)',
- placeholder: 'Grouped items expanding upward',
- showLabel: true,
- allowCreate: true,
- expandUp: true,
- },
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Autocomplete',
- component: OLAutocomplete,
- tags: ['autodocs'],
- parameters: {
- controls: {
- include: [
- 'items',
- 'label',
- 'placeholder',
- 'showLabel',
- 'allowCreate',
- 'disabled',
- 'createOptionPrefix',
- 'useFuzzySearch',
- 'expandUp',
- ],
- },
- },
- argTypes: {
- items: {
- control: 'object',
- description:
- 'Array of available options; use the optional group property for grouped sections',
- },
- onChange: {
- description: 'Callback when value changes',
- },
- label: {
- control: 'text',
- description: 'Label for the input',
- },
- placeholder: {
- control: 'text',
- description: 'Placeholder text',
- },
- showLabel: {
- control: 'boolean',
- description: 'Show or hide the label visually',
- },
- allowCreate: {
- control: 'boolean',
- description:
- 'Allow creating custom values, or provide a predicate function `(value) => boolean` to conditionally allow create per input value',
- table: {
- type: {
- summary: 'boolean | ((value: string) => boolean)',
- },
- },
- },
- disabled: {
- control: 'boolean',
- description: 'Disable the input',
- },
- createOptionPrefix: {
- control: 'text',
- description: 'Text prefix for the create option',
- },
- useFuzzySearch: {
- control: 'boolean',
- description: 'Enable fuzzy search matching for suggestions',
- },
- expandUp: {
- control: 'boolean',
- description:
- 'When true, the dropdown expands upward and the search bar appears below the results list',
- },
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/badge.stories.tsx b/services/web/frontend/stories/shared/badge.stories.tsx
deleted file mode 100644
index 25098e0ad3..0000000000
--- a/services/web/frontend/stories/shared/badge.stories.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import classnames from 'classnames'
-import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
-import Badge from '@/shared/components/badge/badge'
-import MaterialIcon from '@/shared/components/material-icon'
-
-const meta: Meta = {
- title: 'Shared / Components / Badge',
- component: Badge,
- args: {
- children: 'Badge',
- },
- argTypes: {
- bg: {
- options: ['light', 'info', 'primary', 'warning', 'danger'],
- control: { type: 'radio' },
- },
- prepend: {
- table: {
- disable: true,
- },
- },
- className: {
- table: {
- disable: true,
- },
- },
- },
-}
-export default meta
-
-type Story = StoryObj
-
-export const BadgeDefault: Story = {
- args: {
- bg: meta.argTypes!.bg!.options![0],
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-9502&m=dev'
- ),
- render: args => (
-
- ),
-}
-
-export const BadgePrepend: Story = {
- args: {
- bg: meta.argTypes!.bg!.options![0],
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-11319&m=dev'
- ),
- render: args => {
- return (
- }
- {...args}
- />
- )
- },
-}
diff --git a/services/web/frontend/stories/shared/billing-period-toggle.stories.tsx b/services/web/frontend/stories/shared/billing-period-toggle.stories.tsx
deleted file mode 100644
index 3dfc48dc74..0000000000
--- a/services/web/frontend/stories/shared/billing-period-toggle.stories.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { useState } from 'react'
-import BillingPeriodToggle, {
- type BillingPeriod,
-} from '../../js/shared/components/billing-period-toggle'
-
-type Args = React.ComponentProps
-
-const meta: Meta = {
- title: 'Subscription / Billing Period Toggle',
- component: BillingPeriodToggle,
- parameters: {
- controls: {
- include: ['value', 'showDiscount', 'variant'],
- },
- },
- argTypes: {
- value: {
- control: 'radio',
- options: ['monthly', 'annual'],
- },
- showDiscount: {
- control: 'boolean',
- },
- variant: {
- control: 'radio',
- options: ['default', 'premium'],
- },
- onChange: { action: 'onChange' },
- },
- args: {
- value: 'monthly',
- showDiscount: true,
- variant: 'default',
- },
-}
-
-export default meta
-
-type Story = StoryObj
-
-const InteractiveToggle = (args: Args) => {
- const [value, setValue] = useState(args.value)
- return (
- {
- setValue(period)
- args.onChange?.(period)
- }}
- />
- )
-}
-
-export const Default: Story = {
- render: InteractiveToggle,
-}
-
-export const WithoutDiscount: Story = {
- render: args => ,
-}
-
-export const AnnualSelected: Story = {
- args: {
- value: 'annual',
- },
- render: InteractiveToggle,
-}
-
-export const PremiumVariant: Story = {
- args: {
- variant: 'premium',
- },
- render: InteractiveToggle,
-}
-
-export const PremiumVariantAnnualSelected: Story = {
- args: {
- variant: 'premium',
- value: 'annual',
- },
- render: InteractiveToggle,
-}
diff --git a/services/web/frontend/stories/shared/button.stories.tsx b/services/web/frontend/stories/shared/button.stories.tsx
deleted file mode 100644
index 63bfda2af5..0000000000
--- a/services/web/frontend/stories/shared/button.stories.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Meta } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
-import OLButton from '@/shared/components/ol/ol-button'
-
-type Args = React.ComponentProps
-
-export const NewButton = (args: Args) => {
- return
-}
-
-export const ButtonWithLeadingIcon = (args: Args) => {
- return
-}
-
-export const ButtonWithTrailingIcon = (args: Args) => {
- return
-}
-
-export const ButtonWithIcons = (args: Args) => {
- return
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Button',
- component: OLButton,
- args: {
- children: 'Button',
- disabled: false,
- isLoading: false,
- },
- argTypes: {
- size: {
- control: 'radio',
- options: ['lg', 'md', 'sm'],
- },
- variant: {
- control: 'radio',
- options: [
- 'primary',
- 'secondary',
- 'ghost',
- 'danger',
- 'danger-ghost',
- 'premium',
- 'premium-secondary',
- 'link',
- ],
- },
- },
- parameters: {
- controls: {
- include: ['children', 'disabled', 'isLoading', 'size', 'variant'],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-22412&m=dev'
- ),
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/buttons.stories.tsx b/services/web/frontend/stories/shared/buttons.stories.tsx
deleted file mode 100644
index 2ae8824222..0000000000
--- a/services/web/frontend/stories/shared/buttons.stories.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Meta, StoryObj } from '@storybook/react-webpack5'
-import OLButton from '@/shared/components/ol/ol-button'
-import { ButtonProps } from '@/shared/components/types/button-props'
-
-export default {
- title: 'Shared / Components / Button',
- component: OLButton,
-} satisfies Meta
-
-type Story = StoryObj
-
-const variants: ButtonProps['variant'][] = [
- 'primary',
- 'secondary',
- 'ghost',
- 'danger',
- 'danger-ghost',
- 'premium',
- 'premium-secondary',
- 'link',
-]
-
-export const Variants: Story = {
- render() {
- return (
- <>
-
- {variants.map(variant => (
-
-
Button
-
- Button
-
-
- Button
-
-
- Button
-
-
- ))}
-
- Dark Mode
-
-
- {variants.map(variant => (
-
-
Button
-
- Button
-
-
- Button
-
-
- Button
-
-
- ))}
-
- >
- )
- },
-}
diff --git a/services/web/frontend/stories/shared/dropdown-menu.stories.tsx b/services/web/frontend/stories/shared/dropdown-menu.stories.tsx
deleted file mode 100644
index 9d0e7e52f0..0000000000
--- a/services/web/frontend/stories/shared/dropdown-menu.stories.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import {
- DropdownMenu,
- DropdownItem,
- DropdownDivider,
- DropdownHeader,
-} from '@/shared/components/dropdown/dropdown-menu'
-import type { Meta } from '@storybook/react-webpack5'
-import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
-import { IdeRedesign } from '../decorators/ide-redesign'
-
-type Args = React.ComponentProps
-
-export const Default = (args: Args) => {
- return (
-
-
-
- Example
-
-
-
-
- Example
-
-
-
-
-
- Example
-
-
-
- )
-}
-
-export const Active = (args: Args) => {
- return (
-
-
-
- Example
-
-
-
-
- Example
-
-
-
-
-
- Example
-
-
-
- )
-}
-
-export const MultipleSelection = () => {
- return (
-
- Header
-
- }
- >
- Example
-
-
-
-
- Example
-
-
-
-
- Example
-
-
-
- )
-}
-
-export const Danger = (args: Args) => {
- return (
-
-
-
- Example
-
-
-
-
- Example
-
-
-
-
-
- Example
-
-
-
- )
-}
-
-export const Description = (args: Args) => {
- return (
-
-
-
- Example
-
-
-
-
- Example
-
-
-
- )
-}
-
-export const LeadingIcon = (args: Args) => {
- return (
-
-
- Editor & PDF
-
-
- Editor only
-
-
- PDF only
-
-
- PDF in separate tab
-
-
- With a description
-
-
- Small icon
-
-
- )
-}
-
-export const TrailingIcon = () => {
- return (
-
-
- Tick
-
-
- With a description
-
-
- With a leading icon
-
-
- )
-}
-
-const meta: Meta = {
- title: 'Shared / Components / DropdownMenu',
- component: DropdownMenu,
- args: {
- disabled: false,
- },
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
- decorators: [IdeRedesign],
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/ds/ds-button.stories.tsx b/services/web/frontend/stories/shared/ds/ds-button.stories.tsx
deleted file mode 100644
index a5a40e22c1..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-button.stories.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Meta } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-import DSButton from '@/shared/components/ds/ds-button'
-
-type Args = React.ComponentProps
-
-export const Button = (args: Args) => {
- return
-}
-
-const meta: Meta = {
- title: 'Shared / DS Components',
- 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', 'isLoading', 'size', 'variant'],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=4565-2932&m=dev'
- ),
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx
deleted file mode 100644
index 5fa5a15146..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-form-check.stories.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import DSFormCheckbox from '@/shared/components/ds/ds-form-checkbox'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta = {
- title: 'Shared / DS Components / Form',
- component: DSFormCheckbox,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj
-
-export const Checkbox: Story = {
- args: {
- id: 'id-1',
- label: 'Label',
- disabled: false,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6135-5430&m=dev'
- ),
-}
diff --git a/services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx
deleted file mode 100644
index b3b3096beb..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-form-control.stories.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Meta } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-import DSFormControl from '@/shared/components/ds/ds-form-control'
-import DSFormText from '@/shared/components/ds/ds-form-text'
-import DSFormGroup from '@/shared/components/ds/ds-form-group'
-import DSFormLabel from '@/shared/components/ds/ds-form-label'
-import { ComponentProps } from 'react'
-
-type Args = ComponentProps & {
- textType: ComponentProps['type']
-}
-
-export const FormControl = ({ textType, value, ...args }: Args) => {
- return (
-
- Form input label
-
- Form input feedback
-
- )
-}
-
-const meta: Meta = {
- title: 'Shared / DS Components / Form',
- component: FormControl,
- argTypes: {
- disabled: { control: 'boolean' },
- isInvalid: { control: 'boolean' },
- placeholder: { control: 'text' },
- readOnly: { control: 'boolean' },
- value: { control: 'text' },
- size: { control: 'radio', options: ['lg', undefined] },
- textType: { control: 'radio', options: ['error', 'success', undefined] },
- },
- parameters: {
- controls: {
- include: [
- 'disabled',
- 'isInvalid',
- 'placeholder',
- 'readOnly',
- 'size',
- 'value',
- 'textType',
- ],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6318-428&t=pcx9KKzhlzpRmA4S-0'
- ),
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx b/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx
deleted file mode 100644
index 99a65cacf1..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-form-radio.stories.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import DSFormRadio from '@/shared/components/ds/ds-form-radio'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta = {
- title: 'Shared / DS Components / Form',
- component: DSFormRadio,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj
-
-export const Radio: Story = {
- args: {
- id: 'id-1',
- label: 'Label',
- disabled: false,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6135-5510&m=dev'
- ),
-}
diff --git a/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx b/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx
deleted file mode 100644
index 98b13c8c41..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-notification.stories.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import DSNotification from '@/shared/components/ds/ds-notification'
-
-type Args = React.ComponentProps
-
-export const Notification = (args: Args) => {
- return
-}
-
-export default {
- title: 'Shared / DS Components / Notification',
- component: DSNotification,
- args: {
- content: (
-
- This can be any HTML passed to the component. For example,
- paragraphs, headers, code samples,{' '}
- links, etc are all supported.
-
- ),
- },
- argTypes: {
- type: {
- control: 'radio',
- options: ['info', 'error'],
- },
- },
- parameters: {
- controls: {
- include: ['content', 'title', 'type'],
- },
- },
-}
diff --git a/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx b/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx
deleted file mode 100644
index 4589e407a2..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-selection-group.stories.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Meta, type StoryObj } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-import DSSelectionGroup from '@/shared/components/ds/ds-selection-group'
-import DSSelectionGroupItem from '@/shared/components/ds/ds-selection-group-item'
-
-type Story = StoryObj
-
-export const SelectionGroupRadio: Story = {
- args: {
- legend: 'Select your favourite animal',
- },
- render: args => {
- return (
-
-
- Wombat
-
-
- Capybara
-
-
- Badger
-
-
- )
- },
-}
-
-export const SelectionGroupCheckbox: Story = {
- args: {
- legend: 'Which animals do you like?',
- },
- render: args => {
- return (
-
-
- Vole
-
-
- Spider
-
-
- Wombat
-
-
- Capybara
-
-
- Badger
-
-
- )
- },
-}
-
-const meta: Meta = {
- title: 'Shared / DS Components',
- component: DSSelectionGroup,
-
- parameters: {
- controls: {
- include: ['legend'],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6276-265&m=dev'
- ),
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx b/services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx
deleted file mode 100644
index 3e31d973ff..0000000000
--- a/services/web/frontend/stories/shared/ds/ds-six-digits-input.stories.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { ComponentProps, useEffect, useState } from 'react'
-import { Meta } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-import DSFormGroup from '@/shared/components/ds/ds-form-group'
-import DSFormLabel from '@/shared/components/ds/ds-form-label'
-import CIAMSixDigitsInput from '@/features/settings/components/emails/ciam-six-digits-input'
-
-type Args = ComponentProps
-
-export const SixDigitsInput = ({ value, ...args }: Args) => {
- const [state, setState] = useState(value)
- useEffect(() => setState(value), [value])
- return (
-
-
- Form input label
- setState(e.target.value)}
- />
-
-
- )
-}
-
-const meta: Meta = {
- title: 'Shared / DS Components',
- component: SixDigitsInput,
- argTypes: {
- value: { control: 'text' },
- },
- parameters: {
- controls: {
- include: ['value'],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6318-428&t=pcx9KKzhlzpRmA4S-0'
- ),
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/feedback-badge.stories.tsx b/services/web/frontend/stories/shared/feedback-badge.stories.tsx
deleted file mode 100644
index aa78e1c3be..0000000000
--- a/services/web/frontend/stories/shared/feedback-badge.stories.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ScopeDecorator } from '../decorators/scope'
-import { FeedbackBadge } from '@/shared/components/feedback-badge'
-
-export const WithDefaultText = () => {
- return (
-
- )
-}
-
-export const WithCustomText = () => {
- const FeedbackContent = () => (
- <>
- This is an example.
-
- Click to find out more
- >
- )
-
- return (
- }
- />
- )
-}
-
-export default {
- title: 'Shared / Components / Feedback Badge',
- component: FeedbackBadge,
- decorators: [ScopeDecorator],
-}
diff --git a/services/web/frontend/stories/shared/form/form-check.stories.tsx b/services/web/frontend/stories/shared/form/form-check.stories.tsx
deleted file mode 100644
index cf94b75024..0000000000
--- a/services/web/frontend/stories/shared/form/form-check.stories.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useRef, useLayoutEffect } from 'react'
-import { Form } from 'react-bootstrap'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-import OLFormCheckbox from '@/shared/components/ol/ol-form-checkbox'
-
-const meta: Meta<(typeof Form)['Check']> = {
- title: 'Shared / Components / Form',
- component: OLFormCheckbox,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj<(typeof Form)['Check']>
-
-export const Checkbox: Story = {
- args: {
- id: 'id-1',
- label: 'Label',
- disabled: false,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3494-247203&m=dev'
- ),
-}
-
-export const CheckboxChecked: Story = {
- args: {
- id: 'id-1',
- label: 'Label',
- disabled: false,
- defaultChecked: true,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3495-249055&m=dev'
- ),
-}
-
-export const CheckboxIndeterminate = (args: Story['args']) => {
- const ref = useRef(null)
-
- useLayoutEffect(() => {
- if (ref.current) {
- ref.current.indeterminate = true
- }
- }, [])
-
- return
-}
-CheckboxIndeterminate.args = {
- id: 'id-2',
- label: 'Label',
- disabled: false,
-}
-CheckboxIndeterminate.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3495-249055&m=dev'
-)
diff --git a/services/web/frontend/stories/shared/form/form-input.stories.tsx b/services/web/frontend/stories/shared/form/form-input.stories.tsx
deleted file mode 100644
index e5f3328d99..0000000000
--- a/services/web/frontend/stories/shared/form/form-input.stories.tsx
+++ /dev/null
@@ -1,291 +0,0 @@
-import { Form } from 'react-bootstrap'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import FormGroup from '@/shared/components/form/form-group'
-import FormText from '@/shared/components/form/form-text'
-import FormControl from '@/shared/components/form/form-control'
-import MaterialIcon from '@/shared/components/material-icon'
-import FormFeedback from '@/shared/components/form/form-feedback'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta> = {
- title: 'Shared / Components / Form / Input',
- component: FormControl,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj>
-
-export const Default: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Helper
-
-
-
- Label
-
- Helper
-
-
-
- Label
-
- Helper
-
- >
- )
- },
-}
-Default.args = {
- disabled: false,
-}
-Default.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-152419&m=dev'
-)
-
-export const Info: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Info
-
-
-
- Label
-
- Info
-
-
-
- Label
-
- Info
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Info.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-152426&m=dev'
-)
-
-export const Error: Story = {
- render: args => {
- return (
- <>
-
- Large input label
-
- Error
-
-
-
- Regular input label
-
- Error
-
-
-
- Small input label
-
- Error
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Error.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-166648&m=dev'
-)
-
-export const Warning: Story = {
- render: args => {
- return (
- <>
-
- Large input label
-
- Warning
-
-
-
- Regular input label
-
- Warning
-
-
-
- Small input label
-
- Warning
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Warning.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-166648&m=dev'
-)
-
-export const Success: Story = {
- render: args => {
- return (
- <>
-
- Large input label
-
- Success
-
-
-
- Regular input label
-
- Success
-
-
-
- Small input label
-
- Success
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Success.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-166648&m=dev'
-)
-
-export const WithIcons: Story = {
- render: args => {
- const handleClear = () => {
- alert('Clicked clear button')
- }
-
- return (
- <>
-
- Label
- }
- append={
-
- }
- size="lg"
- {...args}
- />
-
-
-
- Label
- }
- append={
-
- }
- {...args}
- />
-
-
-
- Label
- }
- append={
-
- }
- size="sm"
- {...args}
- />
-
-
-
-
- Disabled state
- }
- append={
-
- }
- disabled
- {...args}
- />
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
diff --git a/services/web/frontend/stories/shared/form/form-radio.stories.tsx b/services/web/frontend/stories/shared/form/form-radio.stories.tsx
deleted file mode 100644
index 1685add1ce..0000000000
--- a/services/web/frontend/stories/shared/form/form-radio.stories.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Form } from 'react-bootstrap'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta<(typeof Form)['Check']> = {
- title: 'Shared / Components / Form',
- component: Form.Check,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj<(typeof Form)['Check']>
-
-export const Radio: Story = {
- args: {
- id: 'id-1',
- type: 'radio',
- label: 'Label',
- disabled: false,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3495-259211&m=dev'
- ),
-}
-
-export const RadioChecked: Story = {
- args: {
- id: 'id-1',
- type: 'radio',
- label: 'Label',
- disabled: false,
- defaultChecked: true,
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3495-259218&m=dev'
- ),
-}
diff --git a/services/web/frontend/stories/shared/form/form-select.stories.tsx b/services/web/frontend/stories/shared/form/form-select.stories.tsx
deleted file mode 100644
index 69be442d14..0000000000
--- a/services/web/frontend/stories/shared/form/form-select.stories.tsx
+++ /dev/null
@@ -1,250 +0,0 @@
-import { Form, FormSelectProps } from 'react-bootstrap'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import FormGroup from '@/shared/components/form/form-group'
-import FormText from '@/shared/components/form/form-text'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta = {
- title: 'Shared / Components / Form / Select',
- component: Form.Select,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj
-
-export const Default: Story = {
- render: args => {
- return (
- <>
-
- Label
-
-
-
-
-
-
- Helper
-
-
-
- Label
-
-
-
-
-
-
- Helper
-
-
-
- Label
-
-
-
-
-
-
- Helper
-
- >
- )
- },
-}
-Default.args = {
- disabled: false,
-}
-Default.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-199797&m=dev'
-)
-
-export const Info: Story = {
- render: args => {
- return (
- <>
-
- Label
-
-
-
-
-
-
- Info
-
-
-
- Label
-
-
-
-
-
-
- Info
-
-
-
- Label
-
-
-
-
-
-
- Info
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-
-export const Error: Story = {
- render: args => {
- return (
- <>
-
- Label
-
-
-
-
-
-
- Error
-
-
-
- Label
-
-
-
-
-
-
- Error
-
-
-
- Label
-
-
-
-
-
-
- Error
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Error.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-200123&m=dev'
-)
-
-export const Warning: Story = {
- render: args => {
- return (
- <>
-
- Label
-
-
-
-
-
-
- Warning
-
-
-
- Label
-
-
-
-
-
-
- Warning
-
-
-
- Label
-
-
-
-
-
-
- Warning
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Warning.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-199800&m=dev'
-)
-
-export const Success: Story = {
- render: args => {
- return (
- <>
-
- Label
-
-
-
-
-
-
- Success
-
-
-
- Label
-
-
-
-
-
-
- Success
-
-
-
- Label
-
-
-
-
-
-
- Success
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Success.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-199800&m=dev'
-)
diff --git a/services/web/frontend/stories/shared/form/form-textarea.stories.tsx b/services/web/frontend/stories/shared/form/form-textarea.stories.tsx
deleted file mode 100644
index 6e84249f62..0000000000
--- a/services/web/frontend/stories/shared/form/form-textarea.stories.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-import { Form } from 'react-bootstrap'
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import FormGroup from '@/shared/components/form/form-group'
-import FormText from '@/shared/components/form/form-text'
-import FormControl from '@/shared/components/form/form-control'
-import { figmaDesignUrl } from '../../../../.storybook/utils/figma-design-url'
-
-const meta: Meta> = {
- title: 'Shared / Components / Form / Textarea',
- component: FormControl,
- parameters: {
- controls: {
- include: ['disabled'],
- },
- },
-}
-export default meta
-
-type Story = StoryObj>
-
-export const Default: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Helper
-
-
-
- Label
-
- Helper
-
-
-
- Label
-
- Helper
-
- >
- )
- },
-}
-Default.args = {
- disabled: false,
-}
-Default.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-176397&m=dev'
-)
-
-export const Info: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Info
-
-
-
- Label
-
- Info
-
-
-
- Label
-
- Info
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-
-export const Error: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Error
-
-
-
- Label
-
- Error
-
-
-
- Label
-
- Error
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Error.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-176403&m=dev'
-)
-
-export const Warning: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Warning
-
-
-
- Label
-
- Warning
-
-
-
- Label
-
- Warning
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Warning.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-176403&m=dev'
-)
-
-export const Success: Story = {
- render: args => {
- return (
- <>
-
- Label
-
- Success
-
-
-
- Label
-
- Success
-
-
-
- Label
-
- Success
-
- >
- )
- },
- args: {
- disabled: false,
- },
-}
-Success.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-176403&m=dev'
-)
diff --git a/services/web/frontend/stories/shared/icon-button.stories.tsx b/services/web/frontend/stories/shared/icon-button.stories.tsx
deleted file mode 100644
index 1e5ac32673..0000000000
--- a/services/web/frontend/stories/shared/icon-button.stories.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import IconButton from '@/shared/components/button/icon-button'
-import type { Meta } from '@storybook/react-webpack5'
-import { useTranslation } from 'react-i18next'
-import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
-
-type Args = React.ComponentProps
-
-export const Icon = (args: Args) => {
- const { t } = useTranslation()
-
- return
-}
-
-const meta: Meta = {
- title: 'Shared / Components / IconButton',
- component: IconButton,
- args: {
- disabled: false,
- icon: 'add',
- isLoading: false,
- },
- argTypes: {
- size: {
- control: 'radio',
- options: ['small', 'default', 'large'],
- },
- variant: {
- control: 'radio',
- options: [
- 'primary',
- 'secondary',
- 'ghost',
- 'danger',
- 'danger-ghost',
- 'premium',
- ],
- },
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3460-168934&m=dev'
- ),
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/input-switch.stories.tsx b/services/web/frontend/stories/shared/input-switch.stories.tsx
deleted file mode 100644
index b23826f75f..0000000000
--- a/services/web/frontend/stories/shared/input-switch.stories.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import OLFormSwitch from '@/shared/components/ol/ol-form-switch'
-import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
-
-type Args = React.ComponentProps
-
-export const Unchecked = (args: Args) => {
- return {}} checked={false} {...args} />
-}
-
-export const UncheckedDisabled = (args: Args) => {
- return {}} checked={false} disabled {...args} />
-}
-
-export const Checked = (args: Args) => {
- return {}} checked {...args} />
-}
-
-export const CheckedDisabled = (args: Args) => {
- return {}} checked disabled {...args} />
-}
-
-export default {
- title: 'Shared / Components / Input Switch',
- component: OLFormSwitch,
- args: {
- checked: false,
- disabled: false,
- },
- parameters: {
- controls: {
- include: ['checked', 'disabled'],
- },
- ...figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-139487&m=dev'
- ),
- },
-}
diff --git a/services/web/frontend/stories/shared/labeled-divider.stories.tsx b/services/web/frontend/stories/shared/labeled-divider.stories.tsx
deleted file mode 100644
index ecf35330db..0000000000
--- a/services/web/frontend/stories/shared/labeled-divider.stories.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import LabeledDivider from '@/shared/components/labeled-divider'
-import { Meta } from '@storybook/react-webpack5'
-
-type Args = React.ComponentProps
-
-export const Default = (args: Args) => {
- return
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Labeled Divider',
- component: LabeledDivider,
- args: {
- children: 'Text',
- },
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/loading-spinner.stories.tsx b/services/web/frontend/stories/shared/loading-spinner.stories.tsx
deleted file mode 100644
index fc1907f9c7..0000000000
--- a/services/web/frontend/stories/shared/loading-spinner.stories.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import LoadingSpinner, {
- FullSizeLoadingSpinner,
-} from '@/shared/components/loading-spinner'
-
-type Story = StoryObj
-
-export const Default: Story = {
- args: {
- loadingText: 'Loading content...',
- },
-}
-
-export const WithDelay: Story = {
- args: {
- delay: 500,
- loadingText: 'This will appear after a 500ms delay...',
- },
-}
-
-export const FullSize: StoryObj = {
- render: args => ,
- args: {
- loadingText: 'Loading entire section...',
- size: 'sm',
- },
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Loading Spinner',
- component: LoadingSpinner,
- parameters: {
- layout: 'centered',
- controls: {
- include: ['loadingText', 'delay', 'size'],
- },
- },
- argTypes: {
- delay: {
- control: 'select',
- options: [0, 500],
- },
- size: {
- control: 'radio',
- options: ['lg', 'sm'],
- },
- },
- args: {
- size: 'sm',
- delay: 0,
- },
- render: args => ,
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/menu-bar.stories.tsx b/services/web/frontend/stories/shared/menu-bar.stories.tsx
deleted file mode 100644
index e1b927eca5..0000000000
--- a/services/web/frontend/stories/shared/menu-bar.stories.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { DropdownDivider } from '@/shared/components/dropdown/dropdown-menu'
-import { MenuBar } from '@/shared/components/menu-bar/menu-bar'
-import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
-import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
-import { Meta } from '@storybook/react-webpack5'
-
-export const Default = () => {
- return (
-
- )
-}
-
-const meta: Meta = {
- title: 'Shared / Components / MenuBar',
- component: MenuBar,
- argTypes: {},
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/modal.stories.tsx b/services/web/frontend/stories/shared/modal.stories.tsx
deleted file mode 100644
index 109f06d1f2..0000000000
--- a/services/web/frontend/stories/shared/modal.stories.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react-webpack5'
-import { figmaDesignUrl } from './../../../.storybook/utils/figma-design-url'
-import {
- OLModal,
- OLModalHeader,
- OLModalBody,
- OLModalFooter,
- OLModalTitle,
-} from '@/shared/components/ol/ol-modal'
-import OLButton from '@/shared/components/ol/ol-button'
-
-type Story = StoryObj
-
-export const Default: Story = {
- args: {
- title: 'Heading',
- children: (
-
- Always use the modal actions in the footer and use descriptive words for
- them.
-
- ),
- footer: (
- <>
- Cancel
- Primary
- >
- ),
- },
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-82657&m=dev'
- ),
-}
-
-export const Informative: Story = {
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86576&m=dev'
- ),
- args: {
- title: 'Informative',
- children: (
-
- Presents information for the user to be aware of and doesn’t require any
- action.
-
- ),
- },
-}
-
-export const Acknowledgment: Story = {
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86581&m=dev'
- ),
- args: {
- title: 'Acknowledgment',
- children: (
-
- System requires an acknowledgment from the user. Usually contains only
- one primary button.
-
- ),
- footer: Accept,
- },
-}
-
-export const Danger: Story = {
- parameters: figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86586&m=dev'
- ),
- args: {
- title: 'Danger',
- children: Used for destructive or irreversible actions.
,
- footer: (
- <>
- Cancel
- Delete
- >
- ),
- },
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Modal',
- component: OLModal,
- parameters: {
- controls: {
- include: ['size', 'title'],
- },
- },
- argTypes: {
- size: {
- control: 'radio',
- options: ['lg', 'md', 'sm'],
- },
- },
- args: {
- show: true,
- size: 'sm',
- onHide: () => {},
- },
- render: ({ title, children, footer, ...args }) => (
-
-
- {title}
-
- {children}
- {footer && {footer}}
-
- ),
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/notification.stories.tsx b/services/web/frontend/stories/shared/notification.stories.tsx
deleted file mode 100644
index 459f0f5138..0000000000
--- a/services/web/frontend/stories/shared/notification.stories.tsx
+++ /dev/null
@@ -1,437 +0,0 @@
-import fetchMock from 'fetch-mock'
-import Notification from '../../js/shared/components/notification'
-import { postJSON } from '../../js/infrastructure/fetch-json'
-import useAsync from '../../js/shared/hooks/use-async'
-import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
-
-type Args = React.ComponentProps
-
-export const NotificationInfo = (args: Args) => {
- return
-}
-NotificationInfo.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3488-103958&m=dev'
-)
-
-export const NotificationSuccess = (args: Args) => {
- return
-}
-NotificationSuccess.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-110077&m=dev'
-)
-
-export const NotificationWarning = (args: Args) => {
- return
-}
-NotificationWarning.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-112036&m=dev'
-)
-
-export const NotificationError = (args: Args) => {
- return
-}
-NotificationError.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-112059&m=dev'
-)
-
-export const NotificationOffer = (args: Args) => {
- return
-}
-
-export const NotificationWithActionBelowContent = (args: Args) => {
- return (
-
- The CTA will always go below the content on small screens.
-
- We can also opt to always put the CTA below the content on all
- screens
-
-
- }
- isDismissible
- isActionBelowContent
- />
- )
-}
-NotificationWithActionBelowContent.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-118816&m=dev'
-)
-
-export const NotificationWithTitle = (args: Args) => {
- return
-}
-NotificationWithTitle.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-115045&m=dev'
-)
-
-export const NotificationWithAction = (args: Args) => {
- return
-}
-NotificationWithAction.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-117190&m=dev'
-)
-
-export const NotificationDismissible = (args: Args) => {
- return
-}
-NotificationDismissible.parameters = figmaDesignUrl(
- 'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3489-116677&m=dev'
-)
-
-export const APlainNotification = (args: Args) => {
- return
-}
-
-export const NotificationWithMultipleParagraphsAndActionAndDismissible = (
- args: Args
-) => {
- return (
-
-
- Lorem ipsum
-
-
- Dolor sit amet, consectetur adipiscing elit. Proin lacus velit,
- faucibus vitae feugiat sit amet, Some link iaculis
- ut mi.
-
-
- Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum
- iaculis eu non diam phasellus.
-
- Aliquam at tempor risus. Vestibulum bibendum ut
-
- }
- />
- )
-}
-
-export const NotificationWithMultipleParagraphsAndDismissible = (
- args: Args
-) => {
- return (
-
-
- Lorem ipsum
-
-
- Dolor sit amet, consectetur adipiscing elit. Proin lacus velit,
- faucibus vitae feugiat sit amet, Some link iaculis
- ut mi.
-
-
- Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum
- iaculis eu non diam phasellus.
-
- Aliquam at tempor risus. Vestibulum bibendum ut
-
- }
- />
- )
-}
-
-export const MultipleParagraphsAndAction = (args: Args) => {
- return (
-
-
- Lorem ipsum
-
-
- Dolor sit amet, consectetur adipiscing elit. Proin lacus velit,
- faucibus vitae feugiat sit amet, Some link iaculis
- ut mi.
-
-
- Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum
- iaculis eu non diam phasellus.
-
- Aliquam at tempor risus. Vestibulum bibendum ut
-
- }
- />
- )
-}
-
-export const MultipleParagraphs = (args: Args) => {
- return (
-
-
- Lorem ipsum
-
-
- Dolor sit amet, consectetur adipiscing elit. Proin lacus velit,
- faucibus vitae feugiat sit amet, Some link iaculis
- ut mi.
-
-
- Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum
- iaculis eu non diam phasellus.
-
- Aliquam at tempor risus. Vestibulum bibendum ut
-
- }
- />
- )
-}
-
-export const ShortText = (args: Args) => {
- return (
- Lorem ipsum
}
- />
- )
-}
-
-export const ShortTextAndDismissible = (args: Args) => {
- return (
- Lorem ipsum} />
- )
-}
-
-export const ShortTextAndActionLinkAsButton = (args: Args) => {
- return (
- Lorem ipsum}
- />
- )
-}
-
-export const ShortTextAndActionAsLink = (args: Args) => {
- return (
- Lorem ipsum}
- action={An action}
- isDismissible={false}
- />
- )
-}
-export const ShortTextAndActionAsLinkButStyledAsButton = (args: Args) => {
- return (
- Lorem ipsum}
- action={
-
- An action
-
- }
- isDismissible={false}
- />
- )
-}
-
-export const LongActionButton = (args: Args) => {
- return (
-
- Action that has a lot of text
-
- }
- />
- )
-}
-
-export const LongActionLink = (args: Args) => {
- return (
- Action that has a lot of text}
- />
- )
-}
-
-export const CustomIcon = (args: Args) => {
- return (
- 🎉}
- />
- )
-}
-
-export const MultipleButtons = (args: Args) => {
- return (
- Lorem ipsum}
- action={
- <>
-
-
- >
- }
- type="info"
- isActionBelowContent
- isDismissible
- />
- )
-}
-
-export const OverlayedWithCustomClass = (args: Args) => {
- return (
- <>
-
- This can be any HTML passed to the component. For example,
- paragraphs, headers, code samples,{' '}
- links, etc are all supported.
-
- }
- className="ol-overlay"
- action={
- <>
-
-
- >
- }
- type="info"
- isActionBelowContent
- isDismissible
- />
-
-
we need filler content, so here are some jokes
-
- - Did you hear about the circus fire? It was in tents!
- - How do you catch a squirrel? Climb a tree and act like a nut!
- -
- Did you hear about the guy who invented Lifesavers? They say he made
- a mint!
-
- -
- Why couldn't the bicycle stand up by itself? It was two tired.
-
- -
- did one hat say to the other?" "Stay here! I'm going on ahead.
-
- -
- Why did Billy get fired from the banana factory? He kept throwing
- away the bent ones.
-
-
-
- >
- )
-}
-
-export const SuccessFlow = (args: Args) => {
- console.log('.....render')
- fetchMock.post('express:/test-success', { status: 200 }, { delay: 250 })
-
- const { isLoading, isSuccess, runAsync } = useAsync()
- function handleClick() {
- console.log('clicked')
- runAsync(postJSON('/test-success')).catch(console.error)
- }
-
- const ctaText = isLoading ? 'Processing' : 'Click'
- const action = (
-
- )
-
- const startNotification = (
-
- This story shows 2 notifications, and it's up to the parent component
- to determine which to show. There's a successful request made after
- clicking the action and so the parent component then renders the
- success notification.
-
- }
- />
- )
- const successNotification = (
- Now follow this link to go home}
- type="success"
- content={Success! You made a successful request.
}
- />
- )
-
- if (isSuccess) return successNotification
- return startNotification
-}
-
-export const ContentAsAString = (args: Args) => {
- return
-}
-
-export default {
- title: 'Shared / Components / Notification',
- component: Notification,
- args: {
- content: (
-
- This can be any HTML passed to the component. For example,
- paragraphs, headers, code samples, links,
- etc are all supported.
-
- ),
- action: ,
- isDismissible: true,
- title: undefined,
- },
- argTypes: {
- content: {
- control: 'text',
- },
- action: {
- control: 'text',
- },
- title: {
- control: 'text',
- },
- },
- parameters: {
- controls: {
- include: [
- 'content',
- 'title',
- 'action',
- 'ariaLive',
- 'type',
- 'isDismissible',
- 'isActionBelowContent',
- ],
- },
- },
-}
diff --git a/services/web/frontend/stories/shared/pagination.stories.jsx b/services/web/frontend/stories/shared/pagination.stories.jsx
deleted file mode 100644
index 75b365fa24..0000000000
--- a/services/web/frontend/stories/shared/pagination.stories.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import Pagination from '../../js/shared/components/pagination'
-
-export const Interactive = args => {
- return
-}
-
-export default {
- title: 'Shared / Components / Pagination',
- component: Pagination,
- args: {
- currentPage: 1,
- totalPages: 10,
- handlePageClick: () => {},
- },
- argTypes: {
- currentPage: { control: { type: 'number', min: 1, max: 10, step: 1 } },
- },
- parameters: {
- controls: {
- include: ['currentPage'],
- },
- },
-}
diff --git a/services/web/frontend/stories/shared/radio-chip.stories.tsx b/services/web/frontend/stories/shared/radio-chip.stories.tsx
deleted file mode 100644
index a782363faf..0000000000
--- a/services/web/frontend/stories/shared/radio-chip.stories.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import RadioChip from '../../js/shared/components/radio-chip'
-
-type Args = React.ComponentProps
-
-export const RadioChipDefault = (args: Args) => {
- return
-}
-
-export const RadioChipDisabled = (args: Args) => {
- return
-}
-
-export const RadioChipDisabledSelected = (args: Args) => {
- return
-}
-
-export default {
- title: 'Shared / Components / RadioChip',
- component: RadioChip,
- args: {
- label: 'Option',
- checked: false,
- disabled: false,
- },
- argTypes: {
- label: {
- control: 'text',
- },
- },
- parameters: {
- controls: {
- include: ['label', 'checked', 'disabled', 'name', 'value'],
- },
- },
-}
diff --git a/services/web/frontend/stories/shared/row.stories.tsx b/services/web/frontend/stories/shared/row.stories.tsx
deleted file mode 100644
index d5ce7ca719..0000000000
--- a/services/web/frontend/stories/shared/row.stories.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Container, Row, Col } from 'react-bootstrap'
-import { Meta } from '@storybook/react-webpack5'
-
-type Args = React.ComponentProps
-
-export const ColumnRowCell = (args: Args) => {
- return (
-
-
-
- Col 1
-
-
- Col 2
-
-
- Col 3
-
-
-
- )
-}
-
-const meta: Meta = {
- title: 'Shared / Components / Column-Row-Cell',
- component: Row,
-}
-
-export default meta
diff --git a/services/web/frontend/stories/shared/select.stories.tsx b/services/web/frontend/stories/shared/select.stories.tsx
deleted file mode 100644
index 1e792872dc..0000000000
--- a/services/web/frontend/stories/shared/select.stories.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Select } from '@/shared/components/select'
-import { Meta } from '@storybook/react-webpack5'
-
-type Args = Pick<
- React.ComponentProps,
- 'disabled' | 'defaultText' | 'isCiam'
->
-
-const items = [1, 2, 3, 4].map(index => ({
- key: index,
- value: `Demo item ${index}`,
- group: index >= 3 ? 'Large numbers' : undefined,
-}))
-
-export const Base = (args: Args) => {
- return (
-