Switch to TestContainers for manging services in Playwright (#28860)

* Switch to TestContainers for manging services in Playwright

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Flip fixture dependency order

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove mas dep

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update matrix-authentication-service in Playwright tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix SMTP port

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Comments

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Strip ansi from playwright logs to make them more readable

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Actually do the update

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove access to homeserver.config.baseUrl field in favour of homeserver.baseUrl

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Use sane default_server_config and specify server.invalid in the specific tests which demand it

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix mas run

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* break cycle

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* typo

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* prettier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Wire up basics of dendriteHomeserver

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
pull/28920/head
Michael Telatynski 2025-01-07 18:11:44 +00:00 committed by GitHub
parent 66bbb84e56
commit f75d1f5a5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1922 additions and 2703 deletions

View File

@ -227,7 +227,11 @@ has to be disabled in Playwright on Firefox & Webkit to retain routing functiona
Anything testing VoIP/microphone will need to have `@no-webkit` as fake microphone functionality is not available
there at this time.
## Colima
## Supporter container runtimes
We use testcontainers to spin up various instances of Synapse, Matrix Authentication Service, and more.
It supports Docker out of the box but also has support for Podman, Colima, Rancher, you just need to follow some instructions to achieve it:
https://node.testcontainers.org/supported-container-runtimes/
If you are running under Colima, you may need to set the environment variable `TMPDIR` to `/tmp/colima` or a path
within `$HOME` to allow bind mounting temporary directories into the Docker containers.

View File

@ -180,6 +180,7 @@
"@sentry/webpack-plugin": "^2.7.1",
"@stylistic/eslint-plugin": "^2.9.0",
"@svgr/webpack": "^8.0.0",
"@testcontainers/postgresql": "^10.16.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
@ -191,7 +192,6 @@
"@types/escape-html": "^1.0.1",
"@types/express": "^5.0.0",
"@types/file-saver": "^2.0.3",
"@types/fs-extra": "^11.0.0",
"@types/glob-to-regexp": "^0.4.1",
"@types/jest": "29.5.12",
"@types/jitsi-meet": "^2.0.2",
@ -243,7 +243,6 @@
"fetch-mock": "9.11.0",
"fetch-mock-jest": "^1.5.1",
"file-loader": "^6.0.0",
"fs-extra": "^11.0.0",
"glob": "^11.0.0",
"html-webpack-plugin": "^5.5.3",
"husky": "^9.0.0",
@ -278,11 +277,13 @@
"rimraf": "^6.0.0",
"semver": "^7.5.2",
"source-map-loader": "^5.0.0",
"strip-ansi": "^7.1.0",
"stylelint": "^16.1.0",
"stylelint-config-standard": "^36.0.0",
"stylelint-scss": "^6.0.0",
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
"terser-webpack-plugin": "^5.3.9",
"testcontainers": "^10.16.0",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.3",
"typescript": "5.7.2",

View File

@ -13,7 +13,9 @@ Please see LICENSE files in the repository root for full details.
import { expect, test } from "../../element-web-test";
test.use({
startHomeserverOpts: "guest-enabled",
synapseConfigOptions: {
allow_guest_access: true,
},
});
test("Shows the welcome page by default", async ({ page }) => {

View File

@ -9,9 +9,10 @@ Please see LICENSE files in the repository root for full details.
import { type Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { test as masTest, registerAccountMas } from "../oidc";
import { registerAccountMas } from "../oidc";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { TestClientServerAPI } from "../csAPI";
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
async function expectBackupVersionToBe(page: Page, version: string) {
await expect(page.locator(".mx_SecureBackupPanel_statusList tr:nth-child(5) td")).toHaveText(
@ -24,22 +25,23 @@ async function expectBackupVersionToBe(page: Page, version: string) {
// These tests register an account with MAS because then we go through the "normal" registration flow
// and crypto gets set up. Using the 'user' fixture create a a user an synthesizes an existing login,
// which is faster but leaves us without crypto set up.
masTest.describe("Encryption state after registration", () => {
masTest.skip(isDendrite, "does not yet support MAS");
test.describe("Encryption state after registration", () => {
test.use(masHomeserver);
test.skip(isDendrite, "does not yet support MAS");
masTest("Key backup is enabled by default", async ({ page, mailhog, app }) => {
test("Key backup is enabled by default", async ({ page, mailhogClient, app }) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, "alice", "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
await app.settings.openUserSettings("Security & Privacy");
await expect(page.getByText("This session is backing up your keys.")).toBeVisible();
});
masTest("user is prompted to set up recovery", async ({ page, mailhog, app }) => {
test("user is prompted to set up recovery", async ({ page, mailhogClient, app }) => {
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, "alice", "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
@ -50,57 +52,53 @@ masTest.describe("Encryption state after registration", () => {
});
});
masTest.describe("Key backup reset from elsewhere", () => {
masTest.skip(isDendrite, "does not yet support MAS");
test.describe("Key backup reset from elsewhere", () => {
test.use(masHomeserver);
test.skip(isDendrite, "does not yet support MAS");
masTest(
"Key backup is disabled when reset from elsewhere",
async ({ page, mailhog, request, masPrepare, homeserver }) => {
const testUsername = "alice";
const testPassword = "Pa$sW0rD!";
test("Key backup is disabled when reset from elsewhere", async ({ page, mailhogClient, request, homeserver }) => {
const testUsername = "alice";
const testPassword = "Pa$sW0rD!";
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
// clock so we can skip the delay
await page.clock.install();
// there's a delay before keys are uploaded so the error doesn't appear immediately: use a fake
// clock so we can skip the delay
await page.clock.install();
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, testUsername, "alice@email.com", testPassword);
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhogClient, testUsername, "alice@email.com", testPassword);
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();
await page.getByRole("button", { name: "Add room" }).click();
await page.getByRole("menuitem", { name: "New room" }).click();
await page.getByRole("textbox", { name: "Name" }).fill("test room");
await page.getByRole("button", { name: "Create room" }).click();
// @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not.
const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken());
// @ts-ignore - this runs in the browser scope where mxMatrixClientPeg is a thing. Here, it is not.
const accessToken = await page.evaluate(() => mxMatrixClientPeg.get().getAccessToken());
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
const csAPI = new TestClientServerAPI(request, homeserver, accessToken);
const backupInfo = await csAPI.getCurrentBackupInfo();
const backupInfo = await csAPI.getCurrentBackupInfo();
await csAPI.deleteBackupVersion(backupInfo.version);
await csAPI.deleteBackupVersion(backupInfo.version);
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
await page.getByRole("button", { name: "Send message" }).click();
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("/discardsession");
await page.getByRole("button", { name: "Send message" }).click();
await page
.getByRole("textbox", { name: "Send an encrypted message…" })
.fill("Message with broken key backup");
await page.getByRole("button", { name: "Send message" }).click();
await page.getByRole("textbox", { name: "Send an encrypted message…" }).fill("Message with broken key backup");
await page.getByRole("button", { name: "Send message" }).click();
// Should be the message we sent plus the room creation event
await expect(page.locator(".mx_EventTile")).toHaveCount(2);
await expect(
page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"),
).toBeVisible();
// Should be the message we sent plus the room creation event
await expect(page.locator(".mx_EventTile")).toHaveCount(2);
await expect(
page.locator(".mx_RoomView_MessageList > .mx_EventTile_last .mx_EventTile_receiptSent"),
).toBeVisible();
// Wait for it to try uploading the key
await page.clock.fastForward(20000);
// Wait for it to try uploading the key
await page.clock.fastForward(20000);
await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible();
},
);
await expect(page.getByRole("heading", { level: 1, name: "New Recovery Method" })).toBeVisible();
});
});
test.describe("Backups", () => {

View File

@ -8,29 +8,10 @@ Please see LICENSE files in the repository root for full details.
import { Locator, type Page } from "@playwright/test";
import { test as base, expect, Fixtures } from "../../element-web-test";
import { test, expect } from "../../element-web-test";
import { viewRoomSummaryByName } from "../right-panel/utils";
import { isDendrite } from "../../plugins/homeserver/dendrite";
const test = base.extend<Fixtures>({
// eslint-disable-next-line no-empty-pattern
startHomeserverOpts: async ({}, use) => {
await use("dehydration");
},
config: async ({ config, context }, use) => {
const wellKnown = {
...config.default_server_config,
"org.matrix.msc3814": true,
};
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use(config);
},
});
const ROOM_NAME = "Test room";
const NAME = "Alice";
@ -43,6 +24,24 @@ test.describe("Dehydration", () => {
test.use({
displayName: NAME,
synapseConfigOptions: {
experimental_features: {
msc2697_enabled: false,
msc3814_enabled: true,
},
},
config: async ({ config, context }, use) => {
const wellKnown = {
...config.default_server_config,
"org.matrix.msc3814": true,
};
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use(config);
},
});
test("Create dehydrated device", async ({ page, user, app }, workerInfo) => {

View File

@ -9,24 +9,24 @@ Please see LICENSE files in the repository root for full details.
import path from "path";
import { readFile } from "node:fs/promises";
import { expect, Fixtures, test as base } from "../../element-web-test";
const test = base.extend<Fixtures>({
// Replace the `user` fixture with one which populates the indexeddb data before starting the app.
user: async ({ context, pageWithCredentials: page, credentials }, use) => {
await page.route(`/test_indexeddb_cryptostore_dump/*`, async (route, request) => {
const resourcePath = path.join(__dirname, new URL(request.url()).pathname);
const body = await readFile(resourcePath, { encoding: "utf-8" });
await route.fulfill({ body });
});
await page.goto("/test_indexeddb_cryptostore_dump/index.html");
await use(credentials);
},
});
import { expect, test } from "../../element-web-test";
test.describe("migration", { tag: "@no-webkit" }, function () {
test.use({ displayName: "Alice" });
test.use({
displayName: "Alice",
// Replace the `user` fixture with one which populates the indexeddb data before starting the app.
user: async ({ context, pageWithCredentials: page, credentials }, use) => {
await page.route(`/test_indexeddb_cryptostore_dump/*`, async (route, request) => {
const resourcePath = path.join(__dirname, new URL(request.url()).pathname);
const body = await readFile(resourcePath, { encoding: "utf-8" });
await route.fulfill({ body });
});
await page.goto("/test_indexeddb_cryptostore_dump/index.html");
await use(credentials);
},
});
test("Should support migration from legacy crypto", async ({ context, user, page }, workerInfo) => {
test.slow();

View File

@ -8,6 +8,8 @@ Please see LICENSE files in the repository root for full details.
import { expect, test } from "../../element-web-test";
import { selectHomeserver } from "../utils";
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
const username = "user1234";
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
@ -15,6 +17,8 @@ const password = "oETo7MPf0o";
const email = "user@nowhere.dummy";
test.describe("Forgot Password", () => {
test.skip(isDendrite, "not yet wired up");
test.use(emailHomeserver);
test.use({
config: {
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
@ -25,14 +29,6 @@ test.describe("Forgot Password", () => {
},
},
},
startHomeserverOpts: ({ mailhog }, use) =>
use({
template: "email",
variables: {
SMTP_HOST: "host.containers.internal",
SMTP_PORT: mailhog.instance.smtpPort,
},
}),
});
test("renders properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {

View File

@ -7,10 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
test.describe("Consent", () => {
test.use(consentHomeserver);
test.use({
startHomeserverOpts: "consent",
displayName: "Bob",
});

View File

@ -13,6 +13,8 @@ import { doTokenRegistration } from "./utils";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { selectHomeserver } from "../utils";
import { Credentials, HomeserverInstance } from "../../plugins/homeserver";
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
const username = "user1234";
const password = "p4s5W0rD";
@ -91,7 +93,7 @@ test.describe("Login", () => {
});
test.describe("Password login", () => {
test.use({ startHomeserverOpts: "consent" });
test.use(consentHomeserver);
let creds: Credentials;
@ -238,14 +240,7 @@ test.describe("Login", () => {
// tests for old-style SSO login, in which we exchange tokens with Synapse, and Synapse talks to an auth server
test.describe("SSO login", () => {
test.skip(isDendrite, "does not yet support SSO");
test.use({
startHomeserverOpts: ({ oAuthServer }, use) =>
use({
template: "default",
oAuthServerPort: oAuthServer.port,
}),
});
test.use(legacyOAuthHomeserver);
test("logs in with SSO and lands on the home screen", async ({ page, homeserver }) => {
// If this test fails with a screen showing "Timeout connecting to remote server", it is most likely due to
@ -259,7 +254,7 @@ test.describe("Login", () => {
});
test.describe("logout", () => {
test.use({ startHomeserverOpts: "consent" });
test.use(consentHomeserver);
test("should go to login page on logout", async ({ page, user }) => {
await page.getByRole("button", { name: "User menu" }).click();
@ -274,8 +269,8 @@ test.describe("Login", () => {
});
test.describe("logout with logout_redirect_url", () => {
test.use(consentHomeserver);
test.use({
startHomeserverOpts: "consent",
config: {
// We redirect to decoder-ring because it's a predictable page that isn't Element itself.
// We could use example.org, matrix.org, or something else, however this puts dependency of external

View File

@ -11,7 +11,7 @@ import { Page } from "@playwright/test";
import { test, expect } from "../../element-web-test";
import { doTokenRegistration } from "./utils";
import { Credentials } from "../../plugins/homeserver";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { legacyOAuthHomeserver } from "../../plugins/homeserver/synapse/legacyOAuthHomeserver.ts";
test.describe("Soft logout", () => {
test.use({
@ -25,11 +25,6 @@ test.describe("Soft logout", () => {
},
},
},
startHomeserverOpts: ({ oAuthServer }, use) =>
use({
template: "default",
oAuthServerPort: oAuthServer.port,
}),
});
test.describe("with password user", () => {
@ -58,8 +53,7 @@ test.describe("Soft logout", () => {
});
test.describe("with SSO user", () => {
test.skip(isDendrite, "does not yet support SSO");
test.use(legacyOAuthHomeserver);
test.use({
user: async ({ page, homeserver }, use) => {
const user = await doTokenRegistration(page, homeserver);

View File

@ -9,57 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { API, Messages } from "mailhog";
import { Page } from "@playwright/test";
import { test as base, expect } from "../../element-web-test";
import { MatrixAuthenticationService } from "../../plugins/matrix-authentication-service";
import { StartHomeserverOpts } from "../../plugins/homeserver";
export const test = base.extend<{
masPrepare: MatrixAuthenticationService;
mas: MatrixAuthenticationService;
}>({
// There's a bit of a chicken and egg problem between MAS & Synapse where they each need to know how to reach each other
// so spinning up a MAS is split into the prepare & start stage: prepare mas -> homeserver -> start mas to disentangle this.
masPrepare: async ({ context }, use) => {
const mas = new MatrixAuthenticationService(context);
await mas.prepare();
await use(mas);
},
mas: [
async ({ masPrepare: mas, homeserver, mailhog }, use, testInfo) => {
await mas.start(homeserver, mailhog.instance);
await use(mas);
await mas.stop(testInfo);
},
{ auto: true },
],
startHomeserverOpts: async ({ masPrepare }, use) => {
await use({
template: "mas-oidc",
variables: {
MAS_PORT: masPrepare.port,
},
});
},
config: async ({ config, startHomeserverOpts, context }, use) => {
const issuer = `http://localhost:${(startHomeserverOpts as StartHomeserverOpts).variables["MAS_PORT"]}/`;
const wellKnown = {
...config.default_server_config,
"org.matrix.msc2965.authentication": {
issuer,
account: `${issuer}account`,
},
};
// Ensure org.matrix.msc2965.authentication is in well-known
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use(config);
},
});
export { expect };
import { expect } from "../../element-web-test";
export async function registerAccountMas(
page: Page,

View File

@ -6,23 +6,26 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { test, expect, registerAccountMas } from ".";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { test, expect } from "../../element-web-test.ts";
import { registerAccountMas } from ".";
import { ElementAppPage } from "../../pages/ElementAppPage.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { masHomeserver } from "../../plugins/homeserver/synapse/masHomeserver.ts";
test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
test.use(masHomeserver);
test.skip(isDendrite, "does not yet support MAS");
test.slow(); // trace recording takes a while here
test("can register the oauth2 client and an account", async ({ context, page, homeserver, mailhog, mas }) => {
const tokenUri = `http://localhost:${mas.port}/oauth2/token`;
test("can register the oauth2 client and an account", async ({ context, page, homeserver, mailhogClient, mas }) => {
const tokenUri = `${mas.baseUrl}/oauth2/token`;
const tokenApiPromise = page.waitForRequest(
(request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code",
);
await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhog.api, "alice", "alice@email.com", "Pa$sW0rD!");
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");
// Eventually, we should end up at the home screen.
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
@ -49,7 +52,7 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
await newPage.close();
// Assert logging out revokes both tokens
const revokeUri = `http://localhost:${mas.port}/oauth2/revoke`;
const revokeUri = `${mas.baseUrl}/oauth2/revoke`;
const revokeAccessTokenPromise = page.waitForRequest(
(request) => request.url() === revokeUri && request.postDataJSON()["token_type_hint"] === "access_token",
);

View File

@ -7,20 +7,13 @@ Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
test.describe("Email Registration", async () => {
test.skip(isDendrite, "not yet wired up");
test.use(emailHomeserver);
test.use({
startHomeserverOpts: ({ mailhog }, use) =>
use({
template: "email",
variables: {
SMTP_HOST: "host.containers.internal",
SMTP_PORT: mailhog.instance.smtpPort,
},
}),
config: ({ config }, use) =>
use({
...config,
@ -40,7 +33,7 @@ test.describe("Email Registration", async () => {
test(
"registers an account and lands on the use case selection screen",
{ tag: "@screenshot" },
async ({ page, mailhog, request, checkA11y }) => {
async ({ page, mailhogClient, request, checkA11y }) => {
await expect(page.getByRole("textbox", { name: "Username" })).toBeVisible();
// Hide the server text as it contains the randomly allocated Homeserver port
const screenshotOptions = { mask: [page.locator(".mx_ServerPicker_server")] };
@ -57,7 +50,7 @@ test.describe("Email Registration", async () => {
await expect(page.getByText("An error was encountered when sending the email")).not.toBeVisible();
const messages = await mailhog.api.messages();
const messages = await mailhogClient.messages();
expect(messages.items).toHaveLength(1);
expect(messages.items[0].to).toEqual("alice@email.com");
const [emailLink] = messages.items[0].text.match(/http.+/);

View File

@ -7,10 +7,11 @@ Please see LICENSE files in the repository root for full details.
*/
import { test, expect } from "../../element-web-test";
import { consentHomeserver } from "../../plugins/homeserver/synapse/consentHomeserver.ts";
test.describe("Registration", () => {
test.use(consentHomeserver);
test.use({
startHomeserverOpts: "consent",
config: {
// The only thing that we really *need* (otherwise Element refuses to load) is a default homeserver.
// We point that to a guaranteed-invalid domain.

View File

@ -7,21 +7,31 @@ Please see LICENSE files in the repository root for full details.
*/
import { Page, Request } from "@playwright/test";
import { GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { test as base, expect } from "../../element-web-test";
import type { ElementAppPage } from "../../pages/ElementAppPage";
import type { Bot } from "../../pages/bot";
import { ProxyInstance, SlidingSyncProxy } from "../../plugins/sliding-sync-proxy";
const test = base.extend<{
slidingSyncProxy: ProxyInstance;
slidingSyncProxy: StartedTestContainer;
testRoom: { roomId: string; name: string };
joinedBot: Bot;
}>({
slidingSyncProxy: async ({ context, page, homeserver }, use) => {
const proxy = new SlidingSyncProxy(homeserver.config.dockerUrl, context);
const proxyInstance = await proxy.start();
const proxyAddress = `http://localhost:${proxyInstance.port}`;
slidingSyncProxy: async ({ logger, network, postgres, page, homeserver }, use, testInfo) => {
const container = await new GenericContainer("ghcr.io/matrix-org/sliding-sync:v0.99.3")
.withNetwork(network)
.withExposedPorts(8008)
.withLogConsumer(logger.getConsumer("sliding-sync-proxy"))
.withWaitStrategy(Wait.forHttp("/client/server.json", 8008))
.withEnvironment({
SYNCV3_SECRET: "bwahahaha",
SYNCV3_DB: `user=${postgres.getUsername()} dbname=postgres password=${postgres.getPassword()} host=postgres sslmode=disable`,
SYNCV3_SERVER: `http://homeserver:8008`,
})
.start();
const proxyAddress = `http://${container.getHost()}:${container.getMappedPort(8008)}`;
await page.addInitScript((proxyAddress) => {
window.localStorage.setItem(
"mx_local_settings",
@ -31,8 +41,8 @@ const test = base.extend<{
);
window.localStorage.setItem("mx_labs_feature_feature_sliding_sync", "true");
}, proxyAddress);
await use(proxyInstance);
await proxy.stop();
await use(container);
await container.stop();
},
// Ensure slidingSyncProxy is set up before the user fixture as it relies on an init script
credentials: async ({ slidingSyncProxy, credentials }, use) => {

View File

@ -6,24 +6,20 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import { test as base, expect as baseExpect, Locator, Page, ExpectMatcherState, ElementHandle } from "@playwright/test";
import { expect as baseExpect, Locator, Page, ExpectMatcherState, ElementHandle } from "@playwright/test";
import { sanitizeForFilePath } from "playwright-core/lib/utils";
import AxeBuilder from "@axe-core/playwright";
import _ from "lodash";
import { basename, extname } from "node:path";
import { extname } from "node:path";
import type mailhog from "mailhog";
import type { IConfigOptions } from "../src/IConfigOptions";
import { Credentials, Homeserver, HomeserverInstance, StartHomeserverOpts } from "./plugins/homeserver";
import { Synapse } from "./plugins/homeserver/synapse";
import { Dendrite, Pinecone } from "./plugins/homeserver/dendrite";
import { Instance, MailHogServer } from "./plugins/mailhog";
import { Credentials } from "./plugins/homeserver";
import { ElementAppPage } from "./pages/ElementAppPage";
import { OAuthServer } from "./plugins/oauth_server";
import { Crypto } from "./pages/crypto";
import { Toasts } from "./pages/toasts";
import { Bot, CreateBotOpts } from "./pages/bot";
import { Webserver } from "./plugins/webserver";
import { test as base } from "./services.ts";
// Enable experimental service worker support
// See https://playwright.dev/docs/service-workers-experimental#how-to-enable
@ -58,14 +54,6 @@ export interface Fixtures {
*/
config: typeof CONFIG_JSON;
/**
* The options with which to run the {@link #homeserver} fixture.
*/
startHomeserverOpts: StartHomeserverOpts | string;
homeserver: HomeserverInstance;
oAuthServer: { port: number };
/**
* The displayname to use for the user registered in {@link #credentials}.
*
@ -103,7 +91,6 @@ export interface Fixtures {
*/
app: ElementAppPage;
mailhog: { api: mailhog.API; instance: Instance };
crypto: Crypto;
room?: { roomId: string };
toasts: Toasts;
@ -149,45 +136,6 @@ export const test = base.extend<Fixtures>({
await use(page);
},
startHomeserverOpts: "default",
homeserver: async ({ request, startHomeserverOpts: opts }, use, testInfo) => {
if (typeof opts === "string") {
opts = { template: opts };
}
let server: Homeserver;
const homeserverName = process.env["PLAYWRIGHT_HOMESERVER"];
switch (homeserverName) {
case "dendrite":
server = new Dendrite(request);
break;
case "pinecone":
server = new Pinecone(request);
break;
default:
server = new Synapse(request);
}
await use(await server.start(opts));
const logs = await server.stop();
if (testInfo.status !== "passed") {
for (const path of logs) {
await testInfo.attach(`homeserver-${basename(path)}`, {
path,
contentType: "text/plain",
});
}
}
},
// eslint-disable-next-line no-empty-pattern
oAuthServer: async ({}, use) => {
const server = new OAuthServer();
const port = server.start();
await use({ port });
server.stop();
},
displayName: undefined,
credentials: async ({ homeserver, displayName: testDisplayName }, use) => {
const names = ["Alice", "Bob", "Charlie", "Daniel", "Eve", "Frank", "Grace", "Hannah", "Isaac", "Judy"];
@ -216,8 +164,14 @@ export const test = base.extend<Fixtures>({
window.localStorage.setItem("mx_has_pickle_key", "false");
window.localStorage.setItem("mx_has_access_token", "true");
// Ensure the language is set to a consistent value
window.localStorage.setItem("mx_local_settings", '{"language":"en"}');
window.localStorage.setItem(
"mx_local_settings",
JSON.stringify({
...JSON.parse(window.localStorage.getItem("mx_local_settings") || "{}"),
// Ensure the language is set to a consistent value
language: "en",
}),
);
},
{ baseUrl: homeserver.baseUrl, credentials },
);
@ -264,14 +218,6 @@ export const test = base.extend<Fixtures>({
await use(bot);
},
// eslint-disable-next-line no-empty-pattern
mailhog: async ({}, use) => {
const mailhog = new MailHogServer();
const instance = await mailhog.start();
await use(instance);
await mailhog.stop();
},
// eslint-disable-next-line no-empty-pattern
webserver: async ({}, use) => {
const webserver = new Webserver();

View File

@ -1,151 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import * as os from "os";
import * as crypto from "crypto";
import * as childProcess from "child_process";
import * as fse from "fs-extra";
/**
* @param cmd - command to execute
* @param args - arguments to pass to executed command
* @param suppressOutput - whether to suppress the stdout and stderr resulting from this command.
* @return Promise which resolves to an object containing the string value of what was
* written to stdout and stderr by the executed command.
*/
const exec = (cmd: string, args: string[], suppressOutput = false): Promise<{ stdout: string; stderr: string }> => {
return new Promise((resolve, reject) => {
if (!suppressOutput) {
const log = ["Running command:", cmd, ...args, "\n"].join(" ");
// When in CI mode we combine reports from multiple runners into a single HTML report
// which has separate files for stdout and stderr, so we print the executed command to both
process.stdout.write(log);
if (process.env.CI) process.stderr.write(log);
}
const { stdout, stderr } = childProcess.execFile(cmd, args, { encoding: "utf8" }, (err, stdout, stderr) => {
if (err) reject(err);
resolve({ stdout, stderr });
if (!suppressOutput) {
process.stdout.write("\n");
if (process.env.CI) process.stderr.write("\n");
}
});
if (!suppressOutput) {
stdout.pipe(process.stdout);
stderr.pipe(process.stderr);
}
});
};
export class Docker {
public id: string;
async run(opts: { image: string; containerName: string; params?: string[]; cmd?: string[] }): Promise<string> {
const userInfo = os.userInfo();
const params = opts.params ?? [];
const isPodman = await Docker.isPodman();
if (params.includes("-v") && userInfo.uid >= 0) {
// Run the docker container as our uid:gid to prevent problems with permissions.
if (isPodman) {
// Note: this setup is for podman rootless containers.
// In podman, run as root in the container, which maps to the current
// user on the host. This is probably the default since Synapse's
// Dockerfile doesn't specify, but we're being explicit here
// because it's important for the permissions to work.
params.push("-u", "0:0");
// Tell Synapse not to switch UID
params.push("-e", "UID=0");
params.push("-e", "GID=0");
} else {
params.push("-u", `${userInfo.uid}:${userInfo.gid}`);
}
}
// Make host.containers.internal work to allow the container to talk to other services via host ports.
if (isPodman) {
params.push("--network");
params.push("slirp4netns:allow_host_loopback=true");
} else {
// Docker for Desktop includes a host-gateway mapping on host.docker.internal but to simplify the config
// we use the Podman variant host.containers.internal in all environments.
params.push("--add-host");
params.push("host.containers.internal:host-gateway");
}
// Provided we are not running in CI, add a `--rm` parameter.
// There is no need to remove containers in CI (since they are automatically removed anyway), and
// `--rm` means that if a container crashes this means its logs are wiped out.
if (!process.env.CI) params.unshift("--rm");
const args = [
"run",
"--name",
`${opts.containerName}-${crypto.randomBytes(4).toString("hex")}`,
"-d",
...params,
opts.image,
];
if (opts.cmd) args.push(...opts.cmd);
const { stdout } = await exec("docker", args);
this.id = stdout.trim();
return this.id;
}
async stop(): Promise<void> {
try {
await exec("docker", ["stop", this.id]);
} catch (err) {
console.error(`Failed to stop docker container`, this.id, err);
}
}
/**
* @param params - list of parameters to pass to `docker exec`
* @param suppressOutput - whether to suppress the stdout and stderr resulting from this command.
*/
async exec(params: string[], suppressOutput = true): Promise<void> {
await exec("docker", ["exec", this.id, ...params], suppressOutput);
}
async getContainerIp(): Promise<string> {
const { stdout } = await exec("docker", ["inspect", "-f", "{{ .NetworkSettings.IPAddress }}", this.id]);
return stdout.trim();
}
async persistLogsToFile(args: { stdoutFile?: string; stderrFile?: string }): Promise<void> {
const stdoutFile = args.stdoutFile ? await fse.open(args.stdoutFile, "w") : "ignore";
const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
await new Promise<void>((resolve) => {
childProcess
.spawn("docker", ["logs", this.id], {
stdio: ["ignore", stdoutFile, stderrFile],
})
.once("close", resolve);
});
if (args.stdoutFile) await fse.close(<number>stdoutFile);
if (args.stderrFile) await fse.close(<number>stderrFile);
}
/**
* Detects whether the docker command is actually podman.
* To do this, it looks for "podman" in the output of "docker --help".
*/
static _isPodman?: boolean;
static async isPodman(): Promise<boolean> {
if (Docker._isPodman === undefined) {
const { stdout } = await exec("docker", ["--help"], true);
Docker._isPodman = stdout.toLowerCase().includes("podman");
}
return Docker._isPodman;
}
}

View File

@ -6,142 +6,32 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import * as path from "node:path";
import * as os from "node:os";
import * as fse from "fs-extra";
import { Fixtures, PlaywrightTestArgs } from "@playwright/test";
import { getFreePort } from "../../utils/port";
import { Homeserver, HomeserverConfig, HomeserverInstance, StartHomeserverOpts } from "../";
import { randB64Bytes } from "../../utils/rand";
import { Synapse } from "../synapse";
import { Docker } from "../../docker";
import { Fixtures as BaseFixtures } from "../../../element-web-test.ts";
import { DendriteContainer, PineconeContainer } from "../../../testcontainers/dendrite.ts";
import { Services } from "../../../services.ts";
const dockerConfigDir = "/etc/dendrite/";
const dendriteConfigFile = "dendrite.yaml";
type Fixture = PlaywrightTestArgs & Services & BaseFixtures;
export const dendriteHomeserver: Fixtures<Fixture, {}, Fixture> = {
_homeserver: async ({ request }, use) => {
const container =
process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite"
? new DendriteContainer(request)
: new PineconeContainer(request);
await use(container);
},
homeserver: async ({ logger, network, _homeserver: homeserver }, use) => {
const container = await homeserver
.withNetwork(network)
.withNetworkAliases("homeserver")
.withLogConsumer(logger.getConsumer("dendrite"))
.start();
// Surprisingly, Dendrite implements the same register user Admin API Synapse, so we can just extend it
export class Dendrite extends Synapse implements Homeserver, HomeserverInstance {
protected image = "matrixdotorg/dendrite-monolith:main";
protected entrypoint = "/usr/bin/dendrite";
/**
* Start a dendrite instance: the template must be the name of one of the templates
* in the playwright/plugins/dendritedocker/templates directory
* @param opts
*/
public async start(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
const denCfg = await cfgDirFromTemplate(this.image, opts);
console.log(`Starting dendrite with config dir ${denCfg.configDir}...`);
const dendriteId = await this.docker.run({
image: this.image,
params: [
"-v",
`${denCfg.configDir}:` + dockerConfigDir,
"-p",
`${denCfg.port}:8008/tcp`,
"--entrypoint",
this.entrypoint,
],
containerName: `react-sdk-playwright-dendrite`,
cmd: ["--config", dockerConfigDir + dendriteConfigFile, "--really-enable-open-registration", "true", "run"],
});
console.log(`Started dendrite with id ${dendriteId} on port ${denCfg.port}.`);
// Await Dendrite healthcheck
await this.docker.exec([
"curl",
"--connect-timeout",
"30",
"--retry",
"30",
"--retry-delay",
"1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/_matrix/client/versions",
]);
const dockerUrl = `http://${await this.docker.getContainerIp()}:8008`;
this.config = {
...denCfg,
serverId: dendriteId,
dockerUrl,
};
return this;
}
public async stop(): Promise<string[]> {
if (!this.config) throw new Error("Missing existing dendrite instance, did you call stop() before start()?");
const dendriteLogsPath = path.join("playwright", "dendritelogs", this.config.serverId);
await fse.ensureDir(dendriteLogsPath);
await this.docker.persistLogsToFile({
stdoutFile: path.join(dendriteLogsPath, "stdout.log"),
stderrFile: path.join(dendriteLogsPath, "stderr.log"),
});
await this.docker.stop();
await fse.remove(this.config.configDir);
console.log(`Stopped dendrite id ${this.config.serverId}.`);
return [path.join(dendriteLogsPath, "stdout.log"), path.join(dendriteLogsPath, "stderr.log")];
}
}
export class Pinecone extends Dendrite {
protected image = "matrixdotorg/dendrite-demo-pinecone:main";
protected entrypoint = "/usr/bin/dendrite-demo-pinecone";
}
async function cfgDirFromTemplate(
dendriteImage: string,
opts: StartHomeserverOpts,
): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const template = "default"; // XXX: for now we only have one template
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-dendritedocker-"));
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== dendriteConfigFile });
const registrationSecret = randB64Bytes(16);
const port = await getFreePort();
const baseUrl = `http://localhost:${port}`;
// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, dendriteConfigFile)}`);
let hsYaml = await fse.readFile(path.join(templateDir, dendriteConfigFile), "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
await fse.writeFile(path.join(tempDir, dendriteConfigFile), hsYaml);
const docker = new Docker();
await docker.run({
image: dendriteImage,
params: ["--entrypoint=", "-v", `${tempDir}:/mnt`],
containerName: `react-sdk-playwright-dendrite-keygen`,
cmd: ["/usr/bin/generate-keys", "-private-key", "/mnt/matrix_key.pem"],
});
return {
port,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
await use(container);
await container.stop();
},
};
export function isDendrite(): boolean {
return process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" || process.env["PLAYWRIGHT_HOMESERVER"] === "pinecone";

View File

@ -1,378 +0,0 @@
# This is the Dendrite configuration file.
#
# The configuration is split up into sections - each Dendrite component has a
# configuration section, in addition to the "global" section which applies to
# all components.
# The version of the configuration file.
version: 2
# Global Matrix configuration. This configuration applies to all components.
global:
# The domain name of this homeserver.
server_name: localhost
# The path to the signing private key file, used to sign requests and events.
# Note that this is NOT the same private key as used for TLS! To generate a
# signing key, use "./bin/generate-keys --private-key matrix_key.pem".
private_key: matrix_key.pem
# The paths and expiry timestamps (as a UNIX timestamp in millisecond precision)
# to old signing keys that were formerly in use on this domain name. These
# keys will not be used for federation request or event signing, but will be
# provided to any other homeserver that asks when trying to verify old events.
old_private_keys:
# If the old private key file is available:
# - private_key: old_matrix_key.pem
# expired_at: 1601024554498
# If only the public key (in base64 format) and key ID are known:
# - public_key: mn59Kxfdq9VziYHSBzI7+EDPDcBS2Xl7jeUdiiQcOnM=
# key_id: ed25519:mykeyid
# expired_at: 1601024554498
# How long a remote server can cache our server signing key before requesting it
# again. Increasing this number will reduce the number of requests made by other
# servers for our key but increases the period that a compromised key will be
# considered valid by other homeservers.
key_validity_period: 168h0m0s
# Global database connection pool, for PostgreSQL monolith deployments only. If
# this section is populated then you can omit the "database" blocks in all other
# sections. For polylith deployments, or monolith deployments using SQLite databases,
# you must configure the "database" block for each component instead.
# database:
# connection_string: postgresql://username:password@hostname/dendrite?sslmode=disable
# max_open_conns: 90
# max_idle_conns: 5
# conn_max_lifetime: -1
# Configuration for in-memory caches. Caches can often improve performance by
# keeping frequently accessed items (like events, identifiers etc.) in memory
# rather than having to read them from the database.
cache:
# The estimated maximum size for the global cache in bytes, or in terabytes,
# gigabytes, megabytes or kilobytes when the appropriate 'tb', 'gb', 'mb' or
# 'kb' suffix is specified. Note that this is not a hard limit, nor is it a
# memory limit for the entire process. A cache that is too small may ultimately
# provide little or no benefit.
max_size_estimated: 1gb
# The maximum amount of time that a cache entry can live for in memory before
# it will be evicted and/or refreshed from the database. Lower values result in
# easier admission of new cache entries but may also increase database load in
# comparison to higher values, so adjust conservatively. Higher values may make
# it harder for new items to make it into the cache, e.g. if new rooms suddenly
# become popular.
max_age: 1h
# The server name to delegate server-server communications to, with optional port
# e.g. localhost:443
well_known_server_name: ""
# The server name to delegate client-server communications to, with optional port
# e.g. localhost:443
well_known_client_name: ""
# Lists of domains that the server will trust as identity servers to verify third
# party identifiers such as phone numbers and email addresses.
trusted_third_party_id_servers:
- matrix.org
- vector.im
# Disables federation. Dendrite will not be able to communicate with other servers
# in the Matrix federation and the federation API will not be exposed.
disable_federation: false
# Configures the handling of presence events. Inbound controls whether we receive
# presence events from other servers, outbound controls whether we send presence
# events for our local users to other servers.
presence:
enable_inbound: false
enable_outbound: false
# Configures phone-home statistics reporting. These statistics contain the server
# name, number of active users and some information on your deployment config.
# We use this information to understand how Dendrite is being used in the wild.
report_stats:
enabled: false
endpoint: https://matrix.org/report-usage-stats/push
# Server notices allows server admins to send messages to all users on the server.
server_notices:
enabled: false
# The local part, display name and avatar URL (as a mxc:// URL) for the user that
# will send the server notices. These are visible to all users on the deployment.
local_part: "_server"
display_name: "Server Alerts"
avatar_url: ""
# The room name to be used when sending server notices. This room name will
# appear in user clients.
room_name: "Server Alerts"
# Configuration for NATS JetStream
jetstream:
# A list of NATS Server addresses to connect to. If none are specified, an
# internal NATS server will be started automatically when running Dendrite in
# monolith mode. For polylith deployments, it is required to specify the address
# of at least one NATS Server node.
addresses:
# - localhost:4222
# Disable the validation of TLS certificates of NATS. This is
# not recommended in production since it may allow NATS traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Persistent directory to store JetStream streams in. This directory should be
# preserved across Dendrite restarts.
storage_path: ./
# The prefix to use for stream names for this homeserver - really only useful
# if you are running more than one Dendrite server on the same NATS deployment.
topic_prefix: Dendrite
# Configuration for Prometheus metric collection.
metrics:
enabled: false
basic_auth:
username: metrics
password: metrics
# Optional DNS cache. The DNS cache may reduce the load on DNS servers if there
# is no local caching resolver available for use.
dns_cache:
enabled: false
cache_size: 256
cache_lifetime: "5m" # 5 minutes; https://pkg.go.dev/time@master#ParseDuration
# Configuration for the Appservice API.
app_service_api:
# Disable the validation of TLS certificates of appservices. This is
# not recommended in production since it may allow appservice traffic
# to be sent to an insecure endpoint.
disable_tls_validation: false
# Appservice configuration files to load into this homeserver.
config_files:
# - /path/to/appservice_registration.yaml
# Configuration for the Client API.
client_api:
# Prevents new users from being able to register on this homeserver, except when
# using the registration shared secret below.
registration_disabled: false
# Prevents new guest accounts from being created. Guest registration is also
# disabled implicitly by setting 'registration_disabled' above.
guests_disabled: true
# If set, allows registration by anyone who knows the shared secret, regardless
# of whether registration is otherwise disabled.
registration_shared_secret: "{{REGISTRATION_SECRET}}"
# Whether to require reCAPTCHA for registration. If you have enabled registration
# then this is HIGHLY RECOMMENDED to reduce the risk of your homeserver being used
# for coordinated spam attacks.
enable_registration_captcha: false
# Settings for ReCAPTCHA.
recaptcha_public_key: ""
recaptcha_private_key: ""
recaptcha_bypass_secret: ""
# To use hcaptcha.com instead of ReCAPTCHA, set the following parameters, otherwise just keep them empty.
# recaptcha_siteverify_api: "https://hcaptcha.com/siteverify"
# recaptcha_api_js_url: "https://js.hcaptcha.com/1/api.js"
# recaptcha_form_field: "h-captcha-response"
# recaptcha_sitekey_class: "h-captcha"
# TURN server information that this homeserver should send to clients.
turn:
turn_user_lifetime: "5m"
turn_uris:
# - turn:turn.server.org?transport=udp
# - turn:turn.server.org?transport=tcp
turn_shared_secret: ""
# If your TURN server requires static credentials, then you will need to enter
# them here instead of supplying a shared secret. Note that these credentials
# will be visible to clients!
# turn_username: ""
# turn_password: ""
# Settings for rate-limited endpoints. Rate limiting kicks in after the threshold
# number of "slots" have been taken by requests from a specific host. Each "slot"
# will be released after the cooloff time in milliseconds. Server administrators
# and appservice users are exempt from rate limiting by default.
rate_limiting:
enabled: true
threshold: 20
cooloff_ms: 500
exempt_user_ids:
# - "@user:domain.com"
# Configuration for the Federation API.
federation_api:
# How many times we will try to resend a failed transaction to a specific server. The
# backoff is 2**x seconds, so 1 = 2 seconds, 2 = 4 seconds, 3 = 8 seconds etc. Once
# the max retries are exceeded, Dendrite will no longer try to send transactions to
# that server until it comes back to life and connects to us again.
send_max_retries: 16
# Disable the validation of TLS certificates of remote federated homeservers. Do not
# enable this option in production as it presents a security risk!
disable_tls_validation: false
# Disable HTTP keepalives, which also prevents connection reuse. Dendrite will typically
# keep HTTP connections open to remote hosts for 5 minutes as they can be reused much
# more quickly than opening new connections each time. Disabling keepalives will close
# HTTP connections immediately after a successful request but may result in more CPU and
# memory being used on TLS handshakes for each new connection instead.
disable_http_keepalives: false
# Perspective keyservers to use as a backup when direct key fetches fail. This may
# be required to satisfy key requests for servers that are no longer online when
# joining some rooms.
key_perspectives:
- server_name: matrix.org
keys:
- key_id: ed25519:auto
public_key: Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw
- key_id: ed25519:a_RXGa
public_key: l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ
# This option will control whether Dendrite will prefer to look up keys directly
# or whether it should try perspective servers first, using direct fetches as a
# last resort.
prefer_direct_fetch: false
database:
connection_string: file:dendrite-federationapi.db
# Configuration for the Media API.
media_api:
# Storage path for uploaded media. May be relative or absolute.
base_path: ./media_store
# The maximum allowed file size (in bytes) for media uploads to this homeserver
# (0 = unlimited). If using a reverse proxy, ensure it allows requests at least
#this large (e.g. the client_max_body_size setting in nginx).
max_file_size_bytes: 10485760
# Whether to dynamically generate thumbnails if needed.
dynamic_thumbnails: false
# The maximum number of simultaneous thumbnail generators to run.
max_thumbnail_generators: 10
# A list of thumbnail sizes to be generated for media content.
thumbnail_sizes:
- width: 32
height: 32
method: crop
- width: 96
height: 96
method: crop
- width: 640
height: 480
method: scale
database:
connection_string: file:dendrite-mediaapi.db
# Configuration for enabling experimental MSCs on this homeserver.
mscs:
mscs:
# - msc2836 # (Threading, see https://github.com/matrix-org/matrix-doc/pull/2836)
# - msc2946 # (Spaces Summary, see https://github.com/matrix-org/matrix-doc/pull/2946)
database:
connection_string: file:dendrite-msc.db
# Configuration for the Sync API.
sync_api:
# This option controls which HTTP header to inspect to find the real remote IP
# address of the client. This is likely required if Dendrite is running behind
# a reverse proxy server.
# real_ip_header: X-Real-IP
# Configuration for the full-text search engine.
search:
# Whether or not search is enabled.
enabled: false
# The path where the search index will be created in.
index_path: "./searchindex"
# The language most likely to be used on the server - used when indexing, to
# ensure the returned results match expectations. A full list of possible languages
# can be found at https://github.com/blevesearch/bleve/tree/master/analysis/lang
language: "en"
database:
connection_string: file:dendrite-syncapi.db
# Configuration for the User API.
user_api:
# The cost when hashing passwords on registration/login. Default: 10. Min: 4, Max: 31
# See https://pkg.go.dev/golang.org/x/crypto/bcrypt for more information.
# Setting this lower makes registration/login consume less CPU resources at the cost
# of security should the database be compromised. Setting this higher makes registration/login
# consume more CPU resources but makes it harder to brute force password hashes. This value
# can be lowered if performing tests or on embedded Dendrite instances (e.g WASM builds).
bcrypt_cost: 10
# The length of time that a token issued for a relying party from
# /_matrix/client/r0/user/{userId}/openid/request_token endpoint
# is considered to be valid in milliseconds.
# The default lifetime is 3600000ms (60 minutes).
# openid_token_lifetime_ms: 3600000
# Users who register on this homeserver will automatically be joined to the rooms listed under "auto_join_rooms" option.
# By default, any room aliases included in this list will be created as a publicly joinable room
# when the first user registers for the homeserver. If the room already exists,
# make certain it is a publicly joinable room, i.e. the join rule of the room must be set to 'public'.
# As Spaces are just rooms under the hood, Space aliases may also be used.
auto_join_rooms:
# - "#main:matrix.org"
account_database:
connection_string: file:dendrite-userapi.db
room_server:
database:
connection_string: file:dendrite-roomserverapi.db
key_server:
database:
connection_string: file:dendrite-keyserverapi.db
relay_api:
database:
connection_string: file:dendrite-relayapi.db
# Configuration for Opentracing.
# See https://github.com/matrix-org/dendrite/tree/master/docs/tracing for information on
# how this works and how to set it up.
tracing:
enabled: false
jaeger:
serviceName: ""
disabled: false
rpc_metrics: false
tags: []
sampler: null
reporter: null
headers: null
baggage_restrictions: null
throttler: null
# Logging configuration. The "std" logging type controls the logs being sent to
# stdout. The "file" logging type controls logs being written to a log folder on
# the disk. Supported log levels are "debug", "info", "warn", "error".
logging:
- type: std
level: debug
- type: file
level: debug
params:
path: ./logs

View File

@ -6,16 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
export interface HomeserverConfig {
readonly configDir: string;
readonly baseUrl: string;
readonly port: number;
readonly registrationSecret: string;
readonly dockerUrl: string;
}
export interface HomeserverInstance {
readonly config: HomeserverConfig;
readonly baseUrl: string;
/**
@ -43,27 +34,6 @@ export interface HomeserverInstance {
setThreepid(userId: string, medium: string, address: string): Promise<void>;
}
export interface StartHomeserverOpts {
/** path to template within playwright/plugins/{homeserver}docker/template/ directory. */
template: string;
/** Port of an OAuth server to configure the homeserver to use */
oAuthServerPort?: number;
/** Additional variables to inject into the configuration template **/
variables?: Record<string, string | number>;
}
export interface Homeserver {
start(opts: StartHomeserverOpts): Promise<HomeserverInstance>;
/**
* Stop this test homeserver instance.
*
* @returns A list of paths relative to the cwd for logfiles generated during this test run.
*/
stop(): Promise<string[]>;
}
export interface Credentials {
accessToken: string;
userId: string;

View File

@ -0,0 +1,56 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Fixtures } from "@playwright/test";
import { Services } from "../../../services.ts";
export const consentHomeserver: Fixtures<Services, {}, Services> = {
_homeserver: async ({ _homeserver: container, mailhog }, use) => {
container
.withCopyDirectoriesToContainer([
{ source: "playwright/plugins/homeserver/synapse/res", target: "/data/res" },
])
.withConfig({
email: {
enable_notifs: false,
smtp_host: "mailhog",
smtp_port: 1025,
smtp_user: "username",
smtp_pass: "password",
require_transport_security: false,
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
app_name: "Matrix",
notif_template_html: "notif_mail.html",
notif_template_text: "notif_mail.txt",
notif_for_new_users: true,
client_base_url: "http://localhost/element",
},
user_consent: {
template_dir: "/data/res/templates/privacy",
version: "1.0",
server_notice_content: {
msgtype: "m.text",
body: "To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
},
send_server_notice_to_guests: true,
block_events_error:
"To continue using this homeserver you must review and agree to the terms and conditions at %(consent_uri)s",
require_at_registration: true,
},
server_notices: {
system_mxid_localpart: "notices",
system_mxid_display_name: "Server Notices",
system_mxid_avatar_url: "mxc://localhost/oumMVlgDnLYFaPVkExemNVVZ",
room_name: "Server Notices",
},
})
.withConfigField("listeners[0].resources[0].names", ["client", "consent"]);
await use(container);
},
};

View File

@ -0,0 +1,28 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Fixtures } from "@playwright/test";
import { Services } from "../../../services.ts";
export const emailHomeserver: Fixtures<Services, {}, Services> = {
_homeserver: async ({ _homeserver: container, mailhog }, use) => {
container.withConfig({
enable_registration_without_verification: undefined,
disable_msisdn_registration: undefined,
registrations_require_3pid: ["email"],
email: {
smtp_host: "mailhog",
smtp_port: 1025,
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>",
app_name: "my_branded_matrix_server",
},
});
await use(container);
},
};

View File

@ -1,243 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import * as path from "node:path";
import * as os from "node:os";
import * as crypto from "node:crypto";
import * as fse from "fs-extra";
import { APIRequestContext } from "@playwright/test";
import { getFreePort } from "../../utils/port";
import { Docker } from "../../docker";
import { HomeserverConfig, HomeserverInstance, Homeserver, StartHomeserverOpts, Credentials } from "..";
import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image.
// We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:39f94b005e87cd3042c2535c37d8d9f915a88072fe79f6283ac18977fe134321";
async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template);
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${opts.template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-synapsedocker-"));
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== "homeserver.yaml" });
const registrationSecret = randB64Bytes(16);
const macaroonSecret = randB64Bytes(16);
const formSecret = randB64Bytes(16);
const port = await getFreePort();
const baseUrl = `http://localhost:${port}`;
// now copy homeserver.yaml, applying substitutions
const templateHomeserver = path.join(templateDir, "homeserver.yaml");
const outputHomeserver = path.join(tempDir, "homeserver.yaml");
console.log(`Gen ${templateHomeserver} -> ${outputHomeserver}`);
let hsYaml = await fse.readFile(templateHomeserver, "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
if (opts.oAuthServerPort) {
hsYaml = hsYaml.replace(/{{OAUTH_SERVER_PORT}}/g, opts.oAuthServerPort.toString());
}
if (opts.variables) {
for (const key in opts.variables) {
hsYaml = hsYaml.replace(new RegExp("%" + key + "%", "g"), String(opts.variables[key]));
}
}
await fse.writeFile(outputHomeserver, hsYaml);
// now generate a signing key (we could use synapse's config generation for
// this, or we could just do this...)
// NB. This assumes the homeserver.yaml specifies the key in this location
const signingKey = randB64Bytes(32);
const outputSigningKey = path.join(tempDir, "localhost.signing.key");
console.log(`Gen -> ${outputSigningKey}`);
await fse.writeFile(outputSigningKey, `ed25519 x ${signingKey}`);
// Allow anyone to read, write and execute in the /temp/react-sdk-synapsedocker-xxx directory
// so that the DIND setup that we use to update the playwright screenshots work without any issues.
await fse.chmod(tempDir, 0o757);
return {
port,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
export class Synapse implements Homeserver, HomeserverInstance {
protected docker: Docker = new Docker();
public config: HomeserverConfig & { serverId: string };
private adminToken?: string;
public constructor(private readonly request: APIRequestContext) {}
/**
* Start a synapse instance: the template must be the name of
* one of the templates in the playwright/plugins/synapsedocker/templates
* directory.
*/
public async start(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
if (this.config) await this.stop();
const synCfg = await cfgDirFromTemplate(opts);
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
const dockerSynapseParams = ["-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`];
const synapseId = await this.docker.run({
image: `ghcr.io/element-hq/synapse:${DOCKER_TAG}`,
containerName: `react-sdk-playwright-synapse`,
params: dockerSynapseParams,
cmd: ["run"],
});
console.log(`Started synapse with id ${synapseId} on port ${synCfg.port}.`);
// Await Synapse healthcheck
await this.docker.exec([
"curl",
"--connect-timeout",
"30",
"--retry",
"30",
"--retry-delay",
"1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/health",
]);
const dockerUrl = `http://${await this.docker.getContainerIp()}:8008`;
this.config = {
...synCfg,
serverId: synapseId,
dockerUrl,
};
return this;
}
public async stop(): Promise<string[]> {
if (!this.config) throw new Error("Missing existing synapse instance, did you call stop() before start()?");
const id = this.config.serverId;
const synapseLogsPath = path.join("playwright", "logs", "synapse", id);
await fse.ensureDir(synapseLogsPath);
await this.docker.persistLogsToFile({
stdoutFile: path.join(synapseLogsPath, "stdout.log"),
stderrFile: path.join(synapseLogsPath, "stderr.log"),
});
await this.docker.stop();
await fse.remove(this.config.configDir);
console.log(`Stopped synapse id ${id}.`);
return [path.join(synapseLogsPath, "stdout.log"), path.join(synapseLogsPath, "stderr.log")];
}
public get baseUrl(): string {
return this.config.baseUrl;
}
private async registerUserInternal(
username: string,
password: string,
displayName?: string,
admin = false,
): Promise<Credentials> {
const url = `${this.config.baseUrl}/_synapse/admin/v1/register`;
const { nonce } = await this.request.get(url).then((r) => r.json());
const mac = crypto
.createHmac("sha1", this.config.registrationSecret)
.update(`${nonce}\0${username}\0${password}\0${admin ? "" : "not"}admin`)
.digest("hex");
const res = await this.request.post(url, {
data: {
nonce,
username,
password,
mac,
admin,
displayname: displayName,
},
});
if (!res.ok()) {
throw await res.json();
}
const data = await res.json();
return {
homeServer: data.home_server,
accessToken: data.access_token,
userId: data.user_id,
deviceId: data.device_id,
password,
displayName,
};
}
public registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
return this.registerUserInternal(username, password, displayName, false);
}
public async loginUser(userId: string, password: string): Promise<Credentials> {
const url = `${this.config.baseUrl}/_matrix/client/v3/login`;
const res = await this.request.post(url, {
data: {
type: "m.login.password",
identifier: {
type: "m.id.user",
user: userId,
},
password: password,
},
});
const json = await res.json();
return {
password,
accessToken: json.access_token,
userId: json.user_id,
deviceId: json.device_id,
homeServer: json.home_server,
};
}
public async setThreepid(userId: string, medium: string, address: string): Promise<void> {
if (this.adminToken === undefined) {
const result = await this.registerUserInternal("admin", "totalyinsecureadminpassword", undefined, true);
this.adminToken = result.accessToken;
}
const url = `${this.config.baseUrl}/_synapse/admin/v2/users/${userId}`;
const res = await this.request.put(url, {
data: {
threepids: [
{
medium,
address,
},
],
},
headers: {
Authorization: `Bearer ${this.adminToken}`,
},
});
if (!res.ok()) {
throw await res.json();
}
}
}

View File

@ -0,0 +1,48 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Fixtures } from "@playwright/test";
import { TestContainers } from "testcontainers";
import { Services } from "../../../services.ts";
import { OAuthServer } from "../../oauth_server";
export const legacyOAuthHomeserver: Fixtures<Services, {}, Services> = {
_homeserver: async ({ _homeserver: container }, use) => {
const server = new OAuthServer();
const port = server.start();
await TestContainers.exposeHostPorts(port);
container.withConfig({
oidc_providers: [
{
idp_id: "test",
idp_name: "OAuth test",
issuer: `http://localhost:${port}/oauth`,
authorization_endpoint: `http://localhost:${port}/oauth/auth.html`,
// the token endpoint receives requests from synapse,
// rather than the webapp, so needs to escape the docker container.
token_endpoint: `http://host.testcontainers.internal:${port}/oauth/token`,
userinfo_endpoint: `http://host.testcontainers.internal:${port}/oauth/userinfo`,
client_id: "synapse",
discover: false,
scopes: ["profile"],
skip_verification: true,
client_auth_method: "none",
user_mapping_provider: {
config: {
display_name_template: "{{ user.name }}",
},
},
},
],
});
await use(container);
server.stop();
},
};

View File

@ -0,0 +1,83 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Fixtures, PlaywrightTestArgs } from "@playwright/test";
import { Services } from "../../../services.ts";
import { Fixtures as BaseFixtures } from "../../../element-web-test.ts";
import { MatrixAuthenticationServiceContainer } from "../../../testcontainers/mas.ts";
type Fixture = PlaywrightTestArgs & Services & BaseFixtures;
export const masHomeserver: Fixtures<Fixture, {}, Fixture> = {
mas: async ({ _homeserver: homeserver, logger, network, postgres, mailhog }, use) => {
const config = {
clients: [
{
client_id: "0000000000000000000SYNAPSE",
client_auth_method: "client_secret_basic",
client_secret: "SomeRandomSecret",
},
],
matrix: {
homeserver: "localhost",
secret: "AnotherRandomSecret",
endpoint: "http://homeserver:8008",
},
};
const container = await new MatrixAuthenticationServiceContainer(postgres)
.withNetwork(network)
.withNetworkAliases("mas")
.withLogConsumer(logger.getConsumer("mas"))
.withConfig(config)
.start();
homeserver.withConfig({
enable_registration: undefined,
enable_registration_without_verification: undefined,
disable_msisdn_registration: undefined,
password_config: undefined,
experimental_features: {
msc3861: {
enabled: true,
issuer: `http://mas:8080/`,
introspection_endpoint: "http://mas:8080/oauth2/introspect",
client_id: config.clients[0].client_id,
client_auth_method: config.clients[0].client_auth_method,
client_secret: config.clients[0].client_secret,
admin_token: config.matrix.secret,
},
},
});
await use(container);
await container.stop();
},
config: async ({ homeserver, context, mas }, use) => {
const issuer = `${mas.baseUrl}/`;
const wellKnown = {
"m.homeserver": {
base_url: homeserver.baseUrl,
},
"org.matrix.msc2965.authentication": {
issuer,
account: `${issuer}account`,
},
};
// Ensure org.matrix.msc2965.authentication is in well-known
await context.route("https://localhost/.well-known/matrix/client", async (route) => {
await route.fulfill({ json: wellKnown });
});
await use({
default_server_config: wellKnown,
});
},
};

View File

@ -1,3 +0,0 @@
# Meta-template for synapse templates
To make another template, you can copy this directory

View File

@ -1,72 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
# XXX: This won't actually be right: it lets docker allocate an ephemeral port,
# so we have a chicken-and-egg problem
public_baseurl: http://localhost:8008/
# Listener is always port 8008 (configured in the container)
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client, federation, consent]
compress: false
# An sqlite in-memory database is fast & automatically wipes each time
database:
name: "sqlite3"
args:
database: ":memory:"
# Needs to be configured to log to the console like a good docker process
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
# These placeholders will be be replaced with values generated at start
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
# Signing key must be here: it will be generated to this file
signing_key_path: "/data/localhost.signing.key"
email:
enable_notifs: false
smtp_host: "localhost"
smtp_port: 25
smtp_user: "exampleusername"
smtp_pass: "examplepassword"
require_transport_security: False
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
app_name: Matrix
notif_template_html: notif_mail.html
notif_template_text: notif_mail.txt
notif_for_new_users: True
client_base_url: "http://localhost/element"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured with user privacy consent enabled

View File

@ -1,84 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client, federation, consent]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
email:
enable_notifs: false
smtp_host: "localhost"
smtp_port: 25
smtp_user: "exampleusername"
smtp_pass: "examplepassword"
require_transport_security: False
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
app_name: Matrix
notif_template_html: notif_mail.html
notif_template_text: notif_mail.txt
notif_for_new_users: True
client_base_url: "http://localhost/element"
user_consent:
template_dir: /data/res/templates/privacy
version: 1.0
server_notice_content:
msgtype: m.text
body: >-
To continue using this homeserver you must review and agree to the
terms and conditions at %(consent_uri)s
send_server_notice_to_guests: True
block_events_error: >-
To continue using this homeserver you must review and agree to the
terms and conditions at %(consent_uri)s
require_at_registration: true
server_notices:
system_mxid_localpart: notices
system_mxid_display_name: "Server Notices"
system_mxid_avatar_url: "mxc://localhost:5005/oumMVlgDnLYFaPVkExemNVVZ"
room_name: "Server Notices"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: DEBUG
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: DEBUG
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured with user privacy consent disabled

View File

@ -1,106 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_joins:
local:
per_second: 9999
burst_count: 9999
remote:
per_second: 9999
burst_count: 9999
rc_joins_per_room:
per_second: 9999
burst_count: 9999
rc_3pid_validation:
per_second: 1000
burst_count: 1000
rc_invites:
per_room:
per_second: 1000
burst_count: 1000
per_user:
per_second: 1000
burst_count: 1000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
ui_auth:
session_timeout: "300s"
oidc_providers:
- idp_id: test
idp_name: "OAuth test"
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
token_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/token"
userinfo_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
client_id: "synapse"
discover: false
scopes: ["profile"]
skip_verification: true
client_auth_method: none
user_mapping_provider:
config:
display_name_template: "{{ user.name }}"
# Inhibit background updates as this Synapse isn't long-lived
background_updates:
min_batch_size: 100000
sleep_duration_ms: 100000
experimental_features:
# Needed for e2e/crypto/crypto.spec.ts > Cryptography > decryption failure
# messages > non-joined historical messages.
# Can be removed after Synapse enables it by default
msc4115_membership_on_events: true
enable_authenticated_media: true

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: DEBUG
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: DEBUG
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured with device dehydration v2 enabled

View File

@ -1,102 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_joins:
local:
per_second: 9999
burst_count: 9999
remote:
per_second: 9999
burst_count: 9999
rc_joins_per_room:
per_second: 9999
burst_count: 9999
rc_3pid_validation:
per_second: 1000
burst_count: 1000
rc_invites:
per_room:
per_second: 1000
burst_count: 1000
per_user:
per_second: 1000
burst_count: 1000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
ui_auth:
session_timeout: "300s"
oidc_providers:
- idp_id: test
idp_name: "OAuth test"
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
token_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/token"
userinfo_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
client_id: "synapse"
discover: false
scopes: ["profile"]
skip_verification: true
client_auth_method: none
user_mapping_provider:
config:
display_name_template: "{{ user.name }}"
# Inhibit background updates as this Synapse isn't long-lived
background_updates:
min_batch_size: 100000
sleep_duration_ms: 100000
experimental_features:
msc2697_enabled: false
msc3814_enabled: true

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: DEBUG
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: DEBUG
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured to require an email for registration

View File

@ -1,44 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
enable_registration: true
registrations_require_3pid:
- email
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
ui_auth:
session_timeout: "300s"
email:
smtp_host: "%SMTP_HOST%"
smtp_port: %SMTP_PORT%
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
app_name: my_branded_matrix_server

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured with guest registration enabled.

View File

@ -1,105 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_joins:
local:
per_second: 9999
burst_count: 9999
remote:
per_second: 9999
burst_count: 9999
rc_joins_per_room:
per_second: 9999
burst_count: 9999
rc_3pid_validation:
per_second: 1000
burst_count: 1000
rc_invites:
per_room:
per_second: 1000
burst_count: 1000
per_user:
per_second: 1000
burst_count: 1000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
allow_guest_access: true
enable_registration: true
enable_registration_without_verification: true
disable_msisdn_registration: false
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
ui_auth:
session_timeout: "300s"
oidc_providers:
- idp_id: test
idp_name: "OAuth test"
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
token_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/token"
userinfo_endpoint: "http://host.containers.internal:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
client_id: "synapse"
discover: false
scopes: ["profile"]
skip_verification: true
client_auth_method: none
user_mapping_provider:
config:
display_name_template: "{{ user.name }}"
# Inhibit background updates as this Synapse isn't long-lived
background_updates:
min_batch_size: 100000
sleep_duration_ms: 100000
experimental_features:
# Needed for e2e/crypto/crypto.spec.ts > Cryptography > decryption failure
# messages > non-joined historical messages.
# Can be removed after Synapse enables it by default
msc4115_membership_on_events: true

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: INFO
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1 +0,0 @@
A synapse configured with auth delegated to via matrix authentication service

View File

@ -1,96 +0,0 @@
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "{{PUBLIC_BASEURL}}"
listeners:
- port: 8008
tls: false
bind_addresses: ["::"]
type: http
x_forwarded: true
resources:
- names: [client]
compress: false
database:
name: "sqlite3"
args:
database: ":memory:"
log_config: "/data/log.config"
rc_messages_per_second: 10000
rc_message_burst_count: 10000
rc_registration:
per_second: 10000
burst_count: 10000
rc_joins:
local:
per_second: 9999
burst_count: 9999
remote:
per_second: 9999
burst_count: 9999
rc_joins_per_room:
per_second: 9999
burst_count: 9999
rc_3pid_validation:
per_second: 1000
burst_count: 1000
rc_invites:
per_room:
per_second: 1000
burst_count: 1000
per_user:
per_second: 1000
burst_count: 1000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
media_store_path: "/data/media_store"
uploads_path: "/data/uploads"
registration_shared_secret: "{{REGISTRATION_SECRET}}"
report_stats: false
macaroon_secret_key: "{{MACAROON_SECRET_KEY}}"
form_secret: "{{FORM_SECRET}}"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
ui_auth:
session_timeout: "300s"
# Inhibit background updates as this Synapse isn't long-lived
background_updates:
min_batch_size: 100000
sleep_duration_ms: 100000
serve_server_wellknown: true
experimental_features:
msc3861:
enabled: true
issuer: http://host.containers.internal:%MAS_PORT%/
introspection_endpoint: http://host.containers.internal:%MAS_PORT%/oauth2/introspect
# Matches the `client_id` in the auth service config
client_id: 0000000000000000000SYNAPSE
# Matches the `client_auth_method` in the auth service config
client_auth_method: client_secret_basic
# Matches the `client_secret` in the auth service config
client_secret: "SomeRandomSecret"
# Matches the `matrix.secret` in the auth service config
admin_token: "AnotherRandomSecret"

View File

@ -1,50 +0,0 @@
# Log configuration for Synapse.
#
# This is a YAML file containing a standard Python logging configuration
# dictionary. See [1] for details on the valid settings.
#
# Synapse also supports structured logging for machine readable logs which can
# be ingested by ELK stacks. See [2] for details.
#
# [1]: https://docs.python.org/3.7/library/logging.config.html#configuration-dictionary-schema
# [2]: https://matrix-org.github.io/synapse/latest/structured_logging.html
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
# A handler that writes logs to stderr. Unused by default, but can be used
# instead of "buffer" and "file" in the logger handlers.
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: DEBUG
twisted:
# We send the twisted logging directly to the file handler,
# to work around https://github.com/matrix-org/synapse/issues/3471
# when using "buffer" logger. Use "console" to log to stderr instead.
handlers: [console]
propagate: false
root:
level: DEBUG
# Write logs to the `buffer` handler, which will buffer them together in memory,
# then write them to a file.
#
# Replace "buffer" with "console" to log to stderr instead. (Note that you'll
# also need to update the configuration for the `twisted` logger above, in
# this case.)
#
handlers: [console]
disable_existing_loggers: false

View File

@ -1,47 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import mailhog from "mailhog";
import { getFreePort } from "../utils/port";
import { Docker } from "../docker";
export interface Instance {
host: string;
smtpPort: number;
httpPort: number;
containerId: string;
}
export class MailHogServer {
private readonly docker: Docker = new Docker();
private instance?: Instance;
async start(): Promise<{ api: mailhog.API; instance: Instance }> {
if (this.instance) throw new Error("Mailhog server is already running!");
const smtpPort = await getFreePort();
const httpPort = await getFreePort();
console.log(`Starting mailhog...`);
const containerId = await this.docker.run({
image: "mailhog/mailhog:latest",
containerName: `react-sdk-playwright-mailhog`,
params: ["-p", `${smtpPort}:1025/tcp`, "-p", `${httpPort}:8025/tcp`],
});
console.log(`Started mailhog on ports smtp=${smtpPort} http=${httpPort}.`);
const host = await this.docker.getContainerIp();
this.instance = { smtpPort, httpPort, containerId, host };
return { api: mailhog({ host: "localhost", port: httpPort }), instance: this.instance };
}
async stop(): Promise<void> {
if (!this.instance) throw new Error("Missing existing mailhog instance, did you call stop() before start()?");
await this.docker.stop();
console.log(`Stopped mailhog id ${this.instance.containerId}.`);
this.instance = undefined;
}
}

View File

@ -1,156 +0,0 @@
clients:
- client_id: 0000000000000000000SYNAPSE
client_auth_method: client_secret_basic
client_secret: "SomeRandomSecret"
http:
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: graphql
playground: true
- name: assets
path: /usr/local/share/mas-cli/assets/
binds:
- address: "[::]:8080"
proxy_protocol: false
- name: internal
resources:
- name: health
binds:
- host: localhost
port: 8081
proxy_protocol: false
trusted_proxies:
- 192.128.0.0/16
- 172.16.0.0/12
- 10.0.0.0/10
- 127.0.0.1/8
- fd00::/8
- ::1/128
public_base: "http://localhost:{{MAS_PORT}}/"
issuer: http://localhost:{{MAS_PORT}}/
database:
host: "{{POSTGRES_HOST}}"
port: 5432
database: postgres
username: postgres
password: "{{POSTGRES_PASSWORD}}"
max_connections: 10
min_connections: 0
connect_timeout: 30
idle_timeout: 600
max_lifetime: 1800
telemetry:
tracing:
exporter: none
propagators: []
metrics:
exporter: none
sentry:
dsn: null
templates:
path: /usr/local/share/mas-cli/templates/
assets_manifest: /usr/local/share/mas-cli/manifest.json
translations_path: /usr/local/share/mas-cli/translations/
email:
from: '"Authentication Service" <root@localhost>'
reply_to: '"Authentication Service" <root@localhost>'
transport: smtp
mode: plain
hostname: "host.containers.internal"
port: %{{SMTP_PORT}}
username: username
password: password
secrets:
encryption: 984b18e207c55ad5fbb2a49b217481a722917ee87b2308d4cf314c83fed8e3b5
keys:
- kid: YEAhzrKipJ
key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuIV+AW5vx52I4CuumgSxp6yvKfIAnRdALeZZCoFkIGxUli1B
S79NJ3ls46oLh1pSD9RrhaMp6HTNoi4K3hnP9Q9v77pD7KwdFKG3UdG1zksIB0s/
+/Ey/DmX4LPluwBBS7r/LkQ1jk745lENA++oiDqZf2D/uP8jCHlvaSNyVKTqi1ki
OXPd4T4xBUjzuas9ze5jQVSYtfOidgnv1EzUipbIxgvH1jNt4raRlmP8mOq7xEnW
R+cF5x6n/g17PdSEfrwO4kz6aKGZuMP5lVlDEEnMHKabFSQDBl7+Mpok6jXutbtA
uiBnsKEahF9eoj4na4fpbRNPdIVyoaN5eGvm5wIDAQABAoIBAApyFCYEmHNWaa83
CdVSOrRhRDE9r+c0r79pcNT1ajOjrk4qFa4yEC4R46YntCtfY5Hd1pBkIjU0l4d8
z8Su9WTMEOwjQUEepS7L0NLi6kXZXYT8L40VpGs+32grBvBFHW0qEtQNrHJ36gMv
x2rXoFTF7HaXiSJx3wvVxAbRqOE9tBXLsmNHaWaAdWQG5o77V9+zvMri3cAeEg2w
VkKokb0dza7es7xG3tqS26k69SrwGeeuKo7qCHPH2cfyWmY5Yhv8iOoA59JzzbiK
UdxyzCHskrPSpRKVkVVwmY3RBt282TmSRG7td7e5ESSj50P2e5BI5uu1Hp/dvU4F
vYjV7kECgYEA6WqYoUpVsgQiqhvJwJIc/8gRm0mUy8TenI36z4Iim01Nt7fibWH7
XnsFqLGjXtYNVWvBcCrUl9doEnRbJeG2eRGbGKYAWVrOeFvwM4fYvw9GoOiJdDj4
cgWDe7eHbHE+UTqR7Nnr/UBfipoNWDh6X68HRBuXowh0Q6tOfxsrRFECgYEAyl/V
4b8bFp3pKZZCb+KPSYsQf793cRmrBexPcLWcDPYbMZQADEZ/VLjbrNrpTOWxUWJT
hr8MrWswnHO+l5AFu5CNO+QgV2dHLk+2w8qpdpFRPJCfXfo2D3wZ0c4cv3VCwv1V
5y7f6XWVjDWZYV4wj6c3shxZJjZ+9Hbhf3/twbcCgYA6fuRRR3fCbRbi2qPtBrEN
yO3gpMgNaQEA6vP4HPzfPrhDWmn8T5nXS61XYW03zxz4U1De81zj0K/cMBzHmZFJ
NghQXQmpWwBzWVcREvJWr1Vb7erEnaJlsMwKrSvbGWYspSj82oAxr3hCG+lMOpsw
b4S6pM+TpAK/EqdRY1WsgQKBgQCGoMaaTRXqL9bC0bEU2XVVCWxKb8c3uEmrwQ7/
/fD4NmjUzI5TnDps1CVfkqoNe+hAKddDFqmKXHqUOfOaxDbsFje+lf5l5tDVoDYH
fjTKKdYPIm7CiAeauYY7qpA5Vfq52Opixy4yEwUPp0CII67OggFtPaqY3zwJyWQt
+57hdQKBgGCXM/KKt7ceUDcNJxSGjvu0zD9D5Sv2ihYlEBT/JLaTCCJdvzREevaJ
1d+mpUAt0Lq6A8NWOMq8HPaxAik3rMQ0WtM5iG+XgsUqvTSb7NcshArDLuWGnW3m
MC4rM0UBYAS4QweduUSH1imrwH/1Gu5+PxbiecceRMMggWpzu0Bq
-----END RSA PRIVATE KEY-----
- kid: 8J1AxrlNZT
key: |
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIF1cjfIOEdy3BXJ72x6fKpEB8WP1ddZAUJAaqqr/6CpToAoGCCqGSM49
AwEHoUQDQgAEfHdNuI1Yeh3/uOq2PlnW2vymloOVpwBYebbw4VVsna9xhnutIdQW
dE8hkX8Yb0pIDasrDiwllVLzSvsWJAI0Kw==
-----END EC PRIVATE KEY-----
- kid: 3BW6un1EBi
key: |
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDA+3ZV17r8TsiMdw1cpbTSNbyEd5SMy3VS1Mk/kz6O2Ev/3QZut8GE2
q3eGtLBoVQigBwYFK4EEACKhZANiAASs8Wxjk/uRimRKXnPr2/wDaXkN9wMDjYQK
mZULb+0ZP1/cXmuXuri8hUGhQvIU8KWY9PkpV+LMPEdpE54mHPKSLjq5CDXoSZ/P
9f7cdRaOZ000KQPZfIFR9ujJTtDN7Vs=
-----END EC PRIVATE KEY-----
- kid: pkZ0pTKK0X
key: |
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIHenfsXYPc5yzjZKUfvmydDR1YRwdsfZYvwHf/2wsYxooAcGBSuBBAAK
oUQDQgAEON1x7Vlu+nA0KvC5vYSOHhDUkfLYNZwYSLPFVT02h9E13yFFMIJegIBl
Aer+6PMZpPc8ycyeH9N+U9NAyliBhQ==
-----END EC PRIVATE KEY-----
passwords:
enabled: true
schemes:
- version: 1
algorithm: argon2id
minimum_complexity: 0
matrix:
homeserver: localhost
secret: AnotherRandomSecret
endpoint: "{{SYNAPSE_URL}}"
policy:
wasm_module: /usr/local/share/mas-cli/policy.wasm
client_registration_entrypoint: client_registration/violation
register_entrypoint: register/violation
authorization_grant_entrypoint: authorization_grant/violation
password_entrypoint: password/violation
email_entrypoint: email/violation
data:
client_registration:
allow_insecure_uris: true # allow non-SSL and localhost URIs
allow_missing_contacts: true # EW doesn't have contacts at this time
upstream_oauth2:
providers: []
branding:
service_name: null
policy_uri: null
tos_uri: null
imprint: null
logo_uri: null
account:
password_registration_enabled: true
experimental:
access_token_ttl: 300
compat_token_ttl: 300

View File

@ -1,145 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import path, { basename } from "node:path";
import os from "node:os";
import * as fse from "fs-extra";
import { BrowserContext, TestInfo } from "@playwright/test";
import { getFreePort } from "../utils/port";
import { Docker } from "../docker";
import { PG_PASSWORD, PostgresDocker } from "../postgres";
import { HomeserverInstance } from "../homeserver";
import { Instance as MailhogInstance } from "../mailhog";
// Docker tag to use for `ghcr.io/matrix-org/matrix-authentication-service` image.
const TAG = "0.12.0";
interface Instance {
containerId: string;
postgresId: string;
configDir: string;
port: number;
}
async function cfgDirFromTemplate(opts: {
postgresHost: string;
synapseUrl: string;
masPort: string;
smtpPort: string;
}): Promise<{
configDir: string;
}> {
const configPath = path.join(__dirname, "config.yaml");
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-mas-"));
const outputHomeserver = path.join(tempDir, "config.yaml");
console.log(`Gen ${configPath} -> ${outputHomeserver}`);
let config = await fse.readFile(configPath, "utf8");
config = config.replace(/{{MAS_PORT}}/g, opts.masPort);
config = config.replace(/{{POSTGRES_HOST}}/g, opts.postgresHost);
config = config.replace(/{{POSTGRES_PASSWORD}}/g, PG_PASSWORD);
config = config.replace(/%{{SMTP_PORT}}/g, opts.smtpPort);
config = config.replace(/{{SYNAPSE_URL}}/g, opts.synapseUrl);
await fse.writeFile(outputHomeserver, config);
// Allow anyone to read, write and execute in the temp directory
// so that the DIND setup that we use to update the playwright screenshots work without any issues.
await fse.chmod(tempDir, 0o757);
return {
configDir: tempDir,
};
}
export class MatrixAuthenticationService {
private readonly masDocker = new Docker();
private readonly postgresDocker = new PostgresDocker("mas");
private instance: Instance;
public port: number;
constructor(private context: BrowserContext) {}
async prepare(): Promise<{ port: number }> {
this.port = await getFreePort();
return { port: this.port };
}
async start(homeserver: HomeserverInstance, mailhog: MailhogInstance): Promise<Instance> {
console.log(new Date(), "Starting mas...");
if (!this.port) await this.prepare();
const port = this.port;
const { containerId: postgresId, ipAddress: postgresIp } = await this.postgresDocker.start();
const { configDir } = await cfgDirFromTemplate({
masPort: port.toString(),
postgresHost: postgresIp,
synapseUrl: homeserver.config.dockerUrl,
smtpPort: mailhog.smtpPort.toString(),
});
console.log(new Date(), "starting mas container...", TAG);
const containerId = await this.masDocker.run({
image: "ghcr.io/element-hq/matrix-authentication-service:" + TAG,
containerName: "react-sdk-playwright-mas",
params: ["-p", `${port}:8080/tcp`, "-v", `${configDir}:/config`],
cmd: ["server", "--config", "/config/config.yaml"],
});
console.log(new Date(), "started!");
// Set up redirects
const baseUrl = `http://localhost:${port}`;
for (const path of [
"**/_matrix/client/*/login",
"**/_matrix/client/*/login/**",
"**/_matrix/client/*/logout",
"**/_matrix/client/*/refresh",
]) {
await this.context.route(path, async (route) => {
await route.continue({
url: new URL(route.request().url().split("/").slice(3).join("/"), baseUrl).href,
});
});
}
this.instance = { containerId, postgresId, port, configDir };
return this.instance;
}
async stop(testInfo: TestInfo): Promise<void> {
if (!this.instance) return; // nothing to stop
const id = this.instance.containerId;
const logPath = path.join("playwright", "logs", "matrix-authentication-service", id);
await fse.ensureDir(logPath);
await this.masDocker.persistLogsToFile({
stdoutFile: path.join(logPath, "stdout.log"),
stderrFile: path.join(logPath, "stderr.log"),
});
await this.masDocker.stop();
await this.postgresDocker.stop();
if (testInfo.status !== "passed") {
const logs = [path.join(logPath, "stdout.log"), path.join(logPath, "stderr.log")];
for (const path of logs) {
await testInfo.attach(`mas-${basename(path)}`, {
path,
contentType: "text/plain",
});
}
await testInfo.attach("mas-config.yaml", {
path: path.join(this.instance.configDir, "config.yaml"),
contentType: "text/plain",
});
}
await fse.remove(this.instance.configDir);
console.log(new Date(), "Stopped mas.");
}
}

View File

@ -1,66 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { Docker } from "../docker";
export const PG_PASSWORD = "p4S5w0rD";
/**
* Class to manage a postgres database in docker
*/
export class PostgresDocker extends Docker {
/**
* @param key an opaque string to use when naming the docker containers instantiated by this class
*/
public constructor(private key: string) {
super();
}
private async waitForPostgresReady(ipAddress: string): Promise<void> {
const waitTimeMillis = 30000;
const startTime = new Date().getTime();
let lastErr: Error | null = null;
while (new Date().getTime() - startTime < waitTimeMillis) {
try {
// Note that we specify the IP address rather than letting it connect to the local
// socket: that's the listener we care about and empirically it matters.
await this.exec(["pg_isready", "-h", ipAddress, "-U", "postgres"], true);
lastErr = null;
break;
} catch (err) {
console.log("pg_isready: failed");
lastErr = err;
}
}
if (lastErr) {
console.log("rethrowing");
throw lastErr;
}
}
public async start(): Promise<{
ipAddress: string;
containerId: string;
}> {
console.log(new Date(), "starting postgres container");
const containerId = await this.run({
image: "postgres",
containerName: `react-sdk-playwright-postgres-${this.key}`,
params: ["--tmpfs=/pgtmpfs", "-e", "PGDATA=/pgtmpfs", "-e", `POSTGRES_PASSWORD=${PG_PASSWORD}`],
// Optimise for testing - https://www.postgresql.org/docs/current/non-durability.html
cmd: ["-c", `fsync=off`, "-c", `synchronous_commit=off`, "-c", `full_page_writes=off`],
});
const ipAddress = await this.getContainerIp();
console.log(new Date(), "postgres container up");
await this.waitForPostgresReady(ipAddress);
console.log(new Date(), "postgres container ready");
return { ipAddress, containerId };
}
}

View File

@ -1,77 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import type { BrowserContext, Route } from "@playwright/test";
import { getFreePort } from "../utils/port";
import { Docker } from "../docker";
import { PG_PASSWORD, PostgresDocker } from "../postgres";
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync` image.
const SLIDING_SYNC_PROXY_TAG = "v0.99.3";
export interface ProxyInstance {
containerId: string;
postgresId: string;
port: number;
}
export class SlidingSyncProxy {
private readonly proxyDocker = new Docker();
private readonly postgresDocker = new PostgresDocker("sliding-sync");
private instance: ProxyInstance;
constructor(
private synapseIp: string,
private context: BrowserContext,
) {}
private syncHandler = async (route: Route) => {
if (!this.instance) return route.abort("blockedbyclient");
const baseUrl = `http://localhost:${this.instance.port}`;
await route.continue({
url: new URL(route.request().url().split("/").slice(3).join("/"), baseUrl).href,
});
};
async start(): Promise<ProxyInstance> {
console.log(new Date(), "Starting sliding sync proxy...");
const { ipAddress: postgresIp, containerId: postgresId } = await this.postgresDocker.start();
const port = await getFreePort();
console.log(new Date(), "starting proxy container...", SLIDING_SYNC_PROXY_TAG);
const containerId = await this.proxyDocker.run({
image: "ghcr.io/matrix-org/sliding-sync:" + SLIDING_SYNC_PROXY_TAG,
containerName: "react-sdk-playwright-sliding-sync-proxy",
params: [
"-p",
`${port}:8008/tcp`,
"-e",
"SYNCV3_SECRET=bwahahaha",
"-e",
`SYNCV3_SERVER=${this.synapseIp}`,
"-e",
`SYNCV3_DB=user=postgres dbname=postgres password=${PG_PASSWORD} host=${postgresIp} sslmode=disable`,
],
});
console.log(new Date(), "started!");
this.instance = { containerId, postgresId, port };
await this.context.route("**/_matrix/client/unstable/org.matrix.msc3575/sync*", this.syncHandler);
return this.instance;
}
async stop(): Promise<void> {
await this.context.unroute("**/_matrix/client/unstable/org.matrix.msc3575/sync*", this.syncHandler);
await this.postgresDocker.stop();
await this.proxyDocker.stop();
console.log(new Date(), "Stopped sliding sync proxy.");
}
}

View File

@ -0,0 +1,16 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
/**
* Deep copy the given object. The object MUST NOT have circular references and
* MUST NOT have functions.
* @param obj - The object to deep copy.
* @returns A copy of the object without any references to the original.
*/
export function deepCopy<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}

109
playwright/services.ts Normal file
View File

@ -0,0 +1,109 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test as base } from "@playwright/test";
import mailhog from "mailhog";
import { GenericContainer, Network, StartedNetwork, StartedTestContainer, Wait } from "testcontainers";
import { PostgreSqlContainer, StartedPostgreSqlContainer } from "@testcontainers/postgresql";
import { SynapseConfigOptions, SynapseContainer } from "./testcontainers/synapse.ts";
import { ContainerLogger } from "./testcontainers/utils.ts";
import { StartedMatrixAuthenticationServiceContainer } from "./testcontainers/mas.ts";
import { HomeserverContainer, StartedHomeserverContainer } from "./testcontainers/HomeserverContainer.ts";
export interface Services {
logger: ContainerLogger;
network: StartedNetwork;
postgres: StartedPostgreSqlContainer;
mailhog: StartedTestContainer;
mailhogClient: mailhog.API;
synapseConfigOptions: SynapseConfigOptions;
_homeserver: HomeserverContainer<any>;
homeserver: StartedHomeserverContainer;
mas?: StartedMatrixAuthenticationServiceContainer;
}
export const test = base.extend<Services>({
// eslint-disable-next-line no-empty-pattern
logger: async ({}, use, testInfo) => {
const logger = new ContainerLogger();
await use(logger);
await logger.testFinished(testInfo);
},
// eslint-disable-next-line no-empty-pattern
network: async ({}, use) => {
const network = await new Network().start();
await use(network);
await network.stop();
},
postgres: async ({ logger, network }, use) => {
const container = await new PostgreSqlContainer()
.withNetwork(network)
.withNetworkAliases("postgres")
.withLogConsumer(logger.getConsumer("postgres"))
.withTmpFs({
"/dev/shm/pgdata/data": "",
})
.withEnvironment({
PG_DATA: "/dev/shm/pgdata/data",
})
.withCommand([
"-c",
"shared_buffers=128MB",
"-c",
`fsync=off`,
"-c",
`synchronous_commit=off`,
"-c",
"full_page_writes=off",
])
.start();
await use(container);
await container.stop();
},
mailhog: async ({ logger, network }, use) => {
const container = await new GenericContainer("mailhog/mailhog:latest")
.withNetwork(network)
.withNetworkAliases("mailhog")
.withExposedPorts(8025)
.withLogConsumer(logger.getConsumer("mailhog"))
.withWaitStrategy(Wait.forListeningPorts())
.start();
await use(container);
await container.stop();
},
mailhogClient: async ({ mailhog: container }, use) => {
await use(mailhog({ host: container.getHost(), port: container.getMappedPort(8025) }));
},
synapseConfigOptions: [{}, { option: true }],
_homeserver: async ({ request }, use) => {
const container = new SynapseContainer(request);
await use(container);
},
homeserver: async ({ logger, network, _homeserver: homeserver, synapseConfigOptions, mas }, use) => {
const container = await homeserver
.withNetwork(network)
.withNetworkAliases("homeserver")
.withLogConsumer(logger.getConsumer("synapse"))
.withConfig(synapseConfigOptions)
.start();
await use(container);
await container.stop();
},
// eslint-disable-next-line no-empty-pattern
mas: async ({}, use) => {
// we stub the mas fixture to allow `homeserver` to depend on it to ensure
// when it is specified by `masHomeserver` it is started before the homeserver
await use(undefined);
},
});

View File

@ -0,0 +1,19 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { AbstractStartedContainer, GenericContainer } from "testcontainers";
import { StartedSynapseContainer } from "./synapse.ts";
import { HomeserverInstance } from "../plugins/homeserver";
export interface HomeserverContainer<Config> extends GenericContainer {
withConfigField(key: string, value: any): this;
withConfig(config: Partial<Config>): this;
start(): Promise<StartedSynapseContainer>;
}
export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {}

View File

@ -0,0 +1,266 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { GenericContainer, Wait } from "testcontainers";
import { APIRequestContext } from "@playwright/test";
import * as YAML from "yaml";
import { set } from "lodash";
import { randB64Bytes } from "../plugins/utils/rand.ts";
import { StartedSynapseContainer } from "./synapse.ts";
import { deepCopy } from "../plugins/utils/object.ts";
import { HomeserverContainer } from "./HomeserverContainer.ts";
const DEFAULT_CONFIG = {
version: 2,
global: {
server_name: "localhost",
private_key: "matrix_key.pem",
old_private_keys: null,
key_validity_period: "168h0m0s",
cache: {
max_size_estimated: "1gb",
max_age: "1h",
},
well_known_server_name: "",
well_known_client_name: "",
trusted_third_party_id_servers: ["matrix.org", "vector.im"],
disable_federation: false,
presence: {
enable_inbound: false,
enable_outbound: false,
},
report_stats: {
enabled: false,
endpoint: "https://matrix.org/report-usage-stats/push",
},
server_notices: {
enabled: false,
local_part: "_server",
display_name: "Server Alerts",
avatar_url: "",
room_name: "Server Alerts",
},
jetstream: {
addresses: null,
disable_tls_validation: false,
storage_path: "./",
topic_prefix: "Dendrite",
},
metrics: {
enabled: false,
basic_auth: {
username: "metrics",
password: "metrics",
},
},
dns_cache: {
enabled: false,
cache_size: 256,
cache_lifetime: "5m",
},
},
app_service_api: {
disable_tls_validation: false,
config_files: null,
},
client_api: {
registration_disabled: false,
guests_disabled: true,
registration_shared_secret: "secret",
enable_registration_captcha: false,
recaptcha_public_key: "",
recaptcha_private_key: "",
recaptcha_bypass_secret: "",
turn: {
turn_user_lifetime: "5m",
turn_uris: null,
turn_shared_secret: "",
},
rate_limiting: {
enabled: true,
threshold: 20,
cooloff_ms: 500,
exempt_user_ids: null,
},
},
federation_api: {
send_max_retries: 16,
disable_tls_validation: false,
disable_http_keepalives: false,
key_perspectives: [
{
server_name: "matrix.org",
keys: [
{
key_id: "ed25519:auto",
public_key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
},
{
key_id: "ed25519:a_RXGa",
public_key: "l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ",
},
],
},
],
prefer_direct_fetch: false,
database: {
connection_string: "file:dendrite-federationapi.db",
},
},
media_api: {
base_path: "./media_store",
max_file_size_bytes: 10485760,
dynamic_thumbnails: false,
max_thumbnail_generators: 10,
thumbnail_sizes: [
{
width: 32,
height: 32,
method: "crop",
},
{
width: 96,
height: 96,
method: "crop",
},
{
width: 640,
height: 480,
method: "scale",
},
],
database: {
connection_string: "file:dendrite-mediaapi.db",
},
},
mscs: {
mscs: null,
database: {
connection_string: "file:dendrite-msc.db",
},
},
sync_api: {
search: {
enabled: false,
index_path: "./searchindex",
language: "en",
},
database: {
connection_string: "file:dendrite-syncapi.db",
},
},
user_api: {
bcrypt_cost: 10,
auto_join_rooms: null,
account_database: {
connection_string: "file:dendrite-userapi.db",
},
},
room_server: {
database: {
connection_string: "file:dendrite-roomserverapi.db",
},
},
key_server: {
database: {
connection_string: "file:dendrite-keyserverapi.db",
},
},
relay_api: {
database: {
connection_string: "file:dendrite-relayapi.db",
},
},
tracing: {
enabled: false,
jaeger: {
serviceName: "",
disabled: false,
rpc_metrics: false,
tags: [],
sampler: null,
reporter: null,
headers: null,
baggage_restrictions: null,
throttler: null,
},
},
logging: [
{
type: "std",
level: "debug",
},
{
type: "file",
level: "debug",
params: {
path: "./logs",
},
},
],
};
export class DendriteContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
private config: typeof DEFAULT_CONFIG;
constructor(
private request: APIRequestContext,
image = "matrixdotorg/dendrite-monolith:main",
binary = "/usr/bin/dendrite",
) {
super(image);
this.config = deepCopy(DEFAULT_CONFIG);
this.config.client_api.registration_shared_secret = randB64Bytes(16);
this.withEntrypoint(["/bin/sh"])
.withCommand([
"-c",
`/usr/bin/generate-keys -private-key /etc/dendrite/matrix_key.pem && ${binary} --config /etc/dendrite/dendrite.yaml --really-enable-open-registration true run`,
])
.withExposedPorts(8008)
.withWaitStrategy(Wait.forHttp("/_matrix/client/versions", 8008));
}
public withConfigField(key: string, value: any): this {
set(this.config, key, value);
return this;
}
public withConfig(config: Partial<typeof DEFAULT_CONFIG>): this {
this.config = {
...this.config,
...config,
};
return this;
}
public override async start(): Promise<StartedSynapseContainer> {
this.withCopyContentToContainer([
{
target: "/etc/dendrite/dendrite.yaml",
content: YAML.stringify(this.config),
},
]);
const container = await super.start();
// Surprisingly, Dendrite implements the same register user Admin API Synapse, so we can just extend it
return new StartedSynapseContainer(
container,
`http://${container.getHost()}:${container.getMappedPort(8008)}`,
this.config.client_api.registration_shared_secret,
this.request,
);
}
}
export class PineconeContainer extends DendriteContainer {
constructor(request: APIRequestContext) {
super(request, "matrixdotorg/dendrite-demo-pinecone:main", "/usr/bin/dendrite-demo-pinecone");
}
}

View File

@ -0,0 +1,224 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { StartedPostgreSqlContainer } from "@testcontainers/postgresql";
import * as YAML from "yaml";
import { getFreePort } from "../plugins/utils/port.ts";
import { deepCopy } from "../plugins/utils/object.ts";
const DEFAULT_CONFIG = {
http: {
listeners: [
{
name: "web",
resources: [
{
name: "discovery",
},
{
name: "human",
},
{
name: "oauth",
},
{
name: "compat",
},
{
name: "graphql",
playground: true,
},
{
name: "assets",
path: "/usr/local/share/mas-cli/assets/",
},
],
binds: [
{
address: "[::]:8080",
},
],
proxy_protocol: false,
},
{
name: "internal",
resources: [
{
name: "health",
},
],
binds: [
{
address: "[::]:8081",
},
],
proxy_protocol: false,
},
],
trusted_proxies: ["192.128.0.0/16", "172.16.0.0/12", "10.0.0.0/10", "127.0.0.1/8", "fd00::/8", "::1/128"],
public_base: "", // Needs to be set
issuer: "", // Needs to be set
},
database: {
host: "postgres",
port: 5432,
database: "postgres",
username: "postgres",
password: "p4S5w0rD",
max_connections: 10,
min_connections: 0,
connect_timeout: 30,
idle_timeout: 600,
max_lifetime: 1800,
},
telemetry: {
tracing: {
exporter: "none",
propagators: [],
},
metrics: {
exporter: "none",
},
sentry: {
dsn: null,
},
},
templates: {
path: "/usr/local/share/mas-cli/templates/",
assets_manifest: "/usr/local/share/mas-cli/manifest.json",
translations_path: "/usr/local/share/mas-cli/translations/",
},
email: {
from: '"Authentication Service" <root@localhost>',
reply_to: '"Authentication Service" <root@localhost>',
transport: "smtp",
mode: "plain",
hostname: "mailhog",
port: 1025,
username: "username",
password: "password",
},
secrets: {
encryption: "984b18e207c55ad5fbb2a49b217481a722917ee87b2308d4cf314c83fed8e3b5",
keys: [
{
kid: "YEAhzrKipJ",
key: "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAuIV+AW5vx52I4CuumgSxp6yvKfIAnRdALeZZCoFkIGxUli1B\nS79NJ3ls46oLh1pSD9RrhaMp6HTNoi4K3hnP9Q9v77pD7KwdFKG3UdG1zksIB0s/\n+/Ey/DmX4LPluwBBS7r/LkQ1jk745lENA++oiDqZf2D/uP8jCHlvaSNyVKTqi1ki\nOXPd4T4xBUjzuas9ze5jQVSYtfOidgnv1EzUipbIxgvH1jNt4raRlmP8mOq7xEnW\nR+cF5x6n/g17PdSEfrwO4kz6aKGZuMP5lVlDEEnMHKabFSQDBl7+Mpok6jXutbtA\nuiBnsKEahF9eoj4na4fpbRNPdIVyoaN5eGvm5wIDAQABAoIBAApyFCYEmHNWaa83\nCdVSOrRhRDE9r+c0r79pcNT1ajOjrk4qFa4yEC4R46YntCtfY5Hd1pBkIjU0l4d8\nz8Su9WTMEOwjQUEepS7L0NLi6kXZXYT8L40VpGs+32grBvBFHW0qEtQNrHJ36gMv\nx2rXoFTF7HaXiSJx3wvVxAbRqOE9tBXLsmNHaWaAdWQG5o77V9+zvMri3cAeEg2w\nVkKokb0dza7es7xG3tqS26k69SrwGeeuKo7qCHPH2cfyWmY5Yhv8iOoA59JzzbiK\nUdxyzCHskrPSpRKVkVVwmY3RBt282TmSRG7td7e5ESSj50P2e5BI5uu1Hp/dvU4F\nvYjV7kECgYEA6WqYoUpVsgQiqhvJwJIc/8gRm0mUy8TenI36z4Iim01Nt7fibWH7\nXnsFqLGjXtYNVWvBcCrUl9doEnRbJeG2eRGbGKYAWVrOeFvwM4fYvw9GoOiJdDj4\ncgWDe7eHbHE+UTqR7Nnr/UBfipoNWDh6X68HRBuXowh0Q6tOfxsrRFECgYEAyl/V\n4b8bFp3pKZZCb+KPSYsQf793cRmrBexPcLWcDPYbMZQADEZ/VLjbrNrpTOWxUWJT\nhr8MrWswnHO+l5AFu5CNO+QgV2dHLk+2w8qpdpFRPJCfXfo2D3wZ0c4cv3VCwv1V\n5y7f6XWVjDWZYV4wj6c3shxZJjZ+9Hbhf3/twbcCgYA6fuRRR3fCbRbi2qPtBrEN\nyO3gpMgNaQEA6vP4HPzfPrhDWmn8T5nXS61XYW03zxz4U1De81zj0K/cMBzHmZFJ\nNghQXQmpWwBzWVcREvJWr1Vb7erEnaJlsMwKrSvbGWYspSj82oAxr3hCG+lMOpsw\nb4S6pM+TpAK/EqdRY1WsgQKBgQCGoMaaTRXqL9bC0bEU2XVVCWxKb8c3uEmrwQ7/\n/fD4NmjUzI5TnDps1CVfkqoNe+hAKddDFqmKXHqUOfOaxDbsFje+lf5l5tDVoDYH\nfjTKKdYPIm7CiAeauYY7qpA5Vfq52Opixy4yEwUPp0CII67OggFtPaqY3zwJyWQt\n+57hdQKBgGCXM/KKt7ceUDcNJxSGjvu0zD9D5Sv2ihYlEBT/JLaTCCJdvzREevaJ\n1d+mpUAt0Lq6A8NWOMq8HPaxAik3rMQ0WtM5iG+XgsUqvTSb7NcshArDLuWGnW3m\nMC4rM0UBYAS4QweduUSH1imrwH/1Gu5+PxbiecceRMMggWpzu0Bq\n-----END RSA PRIVATE KEY-----\n",
},
{
kid: "8J1AxrlNZT",
key: "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIF1cjfIOEdy3BXJ72x6fKpEB8WP1ddZAUJAaqqr/6CpToAoGCCqGSM49\nAwEHoUQDQgAEfHdNuI1Yeh3/uOq2PlnW2vymloOVpwBYebbw4VVsna9xhnutIdQW\ndE8hkX8Yb0pIDasrDiwllVLzSvsWJAI0Kw==\n-----END EC PRIVATE KEY-----\n",
},
{
kid: "3BW6un1EBi",
key: "-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDA+3ZV17r8TsiMdw1cpbTSNbyEd5SMy3VS1Mk/kz6O2Ev/3QZut8GE2\nq3eGtLBoVQigBwYFK4EEACKhZANiAASs8Wxjk/uRimRKXnPr2/wDaXkN9wMDjYQK\nmZULb+0ZP1/cXmuXuri8hUGhQvIU8KWY9PkpV+LMPEdpE54mHPKSLjq5CDXoSZ/P\n9f7cdRaOZ000KQPZfIFR9ujJTtDN7Vs=\n-----END EC PRIVATE KEY-----\n",
},
{
kid: "pkZ0pTKK0X",
key: "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIHenfsXYPc5yzjZKUfvmydDR1YRwdsfZYvwHf/2wsYxooAcGBSuBBAAK\noUQDQgAEON1x7Vlu+nA0KvC5vYSOHhDUkfLYNZwYSLPFVT02h9E13yFFMIJegIBl\nAer+6PMZpPc8ycyeH9N+U9NAyliBhQ==\n-----END EC PRIVATE KEY-----\n",
},
],
},
passwords: {
enabled: true,
schemes: [
{
version: 1,
algorithm: "argon2id",
},
],
minimum_complexity: 0,
},
policy: {
wasm_module: "/usr/local/share/mas-cli/policy.wasm",
client_registration_entrypoint: "client_registration/violation",
register_entrypoint: "register/violation",
authorization_grant_entrypoint: "authorization_grant/violation",
password_entrypoint: "password/violation",
email_entrypoint: "email/violation",
data: {
client_registration: {
// allow non-SSL and localhost URIs
allow_insecure_uris: true,
// EW doesn't have contacts at this time
allow_missing_contacts: true,
},
},
},
upstream_oauth2: {
providers: [],
},
branding: {
service_name: null,
policy_uri: null,
tos_uri: null,
imprint: null,
logo_uri: null,
},
account: {
password_registration_enabled: true,
},
experimental: {
access_token_ttl: 300,
compat_token_ttl: 300,
},
};
export class MatrixAuthenticationServiceContainer extends GenericContainer {
private config: typeof DEFAULT_CONFIG;
constructor(db: StartedPostgreSqlContainer) {
super("ghcr.io/element-hq/matrix-authentication-service:0.12.0");
this.config = deepCopy(DEFAULT_CONFIG);
this.config.database.username = db.getUsername();
this.config.database.password = db.getPassword();
this.withExposedPorts(8080, 8081)
.withWaitStrategy(Wait.forHttp("/health", 8081))
.withCommand(["server", "--config", "/config/config.yaml"]);
}
public withConfig(config: object): this {
this.config = {
...this.config,
...config,
};
return this;
}
public override async start(): Promise<StartedMatrixAuthenticationServiceContainer> {
// MAS config issuer needs to know what URL it'll be accessed from, so we have to map the port manually
const port = await getFreePort();
this.config.http.public_base = `http://localhost:${port}/`;
this.config.http.issuer = `http://localhost:${port}/`;
this.withExposedPorts({
container: 8080,
host: port,
}).withCopyContentToContainer([
{
target: "/config/config.yaml",
content: YAML.stringify(this.config),
},
]);
return new StartedMatrixAuthenticationServiceContainer(await super.start(), `http://localhost:${port}`);
}
}
export class StartedMatrixAuthenticationServiceContainer extends AbstractStartedContainer {
constructor(
container: StartedTestContainer,
public readonly baseUrl: string,
) {
super(container);
}
}

View File

@ -0,0 +1,331 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { APIRequestContext } from "@playwright/test";
import crypto from "node:crypto";
import * as YAML from "yaml";
import { set } from "lodash";
import { getFreePort } from "../plugins/utils/port.ts";
import { randB64Bytes } from "../plugins/utils/rand.ts";
import { Credentials } from "../plugins/homeserver";
import { deepCopy } from "../plugins/utils/object.ts";
import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverContainer.ts";
const TAG = "develop@sha256:17cc0a301447430624afb860276e5c13270ddeb99a3f6d1c6d519a20b1a8f650";
const DEFAULT_CONFIG = {
server_name: "localhost",
public_baseurl: "", // set by start method
pid_file: "/homeserver.pid",
web_client: false,
soft_file_limit: 0,
// Needs to be configured to log to the console like a good docker process
log_config: "/data/log.config",
listeners: [
{
// Listener is always port 8008 (configured in the container)
port: 8008,
tls: false,
bind_addresses: ["::"],
type: "http",
x_forwarded: true,
resources: [
{
names: ["client"],
compress: false,
},
],
},
],
database: {
// An sqlite in-memory database is fast & automatically wipes each time
name: "sqlite3",
args: {
database: ":memory:",
},
},
rc_messages_per_second: 10000,
rc_message_burst_count: 10000,
rc_registration: {
per_second: 10000,
burst_count: 10000,
},
rc_joins: {
local: {
per_second: 9999,
burst_count: 9999,
},
remote: {
per_second: 9999,
burst_count: 9999,
},
},
rc_joins_per_room: {
per_second: 9999,
burst_count: 9999,
},
rc_3pid_validation: {
per_second: 1000,
burst_count: 1000,
},
rc_invites: {
per_room: {
per_second: 1000,
burst_count: 1000,
},
per_user: {
per_second: 1000,
burst_count: 1000,
},
},
rc_login: {
address: {
per_second: 10000,
burst_count: 10000,
},
account: {
per_second: 10000,
burst_count: 10000,
},
failed_attempts: {
per_second: 10000,
burst_count: 10000,
},
},
media_store_path: "/tmp/media_store",
max_upload_size: "50M",
max_image_pixels: "32M",
dynamic_thumbnails: false,
enable_registration: true,
enable_registration_without_verification: true,
disable_msisdn_registration: false,
registrations_require_3pid: [],
enable_metrics: false,
report_stats: false,
// These placeholders will be replaced with values generated at start
registration_shared_secret: "secret",
macaroon_secret_key: "secret",
form_secret: "secret",
// Signing key must be here: it will be generated to this file
signing_key_path: "/data/localhost.signing.key",
trusted_key_servers: [],
password_config: {
enabled: true,
},
ui_auth: {
session_timeout: "300s",
},
background_updates: {
// Inhibit background updates as this Synapse isn't long-lived
min_batch_size: 100000,
sleep_duration_ms: 100000,
},
enable_authenticated_media: true,
email: undefined,
user_consent: undefined,
server_notices: undefined,
allow_guest_access: false,
experimental_features: {},
oidc_providers: [],
serve_server_wellknown: true,
};
export type SynapseConfigOptions = Partial<typeof DEFAULT_CONFIG>;
export class SynapseContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
private config: typeof DEFAULT_CONFIG;
constructor(private readonly request: APIRequestContext) {
super(`ghcr.io/element-hq/synapse:${TAG}`);
this.config = deepCopy(DEFAULT_CONFIG);
this.config.registration_shared_secret = randB64Bytes(16);
this.config.macaroon_secret_key = randB64Bytes(16);
this.config.form_secret = randB64Bytes(16);
const signingKey = randB64Bytes(32);
this.withWaitStrategy(Wait.forHttp("/health", 8008)).withCopyContentToContainer([
{ target: this.config.signing_key_path, content: `ed25519 x ${signingKey}` },
{
target: this.config.log_config,
content: YAML.stringify({
version: 1,
formatters: {
precise: {
format: "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s",
},
},
handlers: {
console: {
class: "logging.StreamHandler",
formatter: "precise",
},
},
loggers: {
"synapse.storage.SQL": {
level: "DEBUG",
},
"twisted": {
handlers: ["console"],
propagate: false,
},
},
root: {
level: "DEBUG",
handlers: ["console"],
},
disable_existing_loggers: false,
}),
},
]);
}
public withConfigField(key: string, value: any): this {
set(this.config, key, value);
return this;
}
public withConfig(config: Partial<typeof DEFAULT_CONFIG>): this {
this.config = {
...this.config,
...config,
};
return this;
}
public override async start(): Promise<StartedSynapseContainer> {
// Synapse config public_baseurl needs to know what URL it'll be accessed from, so we have to map the port manually
const port = await getFreePort();
this.withExposedPorts({
container: 8008,
host: port,
})
.withConfig({
public_baseurl: `http://localhost:${port}`,
})
.withCopyContentToContainer([
{
target: "/data/homeserver.yaml",
content: YAML.stringify(this.config),
},
]);
return new StartedSynapseContainer(
await super.start(),
`http://localhost:${port}`,
this.config.registration_shared_secret,
this.request,
);
}
}
export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
private adminToken?: string;
constructor(
container: StartedTestContainer,
public readonly baseUrl: string,
private readonly registrationSharedSecret: string,
private readonly request: APIRequestContext,
) {
super(container);
}
private async registerUserInternal(
username: string,
password: string,
displayName?: string,
admin = false,
): Promise<Credentials> {
const url = `${this.baseUrl}/_synapse/admin/v1/register`;
const { nonce } = await this.request.get(url).then((r) => r.json());
const mac = crypto
.createHmac("sha1", this.registrationSharedSecret)
.update(`${nonce}\0${username}\0${password}\0${admin ? "" : "not"}admin`)
.digest("hex");
const res = await this.request.post(url, {
data: {
nonce,
username,
password,
mac,
admin,
displayname: displayName,
},
});
if (!res.ok()) {
throw await res.json();
}
const data = await res.json();
return {
homeServer: data.home_server,
accessToken: data.access_token,
userId: data.user_id,
deviceId: data.device_id,
password,
displayName,
};
}
public registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
return this.registerUserInternal(username, password, displayName, false);
}
public async loginUser(userId: string, password: string): Promise<Credentials> {
const url = `${this.baseUrl}/_matrix/client/v3/login`;
const res = await this.request.post(url, {
data: {
type: "m.login.password",
identifier: {
type: "m.id.user",
user: userId,
},
password: password,
},
});
const json = await res.json();
return {
password,
accessToken: json.access_token,
userId: json.user_id,
deviceId: json.device_id,
homeServer: json.home_server,
};
}
public async setThreepid(userId: string, medium: string, address: string): Promise<void> {
if (this.adminToken === undefined) {
const result = await this.registerUserInternal("admin", "totalyinsecureadminpassword", undefined, true);
this.adminToken = result.accessToken;
}
const url = `${this.baseUrl}/_synapse/admin/v2/users/${userId}`;
const res = await this.request.put(url, {
data: {
threepids: [
{
medium,
address,
},
],
},
headers: {
Authorization: `Bearer ${this.adminToken}`,
},
});
if (!res.ok()) {
throw await res.json();
}
}
}

View File

@ -0,0 +1,37 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { TestInfo } from "@playwright/test";
import { Readable } from "stream";
import stripAnsi from "strip-ansi";
export class ContainerLogger {
private logs: Record<string, string> = {};
public getConsumer(container: string) {
this.logs[container] = "";
return (stream: Readable) => {
stream.on("data", (chunk) => {
this.logs[container] += chunk.toString();
});
stream.on("err", (chunk) => {
this.logs[container] += "ERR " + chunk.toString();
});
};
}
public async testFinished(testInfo: TestInfo) {
if (testInfo.status !== "passed") {
for (const container in this.logs) {
await testInfo.attach(container, {
body: stripAnsi(this.logs[container]),
contentType: "text/plain",
});
}
}
}
}

586
yarn.lock
View File

@ -1158,6 +1158,11 @@
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@balena/dockerignore@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d"
integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@ -1562,6 +1567,11 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@fastify/busboy@^2.0.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@floating-ui/core@^1.6.0":
version "1.6.8"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12"
@ -2096,6 +2106,11 @@
tslib "^2.6.2"
webcrypto-core "^1.8.0"
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@playwright/test@^1.40.1":
version "1.49.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.1.tgz#55fa360658b3187bfb6371e2f8a64f50ef80c827"
@ -2677,6 +2692,13 @@
"@svgr/plugin-jsx" "8.1.0"
"@svgr/plugin-svgo" "8.1.0"
"@testcontainers/postgresql@^10.16.0":
version "10.16.0"
resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-10.16.0.tgz#0437a9b426d64ea958e745a0e2ae19462b786f81"
integrity sha512-zWFQI+3QxlEELRvVv27i6zlVEPNUz9zKaSh7iWmFlCdfhcyr78daS0FG8FIfdQ79VK7YXA4jv+dTYXa2SwXu/w==
dependencies:
testcontainers "^10.16.0"
"@testing-library/dom@^10.4.0":
version "10.4.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
@ -2844,6 +2866,23 @@
resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af"
integrity sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==
"@types/docker-modem@*":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/docker-modem/-/docker-modem-3.0.6.tgz#1f9262fcf85425b158ca725699a03eb23cddbf87"
integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==
dependencies:
"@types/node" "*"
"@types/ssh2" "*"
"@types/dockerode@^3.3.29":
version "3.3.33"
resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.33.tgz#67d9b4223caf41a0735695abe89c292e05d305c9"
integrity sha512-7av8lVOhkW7Xd11aZTSq5zhdpyNraldXwQR0pxUCiSNTvIzsP86KrFrmrZgxtrXD2Zrtzwt4H6OYLbATONWzWg==
dependencies:
"@types/docker-modem" "*"
"@types/node" "*"
"@types/ssh2" "*"
"@types/escape-html@^1.0.1":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.4.tgz#dc7c166b76c7b03b27e32f80edf01d91eb5d9af2"
@ -2920,14 +2959,6 @@
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d"
integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==
"@types/fs-extra@^11.0.0":
version "11.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45"
integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==
dependencies:
"@types/jsonfile" "*"
"@types/node" "*"
"@types/geojson-vt@3.2.5":
version "3.2.5"
resolved "https://registry.yarnpkg.com/@types/geojson-vt/-/geojson-vt-3.2.5.tgz#b6c356874991d9ab4207533476dfbcdb21e38408"
@ -3033,13 +3064,6 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/jsonfile@*":
version "6.1.4"
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702"
integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==
dependencies:
"@types/node" "*"
"@types/jsrsasign@^10.5.4":
version "10.5.15"
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.15.tgz#5cf1ee506b2fa2435b6e1786a873285c7110eb82"
@ -3113,6 +3137,13 @@
dependencies:
undici-types "~5.26.4"
"@types/node@^18.11.18":
version "18.19.69"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.69.tgz#748d301818ba4b238854c53d290257a70aae7d01"
integrity sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==
dependencies:
undici-types "~5.26.4"
"@types/normalize-package-data@^2.4.0":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
@ -3253,6 +3284,28 @@
dependencies:
"@types/node" "*"
"@types/ssh2-streams@*":
version "0.1.12"
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f"
integrity sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==
dependencies:
"@types/node" "*"
"@types/ssh2@*":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.1.tgz#4db4b6864abca09eb299fe5354fa591add412223"
integrity sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==
dependencies:
"@types/node" "^18.11.18"
"@types/ssh2@^0.5.48":
version "0.5.52"
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741"
integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==
dependencies:
"@types/node" "*"
"@types/ssh2-streams" "*"
"@types/stack-utils@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
@ -3635,6 +3688,13 @@ abab@^2.0.6:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
@ -3793,6 +3853,32 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
archiver-utils@^5.0.0, archiver-utils@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d"
integrity sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==
dependencies:
glob "^10.0.0"
graceful-fs "^4.2.0"
is-stream "^2.0.1"
lazystream "^1.0.0"
lodash "^4.17.15"
normalize-path "^3.0.0"
readable-stream "^4.0.0"
archiver@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61"
integrity sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==
dependencies:
archiver-utils "^5.0.2"
async "^3.2.4"
buffer-crc32 "^1.0.0"
readable-stream "^4.0.0"
readdir-glob "^1.1.2"
tar-stream "^3.0.0"
zip-stream "^6.0.1"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
@ -3928,6 +4014,13 @@ arraybuffer.prototype.slice@^1.0.3:
is-array-buffer "^3.0.4"
is-shared-array-buffer "^1.0.2"
asn1@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
dependencies:
safer-buffer "~2.1.0"
asn1js@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
@ -3947,7 +4040,12 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async@^3.2.3:
async-lock@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f"
integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==
async@^3.2.3, async@^3.2.4:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
@ -3991,6 +4089,11 @@ axobject-query@^4.1.0:
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
b4a@^1.6.4:
version "1.6.7"
resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4"
integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==
babel-jest@^29.0.0, babel-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
@ -4101,6 +4204,39 @@ balanced-match@^2.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
bare-events@^2.0.0, bare-events@^2.2.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc"
integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==
bare-fs@^2.1.1:
version "2.3.5"
resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.5.tgz#05daa8e8206aeb46d13c2fe25a2cd3797b0d284a"
integrity sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==
dependencies:
bare-events "^2.0.0"
bare-path "^2.0.0"
bare-stream "^2.0.0"
bare-os@^2.1.0:
version "2.4.4"
resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.4.4.tgz#01243392eb0a6e947177bb7c8a45123d45c9b1a9"
integrity sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==
bare-path@^2.0.0, bare-path@^2.1.0:
version "2.1.3"
resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.3.tgz#594104c829ef660e43b5589ec8daef7df6cedb3e"
integrity sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==
dependencies:
bare-os "^2.1.0"
bare-stream@^2.0.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.1.tgz#b3b9874fab05b662c9aea2706a12fb0698c46836"
integrity sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==
dependencies:
streamx "^2.21.0"
base-x@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b"
@ -4111,11 +4247,23 @@ base64-arraybuffer@^1.0.2:
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
batch@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
dependencies:
tweetnacl "^0.14.3"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -4126,6 +4274,15 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
blob-polyfill@^9.0.0:
version "9.0.20240710"
resolved "https://registry.yarnpkg.com/blob-polyfill/-/blob-polyfill-9.0.20240710.tgz#2ef075a207311ea327704f04dc4a98cbefe4143b"
@ -4227,11 +4384,37 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buffer-crc32@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405"
integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
buildcheck@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
builtin-modules@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
@ -4244,6 +4427,11 @@ bundle-name@^4.1.0:
dependencies:
run-applescript "^7.0.0"
byline@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@ -4388,6 +4576,11 @@ chokidar@^4.0.0:
dependencies:
readdirp "^4.0.1"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chrome-trace-event@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
@ -4579,6 +4772,17 @@ commonmark@^0.31.0:
mdurl "~1.0.1"
minimist "~1.2.8"
compress-commons@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e"
integrity sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==
dependencies:
crc-32 "^1.2.0"
crc32-stream "^6.0.0"
is-stream "^2.0.1"
normalize-path "^3.0.0"
readable-stream "^4.0.0"
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@ -4720,11 +4924,32 @@ counterpart@^0.18.6:
pluralizers "^0.1.7"
sprintf-js "^1.0.3"
cpu-features@~0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5"
integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==
dependencies:
buildcheck "~0.0.6"
nan "^2.19.0"
crc-32@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e"
integrity sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==
crc-32@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
crc32-stream@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430"
integrity sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==
dependencies:
crc-32 "^1.2.0"
readable-stream "^4.0.0"
create-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320"
@ -5033,7 +5258,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7:
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
@ -5201,6 +5426,32 @@ dns-packet@^5.2.2:
dependencies:
"@leichtgewicht/ip-codec" "^2.0.1"
docker-compose@^0.24.8:
version "0.24.8"
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.24.8.tgz#6c125e6b9e04cf68ced47e2596ef2bb93ee9694e"
integrity sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==
dependencies:
yaml "^2.2.2"
docker-modem@^3.0.0:
version "3.0.8"
resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a"
integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==
dependencies:
debug "^4.1.1"
readable-stream "^3.5.0"
split-ca "^1.0.1"
ssh2 "^1.11.0"
dockerode@^3.3.5:
version "3.3.5"
resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629"
integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==
dependencies:
"@balena/dockerignore" "^1.0.2"
docker-modem "^3.0.0"
tar-fs "~2.0.1"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@ -5415,6 +5666,13 @@ encodeurl@~2.0.0:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
enhanced-resolve@^5.17.1:
version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
@ -5893,6 +6151,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@ -5903,7 +6166,7 @@ eventemitter3@^5.0.1:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
events@^3.2.0:
events@^3.2.0, events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@ -6013,6 +6276,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-fifo@^1.2.0, fast-fifo@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
fast-glob@^3.2.7:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@ -6293,14 +6561,10 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
fs-extra@^11.0.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs.realpath@^1.0.0:
version "1.0.0"
@ -6397,6 +6661,11 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
get-port@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
get-stream@^6.0.0, get-stream@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
@ -6445,6 +6714,18 @@ glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^10.0.0:
version "10.4.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
dependencies:
foreground-child "^3.1.0"
jackspeak "^3.1.2"
minimatch "^9.0.4"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
glob@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e"
@ -6570,7 +6851,7 @@ gopd@^1.1.0:
dependencies:
get-intrinsic "^1.2.4"
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
graceful-fs@^4.1.2, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@ -6857,7 +7138,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ieee754@^1.1.12:
ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@ -6916,7 +7197,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -7204,7 +7485,7 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
dependencies:
call-bind "^1.0.7"
is-stream@^2.0.0:
is-stream@^2.0.0, is-stream@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
@ -7357,6 +7638,15 @@ iterator.prototype@^1.1.3:
reflect.getprototypeof "^1.0.4"
set-function-name "^2.0.1"
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jackspeak@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015"
@ -7892,15 +8182,6 @@ json5@^2.1.2, json5@^2.1.3, json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsqr@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1"
@ -8012,6 +8293,13 @@ launch-editor@^2.6.1:
picocolors "^1.0.0"
shell-quote "^1.8.1"
lazystream@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638"
integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==
dependencies:
readable-stream "^2.0.5"
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@ -8167,7 +8455,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
lodash@^4.17.20, lodash@^4.17.21:
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -8511,7 +8799,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
minimatch@^5.0.1, minimatch@^5.1.0:
version "5.1.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@ -8547,6 +8835,11 @@ minipass@^4.2.4:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
@ -8607,6 +8900,11 @@ murmurhash-js@^1.0.0:
resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
nan@^2.19.0, nan@^2.20.0:
version "2.22.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3"
integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==
nanoid@^3.3.7:
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
@ -8789,7 +9087,7 @@ on-headers@~1.0.2:
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
once@^1.3.0:
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@ -9025,7 +9323,7 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^1.6.1:
path-scurry@^1.11.1, path-scurry@^1.6.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
@ -9861,6 +10159,22 @@ prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
proper-lockfile@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
dependencies:
graceful-fs "^4.2.4"
retry "^0.12.0"
signal-exit "^3.0.2"
properties-reader@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/properties-reader/-/properties-reader-2.3.0.tgz#f3ab84224c9535a7a36e011ae489a79a13b472b2"
integrity sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==
dependencies:
mkdirp "^1.0.4"
protocol-buffers-schema@^3.3.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
@ -9884,6 +10198,14 @@ psl@^1.1.33:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
pump@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8"
integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^2.1.0, punycode@^2.1.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@ -9937,6 +10259,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
queue-tick@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
quickselect@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
@ -10125,7 +10452,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
readable-stream@^2.0.1, readable-stream@~2.3.6:
readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@ -10138,7 +10465,7 @@ readable-stream@^2.0.1, readable-stream@~2.3.6:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.0.6:
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@ -10147,6 +10474,24 @@ readable-stream@^3.0.6:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@^4.0.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.6.0.tgz#ce412dfb19c04efde1c5936d99c27f37a1ff94c9"
integrity sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw==
dependencies:
abort-controller "^3.0.0"
buffer "^6.0.3"
events "^3.3.0"
process "^0.11.10"
string_decoder "^1.3.0"
readdir-glob@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584"
integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==
dependencies:
minimatch "^5.1.0"
readdirp@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
@ -10365,6 +10710,11 @@ restore-cursor@^5.0.0:
onetime "^7.0.0"
signal-exit "^4.1.0"
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
retry@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
@ -10453,7 +10803,7 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@ -10673,7 +11023,7 @@ side-channel@^1.0.4, side-channel@^1.0.6:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
signal-exit@^3.0.3, signal-exit@^3.0.7:
signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@ -10837,6 +11187,11 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
split-ca@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6"
integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==
sprintf-js@^1.0.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
@ -10847,6 +11202,25 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
ssh-remote-port-forward@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz#72b0c5df8ec27ca300c75805cc6b266dee07e298"
integrity sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==
dependencies:
"@types/ssh2" "^0.5.48"
ssh2 "^1.4.0"
ssh2@^1.11.0, ssh2@^1.4.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.16.0.tgz#79221d40cbf4d03d07fe881149de0a9de928c9f0"
integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.10"
nan "^2.20.0"
stack-utils@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
@ -10864,6 +11238,17 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
streamx@^2.15.0, streamx@^2.21.0:
version "2.21.1"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.1.tgz#f02979d8395b6b637d08a589fb514498bed55845"
integrity sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==
dependencies:
fast-fifo "^1.3.2"
queue-tick "^1.0.1"
text-decoder "^1.1.0"
optionalDependencies:
bare-events "^2.2.0"
string-argv@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
@ -10976,7 +11361,7 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
string_decoder@^1.1.1:
string_decoder@^1.1.1, string_decoder@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
@ -11234,11 +11619,52 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
tar-fs@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217"
integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==
dependencies:
pump "^3.0.0"
tar-stream "^3.1.5"
optionalDependencies:
bare-fs "^2.1.1"
bare-path "^2.1.0"
tar-fs@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.0.0"
tar-js@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/tar-js/-/tar-js-0.3.0.tgz#6949aabfb0ba18bb1562ae51a439fd0f30183a17"
integrity sha512-9uqP2hJUZNKRkwPDe5nXxXdzo6w+BFBPq9x/tyi5/U/DneuSesO/HMb0y5TeWpfcv49YDJTs7SrrZeeu8ZHWDA==
tar-stream@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar-stream@^3.0.0, tar-stream@^3.1.5:
version "3.1.7"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b"
integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==
dependencies:
b4a "^1.6.4"
fast-fifo "^1.2.0"
streamx "^2.15.0"
temporal-polyfill@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/temporal-polyfill/-/temporal-polyfill-0.2.5.tgz#0796c40a50754c69ec0f9a2db3f6c582b9721aaf"
@ -11291,6 +11717,34 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
testcontainers@^10.16.0:
version "10.16.0"
resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-10.16.0.tgz#8a7e69ada5cd2c6cce1c6db72b3a3e8e412fcaf6"
integrity sha512-oxPLuOtrRWS11A+Yn0+zXB7GkmNarflWqmy6CQJk8KJ75LZs2/zlUXDpizTbPpCGtk4kE2EQYwFZjrE967F8Wg==
dependencies:
"@balena/dockerignore" "^1.0.2"
"@types/dockerode" "^3.3.29"
archiver "^7.0.1"
async-lock "^1.4.1"
byline "^5.0.0"
debug "^4.3.5"
docker-compose "^0.24.8"
dockerode "^3.3.5"
get-port "^5.1.1"
proper-lockfile "^4.1.2"
properties-reader "^2.3.0"
ssh-remote-port-forward "^1.0.4"
tar-fs "^3.0.6"
tmp "^0.2.3"
undici "^5.28.4"
text-decoder@^1.1.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65"
integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==
dependencies:
b4a "^1.6.4"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -11324,6 +11778,11 @@ tinyqueue@^3.0.0:
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e"
integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==
tmp@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@ -11476,6 +11935,11 @@ tslib@^2.0.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2, tslib@^2.7.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b"
integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
tweetnacl@^0.14.3:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@ -11601,6 +12065,13 @@ undici-types@~6.20.0:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici@^5.28.4:
version "5.28.4"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==
dependencies:
"@fastify/busboy" "^2.0.0"
unhomoglyph@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3"
@ -11639,11 +12110,6 @@ universalify@^0.2.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@ -12255,6 +12721,11 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.2.2:
version "2.7.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
yaml@^2.3.3:
version "2.6.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773"
@ -12323,6 +12794,15 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
zip-stream@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb"
integrity sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==
dependencies:
archiver-utils "^5.0.0"
compress-commons "^6.0.2"
readable-stream "^4.0.0"
zod-validation-error@^3.0.3:
version "3.4.0"
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6"