(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick 2024-04-29 15:01:40 -04:00
commit a3442aee77
16 changed files with 1470 additions and 1159 deletions

132
README.md
View File

@ -233,72 +233,72 @@ For more on Grist Labs' history and principles, see our [About Us](https://www.g
Grist can be configured in many ways. Here are the main environment variables it is sensitive to: Grist can be configured in many ways. Here are the main environment variables it is sensitive to:
Variable | Purpose | Variable | Purpose |
-------- | ------- |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com). You can set this to `*` to allow all domains, but if doing so, we recommend using a carefully locked-down proxy (see `GRIST_HTTPS_PROXY`) if you do not entirely trust users. Otherwise services on your internal network may become vulnerable to manipulation. | ALLOWED_WEBHOOK_DOMAINS | comma-separated list of permitted domains to use in webhooks (e.g. webhook.site,zapier.com). You can set this to `*` to allow all domains, but if doing so, we recommend using a carefully locked-down proxy (see `GRIST_HTTPS_PROXY`) if you do not entirely trust users. Otherwise services on your internal network may become vulnerable to manipulation. |
APP_DOC_URL | doc worker url, set when starting an individual doc worker (other servers will find doc worker urls via redis) | APP_DOC_URL | doc worker url, set when starting an individual doc worker (other servers will find doc worker urls via redis) |
APP_DOC_INTERNAL_URL | like `APP_DOC_URL` but used by the home server to reach the server using an internal domain name resolution (like in a docker environment). Defaults to `APP_DOC_URL` | APP_DOC_INTERNAL_URL | like `APP_DOC_URL` but used by the home server to reach the server using an internal domain name resolution (like in a docker environment). Defaults to `APP_DOC_URL` |
APP_HOME_URL | url prefix for home api (home and doc servers need this) | APP_HOME_URL | url prefix for home api (home and doc servers need this) |
APP_STATIC_URL | url prefix for static resources | APP_STATIC_URL | url prefix for static resources |
APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages | APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages |
APP_UNTRUSTED_URL | URL at which to serve/expect plugin content. | APP_UNTRUSTED_URL | URL at which to serve/expect plugin content. |
GRIST_ADAPT_DOMAIN | set to "true" to support multiple base domains (careful, host header should be trustworthy) | GRIST_ADAPT_DOMAIN | set to "true" to support multiple base domains (careful, host header should be trustworthy) |
GRIST_APP_ROOT | directory containing Grist sandbox and assets (specifically the sandbox and static subdirectories). | GRIST_APP_ROOT | directory containing Grist sandbox and assets (specifically the sandbox and static subdirectories). |
GRIST_BACKUP_DELAY_SECS | wait this long after a doc change before making a backup | GRIST_BACKUP_DELAY_SECS | wait this long after a doc change before making a backup |
GRIST_BOOT_KEY | if set, offer diagnostics at /boot/GRIST_BOOT_KEY | GRIST_BOOT_KEY | if set, offer diagnostics at /boot/GRIST_BOOT_KEY |
GRIST_DATA_DIR | directory in which to store document caches. | GRIST_DATA_DIR | Directory in which to store documents. Defaults to `docs/` relative to the Grist application directory. In Grist's default Docker image, its default value is /persist/docs so that it will be used as a mounted volume. |
GRIST_DEFAULT_EMAIL | if set, login as this user if no other credentials presented | GRIST_DEFAULT_EMAIL | if set, login as this user if no other credentials presented |
GRIST_DEFAULT_PRODUCT | if set, this controls enabled features and limits of new sites. See names of PRODUCTS in Product.ts. | GRIST_DEFAULT_PRODUCT | if set, this controls enabled features and limits of new sites. See names of PRODUCTS in Product.ts. |
GRIST_DEFAULT_LOCALE | Locale to use as fallback when Grist cannot honour the browser locale. | GRIST_DEFAULT_LOCALE | Locale to use as fallback when Grist cannot honour the browser locale. |
GRIST_DOMAIN | in hosted Grist, Grist is served from subdomains of this domain. Defaults to "getgrist.com". | GRIST_DOMAIN | in hosted Grist, Grist is served from subdomains of this domain. Defaults to "getgrist.com". |
GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins | GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins |
GRIST_ENABLE_REQUEST_FUNCTION | enables the REQUEST function. This function performs HTTP requests in a similar way to `requests.request`. This function presents a significant security risk, since it can let users call internal endpoints when Grist is available publicly. This function can also cause performance issues. Unset by default. | GRIST_ENABLE_REQUEST_FUNCTION | enables the REQUEST function. This function performs HTTP requests in a similar way to `requests.request`. This function presents a significant security risk, since it can let users call internal endpoints when Grist is available publicly. This function can also cause performance issues. Unset by default. |
GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled. | GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled. |
GRIST_HOST | hostname to use when listening on a port. | GRIST_HOST | hostname to use when listening on a port. |
GRIST_HTTPS_PROXY | if set, use this proxy for webhook payload delivery. | GRIST_HTTPS_PROXY | if set, use this proxy for webhook payload delivery. |
GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*. | GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*. |
GRIST_IGNORE_SESSION | if set, Grist will not use a session for authentication. | GRIST_IGNORE_SESSION | if set, Grist will not use a session for authentication. |
GRIST_INCLUDE_CUSTOM_SCRIPT_URL | if set, will load the referenced URL in a `<script>` tag on all app pages. | GRIST_INCLUDE_CUSTOM_SCRIPT_URL | if set, will load the referenced URL in a `<script>` tag on all app pages. |
GRIST_INST_DIR | path to Grist instance configuration files, for Grist server. | GRIST_INST_DIR | path to Grist instance configuration files, for Grist server. |
GRIST_LIST_PUBLIC_SITES | if set to true, sites shared with the public will be listed for anonymous users. Defaults to false. | GRIST_LIST_PUBLIC_SITES | if set to true, sites shared with the public will be listed for anonymous users. Defaults to false. |
GRIST_MANAGED_WORKERS | if set, Grist can assume that if a url targeted at a doc worker returns a 404, that worker is gone | GRIST_MANAGED_WORKERS | if set, Grist can assume that if a url targeted at a doc worker returns a 404, that worker is gone |
GRIST_MAX_UPLOAD_ATTACHMENT_MB | max allowed size for attachments (0 or empty for unlimited). | GRIST_MAX_UPLOAD_ATTACHMENT_MB | max allowed size for attachments (0 or empty for unlimited). |
GRIST_MAX_UPLOAD_IMPORT_MB | max allowed size for imports (except .grist files) (0 or empty for unlimited). | GRIST_MAX_UPLOAD_IMPORT_MB | max allowed size for imports (except .grist files) (0 or empty for unlimited). |
GRIST_OFFER_ALL_LANGUAGES | if set, all translated langauages are offered to the user (by default, only languages with a special 'good enough' key set are offered to user). | GRIST_OFFER_ALL_LANGUAGES | if set, all translated langauages are offered to the user (by default, only languages with a special 'good enough' key set are offered to user). |
GRIST_ORG_IN_PATH | if true, encode org in path rather than domain | GRIST_ORG_IN_PATH | if true, encode org in path rather than domain |
GRIST_PAGE_TITLE_SUFFIX | a string to append to the end of the `<title>` in HTML documents. Defaults to `" - Grist"`. Set to `_blank` for no suffix at all. | GRIST_PAGE_TITLE_SUFFIX | a string to append to the end of the `<title>` in HTML documents. Defaults to `" - Grist"`. Set to `_blank` for no suffix at all. |
~GRIST_PROXY_AUTH_HEADER~ | Deprecated, and interpreted as a synonym for GRIST_FORWARD_AUTH_HEADER. | ~GRIST_PROXY_AUTH_HEADER~ | Deprecated, and interpreted as a synonym for GRIST_FORWARD_AUTH_HEADER. |
GRIST_ROUTER_URL | optional url for an api that allows servers to be (un)registered with a load balancer | GRIST_ROUTER_URL | optional url for an api that allows servers to be (un)registered with a load balancer |
GRIST_SERVE_SAME_ORIGIN | set to "true" to access home server and doc workers on the same protocol-host-port as the top-level page, same as for custom domains (careful, host header should be trustworthy) | GRIST_SERVE_SAME_ORIGIN | set to "true" to access home server and doc workers on the same protocol-host-port as the top-level page, same as for custom domains (careful, host header should be trustworthy) |
GRIST_SERVERS | the types of server to setup. Comma separated values which may contain "home", "docs", static" and/or "app". Defaults to "home,docs,static". | GRIST_SERVERS | the types of server to setup. Comma separated values which may contain "home", "docs", static" and/or "app". Defaults to "home,docs,static". |
GRIST_SESSION_COOKIE | if set, overrides the name of Grist's cookie | GRIST_SESSION_COOKIE | if set, overrides the name of Grist's cookie |
GRIST_SESSION_DOMAIN | if set, associates the cookie with the given domain - otherwise defaults to GRIST_DOMAIN | GRIST_SESSION_DOMAIN | if set, associates the cookie with the given domain - otherwise defaults to GRIST_DOMAIN |
GRIST_SESSION_SECRET | a key used to encode sessions | GRIST_SESSION_SECRET | a key used to encode sessions |
GRIST_SKIP_BUNDLED_WIDGETS | if set, Grist will ignore any bundled widgets included via NPM packages. | GRIST_SKIP_BUNDLED_WIDGETS | if set, Grist will ignore any bundled widgets included via NPM packages. |
GRIST_ANON_PLAYGROUND | When set to 'false' deny anonymous users access to the home page | GRIST_ANON_PLAYGROUND | When set to 'false' deny anonymous users access to the home page |
GRIST_FORCE_LOGIN | Much like GRIST_ANON_PLAYGROUND but don't support anonymous access at all (features like sharing docs publicly requires authentication) | GRIST_FORCE_LOGIN | Much like GRIST_ANON_PLAYGROUND but don't support anonymous access at all (features like sharing docs publicly requires authentication) |
GRIST_SINGLE_ORG | set to an org "domain" to pin client to that org | GRIST_SINGLE_ORG | set to an org "domain" to pin client to that org |
GRIST_TEMPLATE_ORG | set to an org "domain" to show public docs from that org | GRIST_TEMPLATE_ORG | set to an org "domain" to show public docs from that org |
GRIST_HELP_CENTER | set the help center link ref | GRIST_HELP_CENTER | set the help center link ref |
FREE_COACHING_CALL_URL | set the link to the human help (example: email adress or meeting scheduling tool) | FREE_COACHING_CALL_URL | set the link to the human help (example: email adress or meeting scheduling tool) |
GRIST_CONTACT_SUPPORT_URL | set the link to contact support on error pages (example: email adress or online form) | GRIST_CONTACT_SUPPORT_URL | set the link to contact support on error pages (example: email adress or online form) |
GRIST_SUPPORT_ANON | if set to 'true', show UI for anonymous access (not shown by default) | GRIST_SUPPORT_ANON | if set to 'true', show UI for anonymous access (not shown by default) |
GRIST_SUPPORT_EMAIL | if set, give a user with the specified email support powers. The main extra power is the ability to share sites, workspaces, and docs with all users in a listed way. | GRIST_SUPPORT_EMAIL | if set, give a user with the specified email support powers. The main extra power is the ability to share sites, workspaces, and docs with all users in a listed way. |
GRIST_TELEMETRY_LEVEL | the telemetry level. Can be set to: `off` (default), `limited`, or `full`. | GRIST_TELEMETRY_LEVEL | the telemetry level. Can be set to: `off` (default), `limited`, or `full`. |
GRIST_THROTTLE_CPU | if set, CPU throttling is enabled | GRIST_THROTTLE_CPU | if set, CPU throttling is enabled |
GRIST_TRUST_PLUGINS | if set, plugins are expect to be served from the same host as the rest of the Grist app, rather than from a distinct host. Ordinarily, plugins are served from a distinct host so that the cookies used by the Grist app are not automatically available to them. Enable this only if you understand the security implications. | GRIST_TRUST_PLUGINS | if set, plugins are expect to be served from the same host as the rest of the Grist app, rather than from a distinct host. Ordinarily, plugins are served from a distinct host so that the cookies used by the Grist app are not automatically available to them. Enable this only if you understand the security implications. |
GRIST_USER_ROOT | an extra path to look for plugins in - Grist will scan for plugins in `$GRIST_USER_ROOT/plugins`. | GRIST_USER_ROOT | an extra path to look for plugins in - Grist will scan for plugins in `$GRIST_USER_ROOT/plugins`. |
GRIST_UI_FEATURES | comma-separated list of UI features to enable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist`. If a part also exists in GRIST_HIDE_UI_ELEMENTS, it won't be enabled. | GRIST_UI_FEATURES | comma-separated list of UI features to enable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist`. If a part also exists in GRIST_HIDE_UI_ELEMENTS, it won't be enabled. |
GRIST_UNTRUSTED_PORT | if set, plugins will be served from the given port. This is an alternative to setting APP_UNTRUSTED_URL. | GRIST_UNTRUSTED_PORT | if set, plugins will be served from the given port. This is an alternative to setting APP_UNTRUSTED_URL. |
GRIST_WIDGET_LIST_URL | a url pointing to a widget manifest, by default `https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json` is used | GRIST_WIDGET_LIST_URL | a url pointing to a widget manifest, by default `https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json` is used |
COOKIE_MAX_AGE | session cookie max age, defaults to 90 days; can be set to "none" to make it a session cookie | COOKIE_MAX_AGE | session cookie max age, defaults to 90 days; can be set to "none" to make it a session cookie |
HOME_PORT | port number to listen on for REST API server; if set to "share", add API endpoints to regular grist port. | HOME_PORT | port number to listen on for REST API server; if set to "share", add API endpoints to regular grist port. |
PORT | port number to listen on for Grist server | PORT | port number to listen on for Grist server |
REDIS_URL | optional redis server for browser sessions and db query caching | REDIS_URL | optional redis server for browser sessions and db query caching |
GRIST_SKIP_REDIS_CHECKSUM_MISMATCH | Experimental. If set, only warn if the checksum in Redis differs with the one in your S3 backend storage. You may turn it on if your backend storage implements the [read-after-write consistency](https://aws.amazon.com/fr/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/). Defaults to false. | GRIST_SKIP_REDIS_CHECKSUM_MISMATCH | Experimental. If set, only warn if the checksum in Redis differs with the one in your S3 backend storage. You may turn it on if your backend storage implements the [read-after-write consistency](https://aws.amazon.com/fr/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/). Defaults to false. |
GRIST_SNAPSHOT_TIME_CAP | optional. Define the caps for tracking buckets. Usage: {"hour": 25, "day": 32, "isoWeek": 12, "month": 96, "year": 1000} | GRIST_SNAPSHOT_TIME_CAP | optional. Define the caps for tracking buckets. Usage: {"hour": 25, "day": 32, "isoWeek": 12, "month": 96, "year": 1000} |
GRIST_SNAPSHOT_KEEP | optional. Number of recent snapshots to retain unconditionally for a document, regardless of when they were made | GRIST_SNAPSHOT_KEEP | optional. Number of recent snapshots to retain unconditionally for a document, regardless of when they were made |
GRIST_PROMCLIENT_PORT | optional. If set, serve the Prometheus metrics on the specified port number. ⚠️ Be sure to use a port which is not publicly exposed ⚠️. | GRIST_PROMCLIENT_PORT | optional. If set, serve the Prometheus metrics on the specified port number. ⚠️ Be sure to use a port which is not publicly exposed ⚠️. |
#### AI Formula Assistant related variables (all optional): #### AI Formula Assistant related variables (all optional):

View File

@ -6,6 +6,7 @@ import {AppModel, reportError} from 'app/client/models/AppModel';
import {IProgress} from 'app/client/models/NotifyModel'; import {IProgress} from 'app/client/models/NotifyModel';
import {openFilePicker} from 'app/client/ui/FileDialog'; import {openFilePicker} from 'app/client/ui/FileDialog';
import {byteString} from 'app/common/gutil'; import {byteString} from 'app/common/gutil';
import { AxiosProgressEvent } from 'axios';
import {Disposable} from 'grainjs'; import {Disposable} from 'grainjs';
/** /**
@ -39,9 +40,9 @@ export async function fileImport(
const timezone = await guessTimezone(); const timezone = await guessTimezone();
if (workspaceId === "unsaved") { if (workspaceId === "unsaved") {
function onUploadProgress(ev: ProgressEvent) { function onUploadProgress(ev: AxiosProgressEvent) {
if (ev.lengthComputable) { if (ev.event.lengthComputable) {
progress.setUploadProgress(ev.loaded / ev.total * 100); // percentage complete progress.setUploadProgress(ev.event.loaded / ev.event.total * 100); // percentage complete
} }
} }
return await app.api.importUnsavedDoc(files[0], {timezone, onUploadProgress}); return await app.api.importUnsavedDoc(files[0], {timezone, onUploadProgress});

View File

@ -22,6 +22,7 @@ import {
WebhookUpdate WebhookUpdate
} from 'app/common/Triggers'; } from 'app/common/Triggers';
import {addCurrentOrgToPath, getGristConfig} from 'app/common/urlUtils'; import {addCurrentOrgToPath, getGristConfig} from 'app/common/urlUtils';
import { AxiosProgressEvent } from 'axios';
import omitBy from 'lodash/omitBy'; import omitBy from 'lodash/omitBy';
@ -405,7 +406,7 @@ export interface UserAPI {
importUnsavedDoc(material: UploadType, options?: { importUnsavedDoc(material: UploadType, options?: {
filename?: string, filename?: string,
timezone?: string, timezone?: string,
onUploadProgress?: (ev: ProgressEvent) => void, onUploadProgress?: (ev: AxiosProgressEvent) => void,
}): Promise<string>; }): Promise<string>;
deleteUser(userId: number, name: string): Promise<void>; deleteUser(userId: number, name: string): Promise<void>;
getBaseUrl(): string; // Get the prefix for all the endpoints this object wraps. getBaseUrl(): string; // Get the prefix for all the endpoints this object wraps.
@ -826,7 +827,7 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
public async importUnsavedDoc(material: UploadType, options?: { public async importUnsavedDoc(material: UploadType, options?: {
filename?: string, filename?: string,
timezone?: string, timezone?: string,
onUploadProgress?: (ev: ProgressEvent) => void, onUploadProgress?: (ev: AxiosProgressEvent) => void,
}): Promise<string> { }): Promise<string> {
options = options || {}; options = options || {};
const formData = this.newFormData(); const formData = this.newFormData();

View File

@ -132,7 +132,7 @@ export class MinIOExternalStorage implements ExternalStorage {
v.lastModified && (v as any).versionId && v.lastModified && (v as any).versionId &&
(options?.includeDeleteMarkers || !(v as any).isDeleteMarker)) (options?.includeDeleteMarkers || !(v as any).isDeleteMarker))
.map(v => ({ .map(v => ({
lastModified: v.lastModified.toISOString(), lastModified: v.lastModified!.toISOString(),
// Circumvent inconsistency of MinIO API with versionId by casting it to string // Circumvent inconsistency of MinIO API with versionId by casting it to string
// PR to MinIO so we don't have to do that anymore: // PR to MinIO so we don't have to do that anymore:
// https://github.com/minio/minio-js/pull/1193 // https://github.com/minio/minio-js/pull/1193

View File

@ -74,6 +74,10 @@ module.exports = {
{ test: /\.js$/, { test: /\.js$/,
use: ["source-map-loader"], use: ["source-map-loader"],
enforce: "pre" enforce: "pre"
},
{
test: /\.css$/,
type: 'asset/resource'
} }
] ]
}, },

View File

@ -87,14 +87,14 @@
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
"app-module-path": "2.2.0", "app-module-path": "2.2.0",
"catw": "1.0.1",
"chai": "4.2.0", "chai": "4.2.0",
"chai-as-promised": "7.1.1", "chai-as-promised": "7.1.1",
"chance": "1.0.16", "chance": "1.0.16",
"chokidar-cli": "3.0.0",
"esbuild-loader": "2.19.0", "esbuild-loader": "2.19.0",
"eslint": "8.18.0", "eslint": "8.18.0",
"http-proxy": "1.18.1", "http-proxy": "1.18.1",
"i18next-scanner": "4.1.0", "i18next-scanner": "4.4.0",
"mocha": "10.2.0", "mocha": "10.2.0",
"mocha-webdriver": "0.3.2", "mocha-webdriver": "0.3.2",
"moment-locales-webpack-plugin": "^1.2.0", "moment-locales-webpack-plugin": "^1.2.0",
@ -106,7 +106,7 @@
"tmp-promise": "1.0.5", "tmp-promise": "1.0.5",
"ts-interface-builder": "0.3.2", "ts-interface-builder": "0.3.2",
"typescript": "4.7.4", "typescript": "4.7.4",
"webpack": "5.73.0", "webpack": "5.91.0",
"webpack-cli": "4.10.0", "webpack-cli": "4.10.0",
"why-is-node-running": "2.0.3" "why-is-node-running": "2.0.3"
}, },
@ -123,9 +123,9 @@
"accept-language-parser": "1.5.0", "accept-language-parser": "1.5.0",
"ace-builds": "1.23.3", "ace-builds": "1.23.3",
"async-mutex": "0.2.4", "async-mutex": "0.2.4",
"axios": "0.21.2", "axios": "1.6.8",
"backbone": "1.3.3", "backbone": "1.3.3",
"bootstrap": "3.3.5", "bootstrap": "3.4.1",
"bootstrap-datepicker": "1.9.0", "bootstrap-datepicker": "1.9.0",
"bowser": "2.7.0", "bowser": "2.7.0",
"collect-js-deps": "^0.1.1", "collect-js-deps": "^0.1.1",
@ -135,7 +135,7 @@
"connect-redis": "3.4.0", "connect-redis": "3.4.0",
"cookie": "0.5.0", "cookie": "0.5.0",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"csv": "4.0.0", "csv": "6.3.8",
"currency-symbol-map": "5.1.0", "currency-symbol-map": "5.1.0",
"diff-match-patch": "1.0.5", "diff-match-patch": "1.0.5",
"dompurify": "3.0.6", "dompurify": "3.0.6",
@ -143,7 +143,7 @@
"engine.io": "^6.5.4", "engine.io": "^6.5.4",
"engine.io-client": "^6.5.3", "engine.io-client": "^6.5.3",
"exceljs": "4.2.1", "exceljs": "4.2.1",
"express": "4.18.2", "express": "4.19.2",
"file-type": "16.5.4", "file-type": "16.5.4",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"grain-rpc": "0.1.7", "grain-rpc": "0.1.7",
@ -160,12 +160,12 @@
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"jsdom": "^23.0.0", "jsdom": "^23.0.0",
"jsesc": "3.0.2", "jsesc": "3.0.2",
"jsonwebtoken": "8.3.0", "jsonwebtoken": "9.0.2",
"knockout": "3.5.0", "knockout": "3.5.0",
"locale-currency": "0.0.2", "locale-currency": "0.0.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"marked": "4.2.12", "marked": "4.2.12",
"minio": "7.0.32", "minio": "7.1.3",
"moment": "2.29.4", "moment": "2.29.4",
"moment-timezone": "0.5.35", "moment-timezone": "0.5.35",
"morgan": "1.9.1", "morgan": "1.9.1",
@ -184,13 +184,13 @@
"randomcolor": "0.5.3", "randomcolor": "0.5.3",
"redis": "3.1.1", "redis": "3.1.1",
"redlock": "3.1.2", "redlock": "3.1.2",
"saml2-js": "2.0.5", "saml2-js": "4.0.2",
"short-uuid": "3.1.1", "short-uuid": "3.1.1",
"slugify": "1.6.6", "slugify": "1.6.6",
"swagger-ui-dist": "5.11.0", "swagger-ui-dist": "5.11.0",
"tmp": "0.0.33", "tmp": "0.0.33",
"ts-interface-checker": "1.0.2", "ts-interface-checker": "1.0.2",
"typeorm": "0.3.9", "typeorm": "0.3.20",
"underscore": "1.12.1", "underscore": "1.12.1",
"uuid": "3.3.2", "uuid": "3.3.2",
"winston": "2.4.5", "winston": "2.4.5",

View File

@ -16,7 +16,9 @@ if [ ! -e _build ]; then
fi fi
tsc --build -w --preserveWatchOutput $PROJECT & tsc --build -w --preserveWatchOutput $PROJECT &
catw app/client/*.css app/client/*/*.css -o static/bundle.css -v & webpack --config $WEBPACK_CONFIG --mode development --watch & css_files="app/client/**/*.css"
chokidar "${css_files}" -c "bash -O globstar -c 'cat ${css_files} > static/bundle.css'" &
webpack --config $WEBPACK_CONFIG --mode development --watch &
NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js & NODE_PATH=_build:_build/stubs:_build/ext nodemon ${NODE_INSPECT:+--inspect} --delay 1 -w _build/app/server -w _build/app/common _build/stubs/app/server/server.js &
wait wait

View File

@ -207,12 +207,12 @@
"Duplicate Table": "Dupliquer la table", "Duplicate Table": "Dupliquer la table",
"You do not have edit access to this document": "Vous navez pas accès en écriture à ce document", "You do not have edit access to this document": "Vous navez pas accès en écriture à ce document",
"Delete {{formattedTableName}} data, and remove it from all pages?": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?", "Delete {{formattedTableName}} data, and remove it from all pages?": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?",
"Edit Record Card": "Modifier la vue carte", "Edit Record Card": "Modifier la vue fiche",
"Rename Table": "Renommer la table", "Rename Table": "Renommer la table",
"{{action}} Record Card": "{{action}} la vue carte", "{{action}} Record Card": "{{action}} la vue fiche",
"Record Card": "Vue carte", "Record Card": "Vue fiche",
"Remove Table": "Supprimer la table", "Remove Table": "Supprimer la table",
"Record Card Disabled": "Vue carte désactivée" "Record Card Disabled": "Vue fiche désactivée"
}, },
"DocHistory": { "DocHistory": {
"Activity": "Activité", "Activity": "Activité",
@ -478,7 +478,8 @@
"Welcome to {{- orgName}}": "Bienvenue sur {{- orgName}}", "Welcome to {{- orgName}}": "Bienvenue sur {{- orgName}}",
"Visit our {{link}} to learn more about Grist.": "Visitez notre {{link}} pour en savoir plus sur Grist.", "Visit our {{link}} to learn more about Grist.": "Visitez notre {{link}} pour en savoir plus sur Grist.",
"Sign in": "Connexion", "Sign in": "Connexion",
"To use Grist, please either sign up or sign in.": "Pour utiliser Grist, connectez-vous ou créez-vous un compte." "To use Grist, please either sign up or sign in.": "Pour utiliser Grist, connectez-vous ou créez-vous un compte.",
"Learn more in our {{helpCenterLink}}, or find an expert via our {{sproutsProgram}}.": "Pour en savoir plus, consultez notre {{helpCenterLink}}, ou trouvez un expert via notre {{sproutsProgram}}."
}, },
"HomeLeftPane": { "HomeLeftPane": {
"All Documents": "Tous les documents", "All Documents": "Tous les documents",
@ -665,7 +666,7 @@
"Duplicate rows_other": "Dupliquer les lignes", "Duplicate rows_other": "Dupliquer les lignes",
"Delete": "Supprimer", "Delete": "Supprimer",
"Copy anchor link": "Copier l'ancre", "Copy anchor link": "Copier l'ancre",
"View as card": "Voir en carte", "View as card": "Voir en fiche",
"Use as table headers": "Utiliser en tant qu'en-têtes de table" "Use as table headers": "Utiliser en tant qu'en-têtes de table"
}, },
"SelectionSummary": { "SelectionSummary": {
@ -694,7 +695,10 @@
"Share": "Partager", "Share": "Partager",
"Download...": "Télécharger...", "Download...": "Télécharger...",
"Microsoft Excel (.xlsx)": "Microsoft Excel (.xlsx)", "Microsoft Excel (.xlsx)": "Microsoft Excel (.xlsx)",
"Export as...": "Exporter en tant que..." "Export as...": "Exporter en tant que...",
"Comma Separated Values (.csv)": "Comma Separated Values (.csv)",
"DOO Separated Values (.dsv)": "DOO Separated Values (.dsv)",
"Tab Separated Values (.tsv)": "Tab Separated Values (.tsv)"
}, },
"SiteSwitcher": { "SiteSwitcher": {
"Switch Sites": "Changer despace", "Switch Sites": "Changer despace",
@ -776,7 +780,7 @@
"Form": "Formulaire", "Form": "Formulaire",
"Compact": "Compact", "Compact": "Compact",
"Blocks": "Blocs", "Blocks": "Blocs",
"Edit Card Layout": "Disposition de la carte", "Edit Card Layout": "Disposition de la fiche",
"Plugin: ": "Plugin : ", "Plugin: ": "Plugin : ",
"Section: ": "Section : " "Section: ": "Section : "
}, },
@ -787,7 +791,7 @@
"Print widget": "Imprimer la vue", "Print widget": "Imprimer la vue",
"Download as CSV": "Télécharger en CSV", "Download as CSV": "Télécharger en CSV",
"Download as XLSX": "Télécharger en XLSX", "Download as XLSX": "Télécharger en XLSX",
"Edit Card Layout": "Disposition de la carte", "Edit Card Layout": "Disposition de la fiche",
"Widget options": "Options de la vue", "Widget options": "Options de la vue",
"Advanced Sort & Filter": "Tri et filtre avancés", "Advanced Sort & Filter": "Tri et filtre avancés",
"Data selection": "Sélection des données", "Data selection": "Sélection des données",
@ -931,7 +935,11 @@
}, },
"NTextBox": { "NTextBox": {
"false": "faux", "false": "faux",
"true": "vrai" "true": "vrai",
"Single line": "Ligne unique",
"Multi line": "Multi-lignes",
"Lines": "Lignes",
"Field Format": "Format du champ"
}, },
"ViewAsDropdown": { "ViewAsDropdown": {
"View As": "Voir en tant que", "View As": "Voir en tant que",
@ -1002,7 +1010,7 @@
"Make it relational! Use the {{ref}} type to link tables. ": "Rendez-le relationnel ! Utilisez le type {{ref}} pour lier les tableaux. ", "Make it relational! Use the {{ref}} type to link tables. ": "Rendez-le relationnel ! Utilisez le type {{ref}} pour lier les tableaux. ",
"Toggle the {{creatorPanel}} to format columns, ": "Ouvrez le {{creatorPanel}} pour formater les colonnes ", "Toggle the {{creatorPanel}} to format columns, ": "Ouvrez le {{creatorPanel}} pour formater les colonnes ",
"Use {{addNew}} to add widgets, pages, or import more data. ": "Cliquez sur {{addNew}} pour ajouter des vues, des pages ou importer plus de données. ", "Use {{addNew}} to add widgets, pages, or import more data. ": "Cliquez sur {{addNew}} pour ajouter des vues, des pages ou importer plus de données. ",
"convert to card view, select data, and more.": "convertir en vue carte, sélectionner des données et plus.", "convert to card view, select data, and more.": "convertir en vue fiche, sélectionner des données et plus.",
"Set formatting options, formulas, or column types, such as dates, choices, or attachments. ": "Définissez des options de formatage de la colonne, des formules ou bien les types de la colonne (dates, liste à choix unique, pièce jointe etc.) ", "Set formatting options, formulas, or column types, such as dates, choices, or attachments. ": "Définissez des options de formatage de la colonne, des formules ou bien les types de la colonne (dates, liste à choix unique, pièce jointe etc.) ",
"Use {{helpCenter}} for documentation or questions.": "Cliquez sur {{helpCenter}} pour voir la documentation et poser vos questions.", "Use {{helpCenter}} for documentation or questions.": "Cliquez sur {{helpCenter}} pour voir la documentation et poser vos questions.",
"creator panel": "menu latéral" "creator panel": "menu latéral"
@ -1065,7 +1073,11 @@
"Decimals": "Décimales", "Decimals": "Décimales",
"Currency": "Devise", "Currency": "Devise",
"Default currency ({{defaultCurrency}})": "Devise par défaut ({{defaultCurrency}})", "Default currency ({{defaultCurrency}})": "Devise par défaut ({{defaultCurrency}})",
"Number Format": "Format de nombre" "Number Format": "Format de nombre",
"min": "min",
"Text": "Texte",
"max": "max",
"Field Format": "Format du champ"
}, },
"LanguageMenu": { "LanguageMenu": {
"Language": "Langue" "Language": "Langue"
@ -1092,12 +1104,12 @@
"Only those rows will appear which match all of the filters.": "Seules les lignes qui correspondent à tous les filtres apparaîtront.", "Only those rows will appear which match all of the filters.": "Seules les lignes qui correspondent à tous les filtres apparaîtront.",
"Select the table to link to.": "Sélectionnez la table vers laquelle vous souhaitez établir un lien.", "Select the table to link to.": "Sélectionnez la table vers laquelle vous souhaitez établir un lien.",
"Selecting Data": "Sélection de données", "Selecting Data": "Sélection de données",
"Rearrange the fields in your card by dragging and resizing cells.": "Réorganisez les champs de votre vue carte en faisant glisser et en redimensionnant les cellules.", "Rearrange the fields in your card by dragging and resizing cells.": "Réorganisez les champs de votre vue fiche en faisant glisser et en redimensionnant les cellules.",
"The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "La page des données sources liste toutes les tables de votre document, y compris les tables récapitulatives et les tables non-inclues dans les mises en page.", "The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "La page des données sources liste toutes les tables de votre document, y compris les tables récapitulatives et les tables non-inclues dans les mises en page.",
"The total size of all data in this document, excluding attachments.": "La taille totale de toutes les données de ce document, à l'exclusion des pièces jointes.", "The total size of all data in this document, excluding attachments.": "La taille totale de toutes les données de ce document, à l'exclusion des pièces jointes.",
"Click the Add New button to create new documents or workspaces, or import data.": "Cliquez sur le bouton Nouveau pour créer de nouveaux documents dans votre espace de travail ou importer des données.", "Click the Add New button to create new documents or workspaces, or import data.": "Cliquez sur le bouton Nouveau pour créer de nouveaux documents dans votre espace de travail ou importer des données.",
"Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.": "En cliquant sur {{EyeHideIcon}} dans chaque cellule, vous masquez le champ de cette vue sans le supprimer.", "Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.": "En cliquant sur {{EyeHideIcon}} dans chaque cellule, vous masquez le champ de cette vue sans le supprimer.",
"Editing Card Layout": "Modification de la mise en page de la carte", "Editing Card Layout": "Modification de la mise en page de la fiche",
"Linking Widgets": "Lier les vues", "Linking Widgets": "Lier les vues",
"Nested Filtering": "Filtrage imbriqué", "Nested Filtering": "Filtrage imbriqué",
"Pinned filters are displayed as buttons above the widget.": "Les filtres épinglés sont affichés sous forme de boutons au-dessus de la vue.", "Pinned filters are displayed as buttons above the widget.": "Les filtres épinglés sont affichés sous forme de boutons au-dessus de la vue.",
@ -1230,7 +1242,8 @@
"Opt in to Telemetry": "S'inscrire à l'envoi de données de télémétrie", "Opt in to Telemetry": "S'inscrire à l'envoi de données de télémétrie",
"Opted In": "Accepté", "Opted In": "Accepté",
"Support Grist page": "Soutenir Grist", "Support Grist page": "Soutenir Grist",
"Admin Panel": "Panneau d'administration" "Admin Panel": "Panneau d'administration",
"Thank you! Your trust and support is greatly appreciated. Opt out any time from the {{link}} in the user menu.": "Nous vous remercions! Votre confiance et votre soutien sont très appréciés. Vous pouvez vous désinscrire à tout moment en cliquant sur le {{link}} dans le menu utilisateur."
}, },
"GridView": { "GridView": {
"Click to insert": "Cliquer pour insérer" "Click to insert": "Cliquer pour insérer"
@ -1327,12 +1340,12 @@
"You have access to the following Grist sites.": "Vous avez accès aux espaces Grist suivants." "You have access to the following Grist sites.": "Vous avez accès aux espaces Grist suivants."
}, },
"CardContextMenu": { "CardContextMenu": {
"Insert card above": "Insérer une carte au dessus", "Insert card above": "Insérer une fiche au dessus",
"Duplicate card": "Dupliquer la carte", "Duplicate card": "Dupliquer la fiche",
"Insert card below": "Insérer une carte en dessous", "Insert card below": "Insérer une fiche en dessous",
"Delete card": "Supprimer la carte", "Delete card": "Supprimer la fiche",
"Copy anchor link": "Copier le lien d'ancrage", "Copy anchor link": "Copier le lien d'ancrage",
"Insert card": "Insérer une carte" "Insert card": "Insérer une fiche"
}, },
"WelcomeCoachingCall": { "WelcomeCoachingCall": {
"Maybe Later": "Peut-être plus tard", "Maybe Later": "Peut-être plus tard",
@ -1387,7 +1400,17 @@
}, },
"FormConfig": { "FormConfig": {
"Field rules": "Règles du champ", "Field rules": "Règles du champ",
"Required field": "Champ obligatoire" "Required field": "Champ obligatoire",
"Ascending": "Croissant",
"Default": "Par défaut",
"Descending": "Décroissant",
"Field Format": "Format du champ",
"Field Rules": "Règles du champ",
"Horizontal": "Horizontale",
"Options Alignment": "Option d'alignement",
"Options Sort Order": "Option d'ordonnancement",
"Vertical": "Verticale",
"Radio": "Radio"
}, },
"Editor": { "Editor": {
"Delete": "Supprimer" "Delete": "Supprimer"
@ -1414,7 +1437,8 @@
}, },
"FormSuccessPage": { "FormSuccessPage": {
"Form Submitted": "Formulaire envoyé", "Form Submitted": "Formulaire envoyé",
"Thank you! Your response has been recorded.": "Nous vous remercions. Votre réponse a été enregistrée." "Thank you! Your response has been recorded.": "Nous vous remercions. Votre réponse a été enregistrée.",
"Submit new response": "Soumettre une nouvelle réponse"
}, },
"DateRangeOptions": { "DateRangeOptions": {
"Last 30 days": "30 derniers jours", "Last 30 days": "30 derniers jours",
@ -1449,5 +1473,29 @@
"Sponsor": "Parrainage", "Sponsor": "Parrainage",
"Support Grist": "Soutenir Grist", "Support Grist": "Soutenir Grist",
"Version": "Version" "Version": "Version"
},
"Field": {
"No choices configured": "Aucun choix configuré",
"No values in show column of referenced table": "Pas de valeurs dans la colonne montrée de la table de référence"
},
"CreateTeamModal": {
"Cancel": "Annuler",
"Choose a name and url for your team site": "Choisissez un nom et une adresse URL pour votre espace d'équipe",
"Create site": "Créer",
"Domain name is invalid": "Le nom de domaine n'est pas valide",
"Domain name is required": "Le nom de domaine est requis",
"Go to your site": "Aller vers votre espace d'équipe",
"Team name": "Nom de l'espace d'équipe",
"Team site created": "L'espace d'équipe a été créé",
"Team url": "Url de l'espace d'équipe",
"Work as a Team": "Travailler en équipe",
"Billing is not supported in grist-core": "La facturation n'est pas prise en charge dans un Grist auto hébergé",
"Team name is required": "Le nom de l'espace d'équipe est requis"
},
"Toggle": {
"Field Format": "Format du champ"
},
"Columns": {
"Remove Column": "Supprimer la colonne"
} }
} }

View File

@ -1274,7 +1274,7 @@
"User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Пользователь наследует разрешения от {{parent}}. Чтобы удалить, установите для параметра 'Наследовать доступ' значение 'None'.", "User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Пользователь наследует разрешения от {{parent}}. Чтобы удалить, установите для параметра 'Наследовать доступ' значение 'None'.",
"Allow anyone with the link to open.": "Разрешить открывать всем, у кого есть ссылка.", "Allow anyone with the link to open.": "Разрешить открывать всем, у кого есть ссылка.",
"Grist support": "Grist поддержка", "Grist support": "Grist поддержка",
"Invite people to {{resourceType}}": "Пригласите людей в {{resourceType}}", "Invite people to {{resourceType}}": "Пригласите людей к {{resourceType}}",
"Create a team to share with more people": "Создайте команду, чтобы поделиться с большим количеством людей", "Create a team to share with more people": "Создайте команду, чтобы поделиться с большим количеством людей",
"Link copied to clipboard": "Ссылка скопирована в буфер обмена", "Link copied to clipboard": "Ссылка скопирована в буфер обмена",
"No default access allows access to be granted to individual documents or workspaces, rather than the full team site.": "Отсутствие доступа по умолчанию позволяет предоставлять доступ к отдельным документам или рабочим областям, а не ко всему групповому сайту.", "No default access allows access to be granted to individual documents or workspaces, rather than the full team site.": "Отсутствие доступа по умолчанию позволяет предоставлять доступ к отдельным документам или рабочим областям, а не ко всему групповому сайту.",

View File

@ -31,7 +31,7 @@ import { ActiveDoc, Deps as ActiveDocDeps } from "app/server/lib/ActiveDoc";
import { DEPS, sendForCompletion } from "app/server/lib/Assistance"; import { DEPS, sendForCompletion } from "app/server/lib/Assistance";
import log from 'app/server/lib/log'; import log from 'app/server/lib/log';
import crypto from 'crypto'; import crypto from 'crypto';
import parse from 'csv-parse/lib/sync'; import { parse } from 'csv-parse/sync';
import fetch, {RequestInfo, RequestInit, Response} from 'node-fetch'; import fetch, {RequestInfo, RequestInit, Response} from 'node-fetch';
import * as fs from "fs"; import * as fs from "fs";
import JSZip from "jszip"; import JSZip from "jszip";

View File

@ -254,7 +254,7 @@ export class TestSession {
) { ) {
const resp = await axios.get(`${this.home.getOwnUrl()}/test/session`, const resp = await axios.get(`${this.home.getOwnUrl()}/test/session`,
{validateStatus: (s => s < 400), headers: this.headers}); {validateStatus: (s => s < 400), headers: this.headers});
const cookie = this.headers.Cookie || resp.headers['set-cookie'][0]; const cookie = this.headers.Cookie || resp.headers['set-cookie']![0];
const cid = decodeURIComponent(cookie.split('=')[1].split(';')[0]); const cid = decodeURIComponent(cookie.split('=')[1].split(';')[0]);
const sessionId = this.home.getSessions().getSessionIdFromCookie(cid); const sessionId = this.home.getSessions().getSessionIdFromCookie(cid);
const scopedSession = this.home.getSessions().getOrCreateSession(sessionId as string, org, ''); const scopedSession = this.home.getSessions().getOrCreateSession(sessionId as string, org, '');

View File

@ -21,7 +21,7 @@ export function configForUser(username: string): AxiosRequestConfig {
} }
}; };
if (username !== 'Anonymous') { if (username !== 'Anonymous') {
config.headers.Authorization = 'Bearer api_key_for_' + username.toLowerCase(); config.headers!.Authorization = 'Bearer api_key_for_' + username.toLowerCase();
} }
return config; return config;
} }

View File

@ -166,7 +166,7 @@ export async function openClient(server: FlexServer, email: string, org: string,
const headers: Record<string, string> = {}; const headers: Record<string, string> = {};
if (!emailHeader) { if (!emailHeader) {
const resp = await axios.get(`${server.getOwnUrl()}/test/session`); const resp = await axios.get(`${server.getOwnUrl()}/test/session`);
const cookie = resp.headers['set-cookie'][0]; const cookie = resp.headers['set-cookie']![0];
if (email !== 'anon@getgrist.com') { if (email !== 'anon@getgrist.com') {
const cid = decodeURIComponent(cookie.split('=')[1].split(';')[0]); const cid = decodeURIComponent(cookie.split('=')[1].split(';')[0]);
const comm = server.getComm(); const comm = server.getComm();

View File

@ -1351,10 +1351,10 @@ function testDocApi() {
} }
} else { } else {
if (sort) { if (sort) {
config.headers['x-sort'] = sort.join(','); config.headers!['x-sort'] = sort.join(',');
} }
if (limit) { if (limit) {
config.headers['x-limit'] = String(limit); config.headers!['x-limit'] = String(limit);
} }
} }
return axios.get(url.href, config); return axios.get(url.href, config);
@ -4976,11 +4976,11 @@ function testDocApi() {
const chimpyConfig = configForUser("Chimpy"); const chimpyConfig = configForUser("Chimpy");
const anonConfig = configForUser("Anonymous"); const anonConfig = configForUser("Anonymous");
delete chimpyConfig.headers["X-Requested-With"]; delete chimpyConfig.headers!["X-Requested-With"];
delete anonConfig.headers["X-Requested-With"]; delete anonConfig.headers!["X-Requested-With"];
// Target a more realistic Host than "localhost:port" // Target a more realistic Host than "localhost:port"
anonConfig.headers.Host = chimpyConfig.headers.Host = 'api.example.com'; anonConfig.headers!.Host = chimpyConfig.headers!.Host = 'api.example.com';
const url = `${serverUrl}/api/docs/${docId}/tables/Table1/records`; const url = `${serverUrl}/api/docs/${docId}/tables/Table1/records`;
const data = { records: [{ fields: {} }] }; const data = { records: [{ fields: {} }] };
@ -4989,7 +4989,7 @@ function testDocApi() {
const forbiddenOrigin = 'http://evil.com'; const forbiddenOrigin = 'http://evil.com';
// Normal same origin requests // Normal same origin requests
anonConfig.headers.Origin = allowedOrigin; anonConfig.headers!.Origin = allowedOrigin;
let response: AxiosResponse; let response: AxiosResponse;
for (response of [ for (response of [
await axios.post(url, data, anonConfig), await axios.post(url, data, anonConfig),
@ -5005,7 +5005,7 @@ function testDocApi() {
// Cross origin requests from untrusted origin. // Cross origin requests from untrusted origin.
for (const config of [anonConfig, chimpyConfig]) { for (const config of [anonConfig, chimpyConfig]) {
config.headers.Origin = forbiddenOrigin; config.headers!.Origin = forbiddenOrigin;
for (response of [ for (response of [
await axios.post(url, data, config), await axios.post(url, data, config),
await axios.get(url, config), await axios.get(url, config),

View File

@ -28,6 +28,12 @@ describe('ManyFetches', function() {
let docs: TestServer; let docs: TestServer;
let userApi: UserAPIImpl; let userApi: UserAPIImpl;
before(function () {
if (!process.env.TEST_REDIS_URL) {
return this.skip();
}
});
beforeEach(async function() { beforeEach(async function() {
oldEnv = new EnvironmentSnapshot(); // Needed for prepareDatabase, which changes process.env oldEnv = new EnvironmentSnapshot(); // Needed for prepareDatabase, which changes process.env
log.info("Starting servers"); log.info("Starting servers");

2331
yarn.lock

File diff suppressed because it is too large Load Diff