diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 715e54ab6..458635772 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -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 diff --git a/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts b/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts index 8324c1034..6872dad52 100644 --- a/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts +++ b/blocksuite/integration-test/src/__tests__/edgeless/shape-dom.spec.ts @@ -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); }); }); diff --git a/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-1-importing-1.png b/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-1-importing-1.png index 753a8012c..9b4ec9199 100644 Binary files a/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-1-importing-1.png and b/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-1-importing-1.png differ diff --git a/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-2-importing-1.png b/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-2-importing-1.png index 027141c57..9b4ec9199 100644 Binary files a/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-2-importing-1.png and b/blocksuite/integration-test/src/__tests__/main/__screenshots__/snapshot.spec.ts/snapshot-2-importing-1.png differ diff --git a/blocksuite/integration-test/src/__tests__/main/editor-semantics.spec.ts b/blocksuite/integration-test/src/__tests__/main/editor-semantics.spec.ts new file mode 100644 index 000000000..3ab92a4db --- /dev/null +++ b/blocksuite/integration-test/src/__tests__/main/editor-semantics.spec.ts @@ -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; + 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}/); + }); +}); diff --git a/blocksuite/integration-test/vitest.config.ts b/blocksuite/integration-test/vitest.config.ts index 066b6ea10..f4ad799ce 100644 --- a/blocksuite/integration-test/vitest.config.ts +++ b/blocksuite/integration-test/vitest.config.ts @@ -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: { diff --git a/packages/frontend/apps/electron-renderer/src/app/global.css b/packages/frontend/apps/electron-renderer/src/app/global.css index a66ae17c4..a36a063fb 100644 --- a/packages/frontend/apps/electron-renderer/src/app/global.css +++ b/packages/frontend/apps/electron-renderer/src/app/global.css @@ -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; } diff --git a/packages/frontend/core/src/desktop/route-paths.ts b/packages/frontend/core/src/desktop/route-paths.ts new file mode 100644 index 000000000..7096e42c4 --- /dev/null +++ b/packages/frontend/core/src/desktop/route-paths.ts @@ -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}`; +} diff --git a/packages/frontend/core/src/desktop/router.tsx b/packages/frontend/core/src/desktop/router.tsx index c89bd4eb9..014ae98eb 100644 --- a/packages/frontend/core/src/desktop/router.tsx +++ b/packages/frontend/core/src/desktop/router.tsx @@ -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'), }, ], diff --git a/packages/frontend/core/src/modules/doc/services/docs.ts b/packages/frontend/core/src/modules/doc/services/docs.ts index 95ca6d81a..e1971d0b2 100644 --- a/packages/frontend/core/src/modules/doc/services/docs.ts +++ b/packages/frontend/core/src/modules/doc/services/docs.ts @@ -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(); diff --git a/packages/frontend/core/src/modules/doc/services/duplicate-title.ts b/packages/frontend/core/src/modules/doc/services/duplicate-title.ts new file mode 100644 index 000000000..d4fa08d50 --- /dev/null +++ b/packages/frontend/core/src/modules/doc/services/duplicate-title.ts @@ -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})` + ); +} diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/local.ts b/packages/frontend/core/src/modules/workspace-engine/impls/local.ts index 37a4cc3a6..9ec7e30e4 100644 --- a/packages/frontend/core/src/modules/workspace-engine/impls/local.ts +++ b/packages/frontend/core/src/modules/workspace-engine/impls/local.ts @@ -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(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 => { diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/workspace-id-utils.ts b/packages/frontend/core/src/modules/workspace-engine/impls/workspace-id-utils.ts new file mode 100644 index 000000000..044cbb446 --- /dev/null +++ b/packages/frontend/core/src/modules/workspace-engine/impls/workspace-id-utils.ts @@ -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)]; +} diff --git a/packages/frontend/core/src/types/types.d.ts b/packages/frontend/core/src/types/types.d.ts index 19dd57cf2..aaf9a5423 100644 --- a/packages/frontend/core/src/types/types.d.ts +++ b/packages/frontend/core/src/types/types.d.ts @@ -1,4 +1,5 @@ -/// +/// +/// declare module '*.md' { const text: string; diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index 29fe097d3..53d39e8a2 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -9,7 +9,7 @@ "es-CL": 98, "es": 96, "fa": 96, - "fr": 98, + "fr": 100, "hi": 1, "it-IT": 98, "it": 1, diff --git a/packages/frontend/i18n/src/resources/fr.json b/packages/frontend/i18n/src/resources/fr.json index 3b151705b..b3c03dfa1 100644 --- a/packages/frontend/i18n/src/resources/fr.json +++ b/packages/frontend/i18n/src/resources/fr.json @@ -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" } - diff --git a/tests/kit/src/utils/load-page.ts b/tests/kit/src/utils/load-page.ts index 3f51dc823..ab46675a2 100644 --- a/tests/kit/src/utils/load-page.ts +++ b/tests/kit/src/utils/load-page.ts @@ -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( diff --git a/tools/cli/package.json b/tools/cli/package.json index 3dfba61cf..6eec88262 100644 --- a/tools/cli/package.json +++ b/tools/cli/package.json @@ -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", diff --git a/tools/cli/src/bundle-shared.ts b/tools/cli/src/bundle-shared.ts new file mode 100644 index 000000000..630f1b063 --- /dev/null +++ b/tools/cli/src/bundle-shared.ts @@ -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(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, + }, + ], +}; diff --git a/tools/cli/src/bundle.ts b/tools/cli/src/bundle.ts index 2a1dd28eb..186573df2 100644 --- a/tools/cli/src/bundle.ts +++ b/tools/cli/src/bundle.ts @@ -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 ); diff --git a/tools/cli/src/bundler.ts b/tools/cli/src/bundler.ts new file mode 100644 index 000000000..49e673527 --- /dev/null +++ b/tools/cli/src/bundler.ts @@ -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); +} diff --git a/tools/cli/src/rspack/index.ts b/tools/cli/src/rspack/index.ts new file mode 100644 index 000000000..112c54fb4 --- /dev/null +++ b/tools/cli/src/rspack/index.ts @@ -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, + htmlConfig: Partial = {}, + 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 + ), + }), + !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 & { 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 + ) + ), + 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 & { 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$)/], + }; +} diff --git a/tools/cli/src/webpack/cache-group.ts b/tools/cli/src/webpack/cache-group.ts index e844a8cf4..f40e47bd8 100644 --- a/tools/cli/src/webpack/cache-group.ts +++ b/tools/cli/src/webpack/cache-group.ts @@ -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}`; diff --git a/tools/cli/src/webpack/html-plugin.ts b/tools/cli/src/webpack/html-plugin.ts index 5f1838c11..85c8e34f8 100644 --- a/tools/cli/src/webpack/html-plugin.ts +++ b/tools/cli/src/webpack/html-plugin.ts @@ -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', diff --git a/yarn.lock b/yarn.lock index 5691dcf92..86ee7b262 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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