Prepare for move to element-web monorepo

This commit is contained in:
Michael Telatynski
2026-06-05 14:28:05 +01:00
parent 8f846d95a2
commit 31dd8fb928
16 changed files with 614 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
**/node_modules/
+79
View File
@@ -0,0 +1,79 @@
module.exports = {
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
extends: ["plugin:matrix-org/typescript", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
parserOptions: {
project: ["./tsconfig.json"],
},
env: {
browser: true,
node: true,
},
rules: {
"react/jsx-key": ["error"],
"no-restricted-properties": [
"error",
...buildRestrictedPropertiesOptions(
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
"Use UIStore to access window dimensions instead.",
),
...buildRestrictedPropertiesOptions(
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
"Use Media helper instead to centralise access for customisation.",
),
...buildRestrictedPropertiesOptions(["window.setImmediate"], "Use setTimeout instead."),
],
"no-restricted-globals": [
"error",
{
name: "setImmediate",
message: "Use setTimeout instead.",
},
{
name: "Buffer",
message: "Buffer is not available in the web.",
},
],
"import/no-duplicates": ["error"],
"matrix-org/require-copyright-header": "error",
"react-compiler/react-compiler": "error",
},
overrides: [
{
files: [
"packages/element-web-playwright-common/**/*.ts",
"playwright/**/*.ts",
"modules/*/element-web/e2e/**/*.{ts,tsx}",
],
rules: {
// This is necessary for Playwright fixtures
"no-empty-pattern": "off",
// This is necessary for Playwright fixtures
"react-hooks/rules-of-hooks": "off",
// This just gets annoying in test code
"@typescript-eslint/explicit-function-return-type": "off",
},
},
],
settings: {
react: {
version: "detect",
},
},
};
function buildRestrictedPropertiesOptions(properties, message) {
return properties.map((prop) => {
let [object, property] = prop.split(".");
if (object === "*") {
object = undefined;
}
return {
object,
property,
message,
};
});
}
+158
View File
@@ -0,0 +1,158 @@
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Python
.venv
__pycache__/
.tox/
.coverage*
coverage.*
*.egg-info/
_trial_temp/
.mypy_cache/
# Custom rules
# Ignore all built libraries
lib/
temp/
coverage/
/playwright-html-report/
**/test-results
**/_results
# Only commit snapshots from Linux
**/e2e/snapshots/**/*.png
**/e2e/snapshots/**/*.yml
!**/e2e/snapshots/**/*-linux.png
!**/e2e/snapshots/**/*-linux.aria.yml
/playwright-report/
.DS_Store
+3
View File
@@ -0,0 +1,3 @@
{
"*": "prettier -u --write"
}
+1
View File
@@ -0,0 +1 @@
*.api.md
+3
View File
@@ -0,0 +1,3 @@
import config from "eslint-plugin-matrix-org/.prettierrc.js";
export default config;
+24
View File
@@ -0,0 +1,24 @@
ARG ELEMENT_VERSION=latest@sha256:a84f294ce46e4327ebacecb78bfc94cf6a45c7ffa5104a28f06b5ac69d0b2548
FROM --platform=$BUILDPLATFORM node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 AS builder
ARG BUILD_CONTEXT
RUN apk add --no-cache jq
WORKDIR /app
COPY package.json yarn.lock ./
# Copy the package.json files of all modules & packages to ensure the frozen workspace lockfile holds up
RUN --mount=type=bind,target=/docker-context \
cd /docker-context/; \
find . -path ./node_modules -prune -o -name "package.json" -mindepth 0 -maxdepth 4 -exec cp --parents "{}" /app/ \;
RUN yarn install --frozen-lockfile --ignore-scripts
COPY tsconfig.json ./
COPY ./$BUILD_CONTEXT ./$BUILD_CONTEXT
RUN cd $BUILD_CONTEXT && yarn vite build
RUN mkdir /modules
RUN cp -r ./$BUILD_CONTEXT/lib/ /modules/$(jq -r '"\(.name)-v\(.version)"' ./$BUILD_CONTEXT/package.json)
FROM ghcr.io/element-hq/element-web:${ELEMENT_VERSION}
COPY --from=builder /modules /modules/
+55
View File
@@ -0,0 +1,55 @@
// https://github.com/docker/metadata-action#bake-definition
target "docker-metadata-action" {}
variable "ELEMENT_VERSION" {
default = "latest"
}
variable "DATE" {
validation {
condition = DATE != ""
error_message = "DATE must be set"
}
validation {
condition = DATE == regex("\\d{6}", DATE)
error_message = "The variable 'DATE' must be of format YYMMNN where NN is the build number of the month."
}
}
variable "BUILD_TAG" {
default = "${ELEMENT_VERSION}-${DATE}"
}
group "default" {
targets = [
"element-web-modules-opendesk-plugin"
]
}
target "_common" {
inherits = ["docker-metadata-action"]
platforms = [
"linux/amd64",
"linux/arm64",
]
context = "."
}
target "_element_web_module_base" {
inherits = ["_common"]
dockerfile = "./Dockerfile"
}
target "element-web-modules-opendesk-plugin" {
inherits = ["_element_web_module_base"]
tags = [
"ghcr.io/element-hq/element-web-modules/opendesk-plugin:latest",
"ghcr.io/element-hq/element-web-modules/opendesk-plugin:${BUILD_TAG}",
"registry.opencode.de/bmi/opendesk/components/supplier/element/images/opendesk-element-web:${BUILD_TAG}"
]
args = {
ELEMENT_VERSION = "${ELEMENT_VERSION}"
BUILD_CONTEXT = "modules/opendesk/element-web"
}
}
+30
View File
@@ -0,0 +1,30 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { KnipConfig } from "knip";
// Specify this as knip loads config files which may conditionally add reporters, e.g. `vitest-sonar-reporter`
process.env.GITHUB_ACTIONS = "1";
export default {
ignoreDependencies: [
// Needed for lint:workflows
"@action-validator/cli",
"@action-validator/core",
// Unlisted peer dependency for @element-hq/element-web-module-api
"matrix-widget-api",
],
ignoreBinaries: [
// False positive for `yarn list`
"list",
],
workspaces: {
"modules/*/element-web": {
entry: "src/index.ts{x,}",
},
},
} satisfies KnipConfig;
+61
View File
@@ -0,0 +1,61 @@
{
"private": true,
"type": "module",
"workspaces": [
"packages/*",
"modules/*/element-web",
"modules/*/synapse"
],
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"lint:workflows": "find .github/workflows -type f \\( -iname '*.yaml' -o -iname '*.yml' \\) | xargs -I {} sh -c 'echo \"Linting {}\"; action-validator \"{}\"'",
"lint:prettier": "prettier --check .",
"lint:prettier:fix": "prettier --write .",
"lint:eslint": "eslint --max-warnings 0 modules/*/element-web/src",
"lint:types": "yarn workspaces run lint:types",
"lint:codestyle": "yarn workspaces run lint:codestyle",
"lint:knip": "knip",
"test": "yarn workspaces run test",
"test:playwright": "playwright test",
"test:playwright:open": "yarn test:playwright --ui",
"test:playwright:screenshots": "playwright-screenshots yarn test:playwright --update-snapshots --grep @screenshot",
"prepare": "patch-package && husky && yarn workspaces run prepare"
},
"devDependencies": {
"@action-validator/cli": "^0.6.0",
"@action-validator/core": "^0.6.0",
"@element-hq/element-web-module-api": "1.14.0",
"@element-hq/element-web-playwright-common": "^6.0.0",
"@playwright/test": "^1.52.0",
"@stylistic/eslint-plugin": "^5.0.0",
"@types/node": "^22.12.0",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"eslint": "^8.26.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-deprecate": "^0.9.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-matrix-org": "^3.0.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-compiler": "^19.0.0-beta-27714ef-20250124",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"husky": "^9.1.7",
"knip": "^6.0.0",
"lint-staged": "^17.0.0",
"patch-package": "^8.0.1",
"prettier": "^3.4.2",
"typescript": "^6.0.0"
},
"resolutions": {
"vite": "8.0.15",
"@vitest/browser-playwright": "4.1.8",
"@vitest/coverage-v8": "4.1.8",
"vitest": "4.1.8"
},
"packageManager": "yarn@1.22.22"
}
@@ -0,0 +1,10 @@
diff --git a/node_modules/@arcmantle/vite-plugin-import-css-sheet/client.d.ts b/node_modules/@arcmantle/vite-plugin-import-css-sheet/client.d.ts
index d884a7e..533850e 100644
--- a/node_modules/@arcmantle/vite-plugin-import-css-sheet/client.d.ts
+++ b/node_modules/@arcmantle/vite-plugin-import-css-sheet/client.d.ts
@@ -1,3 +1,4 @@
declare module '*.css' {
- export default styles as CSSStyleSheet;
+ const styles: CSSStyleSheet;
+ export default styles;
}
@@ -0,0 +1,22 @@
diff --git a/node_modules/@element-hq/element-web-playwright-common/playwright-screenshots.sh b/node_modules/@element-hq/element-web-playwright-common/playwright-screenshots.sh
index d860e34..d252f3a 100755
--- a/node_modules/@element-hq/element-web-playwright-common/playwright-screenshots.sh
+++ b/node_modules/@element-hq/element-web-playwright-common/playwright-screenshots.sh
@@ -16,7 +16,7 @@ function build_image() {
WS_PORT=3000
# Check the playwright version
-PW_VERSION=$(pnpm --silent -- playwright --version | awk '{print $2}')
+PW_VERSION=$(yarn --silent run playwright --version | awk '{print $2}')
IMAGE_NAME="ghcr.io/element-hq/element-web/playwright-server:$PW_VERSION"
# If the image exists in the repository, pull it; otherwise, build it.
@@ -42,7 +42,7 @@ trap clean_up EXIT
# Wait for playwright-server to be ready
echo "playwright-screenshots: Waiting for playwright-server"
-pnpm --dir "$SCRIPT_DIR" exec wait-on "tcp:$WS_PORT"
+npx -y wait-on "tcp:$WS_PORT"
# Playwright seems to overwrite the last line from the console, so add an
# extra newline to make sure this doesn't get lost.
+105
View File
@@ -0,0 +1,105 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { defineConfig, devices, type Project } from "@playwright/test";
import fs, { globSync } from "node:fs";
import path from "node:path";
import type { Options } from "./playwright/element-web-test";
const chromeProject = {
...devices["Desktop Chrome"],
channel: "chromium",
permissions: ["clipboard-write", "clipboard-read", "microphone"],
launchOptions: {
args: ["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream", "--mute-audio"],
},
connectOptions: process.env.PW_TEST_CONNECT_WS_ENDPOINT
? {
wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT,
exposeNetwork: "<loopback>",
}
: undefined,
};
/**
We assume that all modules will have the following directory structure:
<repo_root>
└── modules/
└── my-module/
└── element-web/
├── e2e/
│ ├── test-1.spec.ts
│ └── test-2.spec.ts
└── package.json
The following code maps each such module (my-module in the example above) to a separate
playwright project.
*/
const projects: Project<Options>[] = [];
// Get all the directories that hold playwright tests
const moduleTestDirectories = globSync("modules/*/element-web/e2e", {});
// Process each directory
for (const testDirectory of moduleTestDirectories) {
// Based on the directory structure, the parent directory of the test directory holds package.json.
const moduleDirectory = path.join(testDirectory, "..");
// Get module name from package.json
const packageJson = JSON.parse(fs.readFileSync(path.join(moduleDirectory, "package.json"), "utf-8"));
const MODULE_PREFIX = "@element-hq/element-web-module-";
const name = packageJson.name.startsWith(MODULE_PREFIX)
? packageJson.name.slice(MODULE_PREFIX.length)
: packageJson.name;
// Create playwright project
projects.push({
name,
use: {
...chromeProject,
moduleDir: moduleDirectory,
},
testDir: testDirectory,
snapshotDir: `${testDirectory}/snapshots`,
outputDir: `${testDirectory}/_results`,
});
}
const baseURL = process.env["BASE_URL"] ?? "http://localhost:8080";
export default defineConfig<Options>({
projects,
use: {
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
video: "retain-on-failure",
baseURL,
trace: "on-first-retry",
},
webServer: {
command: process.env.WEBAPP_PATH
? `npx serve -p 8080 -L ${process.env.WEBAPP_PATH}`
: "docker run --rm -p 8080:80 ghcr.io/element-hq/element-web:develop",
url: `${baseURL}/config.json`,
reuseExistingServer: true,
timeout: (process.env.CI ? 30 : 120) * 1000,
},
workers: 1,
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI
? [
["list"],
["html"],
["github"],
["@element-hq/element-web-playwright-common/lib/stale-screenshot-reporter.js"],
]
: [["list"], ["html", { outputFolder: "playwright-html-report" }]],
// When running the browser in docker, set the platform to `linux` as that is the platform where the browser is running
snapshotPathTemplate: `{snapshotDir}/{testFilePath}/{arg}-${process.env.PW_TEST_CONNECT_WS_ENDPOINT ? "linux" : "{platform}"}{ext}`,
forbidOnly: !!process.env.CI,
});
+35
View File
@@ -0,0 +1,35 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { test as base, expect, type TestFixtures } from "@element-hq/element-web-playwright-common";
export interface Options {
moduleDir: string;
modules: string[];
}
export const test = base.extend<TestFixtures & Options>({
moduleDir: ["", { option: true }],
modules: async ({ moduleDir }, use) => {
await use([`${moduleDir}/lib/index.js`]);
},
page: async ({ page, modules, config }, use) => {
config.modules = [];
for (let i = 0; i < modules.length; i++) {
const module = `/modules/module-${i}/index.js`;
await page.route(module, async (route) => {
await route.fulfill({ path: modules[i] });
});
config.modules.push(module);
}
await use(page);
},
});
export { expect };
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2022",
"jsx": "react",
"lib": ["ESNext", "es2022", "dom", "dom.iterable"],
"resolveJsonModule": true,
"esModuleInterop": true,
"moduleResolution": "node",
"module": "es2022",
"allowImportingTsExtensions": true
},
"include": ["**/*.ts", "../modules/*/element-web/test/**/*.ts"]
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["dom", "es2022", "esnext"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"allowImportingTsExtensions": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"declaration": true
}
}