restructuring (#56)

* restructuring

* pin svelte kit version & parallel execution

* update svelte kit

* correct test result assets

* add timeout

* correct locale path

* simplify crypto

* fix for #58

* add verbosity flag

* disable flaky test
This commit is contained in:
Nicco 2022-10-07 21:28:25 +02:00 committed by GitHub
parent 2d573edcac
commit cacb808117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1757 additions and 1746 deletions

View File

@ -23,14 +23,14 @@ jobs:
- name: Prepare - name: Prepare
run: | run: |
pnpm install pnpm install
pnpm run ci:prepare pnpm run test:prepare
- name: Install Playwright - name: Install Playwright
run: npx playwright install --with-deps run: npx playwright install --with-deps
- name: Run your tests - name: Run your tests
run: pnpm run test run: pnpm run test:run
- name: Upload test results - name: Upload test results
if: always() if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: playwright-report name: test-results
path: playwright-report path: test-results

View File

@ -1,6 +1,6 @@
{ {
"cSpell.words": ["ciphertext", "cryptgeon"], "cSpell.words": ["ciphertext", "cryptgeon"],
"i18n-ally.localesPaths": ["frontend/locales"], "i18n-ally.localesPaths": ["packages/frontend/locales"],
"i18n-ally.enabledFrameworks": ["svelte"], "i18n-ally.enabledFrameworks": ["svelte"],
"i18n-ally.keystyle": "nested" "i18n-ally.keystyle": "nested"
} }

View File

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.3] - 2022-10-07
### Added
- Flag for verbosity.
### Fixed
- #58 Fixed bug in the max views frontend form.
## [2.0.2] - 2022-07-20 ## [2.0.2] - 2022-07-20
### Added ### Added

View File

@ -2,7 +2,7 @@
FROM node:16-alpine as client FROM node:16-alpine as client
WORKDIR /tmp WORKDIR /tmp
RUN npm install -g pnpm@7 RUN npm install -g pnpm@7
COPY ./frontend ./ COPY ./packages/frontend ./
RUN pnpm install RUN pnpm install
RUN pnpm exec svelte-kit sync RUN pnpm exec svelte-kit sync
RUN pnpm run build RUN pnpm run build
@ -12,9 +12,9 @@ RUN pnpm run build
FROM rust:1.61-alpine as backend FROM rust:1.61-alpine as backend
WORKDIR /tmp WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./backend/Cargo.* ./ COPY ./packages/backend/Cargo.* ./
RUN cargo fetch RUN cargo fetch
COPY ./backend ./ COPY ./packages/backend ./
RUN cargo build --release RUN cargo build --release

View File

@ -59,6 +59,7 @@ of the notes even if it tried to.
| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | | `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. |
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | | `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | | `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
## Deployment ## Deployment
@ -146,9 +147,9 @@ You can see the app under [localhost:1234](http://localhost:1234).
Tests are end to end tests written with Playwright. Tests are end to end tests written with Playwright.
```sh ```sh
pnpm run ci:prepare pnpm run test:prepare
docker compose up redis -d docker compose up redis -d
pnpm run ci:server pnpm run test:server
# In another terminal. # In another terminal.
# Use the test or test:local script. The local version only runs in one browser for quicker development. # Use the test or test:local script. The local version only runs in one browser for quicker development.

View File

@ -47,15 +47,15 @@ _加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全
## 环境变量 ## 环境变量
| 变量名称 | 默认值 | 描述 | | 变量名称 | 默认值 | 描述 |
| ----------------- | ---------------- | --------------------------------------------------------------------------------- | | ----------------- | ---------------- | --------------------------------------------------------------------------------- |
| `REDIS` | `redis://redis/` | Redis 连接 URL。 | | `REDIS` | `redis://redis/` | Redis 连接 URL。 |
| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。有关支持的数值请查看 [字节单位](https://docs.rs/byte-unit/) | | `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。有关支持的数值请查看 [字节单位](https://docs.rs/byte-unit/) |
| `MAX_VIEWS` | `100` | 密信最多查看次数限制 | | `MAX_VIEWS` | `100` | 密信最多查看次数限制 |
| ` MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟) | | ` MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟) |
| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块 | | `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块 |
| `THEME_IMAGE` | `""` | 自定义Logo图片你在这里填写的的图片链接必须是可以公开访问的。 | | `THEME_IMAGE` | `""` | 自定义 Logo 图片,你在这里填写的的图片链接必须是可以公开访问的。 |
| `THEME_TEXT` | `""` | 自定义在Logo下方的文本。 | | `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 |
## 部署 ## 部署
@ -148,7 +148,7 @@ cargo install cargo-watch
确保你的 Docker 正在运行 确保你的 Docker 正在运行
> 如果你用的是 `macOS` 的话你可能需要关闭AirPlay接收功能因为该功能需要占用5000端口...) > 如果你用的是 `macOS` 的话你可能需要关闭 AirPlay 接收功能因为该功能需要占用 5000 端口...)
> https://developer.apple.com/forums/thread/682332 > https://developer.apple.com/forums/thread/682332
```bash ```bash
@ -165,12 +165,12 @@ pnpm run dev
## 测试 ## 测试
这些测试是用Playwright实现的一些端到端测试用例。 这些测试是用 Playwright 实现的一些端到端测试用例。
```sh ```sh
pnpm run ci:prepare pnpm run test:prepare
docker compose up redis -d docker compose up redis -d
pnpm run ci:server pnpm run test:server
# 在另一个终端中: # 在另一个终端中:
# 使用test或者test:local script。为了更快的开发本地版本只会在一个浏览器中运行。 # 使用test或者test:local script。为了更快的开发本地版本只会在一个浏览器中运行。

View File

@ -10,10 +10,11 @@ services:
- 6379:6379 - 6379:6379
app: app:
build: . # build: .
image: cupcakearmy/cryptgeon
depends_on: depends_on:
- redis - redis
environment: environment:
SIZE_LIMIT: 128 MiB SIZE_LIMIT: 10 MiB
ports: ports:
- 1234:5000 - 1234:5000

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,17 @@
{ {
"scripts": { "scripts": {
"dev:docker": "docker-compose up redis", "dev:docker": "docker-compose up redis",
"dev:backend": "cd backend && cargo watch -x 'run --bin cryptgeon'", "dev:packages": "pnpm --parallel run dev",
"dev:front": "pnpm --prefix frontend run dev",
"dev:proxy": "node proxy.mjs", "dev:proxy": "node proxy.mjs",
"dev": "run-p dev:*", "dev": "run-p dev:*",
"test": "playwright test --project chrome firefox safari", "test:run": "playwright test --project chrome firefox safari",
"test:local": "playwright test --project local", "test:local": "playwright test --project local",
"ci:server": "cd backend && SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run", "test:server": "pnpm --parallel run test:server",
"ci:prepare": "run-p ci:prepare:*", "test:prepare": "pnpm --parallel run test:prepare"
"ci:prepare:backend": "cd backend && cargo build",
"ci:prepare:front": "pnpm --prefix frontend install && pnpm --prefix frontend run build"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.23.4", "@playwright/test": "^1.25.1",
"@types/node": "16", "@types/node": "^16.11.57",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5" "npm-run-all": "^4.1.5"
} }

View File

@ -424,7 +424,7 @@ dependencies = [
[[package]] [[package]]
name = "cryptgeon" name = "cryptgeon"
version = "2.0.2" version = "2.0.3"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "cryptgeon" name = "cryptgeon"
version = "2.0.2" version = "2.0.3"
authors = ["cupcakearmy <hi@nicco.io>"] authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2021" edition = "2021"

View File

@ -0,0 +1,10 @@
{
"name": "backend",
"private": true,
"scripts": {
"dev": "cargo watch -x 'run --bin cryptgeon'",
"build": "cargo build --release",
"test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run",
"test:prepare": "cargo build"
}
}

View File

@ -9,6 +9,7 @@ lazy_static! {
std::env::var("FRONTEND_PATH").unwrap_or("../frontend/build".to_string()); std::env::var("FRONTEND_PATH").unwrap_or("../frontend/build".to_string());
pub static ref LISTEN_ADDR: String = pub static ref LISTEN_ADDR: String =
std::env::var("LISTEN_ADDR").unwrap_or("0.0.0.0:5000".to_string()); std::env::var("LISTEN_ADDR").unwrap_or("0.0.0.0:5000".to_string());
pub static ref VERBOSITY: String = std::env::var("VERBOSITY").unwrap_or("warn".to_string());
} }
// CONFIG // CONFIG

View File

@ -18,10 +18,11 @@ mod store;
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("warning")); env_logger::init_from_env(env_logger::Env::new().default_filter_or(config::VERBOSITY.as_str()));
return HttpServer::new(|| { return HttpServer::new(|| {
App::new() App::new()
.wrap(Logger::new("%a \"%r\" %s %b %T")) .wrap(Logger::new("\"%r\" %s %b %T"))
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.wrap(middleware::DefaultHeaders::default()) .wrap(middleware::DefaultHeaders::default())
.configure(size::init) .configure(size::init)

View File

@ -7,5 +7,6 @@ pub fn init(cfg: &mut web::ServiceConfig) {
let plain = web::PayloadConfig::default() let plain = web::PayloadConfig::default()
.limit(*config::LIMIT) .limit(*config::LIMIT)
.mimetype(mime::STAR_STAR); .mimetype(mime::STAR_STAR);
// cfg.app_data(plain);
cfg.app_data(json).app_data(plain); cfg.app_data(json).app_data(plain);
} }

View File

@ -1,8 +1,8 @@
├─ MIT: 12 ├─ MIT: 13
├─ ISC: 2
├─ BSD-3-Clause: 1 ├─ BSD-3-Clause: 1
├─ (MPL-2.0 OR Apache-2.0): 1 ├─ (MPL-2.0 OR Apache-2.0): 1
├─ BSD-2-Clause: 1 ├─ BSD-2-Clause: 1
├─ ISC: 1
├─ 0BSD: 1 ├─ 0BSD: 1
└─ Apache-2.0: 1 └─ Apache-2.0: 1
1 ├─ MIT: 12 ├─ MIT: 13
2 ├─ ISC: 2
3 ├─ BSD-3-Clause: 1 ├─ BSD-3-Clause: 1
4 ├─ (MPL-2.0 OR Apache-2.0): 1 ├─ (MPL-2.0 OR Apache-2.0): 1
5 ├─ BSD-2-Clause: 1 ├─ BSD-2-Clause: 1
├─ ISC: 1
6 ├─ 0BSD: 1 ├─ 0BSD: 1
7 └─ Apache-2.0: 1 └─ Apache-2.0: 1
8

View File

@ -6,13 +6,14 @@
"preview": "vite preview --port 3000", "preview": "vite preview --port 3000",
"check": "svelte-check --tsconfig tsconfig.json", "check": "svelte-check --tsconfig tsconfig.json",
"licenses": "license-checker --summary > licenses.csv", "licenses": "license-checker --summary > licenses.csv",
"locale:download": "node scripts/locale.js" "locale:download": "node scripts/locale.js",
"test:prepare": "pnpm run build"
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@lokalise/node-api": "^7.3.1", "@lokalise/node-api": "^7.3.1",
"@sveltejs/adapter-static": "^1.0.0-next.38", "@sveltejs/adapter-static": "1.0.0-next.42",
"@sveltejs/kit": "^1.0.0-next.384", "@sveltejs/kit": "1.0.0-next.480",
"@types/dompurify": "^2.3.3", "@types/dompurify": "^2.3.3",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@zerodevx/svelte-toast": "^0.7.2", "@zerodevx/svelte-toast": "^0.7.2",

View File

@ -35,6 +35,31 @@ export class ArrayBufferUtils {
} }
} }
export class Keys {
public static async generateKey(size: 128 | 192 | 256 = 256): Promise<CryptoKey> {
const key = await window.crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: size,
},
true,
['encrypt', 'decrypt']
)
return key
}
public static async export(key: CryptoKey): Promise<string> {
return Hex.encode(await window.crypto.subtle.exportKey('raw', key))
}
public static async import(key: string): Promise<CryptoKey> {
return window.crypto.subtle.importKey('raw', Hex.decode(key), { name: 'AES-GCM' }, true, [
'encrypt',
'decrypt',
])
}
}
export class Crypto { export class Crypto {
private static ALG = 'AES-GCM' private static ALG = 'AES-GCM'
private static DELIMITER = ':::' private static DELIMITER = ':::'
@ -43,55 +68,22 @@ export class Crypto {
return window.crypto.getRandomValues(new Uint8Array(size)) return window.crypto.getRandomValues(new Uint8Array(size))
} }
public static getKeyFromString(password: string) {
return window.crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
)
}
public static async getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) {
const iterations = 100_000
return window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations,
hash: 'SHA-512',
},
key,
{ name: this.ALG, length: 256 },
true,
['encrypt', 'decrypt']
)
}
public static async encrypt(plaintext: ArrayBuffer, key: CryptoKey): Promise<string> { public static async encrypt(plaintext: ArrayBuffer, key: CryptoKey): Promise<string> {
const salt = this.getRandomBytes(16) const iv = this.getRandomBytes(12) // AES-GCM needs a 96bit IV
const derived = await this.getDerivedForKey(key, salt)
const iv = this.getRandomBytes(16)
const encrypted: ArrayBuffer = await window.crypto.subtle.encrypt( const encrypted: ArrayBuffer = await window.crypto.subtle.encrypt(
{ name: this.ALG, iv }, { name: this.ALG, iv },
derived, key,
plaintext plaintext
) )
const data = [ const data = [Hex.encode(iv), await ArrayBufferUtils.toString(encrypted)].join(this.DELIMITER)
Hex.encode(salt),
Hex.encode(iv),
await ArrayBufferUtils.toString(encrypted),
].join(this.DELIMITER)
return data return data
} }
public static async decrypt(ciphertext: string, key: CryptoKey): Promise<ArrayBuffer> { public static async decrypt(ciphertext: string, key: CryptoKey): Promise<ArrayBuffer> {
const splitted = ciphertext.split(this.DELIMITER) const splitted = ciphertext.split(this.DELIMITER)
const salt = Hex.decode(splitted[0]) const iv = Hex.decode(splitted[0])
const iv = Hex.decode(splitted[1]) const encrypted = await ArrayBufferUtils.fromString(splitted[1])
const encrypted = await ArrayBufferUtils.fromString(splitted[2]) const plaintext = await window.crypto.subtle.decrypt({ name: this.ALG, iv }, key, encrypted)
const derived = await this.getDerivedForKey(key, salt)
const plaintext = await window.crypto.subtle.decrypt({ name: this.ALG, iv }, derived, encrypted)
return plaintext return plaintext
} }
} }

View File

Before

Width:  |  Height:  |  Size: 287 B

After

Width:  |  Height:  |  Size: 287 B

View File

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 325 B

View File

Before

Width:  |  Height:  |  Size: 736 B

After

Width:  |  Height:  |  Size: 736 B

View File

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 483 B

View File

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 732 B

View File

@ -19,7 +19,7 @@
disabled={timeExpiration} disabled={timeExpiration}
max={$status?.max_views} max={$status?.max_views}
validate={(v) => validate={(v) =>
($status && v < $status?.max_views) || ($status && v <= $status?.max_views) ||
$t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })} $t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })}
/> />
<div class="middle-switch"> <div class="middle-switch">

View File

Before

Width:  |  Height:  |  Size: 784 B

After

Width:  |  Height:  |  Size: 784 B

View File

@ -24,7 +24,7 @@
hidden = !hidden hidden = !hidden
} }
function randomFN() { function randomFN() {
value = Hex.encode(Crypto.getRandomBytes(20)) value = Hex.encode(Crypto.getRandomBytes(32))
} }
</script> </script>

View File

@ -5,7 +5,7 @@
import { Adapters } from '$lib/adapters' import { Adapters } from '$lib/adapters'
import type { FileDTO, Note } from '$lib/api' import type { FileDTO, Note } from '$lib/api'
import { create, PayloadToLargeError } from '$lib/api' import { create, PayloadToLargeError } from '$lib/api'
import { Crypto, Hex } from '$lib/crypto' import { Keys } from '$lib/crypto'
import { status } from '$lib/stores/status' import { status } from '$lib/stores/status'
import { notify } from '$lib/toast' import { notify } from '$lib/toast'
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte' import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
@ -58,8 +58,8 @@
try { try {
loading = $t('common.encrypting') loading = $t('common.encrypting')
const password = Hex.encode(Crypto.getRandomBytes(32)) const key = await Keys.generateKey()
const key = await Crypto.getKeyFromString(password) const password = await Keys.export(key)
const data: Note = { const data: Note = {
contents: '', contents: '',

View File

@ -1,14 +1,7 @@
<script lang="ts" context="module">
import { getLocaleFromNavigator, init, waitLocale } from 'svelte-intl-precompile'
// @ts-ignore
import { registerAll } from '$locales'
registerAll()
init({ initialLocale: getLocaleFromNavigator() ?? undefined, fallbackLocale: 'en' })
</script>
<script lang="ts"> <script lang="ts">
import { SvelteToast } from '@zerodevx/svelte-toast' import { SvelteToast } from '@zerodevx/svelte-toast'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { waitLocale } from 'svelte-intl-precompile'
import '../app.css' import '../app.css'

View File

@ -0,0 +1,5 @@
import { getLocaleFromNavigator, init } from 'svelte-intl-precompile'
// @ts-ignore
import { registerAll } from '$locales'
registerAll()
init({ initialLocale: getLocaleFromNavigator() ?? undefined, fallbackLocale: 'en' })

View File

@ -1,10 +1,6 @@
<script context="module"> <script lang="ts">
import { browser, dev } from '$app/env'
import { status } from '$lib/stores/status' import { status } from '$lib/stores/status'
import AboutParagraph from '$lib/ui/AboutParagraph.svelte' import AboutParagraph from '$lib/ui/AboutParagraph.svelte'
export const hydrate = dev
export const router = browser
</script> </script>
<svelte:head> <svelte:head>

View File

@ -1,26 +1,18 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit'
export const load: Load = async ({ params }) => {
return {
props: params,
}
}
</script>
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { t } from 'svelte-intl-precompile' import { t } from 'svelte-intl-precompile'
import { Adapters } from '$lib/adapters' import { Adapters } from '$lib/adapters'
import { get, info } from '$lib/api' import { get, info } from '$lib/api'
import { Crypto } from '$lib/crypto' import { Keys } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte' import Button from '$lib/ui/Button.svelte'
import Loader from '$lib/ui/Loader.svelte' import Loader from '$lib/ui/Loader.svelte'
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte' import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
import type { PageData } from './$types'
export let id: string export let data: PageData
let id = data.id
let password: string let password: string
let note: DecryptedNote | null = null let note: DecryptedNote | null = null
let exists = false let exists = false
@ -51,7 +43,7 @@
loading = $t('common.downloading') loading = $t('common.downloading')
const data = await get(id) const data = await get(id)
loading = $t('common.decrypting') loading = $t('common.decrypting')
const key = await Crypto.getKeyFromString(password) const key = await Keys.import(password)
switch (data.meta.type) { switch (data.meta.type) {
case 'text': case 'text':
note = { note = {

View File

@ -0,0 +1,5 @@
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ params }) => {
return params
}

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -3,6 +3,7 @@ import precompileIntl from 'svelte-intl-precompile/sveltekit-plugin'
/** @type {import('vite').UserConfig} */ /** @type {import('vite').UserConfig} */
const config = { const config = {
clearScreen: false,
server: { server: {
port: 3000, port: 3000,
}, },

View File

@ -9,12 +9,15 @@ const config: PlaywrightTestConfig = {
outputDir: './test-results', outputDir: './test-results',
testDir: './test', testDir: './test',
timeout: 60_000,
testIgnore: ['file/too-big.spec.ts'],
webServer: { webServer: {
command: 'pnpm run ci:server', command: 'pnpm run test:server',
port: 1234, port: 1234,
reuseExistingServer: true, reuseExistingServer: true,
}, },
projects: [ projects: [
{ name: 'chrome', use: { ...devices['Desktop Chrome'] } }, { name: 'chrome', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } },

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- "packages/**"

View File

@ -0,0 +1,8 @@
import { test } from '@playwright/test'
import { createNote } from '../utils'
import Files from './files'
test('to big zip', async ({ page }) => {
const files = [Files.Zip]
const link = await createNote(page, { files, error: 'note is to big' })
})