feat: improve test & bundler (#14434)

#### PR Dependency Tree


* **PR #14434** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced rspack bundler as an alternative to webpack for optimized
builds.

* **Tests & Quality**
* Added comprehensive editor semantic tests covering markdown, hotkeys,
and slash-menu operations.
* Expanded CI cross-browser testing to Chromium, Firefox, and WebKit;
improved shape-rendering tests to account for zoom.

* **Bug Fixes**
  * Corrected CSS overlay styling for development servers.
  * Fixed TypeScript typings for build tooling.

* **Other**
  * Document duplication now produces consistent "(n)" suffixes.
  * French i18n completeness increased to 100%.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-14 16:09:09 +08:00
committed by GitHub
parent 3bc28ba78c
commit 2b71b3f345
25 changed files with 1913 additions and 333 deletions
+52 -10
View File
@@ -210,18 +210,13 @@ jobs:
e2e-blocksuite-cross-browser-test:
name: E2E BlockSuite Cross Browser Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1]
browser: ['chromium', 'firefox', 'webkit']
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: true
playwright-platform: ${{ matrix.browser }}
playwright-platform: 'chromium,firefox,webkit'
electron-install: false
full-cache: true
@@ -229,18 +224,64 @@ jobs:
run: yarn workspace @blocksuite/playground build
- name: Run playwright tests
env:
BROWSER: ${{ matrix.browser }}
run: yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
run: |
yarn workspace @blocksuite/integration-test test:unit
yarn workspace @affine-test/blocksuite test "cross-platform/" --forbid-only
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-e2e-bs-cross-browser-${{ matrix.browser }}-${{ matrix.shard }}
name: test-results-e2e-bs-cross-browser
path: ./test-results
if-no-files-found: ignore
bundler-matrix:
name: Bundler Matrix (${{ matrix.bundler }})
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
bundler: [webpack, rspack]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
with:
playwright-install: false
electron-install: false
full-cache: true
- name: Run frontend build matrix
env:
AFFINE_BUNDLER: ${{ matrix.bundler }}
run: |
set -euo pipefail
packages=(
"@affine/web"
"@affine/mobile"
"@affine/ios"
"@affine/android"
"@affine/admin"
"@affine/electron-renderer"
)
summary="test-results-bundler-${AFFINE_BUNDLER}.txt"
: > "$summary"
for pkg in "${packages[@]}"; do
start=$(date +%s)
yarn affine "$pkg" build
end=$(date +%s)
echo "${pkg},$((end-start))" >> "$summary"
done
- name: Upload bundler timing
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-bundler-${{ matrix.bundler }}
path: ./test-results-bundler-${{ matrix.bundler }}.txt
if-no-files-found: ignore
e2e-test:
name: E2E Test
runs-on: ubuntu-24.04-arm
@@ -321,6 +362,7 @@ jobs:
with:
electron-install: true
playwright-install: true
playwright-platform: 'chromium,firefox,webkit'
full-cache: true
- name: Download affine.linux-x64-gnu.node
@@ -5,6 +5,14 @@ import { wait } from '../utils/common.js';
import { getSurface } from '../utils/edgeless.js';
import { setupEditor } from '../utils/setup.js';
function expectPxCloseTo(
value: string,
expected: number,
precision: number = 2
) {
expect(Number.parseFloat(value)).toBeCloseTo(expected, precision);
}
describe('Shape rendering with DOM renderer', () => {
beforeEach(async () => {
const cleanup = await setupEditor('edgeless', [], {
@@ -59,7 +67,8 @@ describe('Shape rendering with DOM renderer', () => {
);
expect(shapeElement).not.toBeNull();
expect(shapeElement?.style.borderRadius).toBe('6px');
const zoom = surfaceView.renderer.viewport.zoom;
expectPxCloseTo(shapeElement!.style.borderRadius, 6 * zoom);
});
test('should remove shape DOM node when element is deleted', async () => {
@@ -110,8 +119,9 @@ describe('Shape rendering with DOM renderer', () => {
);
expect(shapeElement).not.toBeNull();
expect(shapeElement?.style.width).toBe('80px');
expect(shapeElement?.style.height).toBe('60px');
const zoom = surfaceView.renderer.viewport.zoom;
expectPxCloseTo(shapeElement!.style.width, 80 * zoom);
expectPxCloseTo(shapeElement!.style.height, 60 * zoom);
});
test('should correctly render triangle shape', async () => {
@@ -132,7 +142,8 @@ describe('Shape rendering with DOM renderer', () => {
);
expect(shapeElement).not.toBeNull();
expect(shapeElement?.style.width).toBe('80px');
expect(shapeElement?.style.height).toBe('60px');
const zoom = surfaceView.renderer.viewport.zoom;
expectPxCloseTo(shapeElement!.style.width, 80 * zoom);
expectPxCloseTo(shapeElement!.style.height, 60 * zoom);
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,363 @@
import { LinkExtension } from '@blocksuite/affine-inline-link';
import { textKeymap } from '@blocksuite/affine-inline-preset';
import type {
ListBlockModel,
ParagraphBlockModel,
} from '@blocksuite/affine-model';
import { insertContent } from '@blocksuite/affine-rich-text';
import { REFERENCE_NODE } from '@blocksuite/affine-shared/consts';
import { createDefaultDoc } from '@blocksuite/affine-shared/utils';
import { TextSelection } from '@blocksuite/std';
import type { InlineMarkdownMatch } from '@blocksuite/std/inline';
import { Text } from '@blocksuite/store';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { defaultSlashMenuConfig } from '../../../../affine/widgets/slash-menu/src/config.js';
import type {
SlashMenuActionItem,
SlashMenuItem,
} from '../../../../affine/widgets/slash-menu/src/types.js';
import { wait } from '../utils/common.js';
import { addNote } from '../utils/edgeless.js';
import { setupEditor } from '../utils/setup.js';
type RichTextElement = HTMLElement & {
inlineEditor: {
getFormat: (range: {
index: number;
length: number;
}) => Record<string, unknown>;
getInlineRange: () => { index: number; length: number } | null;
setInlineRange: (range: { index: number; length: number }) => void;
yTextString: string;
};
markdownMatches: InlineMarkdownMatch[];
undoManager: {
stopCapturing: () => void;
};
};
function findSlashActionItem(
items: SlashMenuItem[],
name: string
): SlashMenuActionItem {
const item = items.find(entry => entry.name === name);
if (!item || !('action' in item)) {
throw new Error(`Cannot find slash-menu action: ${name}`);
}
return item;
}
function getRichTextByBlockId(blockId: string): RichTextElement {
const block = editor.host?.view.getBlock(blockId) as HTMLElement | null;
if (!block) {
throw new Error(`Cannot find block view: ${blockId}`);
}
const richText = block.querySelector('rich-text') as RichTextElement | null;
if (!richText) {
throw new Error(`Cannot find rich-text for block: ${blockId}`);
}
return richText;
}
async function createParagraph(text = '') {
const noteId = addNote(doc);
const note = doc.getBlock(noteId)?.model;
if (!note) {
throw new Error('Cannot find note model');
}
const paragraph = note.children[0] as ParagraphBlockModel | undefined;
if (!paragraph) {
throw new Error('Cannot find paragraph model');
}
if (text) {
doc.updateBlock(paragraph, {
text: new Text(text),
});
}
await wait();
return {
noteId,
paragraphId: paragraph.id,
};
}
function setTextSelection(blockId: string, index: number, length: number) {
const to = length
? {
blockId,
index: index + length,
length: 0,
}
: null;
const selection = editor.host?.selection.create(TextSelection, {
from: {
blockId,
index,
length: 0,
},
to,
});
if (!selection) {
throw new Error('Cannot create text selection');
}
editor.host?.selection.setGroup('note', [selection]);
const richText = getRichTextByBlockId(blockId);
richText.inlineEditor.setInlineRange({ index, length });
}
async function triggerMarkdown(
blockId: string,
input: string,
matcherName: string
) {
const model = doc.getBlock(blockId)?.model as ParagraphBlockModel | undefined;
if (!model) {
throw new Error(`Cannot find paragraph model: ${blockId}`);
}
doc.updateBlock(model, {
text: new Text(input),
});
await wait();
const richText = getRichTextByBlockId(blockId);
const matcher = richText.markdownMatches.find(
item => item.name === matcherName
);
if (!matcher) {
throw new Error(`Cannot find markdown matcher: ${matcherName}`);
}
const inlineRange = { index: input.length, length: 0 };
setTextSelection(blockId, inlineRange.index, 0);
matcher.action({
inlineEditor: richText.inlineEditor as any,
prefixText: input,
inlineRange,
pattern: matcher.pattern,
undoManager: richText.undoManager as any,
});
await wait();
}
function mockKeyboardContext() {
const preventDefault = vi.fn();
const ctx = {
get(key: string) {
if (key === 'keyboardState') {
return { raw: { preventDefault } };
}
throw new Error(`Unexpected state key: ${key}`);
},
};
return { ctx: ctx as any, preventDefault };
}
beforeEach(async () => {
const cleanup = await setupEditor('page', [LinkExtension]);
return cleanup;
});
describe('markdown/list/paragraph/quote/code/link', () => {
test('markdown list shortcut converts to todo list and keeps checked state', async () => {
const { noteId, paragraphId } = await createParagraph();
await triggerMarkdown(paragraphId, '[x] ', 'list');
const note = doc.getBlock(noteId)?.model;
if (!note) {
throw new Error('Cannot find note model');
}
const model = note.children[0] as ListBlockModel;
expect(model.flavour).toBe('affine:list');
expect(model.props.type).toBe('todo');
expect(model.props.checked).toBe(true);
});
test('markdown heading and quote shortcuts convert paragraph type', async () => {
const { noteId: headingNoteId, paragraphId: headingParagraphId } =
await createParagraph();
await triggerMarkdown(headingParagraphId, '# ', 'heading');
const headingNote = doc.getBlock(headingNoteId)?.model;
if (!headingNote) {
throw new Error('Cannot find heading note model');
}
const headingModel = headingNote.children[0] as ParagraphBlockModel;
expect(headingModel.flavour).toBe('affine:paragraph');
expect(headingModel.props.type).toBe('h1');
const { noteId: quoteNoteId, paragraphId: quoteParagraphId } =
await createParagraph();
await triggerMarkdown(quoteParagraphId, '> ', 'heading');
const quoteNote = doc.getBlock(quoteNoteId)?.model;
if (!quoteNote) {
throw new Error('Cannot find quote note model');
}
const quoteModel = quoteNote.children[0] as ParagraphBlockModel;
expect(quoteModel.flavour).toBe('affine:paragraph');
expect(quoteModel.props.type).toBe('quote');
});
test('markdown code shortcut converts paragraph to code block with language', async () => {
const { noteId, paragraphId } = await createParagraph();
await triggerMarkdown(paragraphId, '```ts ', 'code-block');
const note = doc.getBlock(noteId)?.model;
if (!note) {
throw new Error('Cannot find note model');
}
const model = note.children[0];
expect(model.flavour).toBe('affine:code');
expect((model as any).props.language).toBe('typescript');
});
test('inline markdown converts style and link attributes', async () => {
const { paragraphId: boldParagraphId } = await createParagraph();
await triggerMarkdown(boldParagraphId, '**bold** ', 'bold');
const boldRichText = getRichTextByBlockId(boldParagraphId);
expect(boldRichText.inlineEditor.yTextString).toBe('bold');
expect(
boldRichText.inlineEditor.getFormat({ index: 1, length: 0 })
).toMatchObject({
bold: true,
});
const { paragraphId: codeParagraphId } = await createParagraph();
await triggerMarkdown(codeParagraphId, '`code` ', 'code');
const codeRichText = getRichTextByBlockId(codeParagraphId);
expect(codeRichText.inlineEditor.yTextString).toBe('code');
expect(
codeRichText.inlineEditor.getFormat({ index: 1, length: 0 })
).toMatchObject({
code: true,
});
const { paragraphId: linkParagraphId } = await createParagraph();
await triggerMarkdown(
linkParagraphId,
'[AFFiNE](https://affine.pro) ',
'link'
);
const linkRichText = getRichTextByBlockId(linkParagraphId);
expect(linkRichText.inlineEditor.yTextString).toBe('AFFiNE');
expect(
linkRichText.inlineEditor.getFormat({ index: 1, length: 0 })
).toMatchObject({
link: 'https://affine.pro',
});
});
});
describe('hotkey/bracket/linked-page', () => {
test('bracket keymap skips redundant right bracket in code block', async () => {
const { noteId, paragraphId } = await createParagraph();
await triggerMarkdown(paragraphId, '```ts ', 'code-block');
const note = doc.getBlock(noteId)?.model;
const codeId = note?.children[0]?.id;
if (!codeId) {
throw new Error('Cannot find code block id');
}
const codeModel = doc.getBlock(codeId)?.model;
if (!codeModel) {
throw new Error('Cannot find code block model');
}
const keymap = textKeymap(editor.std);
const leftHandler = keymap['('];
const rightHandler = keymap[')'];
expect(leftHandler).toBeDefined();
if (!rightHandler) {
throw new Error('Cannot find bracket key handlers');
}
doc.updateBlock(codeModel, {
text: new Text('()'),
});
await wait();
const codeRichText = getRichTextByBlockId(codeId);
setTextSelection(codeId, 1, 0);
const rightContext = mockKeyboardContext();
rightHandler(rightContext.ctx);
expect(rightContext.preventDefault).not.toHaveBeenCalled();
expect(codeRichText.inlineEditor.yTextString).toBe('()');
});
test('consecutive linked-page reference nodes render as separate references', async () => {
const { paragraphId } = await createParagraph();
const paragraphModel = doc.getBlock(paragraphId)?.model as
| ParagraphBlockModel
| undefined;
if (!paragraphModel) {
throw new Error('Cannot find paragraph model');
}
const linkedDoc = createDefaultDoc(collection, {
title: 'Linked page',
});
setTextSelection(paragraphId, 0, 0);
insertContent(editor.std, paragraphModel, REFERENCE_NODE, {
reference: {
type: 'LinkedPage',
pageId: linkedDoc.id,
},
});
insertContent(editor.std, paragraphModel, REFERENCE_NODE, {
reference: {
type: 'LinkedPage',
pageId: linkedDoc.id,
},
});
await wait();
expect(collection.docs.has(linkedDoc.id)).toBe(true);
const richText = getRichTextByBlockId(paragraphId);
expect(richText.querySelectorAll('affine-reference').length).toBe(2);
expect(richText.inlineEditor.yTextString.length).toBe(2);
});
});
describe('slash-menu action semantics', () => {
test('date and move actions mutate block content/order as expected', async () => {
const noteId = addNote(doc);
const note = doc.getBlock(noteId)?.model;
if (!note) {
throw new Error('Cannot find note model');
}
const first = note.children[0] as ParagraphBlockModel;
const secondId = doc.addBlock(
'affine:paragraph',
{ text: new Text('second') },
noteId
);
const second = doc.getBlock(secondId)?.model as
| ParagraphBlockModel
| undefined;
if (!second) {
throw new Error('Cannot find second paragraph model');
}
doc.updateBlock(first, { text: new Text('first') });
await wait();
const slashItems = defaultSlashMenuConfig.items;
const items =
typeof slashItems === 'function'
? slashItems({ std: editor.std, model: first })
: slashItems;
const today = findSlashActionItem(items, 'Today');
const moveDown = findSlashActionItem(items, 'Move Down');
const moveUp = findSlashActionItem(items, 'Move Up');
moveDown.action({ std: editor.std, model: first });
await wait();
expect(note.children.map(child => child.id)).toEqual([second.id, first.id]);
moveUp.action({ std: editor.std, model: first });
await wait();
expect(note.children.map(child => child.id)).toEqual([first.id, second.id]);
setTextSelection(first.id, 0, 0);
today.action({ std: editor.std, model: first });
await wait();
const richText = getRichTextByBlockId(first.id);
expect(richText.inlineEditor.yTextString).toMatch(/\d{4}-\d{2}-\d{2}/);
});
});
+5 -1
View File
@@ -19,7 +19,11 @@ export default defineConfig(_configEnv =>
browser: {
enabled: true,
headless: process.env.CI === 'true',
instances: [{ browser: 'chromium' }],
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
{ browser: 'webkit' },
],
provider: 'playwright',
isolate: false,
viewport: {
@@ -6,7 +6,8 @@ textarea
-webkit-app-region: no-drag;
}
#webpack-dev-server-client-overlay {
#webpack-dev-server-client-overlay,
#rspack-dev-server-client-overlay {
-webkit-app-region: no-drag;
}
@@ -0,0 +1,8 @@
export const WORKSPACE_ROUTE_PATH = '/workspace/:workspaceId/*';
export const SHARE_ROUTE_PATH = '/share/:workspaceId/:pageId';
export const NOT_FOUND_ROUTE_PATH = '/404';
export const CATCH_ALL_ROUTE_PATH = '*';
export function getWorkspaceDocPath(workspaceId: string, docId: string) {
return `/workspace/${workspaceId}/${docId}`;
}
+14 -5
View File
@@ -10,6 +10,13 @@ import {
import { AffineErrorComponent } from '../components/affine/affine-error-boundary/affine-error-fallback';
import { NavigateContext } from '../components/hooks/use-navigate-helper';
import { RootWrapper } from './pages/root';
import {
CATCH_ALL_ROUTE_PATH,
getWorkspaceDocPath,
NOT_FOUND_ROUTE_PATH,
SHARE_ROUTE_PATH,
WORKSPACE_ROUTE_PATH,
} from './route-paths';
export function RootRouter() {
const navigate = useNavigate();
@@ -38,17 +45,19 @@ export const topLevelRoutes = [
lazy: () => import('./pages/index'),
},
{
path: '/workspace/:workspaceId/*',
path: WORKSPACE_ROUTE_PATH,
lazy: () => import('./pages/workspace/index'),
},
{
path: '/share/:workspaceId/:pageId',
path: SHARE_ROUTE_PATH,
loader: ({ params }) => {
return redirect(`/workspace/${params.workspaceId}/${params.pageId}`);
return redirect(
getWorkspaceDocPath(params.workspaceId ?? '', params.pageId ?? '')
);
},
},
{
path: '/404',
path: NOT_FOUND_ROUTE_PATH,
lazy: () => import('./pages/404'),
},
{
@@ -175,7 +184,7 @@ export const topLevelRoutes = [
lazy: () => import('./pages/open-app'),
},
{
path: '*',
path: CATCH_ALL_ROUTE_PATH,
lazy: () => import('./pages/404'),
},
],
@@ -18,6 +18,7 @@ import type { DocPropertiesStore } from '../stores/doc-properties';
import type { DocsStore } from '../stores/docs';
import type { DocCreateOptions } from '../types';
import { DocService } from './doc';
import { getDuplicatedDocTitle } from './duplicate-title';
const logger = new DebugLogger('DocsService');
@@ -286,13 +287,7 @@ export class DocsService extends Service {
});
// duplicate doc title
const originalTitle = sourceDoc.title$.value;
const lastDigitRegex = /\((\d+)\)$/;
const match = originalTitle.match(lastDigitRegex);
const newNumber = match ? parseInt(match[1], 10) + 1 : 1;
const newPageTitle =
originalTitle.replace(lastDigitRegex, '') + `(${newNumber})`;
targetDoc.changeDocTitle(newPageTitle);
targetDoc.changeDocTitle(getDuplicatedDocTitle(sourceDoc.title$.value));
// duplicate doc properties
const properties = sourceDoc.getProperties();
@@ -0,0 +1,9 @@
const DUPLICATED_DOC_TITLE_SUFFIX = /\((\d+)\)$/;
export function getDuplicatedDocTitle(originalTitle: string) {
const match = originalTitle.match(DUPLICATED_DOC_TITLE_SUFFIX);
const nextSequence = match ? parseInt(match[1], 10) + 1 : 1;
return (
originalTitle.replace(DUPLICATED_DOC_TITLE_SUFFIX, '') + `(${nextSequence})`
);
}
@@ -46,6 +46,10 @@ import type {
} from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impls/workspace';
import { getWorkspaceProfileWorker } from './out-worker';
import {
dedupeWorkspaceIds,
normalizeWorkspaceIds,
} from './workspace-id-utils';
export const LOCAL_WORKSPACE_LOCAL_STORAGE_KEY = 'affine-local-workspace';
export const LOCAL_WORKSPACE_GLOBAL_STATE_KEY =
@@ -61,13 +65,6 @@ type GlobalStateStorageLike = {
set<T>(key: string, value: T): void;
};
function normalizeWorkspaceIds(ids: unknown): string[] {
if (!Array.isArray(ids)) {
return [];
}
return ids.filter((id): id is string => typeof id === 'string');
}
function getElectronGlobalStateStorage(): GlobalStateStorageLike | null {
if (!BUILD_CONFIG.isElectron) {
return null;
@@ -113,7 +110,7 @@ export function setLocalWorkspaceIds(
? idsOrUpdater(getLocalWorkspaceIds())
: idsOrUpdater
);
const deduplicated = [...new Set(next)];
const deduplicated = dedupeWorkspaceIds(next);
const globalState = getElectronGlobalStateStorage();
if (globalState) {
@@ -168,14 +165,12 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
}
setLocalWorkspaceIds(currentIds => {
return [
...new Set([
...currentIds,
...persistedIds,
...legacyIds,
...scannedIds,
]),
];
return dedupeWorkspaceIds([
...currentIds,
...persistedIds,
...legacyIds,
...scannedIds,
]);
});
})()
.catch(e => {
@@ -0,0 +1,8 @@
export function normalizeWorkspaceIds(ids: unknown): string[] {
if (!Array.isArray(ids)) return [];
return ids.filter((id): id is string => typeof id === 'string');
}
export function dedupeWorkspaceIds(ids: string[]): string[] {
return [...new Set(ids)];
}
+2 -1
View File
@@ -1,4 +1,5 @@
/// <reference types="@webpack/env"" />
/// <reference types="@webpack/env" />
/// <reference types="@rspack/core/module" />
declare module '*.md' {
const text: string;
@@ -9,7 +9,7 @@
"es-CL": 98,
"es": 96,
"fa": 96,
"fr": 98,
"fr": 100,
"hi": 1,
"it-IT": 98,
"it": 1,
@@ -2311,4 +2311,3 @@
"error.RESPONSE_TOO_LARGE_ERROR": "Réponse trop volumineuse ({{receivedBytes}} octets), la limite est de {{limitBytes}} octets",
"error.SSRF_BLOCKED_ERROR": "URL invalide"
}
+1 -1
View File
@@ -1,5 +1,4 @@
import type { Page } from '@playwright/test';
import { expect } from '@playwright/test';
export let coreUrl = 'http://localhost:8080';
@@ -21,6 +20,7 @@ export async function confirmCreateJournal(page: Page) {
}
export async function openJournalsPage(page: Page) {
const { expect } = await import('@playwright/test');
await page.getByTestId('slider-bar-journals-button').click();
await confirmCreateJournal(page);
await expect(
+2
View File
@@ -19,6 +19,8 @@
"@affine/s3-compat": "workspace:*",
"@napi-rs/simple-git": "^0.1.22",
"@perfsee/webpack": "^1.13.0",
"@rspack/core": "^1.7.6",
"@rspack/dev-server": "^1.1.3",
"@sentry/webpack-plugin": "^4.0.0",
"@swc/core": "^1.10.1",
"@tailwindcss/postcss": "^4.0.0",
+77
View File
@@ -0,0 +1,77 @@
import type { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
export const RSPACK_SUPPORTED_PACKAGES = [
'@affine/admin',
'@affine/web',
'@affine/mobile',
'@affine/ios',
'@affine/android',
'@affine/electron-renderer',
'@affine/server',
] as const;
const rspackSupportedPackageSet = new Set<string>(RSPACK_SUPPORTED_PACKAGES);
export function isRspackSupportedPackageName(name: string) {
return rspackSupportedPackageSet.has(name);
}
export function assertRspackSupportedPackageName(name: string) {
if (isRspackSupportedPackageName(name)) {
return;
}
throw new Error(
`AFFINE_BUNDLER=rspack currently supports: ${Array.from(RSPACK_SUPPORTED_PACKAGES).join(', ')}. Use AFFINE_BUNDLER=webpack for ${name}.`
);
}
const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';
export const DEFAULT_DEV_SERVER_CONFIG: WebpackDevServerConfiguration = {
host: '0.0.0.0',
allowedHosts: 'all',
hot: false,
liveReload: true,
compress: !process.env.CI,
setupExitSignals: true,
client: {
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
logging: process.env.CI ? 'none' : 'error',
// see: https://webpack.js.org/configuration/dev-server/#websocketurl
// must be an explicit ws/wss URL because custom protocols (e.g. assets://)
// cannot be used to construct WebSocket endpoints in Electron
webSocketURL: 'ws://0.0.0.0:8080/ws',
},
historyApiFallback: {
rewrites: [
{
from: /.*/,
to: () => {
return process.env.SELF_HOSTED === 'true'
? '/selfhost.html'
: '/index.html';
},
},
],
},
proxy: [
{
context: '/api',
target: 'http://localhost:3010',
logLevel: httpProxyMiddlewareLogLevel,
},
{
context: '/socket.io',
target: 'http://localhost:3010',
ws: true,
logLevel: httpProxyMiddlewareLogLevel,
},
{
context: '/graphql',
target: 'http://localhost:3010',
logLevel: httpProxyMiddlewareLogLevel,
},
],
};
+226 -70
View File
@@ -3,20 +3,46 @@ import { cpus } from 'node:os';
import { Logger } from '@affine-tools/utils/logger';
import { Package } from '@affine-tools/utils/workspace';
import rspack, { type MultiRspackOptions } from '@rspack/core';
import {
type Configuration as RspackDevServerConfiguration,
RspackDevServer,
} from '@rspack/dev-server';
import { merge } from 'lodash-es';
import webpack from 'webpack';
import WebpackDevServer, {
type Configuration as DevServerConfiguration,
type Configuration as WebpackDevServerConfiguration,
} from 'webpack-dev-server';
import {
assertRspackSupportedPackageName,
DEFAULT_DEV_SERVER_CONFIG,
isRspackSupportedPackageName,
} from './bundle-shared';
import { type Bundler, getBundler } from './bundler';
import { Option, PackageCommand } from './command';
import {
createHTMLTargetConfig,
createNodeTargetConfig,
createWorkerTargetConfig,
createHTMLTargetConfig as createRspackHTMLTargetConfig,
createNodeTargetConfig as createRspackNodeTargetConfig,
createWorkerTargetConfig as createRspackWorkerTargetConfig,
} from './rspack';
import {
createHTMLTargetConfig as createWebpackHTMLTargetConfig,
createNodeTargetConfig as createWebpackNodeTargetConfig,
createWorkerTargetConfig as createWebpackWorkerTargetConfig,
} from './webpack';
function getBaseWorkerConfigs(pkg: Package) {
type WorkerConfig = { name: string };
type CreateWorkerTargetConfig = (pkg: Package, entry: string) => WorkerConfig;
function assertRspackSupportedPackage(pkg: Package) {
assertRspackSupportedPackageName(pkg.name);
}
function getBaseWorkerConfigs(
pkg: Package,
createWorkerTargetConfig: CreateWorkerTargetConfig
) {
const core = new Package('@affine/core');
return [
@@ -39,27 +65,30 @@ function getBaseWorkerConfigs(pkg: Package) {
];
}
function getBundleConfigs(pkg: Package): webpack.MultiConfiguration {
function getWebpackBundleConfigs(pkg: Package): webpack.MultiConfiguration {
switch (pkg.name) {
case '@affine/admin': {
return [
createHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value),
createWebpackHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value),
] as webpack.MultiConfiguration;
}
case '@affine/web':
case '@affine/mobile':
case '@affine/ios':
case '@affine/android': {
const workerConfigs = getBaseWorkerConfigs(pkg);
const workerConfigs = getBaseWorkerConfigs(
pkg,
createWebpackWorkerTargetConfig
);
workerConfigs.push(
createWorkerTargetConfig(
createWebpackWorkerTargetConfig(
pkg,
pkg.srcPath.join('nbstore.worker.ts').value
)
);
return [
createHTMLTargetConfig(
createWebpackHTMLTargetConfig(
pkg,
pkg.srcPath.join('index.tsx').value,
{},
@@ -69,10 +98,13 @@ function getBundleConfigs(pkg: Package): webpack.MultiConfiguration {
] as webpack.MultiConfiguration;
}
case '@affine/electron-renderer': {
const workerConfigs = getBaseWorkerConfigs(pkg);
const workerConfigs = getBaseWorkerConfigs(
pkg,
createWebpackWorkerTargetConfig
);
return [
createHTMLTargetConfig(
createWebpackHTMLTargetConfig(
pkg,
{
index: pkg.srcPath.join('app/index.tsx').value,
@@ -93,7 +125,7 @@ function getBundleConfigs(pkg: Package): webpack.MultiConfiguration {
}
case '@affine/server': {
return [
createNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
createWebpackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
] as webpack.MultiConfiguration;
}
}
@@ -101,55 +133,75 @@ function getBundleConfigs(pkg: Package): webpack.MultiConfiguration {
throw new Error(`Unsupported package: ${pkg.name}`);
}
const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';
function getRspackBundleConfigs(pkg: Package): MultiRspackOptions {
assertRspackSupportedPackage(pkg);
const defaultDevServerConfig: DevServerConfiguration = {
host: '0.0.0.0',
allowedHosts: 'all',
hot: false,
liveReload: true,
compress: !process.env.CI,
setupExitSignals: true,
client: {
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
logging: process.env.CI ? 'none' : 'error',
// see: https://webpack.js.org/configuration/dev-server/#websocketurl
// must be an explicit ws/wss URL because custom protocols (e.g. assets://)
// cannot be used to construct WebSocket endpoints in Electron
webSocketURL: 'ws://0.0.0.0:8080/ws',
},
historyApiFallback: {
rewrites: [
{
from: /.*/,
to: () => {
return process.env.SELF_HOSTED === 'true'
? '/selfhost.html'
: '/index.html';
},
},
],
},
proxy: [
{
context: '/api',
target: 'http://localhost:3010',
logLevel: httpProxyMiddlewareLogLevel,
},
{
context: '/socket.io',
target: 'http://localhost:3010',
ws: true,
logLevel: httpProxyMiddlewareLogLevel,
},
{
context: '/graphql',
target: 'http://localhost:3010',
logLevel: httpProxyMiddlewareLogLevel,
},
],
};
switch (pkg.name) {
case '@affine/admin': {
return [
createRspackHTMLTargetConfig(pkg, pkg.srcPath.join('index.tsx').value),
] as MultiRspackOptions;
}
case '@affine/web':
case '@affine/mobile':
case '@affine/ios':
case '@affine/android': {
const workerConfigs = getBaseWorkerConfigs(
pkg,
createRspackWorkerTargetConfig
);
workerConfigs.push(
createRspackWorkerTargetConfig(
pkg,
pkg.srcPath.join('nbstore.worker.ts').value
)
);
return [
createRspackHTMLTargetConfig(
pkg,
pkg.srcPath.join('index.tsx').value,
{},
workerConfigs.map(config => config.name)
),
...workerConfigs,
] as MultiRspackOptions;
}
case '@affine/electron-renderer': {
const workerConfigs = getBaseWorkerConfigs(
pkg,
createRspackWorkerTargetConfig
);
return [
createRspackHTMLTargetConfig(
pkg,
{
index: pkg.srcPath.join('app/index.tsx').value,
shell: pkg.srcPath.join('shell/index.tsx').value,
popup: pkg.srcPath.join('popup/index.tsx').value,
backgroundWorker: pkg.srcPath.join('background-worker/index.ts')
.value,
},
{
additionalEntryForSelfhost: false,
injectGlobalErrorHandler: false,
emitAssetsManifest: false,
},
workerConfigs.map(config => config.name)
),
...workerConfigs,
] as MultiRspackOptions;
}
case '@affine/server': {
return [
createRspackNodeTargetConfig(pkg, pkg.srcPath.join('index.ts').value),
] as MultiRspackOptions;
}
}
throw new Error(`Unsupported package: ${pkg.name}`);
}
export class BundleCommand extends PackageCommand {
static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']];
@@ -164,22 +216,36 @@ export class BundleCommand extends PackageCommand {
async execute() {
const pkg = this.workspace.getPackage(this.package);
const bundler = getBundler();
if (this.dev) {
await BundleCommand.dev(pkg);
await BundleCommand.dev(pkg, bundler);
} else {
await BundleCommand.build(pkg);
await BundleCommand.build(pkg, bundler);
}
}
static async build(pkg: Package) {
static async build(pkg: Package, bundler: Bundler = getBundler()) {
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) {
return BundleCommand.buildWithWebpack(pkg);
}
switch (bundler) {
case 'webpack':
return BundleCommand.buildWithWebpack(pkg);
case 'rspack':
return BundleCommand.buildWithRspack(pkg);
}
}
static async buildWithWebpack(pkg: Package) {
process.env.NODE_ENV = 'production';
const logger = new Logger('bundle');
logger.info(`Packing package ${pkg.name}...`);
logger.info(`Packing package ${pkg.name} with webpack...`);
logger.info('Cleaning old output...');
rmSync(pkg.distPath.value, { recursive: true, force: true });
const config = getBundleConfigs(pkg);
const config = getWebpackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = webpack(config);
@@ -203,12 +269,43 @@ export class BundleCommand extends PackageCommand {
});
}
static async dev(pkg: Package, devServerConfig?: DevServerConfiguration) {
static async dev(
pkg: Package,
bundler: Bundler = getBundler(),
devServerConfig?:
| WebpackDevServerConfiguration
| RspackDevServerConfiguration
) {
if (bundler === 'rspack' && !isRspackSupportedPackageName(pkg.name)) {
return BundleCommand.devWithWebpack(
pkg,
devServerConfig as WebpackDevServerConfiguration | undefined
);
}
switch (bundler) {
case 'webpack':
return BundleCommand.devWithWebpack(
pkg,
devServerConfig as WebpackDevServerConfiguration | undefined
);
case 'rspack':
return BundleCommand.devWithRspack(
pkg,
devServerConfig as RspackDevServerConfiguration | undefined
);
}
}
static async devWithWebpack(
pkg: Package,
devServerConfig?: WebpackDevServerConfiguration
) {
process.env.NODE_ENV = 'development';
const logger = new Logger('bundle');
logger.info(`Starting dev server for ${pkg.name}...`);
logger.info(`Starting webpack dev server for ${pkg.name}...`);
const config = getBundleConfigs(pkg);
const config = getWebpackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = webpack(config);
@@ -217,7 +314,66 @@ export class BundleCommand extends PackageCommand {
}
const devServer = new WebpackDevServer(
merge({}, defaultDevServerConfig, devServerConfig),
merge({}, DEFAULT_DEV_SERVER_CONFIG, devServerConfig),
compiler
);
await devServer.start();
}
static async buildWithRspack(pkg: Package) {
process.env.NODE_ENV = 'production';
assertRspackSupportedPackage(pkg);
const logger = new Logger('bundle');
logger.info(`Packing package ${pkg.name} with rspack...`);
logger.info('Cleaning old output...');
rmSync(pkg.distPath.value, { recursive: true, force: true });
const config = getRspackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = rspack(config);
if (!compiler) {
throw new Error('Failed to create rspack compiler');
}
compiler.run((error, stats) => {
if (error) {
console.error(error);
process.exit(1);
}
if (stats) {
if (stats.hasErrors()) {
console.error(stats.toString('errors-only'));
process.exit(1);
} else {
console.log(stats.toString('minimal'));
}
}
});
}
static async devWithRspack(
pkg: Package,
devServerConfig?: RspackDevServerConfiguration
) {
process.env.NODE_ENV = 'development';
assertRspackSupportedPackage(pkg);
const logger = new Logger('bundle');
logger.info(`Starting rspack dev server for ${pkg.name}...`);
const config = getRspackBundleConfigs(pkg);
config.parallelism = cpus().length;
const compiler = rspack(config);
if (!compiler) {
throw new Error('Failed to create rspack compiler');
}
const devServer = new RspackDevServer(
merge({}, DEFAULT_DEV_SERVER_CONFIG, devServerConfig),
compiler
);
+27
View File
@@ -0,0 +1,27 @@
export const SUPPORTED_BUNDLERS = ['webpack', 'rspack'] as const;
export type Bundler = (typeof SUPPORTED_BUNDLERS)[number];
export const DEFAULT_BUNDLER: Bundler = 'rspack';
function isBundler(value: string): value is Bundler {
return SUPPORTED_BUNDLERS.includes(value as Bundler);
}
export function normalizeBundler(input: string | undefined | null): Bundler {
const value = input?.trim().toLowerCase();
if (!value) {
return DEFAULT_BUNDLER;
}
if (isBundler(value)) {
return value;
}
throw new Error(
`Unsupported AFFINE_BUNDLER: "${input}". Expected one of: ${SUPPORTED_BUNDLERS.join(', ')}.`
);
}
export function getBundler(env: NodeJS.ProcessEnv = process.env): Bundler {
return normalizeBundler(env.AFFINE_BUNDLER);
}
+638
View File
@@ -0,0 +1,638 @@
import { createRequire } from 'node:module';
import path from 'node:path';
import { getBuildConfig } from '@affine-tools/utils/build-config';
import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Package } from '@affine-tools/utils/workspace';
import rspack, {
type Configuration as RspackConfiguration,
} from '@rspack/core';
import { sentryWebpackPlugin } from '@sentry/webpack-plugin';
import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin';
import cssnano from 'cssnano';
import { compact, merge } from 'lodash-es';
import { productionCacheGroups } from '../webpack/cache-group.js';
import {
type CreateHTMLPluginConfig,
createHTMLPlugins as createWebpackCompatibleHTMLPlugins,
} from '../webpack/html-plugin.js';
import { WebpackS3Plugin } from '../webpack/s3-plugin.js';
const require = createRequire(import.meta.url);
const IN_CI = !!process.env.CI;
const availableChannels = ['canary', 'beta', 'stable', 'internal'];
function getBuildConfigFromEnv(pkg: Package) {
const channel = process.env.BUILD_TYPE ?? 'canary';
const dev = process.env.NODE_ENV === 'development';
if (!availableChannels.includes(channel)) {
throw new Error(
`BUILD_TYPE must be one of ${availableChannels.join(', ')}, received [${channel}]`
);
}
return getBuildConfig(pkg, {
// @ts-expect-error checked
channel,
mode: dev ? 'development' : 'production',
});
}
export function createHTMLTargetConfig(
pkg: Package,
entry: string | Record<string, string>,
htmlConfig: Partial<CreateHTMLPluginConfig> = {},
deps?: string[]
): RspackConfiguration {
entry = typeof entry === 'string' ? { index: entry } : entry;
htmlConfig = merge(
{},
{
filename: 'index.html',
additionalEntryForSelfhost: true,
injectGlobalErrorHandler: true,
emitAssetsManifest: true,
},
htmlConfig
);
const buildConfig = getBuildConfigFromEnv(pkg);
console.log(
`Building [${pkg.name}] for [${buildConfig.appBuildType}] channel in [${buildConfig.debug ? 'development' : 'production'}] mode.`
);
console.log(
`Entry points: ${Object.entries(entry)
.map(([name, path]) => `${name}: ${path}`)
.join(', ')}`
);
console.log(`Output path: ${pkg.distPath.value}`);
console.log(`Config: ${JSON.stringify(buildConfig, null, 2)}`);
const config: RspackConfiguration = {
//#region basic webpack config
name: entry['index'],
dependencies: deps,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: false,
asyncWebAssembly: true,
},
entry,
output: {
environment: { module: true, dynamicImport: true },
filename: buildConfig.debug
? 'js/[name].js'
: 'js/[name].[contenthash:8].js',
assetModuleFilename: buildConfig.debug
? '[name].[contenthash:8][ext]'
: 'assets/[name].[contenthash:8][ext][query]',
path: pkg.distPath.value,
clean: false,
globalObject: 'globalThis',
// NOTE: always keep it '/'
publicPath: '/',
},
target: ['web', 'es2022'],
mode: buildConfig.debug ? 'development' : 'production',
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: {
symlinks: true,
extensionAlias: {
'.js': ['.js', '.tsx', '.ts'],
'.mjs': ['.mjs', '.mts'],
},
extensions: ['.js', '.ts', '.tsx'],
alias: {
yjs: ProjectRoot.join('node_modules', 'yjs').value,
lit: ProjectRoot.join('node_modules', 'lit').value,
'@preact/signals-core': ProjectRoot.join(
'node_modules',
'@preact',
'signals-core'
).value,
},
},
//#endregion
//#region module config
module: {
parser: {
javascript: {
// Do not mock Node.js globals
node: false,
requireJs: false,
import: true,
// Treat as missing export as error
strictExportPresence: true,
},
},
//#region rules
rules: [
{ test: /\.m?js?$/, resolve: { fullySpecified: false } },
{
test: /\.js$/,
enforce: 'pre',
include: /@blocksuite/,
use: ['source-map-loader'],
},
{
oneOf: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: false,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
{
test: /\.tsx$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
react: { runtime: 'automatic' },
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
{
test: /\.(png|jpg|gif|svg|webp|mp4|zip)$/,
type: 'asset/resource',
},
{ test: /\.(ttf|eot|woff|woff2)$/, type: 'asset/resource' },
{ test: /\.txt$/, type: 'asset/source' },
{ test: /\.inline\.svg$/, type: 'asset/inline' },
{
test: /\.css$/,
use: [
buildConfig.debug
? 'style-loader'
: rspack.CssExtractRspackPlugin.loader,
{
loader: 'css-loader',
options: {
url: true,
sourceMap: false,
modules: false,
import: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: pkg.join('tailwind.config.js').exists()
? [
[
'@tailwindcss/postcss',
require(pkg.join('tailwind.config.js').value),
],
['autoprefixer'],
]
: [
cssnano({
preset: ['default', { convertValues: false }],
}),
],
},
},
},
],
},
],
},
],
//#endregion
},
//#endregion
//#region plugins
plugins: compact([
!IN_CI && new rspack.ProgressPlugin(),
...createWebpackCompatibleHTMLPlugins(buildConfig, htmlConfig),
new rspack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
...Object.entries(buildConfig).reduce(
(def, [k, v]) => {
def[`BUILD_CONFIG.${k}`] = JSON.stringify(v);
return def;
},
{} as Record<string, string>
),
}),
!buildConfig.debug &&
// todo: support multiple entry points
new rspack.CssExtractRspackPlugin({
filename: `[name].[contenthash:8].css`,
ignoreOrder: true,
}),
new VanillaExtractPlugin(),
!buildConfig.isAdmin &&
new rspack.CopyRspackPlugin({
patterns: [
{
// copy the shared public assets into dist
from: new Package('@affine/core').join('public').value,
},
],
}),
!buildConfig.debug &&
(buildConfig.isWeb || buildConfig.isMobileWeb || buildConfig.isAdmin) &&
process.env.R2_SECRET_ACCESS_KEY &&
new WebpackS3Plugin(),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
// sourcemap url like # sourceMappingURL=76-6370cd185962bc89.js.map wont load in electron
// this is because the default file:// protocol will be ignored by Chromium
// so we need to replace the sourceMappingURL to assets:// protocol
// for example:
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
buildConfig.isElectron &&
new rspack.SourceMapDevToolPlugin({
append: (pathData: { filename?: string }) => {
return `\n//# sourceMappingURL=assets://./${pathData.filename ?? ''}.map`;
},
filename: '[file].map',
}),
]),
//#endregion
stats: { errorDetails: true },
//#region optimization
optimization: {
minimize: !buildConfig.debug,
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
extractComments: true,
minimizerOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: { name: 'runtime' },
splitChunks: {
chunks: 'all',
minSize: 1,
minChunks: 1,
maxInitialRequests: Number.MAX_SAFE_INTEGER,
maxAsyncRequests: Number.MAX_SAFE_INTEGER,
cacheGroups: {
...productionCacheGroups,
// Rspack tends to pull async node_modules into the initial vendor chunk
// when `vendor` is configured as `chunks: 'all'`.
vendor: {
...productionCacheGroups.vendor,
chunks: 'initial',
},
},
},
},
//#endregion
};
if (buildConfig.debug && !IN_CI) {
config.optimization = {
...config.optimization,
minimize: false,
runtimeChunk: false,
splitChunks: {
maxInitialRequests: Infinity,
chunks: 'all',
cacheGroups: {
defaultVendors: {
test: `[\\/]node_modules[\\/](?!.*vanilla-extract)`,
priority: -10,
reuseExistingChunk: true,
},
default: { minChunks: 2, priority: -20, reuseExistingChunk: true },
styles: {
name: 'styles',
type: 'css/mini-extract',
chunks: 'all',
enforce: true,
},
},
},
};
}
return config;
}
export function createWorkerTargetConfig(
pkg: Package,
entry: string
): Omit<RspackConfiguration, 'name'> & { name: string } {
const workerName = path.basename(entry).replace(/\.worker\.ts$/, '');
const buildConfig = getBuildConfigFromEnv(pkg);
return {
name: entry,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: false,
asyncWebAssembly: true,
},
entry: { [workerName]: entry },
output: {
filename: `js/${workerName}-${buildConfig.appVersion}.worker.js`,
path: pkg.distPath.value,
clean: false,
globalObject: 'globalThis',
// NOTE: always keep it '/'
publicPath: '/',
},
target: ['webworker', 'es2022'],
mode: buildConfig.debug ? 'development' : 'production',
devtool: buildConfig.debug ? 'cheap-module-source-map' : 'source-map',
resolve: {
symlinks: true,
extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
extensions: ['.js', '.ts'],
alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
},
module: {
parser: {
javascript: {
// Do not mock Node.js globals
node: false,
requireJs: false,
import: true,
// Treat as missing export as error
strictExportPresence: true,
},
},
rules: [
{ test: /\.m?js?$/, resolve: { fullySpecified: false } },
{
test: /\.js$/,
enforce: 'pre',
include: /@blocksuite/,
use: ['source-map-loader'],
},
{
oneOf: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: false,
tsx: false,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
useDefineForClassFields: false,
decoratorVersion: '2022-03',
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
],
},
],
},
plugins: compact([
new rspack.DefinePlugin(
Object.entries(buildConfig).reduce(
(def, [k, v]) => {
def[`BUILD_CONFIG.${k}`] = JSON.stringify(v);
return def;
},
{} as Record<string, string>
)
),
new rspack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT &&
sentryWebpackPlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
]),
stats: { errorDetails: true },
optimization: {
minimize: !buildConfig.debug,
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
extractComments: true,
minimizerOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
removeEmptyChunks: true,
providedExports: true,
usedExports: true,
sideEffects: true,
removeAvailableModules: true,
runtimeChunk: false,
splitChunks: false,
},
performance: { hints: false },
};
}
export function createNodeTargetConfig(
pkg: Package,
entry: string
): Omit<RspackConfiguration, 'name'> & { name: string } {
const dev = process.env.NODE_ENV === 'development';
return {
name: entry,
context: ProjectRoot.value,
experiments: {
topLevelAwait: true,
outputModule: pkg.packageJson.type === 'module',
asyncWebAssembly: true,
},
entry: { index: entry },
output: {
filename: `main.js`,
path: pkg.distPath.value,
clean: true,
globalObject: 'globalThis',
},
target: ['node', 'es2022'],
externals: ((data: any, callback: (err: null, value: boolean) => void) => {
if (
data.request &&
// import ... from 'module'
/^[a-zA-Z@]/.test(data.request) &&
// not workspace deps
!pkg.deps.some(dep => data.request!.startsWith(dep.name))
) {
callback(null, true);
} else {
callback(null, false);
}
}) as any,
externalsPresets: { node: true },
node: { __dirname: false, __filename: false },
mode: dev ? 'development' : 'production',
devtool: 'source-map',
resolve: {
symlinks: true,
extensionAlias: { '.js': ['.js', '.ts'], '.mjs': ['.mjs', '.mts'] },
extensions: ['.js', '.ts', '.tsx', '.node'],
alias: { yjs: ProjectRoot.join('node_modules', 'yjs').value },
},
module: {
parser: {
javascript: { url: false, importMeta: false, createRequire: false },
},
rules: [
{
test: /\.js$/,
enforce: 'pre',
include: /@blocksuite/,
use: ['source-map-loader'],
},
{
test: /\.node$/,
loader: Path.dir(import.meta.url).join('../webpack/node-loader.js')
.value,
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: 'swc-loader',
options: {
// https://swc.rs/docs/configuring-swc/
jsc: {
preserveAllComments: true,
parser: {
syntax: 'typescript',
dynamicImport: true,
topLevelAwait: true,
tsx: true,
decorators: true,
},
target: 'es2022',
externalHelpers: false,
transform: {
legacyDecorator: true,
decoratorMetadata: true,
react: { runtime: 'automatic' },
},
},
sourceMaps: true,
inlineSourcesContent: true,
},
},
],
},
plugins: compact([
new rspack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
new rspack.IgnorePlugin({
checkResource(resource) {
const lazyImports = [
'@nestjs/microservices',
'@nestjs/websockets/socket-module',
'@apollo/subgraph',
'@apollo/gateway',
'@as-integrations/fastify',
'ts-morph',
'class-validator',
'class-transformer',
];
return lazyImports.some(lazyImport =>
resource.startsWith(lazyImport)
);
},
}),
new rspack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
]),
stats: { errorDetails: true },
optimization: {
nodeEnv: false,
minimize: !dev,
minimizer: [
new rspack.SwcJsMinimizerRspackPlugin({
extractComments: true,
minimizerOptions: {
ecma: 2020,
compress: { unused: true },
mangle: { keep_classnames: true },
},
}),
],
},
performance: { hints: false },
ignoreWarnings: [/^(?!CriticalDependenciesWarning$)/],
};
}
+9 -2
View File
@@ -21,11 +21,18 @@ export const productionCacheGroups = {
asyncVendor: {
test: /[\\/]node_modules[\\/]/,
name(module: any) {
const modulePath =
module?.nameForCondition?.() || module?.context || module?.resource;
if (!modulePath || typeof modulePath !== 'string') {
return 'app-async';
}
// monorepo linked in node_modules, so it's not a npm package
if (!module.context.includes('node_modules')) {
if (!modulePath.includes('node_modules')) {
return `app-async`;
}
const name = module.context.match(
const name = modulePath.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)?.[1];
return `npm-async-${name}`;
+31 -7
View File
@@ -5,8 +5,31 @@ import { Path, ProjectRoot } from '@affine-tools/utils/path';
import { Repository } from '@napi-rs/simple-git';
import HTMLPlugin from 'html-webpack-plugin';
import { once } from 'lodash-es';
import type { Compiler, WebpackPluginInstance } from 'webpack';
import webpack from 'webpack';
import type { WebpackPluginInstance } from 'webpack';
type CompilerLike = {
webpack?: {
sources?: {
RawSource?: new (source: string) => unknown;
};
};
hooks: {
compilation: {
tap: (name: string, callback: (compilation: any) => void) => void;
};
};
};
function createRawSource(compiler: CompilerLike, source: string) {
const RawSource = compiler.webpack?.sources?.RawSource;
if (!RawSource) {
throw new Error(
'compiler.webpack.sources.RawSource is required for html plugin assets emission'
);
}
return new RawSource(source);
}
export const getPublicPath = (BUILD_CONFIG: BUILD_CONFIG_TYPE) => {
const { BUILD_TYPE } = process.env;
@@ -86,7 +109,7 @@ function getHTMLPluginOptions(BUILD_CONFIG: BUILD_CONFIG_TYPE) {
}
const AssetsManifestPlugin = {
apply(compiler: Compiler) {
apply(compiler: CompilerLike) {
compiler.hooks.compilation.tap('assets-manifest-plugin', compilation => {
HTMLPlugin.getHooks(compilation).beforeAssetTagGeneration.tap(
'assets-manifest-plugin',
@@ -94,7 +117,8 @@ const AssetsManifestPlugin = {
if (!compilation.getAsset('assets-manifest.json')) {
compilation.emitAsset(
`assets-manifest.json`,
new webpack.sources.RawSource(
createRawSource(
compiler,
JSON.stringify(
{
...arg.assets,
@@ -125,7 +149,7 @@ const AssetsManifestPlugin = {
};
const GlobalErrorHandlerPlugin = {
apply(compiler: Compiler) {
apply(compiler: CompilerLike) {
const globalErrorHandler = [
'js/global-error-handler.js',
readFileSync(currentDir.join('./error-handler.js').toString(), 'utf-8'),
@@ -140,7 +164,7 @@ const GlobalErrorHandlerPlugin = {
if (!compilation.getAsset(globalErrorHandler[0])) {
compilation.emitAsset(
globalErrorHandler[0],
new webpack.sources.RawSource(globalErrorHandler[1])
createRawSource(compiler, globalErrorHandler[1])
);
arg.assets.js.unshift(
arg.assets.publicPath + globalErrorHandler[0]
@@ -156,7 +180,7 @@ const GlobalErrorHandlerPlugin = {
};
const CorsPlugin = {
apply(compiler: Compiler) {
apply(compiler: CompilerLike) {
compiler.hooks.compilation.tap('html-js-cors-plugin', compilation => {
HTMLPlugin.getHooks(compilation).alterAssetTags.tap(
'html-js-cors-plugin',
+409 -205
View File
@@ -119,6 +119,8 @@ __metadata:
"@affine/s3-compat": "workspace:*"
"@napi-rs/simple-git": "npm:^0.1.22"
"@perfsee/webpack": "npm:^1.13.0"
"@rspack/core": "npm:^1.7.6"
"@rspack/dev-server": "npm:^1.1.3"
"@sentry/webpack-plugin": "npm:^4.0.0"
"@swc/core": "npm:^1.10.1"
"@tailwindcss/postcss": "npm:^4.0.0"
@@ -4899,13 +4901,13 @@ __metadata:
languageName: node
linkType: hard
"@emnapi/core@npm:^1.4.0, @emnapi/core@npm:^1.7.1":
version: 1.7.1
resolution: "@emnapi/core@npm:1.7.1"
"@emnapi/core@npm:^1.4.0, @emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.7.1":
version: 1.8.1
resolution: "@emnapi/core@npm:1.8.1"
dependencies:
"@emnapi/wasi-threads": "npm:1.1.0"
tslib: "npm:^2.4.0"
checksum: 10/260841f6dd2a7823a964d9de6da3a5e6f565dac8d21a5bd8f6215b87c45c22a4dc371b9ad877961579ee3cca8a76e55e3dd033ae29cba1998999cda6d794bdab
checksum: 10/904ea60c91fc7d8aeb4a8f2c433b8cfb47c50618f2b6f37429fc5093c857c6381c60628a5cfbc3a7b0d75b0a288f21d4ed2d4533e82f92c043801ef255fd6a5c
languageName: node
linkType: hard
@@ -8247,6 +8249,61 @@ __metadata:
languageName: node
linkType: hard
"@module-federation/error-codes@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/error-codes@npm:0.22.0"
checksum: 10/4edb269e9f3039899f879788c84d2bfecff94ca8e87ffcd80dbf8589d8543ec32558b3fa05c8549a8abd3ac33e856ff2aacf458dea5c0d7bea608bf12bb13359
languageName: node
linkType: hard
"@module-federation/runtime-core@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/runtime-core@npm:0.22.0"
dependencies:
"@module-federation/error-codes": "npm:0.22.0"
"@module-federation/sdk": "npm:0.22.0"
checksum: 10/d21969198322b6f79e0513b702d0af5097613d47819724c849b6c677c163cd10fb8c89e3ff62b798bec498ee4d8e95dec71861071bc4ed74bd86a7e43193bc05
languageName: node
linkType: hard
"@module-federation/runtime-tools@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/runtime-tools@npm:0.22.0"
dependencies:
"@module-federation/runtime": "npm:0.22.0"
"@module-federation/webpack-bundler-runtime": "npm:0.22.0"
checksum: 10/0e7693c1ec02fc5bef770b478c8757cad9cfefb2310d1943151d0ad079b72472d9b2c8a087299e9124dfcd6b649c83290c7fdfa333865baab4ba193f39e7b6bd
languageName: node
linkType: hard
"@module-federation/runtime@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/runtime@npm:0.22.0"
dependencies:
"@module-federation/error-codes": "npm:0.22.0"
"@module-federation/runtime-core": "npm:0.22.0"
"@module-federation/sdk": "npm:0.22.0"
checksum: 10/eca608be999d7d2e83abc1169643c2f795a5ed950f9e2bdf7000400a30b3e1e0ca4bdaa5daa09f55e44868383d444707e40236cec1aaa7b40432b0cce800b7f3
languageName: node
linkType: hard
"@module-federation/sdk@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/sdk@npm:0.22.0"
checksum: 10/d7085d883730a33145052520787a7e59cf9c54b51b2946bebc7c63a6bb668bcc6cbdc27fa0b7354a62f5a7ee4e8829a66b84e644607498f2e37cfd5eb4ded0da
languageName: node
linkType: hard
"@module-federation/webpack-bundler-runtime@npm:0.22.0":
version: 0.22.0
resolution: "@module-federation/webpack-bundler-runtime@npm:0.22.0"
dependencies:
"@module-federation/runtime": "npm:0.22.0"
"@module-federation/sdk": "npm:0.22.0"
checksum: 10/afd24406817dfc6474ebcf5be714ccf26690eb3f6f5172bda711c8f23dba149fe47293f7aa2d0733dfed0334c98d4d3d9e7c2da2be78750cae5a72d72f32ce93
languageName: node
linkType: hard
"@monaco-editor/loader@npm:^1.5.0":
version: 1.7.0
resolution: "@monaco-editor/loader@npm:1.7.0"
@@ -9131,6 +9188,17 @@ __metadata:
languageName: node
linkType: hard
"@napi-rs/wasm-runtime@npm:1.0.7":
version: 1.0.7
resolution: "@napi-rs/wasm-runtime@npm:1.0.7"
dependencies:
"@emnapi/core": "npm:^1.5.0"
"@emnapi/runtime": "npm:^1.5.0"
"@tybys/wasm-util": "npm:^0.10.1"
checksum: 10/6bc32d32d486d07b83220a9b7b2b715e39acacbacef0011ebca05c00b41d80a0535123da10fea7a7d6d7e206712bb50dc50ac3cf88b770754d44378570fb5c05
languageName: node
linkType: hard
"@napi-rs/wasm-runtime@npm:^0.2.5, @napi-rs/wasm-runtime@npm:^0.2.9":
version: 0.2.9
resolution: "@napi-rs/wasm-runtime@npm:0.2.9"
@@ -15058,6 +15126,178 @@ __metadata:
languageName: node
linkType: hard
"@rspack/binding-darwin-arm64@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-darwin-arm64@npm:1.7.6"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-darwin-x64@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-darwin-x64@npm:1.7.6"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-gnu@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.6"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-arm64-musl@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.6"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-linux-x64-gnu@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.6"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@rspack/binding-linux-x64-musl@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-linux-x64-musl@npm:1.7.6"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@rspack/binding-wasm32-wasi@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-wasm32-wasi@npm:1.7.6"
dependencies:
"@napi-rs/wasm-runtime": "npm:1.0.7"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@rspack/binding-win32-arm64-msvc@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.6"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@rspack/binding-win32-ia32-msvc@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.6"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@rspack/binding-win32-x64-msvc@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.6"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@rspack/binding@npm:1.7.6":
version: 1.7.6
resolution: "@rspack/binding@npm:1.7.6"
dependencies:
"@rspack/binding-darwin-arm64": "npm:1.7.6"
"@rspack/binding-darwin-x64": "npm:1.7.6"
"@rspack/binding-linux-arm64-gnu": "npm:1.7.6"
"@rspack/binding-linux-arm64-musl": "npm:1.7.6"
"@rspack/binding-linux-x64-gnu": "npm:1.7.6"
"@rspack/binding-linux-x64-musl": "npm:1.7.6"
"@rspack/binding-wasm32-wasi": "npm:1.7.6"
"@rspack/binding-win32-arm64-msvc": "npm:1.7.6"
"@rspack/binding-win32-ia32-msvc": "npm:1.7.6"
"@rspack/binding-win32-x64-msvc": "npm:1.7.6"
dependenciesMeta:
"@rspack/binding-darwin-arm64":
optional: true
"@rspack/binding-darwin-x64":
optional: true
"@rspack/binding-linux-arm64-gnu":
optional: true
"@rspack/binding-linux-arm64-musl":
optional: true
"@rspack/binding-linux-x64-gnu":
optional: true
"@rspack/binding-linux-x64-musl":
optional: true
"@rspack/binding-wasm32-wasi":
optional: true
"@rspack/binding-win32-arm64-msvc":
optional: true
"@rspack/binding-win32-ia32-msvc":
optional: true
"@rspack/binding-win32-x64-msvc":
optional: true
checksum: 10/fec6c978e51f20471e278a07018b414125cf3bccf9c6bd7032ca65603cfe5bf0fdd7f58c156c0640b5dfab05e82a1e1170ac6d1aacaf4f46b61564be77dbe41b
languageName: node
linkType: hard
"@rspack/core@npm:^1.7.6":
version: 1.7.6
resolution: "@rspack/core@npm:1.7.6"
dependencies:
"@module-federation/runtime-tools": "npm:0.22.0"
"@rspack/binding": "npm:1.7.6"
"@rspack/lite-tapable": "npm:1.1.0"
peerDependencies:
"@swc/helpers": ">=0.5.1"
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 10/9f23c4849926d9ddff34f703ab2be41878bca9e877c130d16d20d911ba4b13f15dfe96d7e86225d7f5a1e48034ab92cccec89f3765f84ff518538f6bb07f1f06
languageName: node
linkType: hard
"@rspack/dev-server@npm:^1.1.3":
version: 1.2.1
resolution: "@rspack/dev-server@npm:1.2.1"
dependencies:
"@types/bonjour": "npm:^3.5.13"
"@types/connect-history-api-fallback": "npm:^1.5.4"
"@types/express": "npm:^4.17.25"
"@types/express-serve-static-core": "npm:^4.17.21"
"@types/serve-index": "npm:^1.9.4"
"@types/serve-static": "npm:^1.15.5"
"@types/sockjs": "npm:^0.3.36"
"@types/ws": "npm:^8.5.10"
ansi-html-community: "npm:^0.0.8"
bonjour-service: "npm:^1.2.1"
chokidar: "npm:^3.6.0"
colorette: "npm:^2.0.10"
compression: "npm:^1.8.1"
connect-history-api-fallback: "npm:^2.0.0"
express: "npm:^4.22.1"
graceful-fs: "npm:^4.2.6"
http-proxy-middleware: "npm:^2.0.9"
ipaddr.js: "npm:^2.1.0"
launch-editor: "npm:^2.6.1"
open: "npm:^10.0.3"
p-retry: "npm:^6.2.0"
schema-utils: "npm:^4.2.0"
selfsigned: "npm:^2.4.1"
serve-index: "npm:^1.9.1"
sockjs: "npm:^0.3.24"
spdy: "npm:^4.0.2"
webpack-dev-middleware: "npm:^7.4.2"
ws: "npm:^8.18.0"
peerDependencies:
"@rspack/core": "*"
checksum: 10/154808faef8079dc1d6eae1712455864cc7bc1ec686f3020f7117ad3e5f2906940f27ec514eb40230276132371570ecdf6b47f7ab117ad209462bcba7c2b0692
languageName: node
linkType: hard
"@rspack/lite-tapable@npm:1.1.0":
version: 1.1.0
resolution: "@rspack/lite-tapable@npm:1.1.0"
checksum: 10/41ff73fe5e1b8dccaad746c9c1bd36dd67649e1ad35776f311b5ba94333a397704e11158579e25a6a7e677c51abe35e66987b1b000faef48d4e4ad2470fea150
languageName: node
linkType: hard
"@scarf/scarf@npm:=1.4.0":
version: 1.4.0
resolution: "@scarf/scarf@npm:1.4.0"
@@ -17079,15 +17319,15 @@ __metadata:
languageName: node
linkType: hard
"@types/express@npm:^4.17.13, @types/express@npm:^4.17.21":
version: 4.17.22
resolution: "@types/express@npm:4.17.22"
"@types/express@npm:^4.17.13, @types/express@npm:^4.17.21, @types/express@npm:^4.17.25":
version: 4.17.25
resolution: "@types/express@npm:4.17.25"
dependencies:
"@types/body-parser": "npm:*"
"@types/express-serve-static-core": "npm:^4.17.33"
"@types/qs": "npm:*"
"@types/serve-static": "npm:*"
checksum: 10/9497634fc341ff4ac966ec0c529ded03bdacd2c3dae164f10a060ff250c66591b873aedce92d0239869cf3d05615ae9bcad584c7349fe68780242f6fef010c62
"@types/serve-static": "npm:^1"
checksum: 10/c309fdb79fb8569b5d8d8f11268d0160b271f8b38f0a82c20a0733e526baf033eb7a921cd51d54fe4333c616de9e31caf7d4f3ef73baaf212d61f23f460b0369
languageName: node
linkType: hard
@@ -17610,13 +17850,13 @@ __metadata:
languageName: node
linkType: hard
"@types/send@npm:*":
version: 0.17.4
resolution: "@types/send@npm:0.17.4"
"@types/send@npm:*, @types/send@npm:<1":
version: 0.17.6
resolution: "@types/send@npm:0.17.6"
dependencies:
"@types/mime": "npm:^1"
"@types/node": "npm:*"
checksum: 10/28320a2aa1eb704f7d96a65272a07c0bf3ae7ed5509c2c96ea5e33238980f71deeed51d3631927a77d5250e4091b3e66bce53b42d770873282c6a20bb8b0280d
checksum: 10/4948ab32ab84a81a0073f8243dd48ee766bc80608d5391060360afd1249f83c08a7476f142669ac0b0b8831c89d909a88bcb392d1b39ee48b276a91b50f3d8d1
languageName: node
linkType: hard
@@ -17629,14 +17869,14 @@ __metadata:
languageName: node
linkType: hard
"@types/serve-static@npm:*, @types/serve-static@npm:^1.15.5":
version: 1.15.7
resolution: "@types/serve-static@npm:1.15.7"
"@types/serve-static@npm:*, @types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5":
version: 1.15.10
resolution: "@types/serve-static@npm:1.15.10"
dependencies:
"@types/http-errors": "npm:*"
"@types/node": "npm:*"
"@types/send": "npm:*"
checksum: 10/c5a7171d5647f9fbd096ed1a26105759f3153ccf683824d99fee4c7eb9cde2953509621c56a070dd9fb1159e799e86d300cbe4e42245ebc5b0c1767e8ca94a67
"@types/send": "npm:<1"
checksum: 10/d9be72487540b9598e7d77260d533f241eb2e5db5181bb885ef2d6bc4592dad1c9e8c0e27f465d59478b2faf90edd2d535e834f20fbd9dd3c0928d43dc486404
languageName: node
linkType: hard
@@ -19555,26 +19795,6 @@ __metadata:
languageName: node
linkType: hard
"body-parser@npm:1.20.3":
version: 1.20.3
resolution: "body-parser@npm:1.20.3"
dependencies:
bytes: "npm:3.1.2"
content-type: "npm:~1.0.5"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
destroy: "npm:1.2.0"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.4.24"
on-finished: "npm:2.4.1"
qs: "npm:6.13.0"
raw-body: "npm:2.5.2"
type-is: "npm:~1.6.18"
unpipe: "npm:1.0.0"
checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca
languageName: node
linkType: hard
"body-parser@npm:^2.2.0, body-parser@npm:^2.2.1":
version: 2.2.2
resolution: "body-parser@npm:2.2.2"
@@ -19592,6 +19812,26 @@ __metadata:
languageName: node
linkType: hard
"body-parser@npm:~1.20.3":
version: 1.20.4
resolution: "body-parser@npm:1.20.4"
dependencies:
bytes: "npm:~3.1.2"
content-type: "npm:~1.0.5"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
destroy: "npm:~1.2.0"
http-errors: "npm:~2.0.1"
iconv-lite: "npm:~0.4.24"
on-finished: "npm:~2.4.1"
qs: "npm:~6.14.0"
raw-body: "npm:~2.5.3"
type-is: "npm:~1.6.18"
unpipe: "npm:~1.0.0"
checksum: 10/ff67e28d3f426707be8697a75fdf8d564dc50c341b41f054264d8ab6e2924e519c7ce8acc9d0de05328fdc41e1d9f3f200aec9c1cfb1867d6b676a410d97c689
languageName: node
linkType: hard
"bonjour-service@npm:^1.2.1":
version: 1.3.0
resolution: "bonjour-service@npm:1.3.0"
@@ -21034,18 +21274,18 @@ __metadata:
languageName: node
linkType: hard
"compression@npm:^1.7.4":
version: 1.8.0
resolution: "compression@npm:1.8.0"
"compression@npm:^1.7.4, compression@npm:^1.8.1":
version: 1.8.1
resolution: "compression@npm:1.8.1"
dependencies:
bytes: "npm:3.1.2"
compressible: "npm:~2.0.18"
debug: "npm:2.6.9"
negotiator: "npm:~0.6.4"
on-headers: "npm:~1.0.2"
on-headers: "npm:~1.1.0"
safe-buffer: "npm:5.2.1"
vary: "npm:~1.1.2"
checksum: 10/ca213b9bd03e56c7c3596399d846237b5f0b31ca4cdeaa76a9547cd3c1465fbcfcb0fe93a5d7ff64eff28383fc65b53f1ef8bb2720d11bb48ad8c0836c502506
checksum: 10/e7552bfbd780f2003c6fe8decb44561f5cc6bc82f0c61e81122caff5ec656f37824084f52155b1e8ef31d7656cecbec9a2499b7a68e92e20780ffb39b479abb7
languageName: node
linkType: hard
@@ -21140,15 +21380,6 @@ __metadata:
languageName: node
linkType: hard
"content-disposition@npm:0.5.4":
version: 0.5.4
resolution: "content-disposition@npm:0.5.4"
dependencies:
safe-buffer: "npm:5.2.1"
checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720
languageName: node
linkType: hard
"content-disposition@npm:^1.0.0":
version: 1.0.0
resolution: "content-disposition@npm:1.0.0"
@@ -21158,6 +21389,15 @@ __metadata:
languageName: node
linkType: hard
"content-disposition@npm:~0.5.4":
version: 0.5.4
resolution: "content-disposition@npm:0.5.4"
dependencies:
safe-buffer: "npm:5.2.1"
checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720
languageName: node
linkType: hard
"content-type@npm:^1.0.5, content-type@npm:~1.0.4, content-type@npm:~1.0.5":
version: 1.0.5
resolution: "content-type@npm:1.0.5"
@@ -21249,14 +21489,14 @@ __metadata:
languageName: node
linkType: hard
"cookie@npm:0.7.1":
version: 0.7.1
resolution: "cookie@npm:0.7.1"
checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380
"cookie-signature@npm:~1.0.6":
version: 1.0.7
resolution: "cookie-signature@npm:1.0.7"
checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710
languageName: node
linkType: hard
"cookie@npm:0.7.2, cookie@npm:^0.7.1, cookie@npm:~0.7.2":
"cookie@npm:0.7.2, cookie@npm:^0.7.1, cookie@npm:~0.7.1, cookie@npm:~0.7.2":
version: 0.7.2
resolution: "cookie@npm:0.7.2"
checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f
@@ -22470,7 +22710,7 @@ __metadata:
languageName: node
linkType: hard
"destroy@npm:1.2.0":
"destroy@npm:1.2.0, destroy@npm:~1.2.0":
version: 1.2.0
resolution: "destroy@npm:1.2.0"
checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38
@@ -23133,13 +23373,6 @@ __metadata:
languageName: node
linkType: hard
"encodeurl@npm:~1.0.2":
version: 1.0.2
resolution: "encodeurl@npm:1.0.2"
checksum: 10/e50e3d508cdd9c4565ba72d2012e65038e5d71bdc9198cb125beb6237b5b1ade6c0d343998da9e170fb2eae52c1bed37d4d6d98a46ea423a0cddbed5ac3f780c
languageName: node
linkType: hard
"encoding@npm:^0.1.13":
version: 0.1.13
resolution: "encoding@npm:0.1.13"
@@ -24075,42 +24308,42 @@ __metadata:
languageName: node
linkType: hard
"express@npm:^4.21.1, express@npm:^4.21.2":
version: 4.21.2
resolution: "express@npm:4.21.2"
"express@npm:^4.21.1, express@npm:^4.21.2, express@npm:^4.22.1":
version: 4.22.1
resolution: "express@npm:4.22.1"
dependencies:
accepts: "npm:~1.3.8"
array-flatten: "npm:1.1.1"
body-parser: "npm:1.20.3"
content-disposition: "npm:0.5.4"
body-parser: "npm:~1.20.3"
content-disposition: "npm:~0.5.4"
content-type: "npm:~1.0.4"
cookie: "npm:0.7.1"
cookie-signature: "npm:1.0.6"
cookie: "npm:~0.7.1"
cookie-signature: "npm:~1.0.6"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
etag: "npm:~1.8.1"
finalhandler: "npm:1.3.1"
fresh: "npm:0.5.2"
http-errors: "npm:2.0.0"
finalhandler: "npm:~1.3.1"
fresh: "npm:~0.5.2"
http-errors: "npm:~2.0.0"
merge-descriptors: "npm:1.0.3"
methods: "npm:~1.1.2"
on-finished: "npm:2.4.1"
on-finished: "npm:~2.4.1"
parseurl: "npm:~1.3.3"
path-to-regexp: "npm:0.1.12"
path-to-regexp: "npm:~0.1.12"
proxy-addr: "npm:~2.0.7"
qs: "npm:6.13.0"
qs: "npm:~6.14.0"
range-parser: "npm:~1.2.1"
safe-buffer: "npm:5.2.1"
send: "npm:0.19.0"
serve-static: "npm:1.16.2"
send: "npm:~0.19.0"
serve-static: "npm:~1.16.2"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
statuses: "npm:~2.0.1"
type-is: "npm:~1.6.18"
utils-merge: "npm:1.0.1"
vary: "npm:~1.1.2"
checksum: 10/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1
checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470
languageName: node
linkType: hard
@@ -24483,21 +24716,6 @@ __metadata:
languageName: node
linkType: hard
"finalhandler@npm:1.3.1":
version: 1.3.1
resolution: "finalhandler@npm:1.3.1"
dependencies:
debug: "npm:2.6.9"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
on-finished: "npm:2.4.1"
parseurl: "npm:~1.3.3"
statuses: "npm:2.0.1"
unpipe: "npm:~1.0.0"
checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b
languageName: node
linkType: hard
"finalhandler@npm:^2.1.0":
version: 2.1.0
resolution: "finalhandler@npm:2.1.0"
@@ -24512,6 +24730,21 @@ __metadata:
languageName: node
linkType: hard
"finalhandler@npm:~1.3.1":
version: 1.3.2
resolution: "finalhandler@npm:1.3.2"
dependencies:
debug: "npm:2.6.9"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
on-finished: "npm:~2.4.1"
parseurl: "npm:~1.3.3"
statuses: "npm:~2.0.2"
unpipe: "npm:~1.0.0"
checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed
languageName: node
linkType: hard
"find-cache-dir@npm:^3.3.2":
version: 3.3.2
resolution: "find-cache-dir@npm:3.3.2"
@@ -24725,13 +24958,6 @@ __metadata:
languageName: node
linkType: hard
"fresh@npm:0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1
languageName: node
linkType: hard
"fresh@npm:^2.0.0":
version: 2.0.0
resolution: "fresh@npm:2.0.0"
@@ -24739,6 +24965,13 @@ __metadata:
languageName: node
linkType: hard
"fresh@npm:~0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1
languageName: node
linkType: hard
"fromentries@npm:^1.3.2":
version: 1.3.2
resolution: "fromentries@npm:1.3.2"
@@ -25985,20 +26218,7 @@ __metadata:
languageName: node
linkType: hard
"http-errors@npm:2.0.0":
version: 2.0.0
resolution: "http-errors@npm:2.0.0"
dependencies:
depd: "npm:2.0.0"
inherits: "npm:2.0.4"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
toidentifier: "npm:1.0.1"
checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439
languageName: node
linkType: hard
"http-errors@npm:^2.0.0, http-errors@npm:~2.0.1":
"http-errors@npm:^2.0.0, http-errors@npm:~2.0.0, http-errors@npm:~2.0.1":
version: 2.0.1
resolution: "http-errors@npm:2.0.1"
dependencies:
@@ -26051,7 +26271,7 @@ __metadata:
languageName: node
linkType: hard
"http-proxy-middleware@npm:^2.0.7":
"http-proxy-middleware@npm:^2.0.7, http-proxy-middleware@npm:^2.0.9":
version: 2.0.9
resolution: "http-proxy-middleware@npm:2.0.9"
dependencies:
@@ -26196,15 +26416,6 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3"
checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3
languageName: node
linkType: hard
"iconv-lite@npm:0.6, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@@ -26214,6 +26425,15 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:^0.4.24, iconv-lite@npm:~0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3"
checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3
languageName: node
linkType: hard
"iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0":
version: 0.7.2
resolution: "iconv-lite@npm:0.7.2"
@@ -26401,7 +26621,7 @@ __metadata:
languageName: node
linkType: hard
"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4":
"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521
@@ -30735,7 +30955,7 @@ __metadata:
languageName: node
linkType: hard
"on-finished@npm:2.4.1, on-finished@npm:^2.4.1":
"on-finished@npm:^2.4.1, on-finished@npm:~2.4.1":
version: 2.4.1
resolution: "on-finished@npm:2.4.1"
dependencies:
@@ -31406,13 +31626,6 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:0.1.12":
version: 0.1.12
resolution: "path-to-regexp@npm:0.1.12"
checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e
languageName: node
linkType: hard
"path-to-regexp@npm:3.3.0":
version: 3.3.0
resolution: "path-to-regexp@npm:3.3.0"
@@ -31441,6 +31654,13 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:~0.1.12":
version: 0.1.12
resolution: "path-to-regexp@npm:0.1.12"
checksum: 10/2e30f6a0144679c1f95c98e166b96e6acd1e72be9417830fefc8de7ac1992147eb9a4c7acaa59119fb1b3c34eec393b2129ef27e24b2054a3906fc4fb0d1398e
languageName: node
linkType: hard
"path-type@npm:^2.0.0":
version: 2.0.0
resolution: "path-type@npm:2.0.0"
@@ -32457,21 +32677,12 @@ __metadata:
languageName: node
linkType: hard
"qs@npm:6.13.0":
version: 6.13.0
resolution: "qs@npm:6.13.0"
dependencies:
side-channel: "npm:^1.0.6"
checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8
languageName: node
linkType: hard
"qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:^6.7.0":
version: 6.14.1
resolution: "qs@npm:6.14.1"
"qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:^6.7.0, qs@npm:~6.14.0":
version: 6.14.2
resolution: "qs@npm:6.14.2"
dependencies:
side-channel: "npm:^1.1.0"
checksum: 10/34b5ab00a910df432d55180ef39c1d1375e550f098b5ec153b41787f1a6a6d7e5f9495593c3b112b77dbc6709d0ae18e55b82847a4c2bbbb0de1e8ccbb1794c5
checksum: 10/682933a85bb4b7bd0d66e13c0a40d9e612b5e4bcc2cb9238f711a9368cd22d91654097a74fff93551e58146db282c56ac094957dfdc60ce64ea72c3c9d7779ac
languageName: node
linkType: hard
@@ -32573,18 +32784,6 @@ __metadata:
languageName: node
linkType: hard
"raw-body@npm:2.5.2":
version: 2.5.2
resolution: "raw-body@npm:2.5.2"
dependencies:
bytes: "npm:3.1.2"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.4.24"
unpipe: "npm:1.0.0"
checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95
languageName: node
linkType: hard
"raw-body@npm:^3.0.0, raw-body@npm:^3.0.1":
version: 3.0.2
resolution: "raw-body@npm:3.0.2"
@@ -32597,6 +32796,18 @@ __metadata:
languageName: node
linkType: hard
"raw-body@npm:~2.5.3":
version: 2.5.3
resolution: "raw-body@npm:2.5.3"
dependencies:
bytes: "npm:~3.1.2"
http-errors: "npm:~2.0.1"
iconv-lite: "npm:~0.4.24"
unpipe: "npm:~1.0.0"
checksum: 10/f35759fe5a6548e7c529121ead1de4dd163f899749a5896c42e278479df2d9d7f98b5bb17312737c03617765e5a1433e586f717616e5cfbebc13b4738b820601
languageName: node
linkType: hard
"rc9@npm:^2.1.2":
version: 2.1.2
resolution: "rc9@npm:2.1.2"
@@ -34159,27 +34370,6 @@ __metadata:
languageName: node
linkType: hard
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
dependencies:
debug: "npm:2.6.9"
depd: "npm:2.0.0"
destroy: "npm:1.2.0"
encodeurl: "npm:~1.0.2"
escape-html: "npm:~1.0.3"
etag: "npm:~1.8.1"
fresh: "npm:0.5.2"
http-errors: "npm:2.0.0"
mime: "npm:1.6.0"
ms: "npm:2.1.3"
on-finished: "npm:2.4.1"
range-parser: "npm:~1.2.1"
statuses: "npm:2.0.1"
checksum: 10/1f6064dea0ae4cbe4878437aedc9270c33f2a6650a77b56a16b62d057527f2766d96ee282997dd53ec0339082f2aad935bc7d989b46b48c82fc610800dc3a1d0
languageName: node
linkType: hard
"send@npm:^1.1.0, send@npm:^1.2.0":
version: 1.2.0
resolution: "send@npm:1.2.0"
@@ -34199,6 +34389,27 @@ __metadata:
languageName: node
linkType: hard
"send@npm:~0.19.0, send@npm:~0.19.1":
version: 0.19.2
resolution: "send@npm:0.19.2"
dependencies:
debug: "npm:2.6.9"
depd: "npm:2.0.0"
destroy: "npm:1.2.0"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
etag: "npm:~1.8.1"
fresh: "npm:~0.5.2"
http-errors: "npm:~2.0.1"
mime: "npm:1.6.0"
ms: "npm:2.1.3"
on-finished: "npm:~2.4.1"
range-parser: "npm:~1.2.1"
statuses: "npm:~2.0.2"
checksum: 10/e932a592f62c58560b608a402d52333a8ae98a5ada076feb5db1d03adaa77c3ca32a7befa1c4fd6dedc186e88f342725b0cb4b3d86835eaf834688b259bef18d
languageName: node
linkType: hard
"sentence-case@npm:^3.0.4":
version: 3.0.4
resolution: "sentence-case@npm:3.0.4"
@@ -34258,18 +34469,6 @@ __metadata:
languageName: node
linkType: hard
"serve-static@npm:1.16.2":
version: 1.16.2
resolution: "serve-static@npm:1.16.2"
dependencies:
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
parseurl: "npm:~1.3.3"
send: "npm:0.19.0"
checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737
languageName: node
linkType: hard
"serve-static@npm:^2.2.0":
version: 2.2.0
resolution: "serve-static@npm:2.2.0"
@@ -34282,6 +34481,18 @@ __metadata:
languageName: node
linkType: hard
"serve-static@npm:~1.16.2":
version: 1.16.3
resolution: "serve-static@npm:1.16.3"
dependencies:
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
parseurl: "npm:~1.3.3"
send: "npm:~0.19.1"
checksum: 10/149d6718dd9e53166784d0a65535e21a7c01249d9c51f57224b786a7306354c6807e7811a9f6c143b45c863b1524721fca2f52b5c81a8b5194e3dde034a03b9c
languageName: node
linkType: hard
"serve@npm:^14.2.4":
version: 14.2.4
resolution: "serve@npm:14.2.4"
@@ -35143,13 +35354,6 @@ __metadata:
languageName: node
linkType: hard
"statuses@npm:2.0.1":
version: 2.0.1
resolution: "statuses@npm:2.0.1"
checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb
languageName: node
linkType: hard
"statuses@npm:>= 1.4.0 < 2":
version: 1.5.0
resolution: "statuses@npm:1.5.0"
@@ -35157,7 +35361,7 @@ __metadata:
languageName: node
linkType: hard
"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2":
"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.1, statuses@npm:~2.0.2":
version: 2.0.2
resolution: "statuses@npm:2.0.2"
checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879
@@ -36120,7 +36324,7 @@ __metadata:
languageName: node
linkType: hard
"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1":
"toidentifier@npm:~1.0.1":
version: 1.0.1
resolution: "toidentifier@npm:1.0.1"
checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45
@@ -36787,7 +36991,7 @@ __metadata:
languageName: node
linkType: hard
"unpipe@npm:1.0.0, unpipe@npm:~1.0.0":
"unpipe@npm:~1.0.0":
version: 1.0.0
resolution: "unpipe@npm:1.0.0"
checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2