From 56612786288af48dca27799e0b2a4c03aef20688 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 27 May 2026 12:08:42 +0200 Subject: [PATCH 01/17] Add `@ckeditor/ckeditor5-dev-manual-server` package. --- .../ckeditor5-dev-manual-server/CHANGELOG.md | 4 + .../ckeditor5-dev-manual-server/LICENSE.md | 16 + .../ckeditor5-dev-manual-server/README.md | 17 + .../ckeditor5-dev-manual-server/package.json | 54 + .../rolldown.config.js | 28 + .../ckeditor5-dev-manual-server/src/index.ts | 9 + .../src/manual-test-plugin/collect-pages.ts | 100 ++ .../src/manual-test-plugin/parse-markdown.ts | 36 + .../src/manual-test-plugin/plugin.ts | 155 +++ .../src/manual-test-plugin/shell-html.ts | 120 ++ .../src/manual-test-plugin/static-assets.ts | 181 +++ .../src/manual-test-plugin/types.ts | 22 + .../ckeditor5-dev-manual-server/src/utils.ts | 39 + .../tests/manual-test-plugin/static-assets.ts | 64 + .../theme/catalog.css | 296 +++++ .../theme/catalog.html | 58 + .../theme/catalog.ts | 126 ++ .../theme/shell.css | 409 +++++++ .../theme/shell.html | 46 + .../theme/shell.ts | 161 +++ .../vitest.config.ts | 27 + pnpm-lock.yaml | 1078 ++++++++++++++--- 22 files changed, 2872 insertions(+), 174 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/CHANGELOG.md create mode 100644 packages/ckeditor5-dev-manual-server/LICENSE.md create mode 100644 packages/ckeditor5-dev-manual-server/README.md create mode 100644 packages/ckeditor5-dev-manual-server/package.json create mode 100644 packages/ckeditor5-dev-manual-server/rolldown.config.js create mode 100644 packages/ckeditor5-dev-manual-server/src/index.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/parse-markdown.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts create mode 100644 packages/ckeditor5-dev-manual-server/src/utils.ts create mode 100644 packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts create mode 100644 packages/ckeditor5-dev-manual-server/theme/catalog.css create mode 100644 packages/ckeditor5-dev-manual-server/theme/catalog.html create mode 100644 packages/ckeditor5-dev-manual-server/theme/catalog.ts create mode 100644 packages/ckeditor5-dev-manual-server/theme/shell.css create mode 100644 packages/ckeditor5-dev-manual-server/theme/shell.html create mode 100644 packages/ckeditor5-dev-manual-server/theme/shell.ts create mode 100644 packages/ckeditor5-dev-manual-server/vitest.config.ts diff --git a/packages/ckeditor5-dev-manual-server/CHANGELOG.md b/packages/ckeditor5-dev-manual-server/CHANGELOG.md new file mode 100644 index 000000000..2cc0cf250 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/CHANGELOG.md @@ -0,0 +1,4 @@ +Changelog +========= + +All changes in the package are documented in the main repository. See: https://github.com/ckeditor/ckeditor5-dev/blob/master/CHANGELOG.md. diff --git a/packages/ckeditor5-dev-manual-server/LICENSE.md b/packages/ckeditor5-dev-manual-server/LICENSE.md new file mode 100644 index 000000000..24a241576 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/LICENSE.md @@ -0,0 +1,16 @@ +Software License Agreement +========================== + +Copyright (c) 2003-2026, [CKSource](http://cksource.com) Holding sp. z o.o. All rights reserved. + +Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). + +Sources of Intellectual Property Included in CKEditor +----------------------------------------------------- + +Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission. + +Trademarks +---------- + +**CKEditor** is a trademark of [CKSource](http://cksource.com) Holding sp. z o.o. All other brand and product names are trademarks, registered trademarks or service marks of their respective holders. diff --git a/packages/ckeditor5-dev-manual-server/README.md b/packages/ckeditor5-dev-manual-server/README.md new file mode 100644 index 000000000..6f205ce8d --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/README.md @@ -0,0 +1,17 @@ +CKEditor 5 manual server +======================= + +[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-dev-manual-server.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-dev-manual-server) +[![CircleCI](https://circleci.com/gh/ckeditor/ckeditor5-dev.svg?style=shield)](https://app.circleci.com/pipelines/github/ckeditor/ckeditor5-dev?branch=master) + +Used to extend Vite to create a manual test server for CKEditor 5. + +More information about development tools packages can be found at the following URL: . + +## Changelog + +See the [`CHANGELOG.md`](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/ckeditor5-dev-manual-server/CHANGELOG.md) file. + +## License + +Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file. diff --git a/packages/ckeditor5-dev-manual-server/package.json b/packages/ckeditor5-dev-manual-server/package.json new file mode 100644 index 000000000..cb08f3785 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/package.json @@ -0,0 +1,54 @@ +{ + "name": "@ckeditor/ckeditor5-dev-manual-server", + "version": "0.1.0", + "description": "Used to extend Vite to create a manual test server for CKEditor 5.", + "keywords": [], + "author": "CKSource (http://cksource.com/)", + "license": "GPL-2.0-or-later", + "homepage": "https://github.com/ckeditor/ckeditor5-dev/tree/master/packages/ckeditor5-dev-manual-server", + "bugs": "https://github.com/ckeditor/ckeditor5/issues", + "repository": { + "type": "git", + "url": "https://github.com/ckeditor/ckeditor5-dev.git", + "directory": "packages/ckeditor5-dev-manual-server" + }, + "engines": { + "node": ">=24.11.0", + "npm": ">=5.7.1" + }, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist", + "theme" + ], + "bin": { + "ckeditor5-dev-manual-server": "dist/index.js" + }, + "scripts": { + "build": "rolldown -c rolldown.config.js", + "dev": "rolldown -c rolldown.config.js --watch", + "test": "vitest run --config vitest.config.ts", + "coverage": "vitest run --config vitest.config.ts --coverage", + "test:dev": "vitest dev" + }, + "dependencies": { + "@ckeditor/ckeditor5-inspector": "^5.0.0", + "@parse5/tools": "^0.7.0", + "hast-util-to-html": "^9.0.5", + "parse5": "^8.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "unified": "^11.0.5", + "vite": "^8.0.13", + "vite-svg-loader": "^5.1.1" + }, + "devDependencies": { + "@vitest/coverage-v8": "^4.1.2", + "rolldown": "^1.0.0", + "upath": "^2.0.1", + "vitest": "^4.1.2" + } +} diff --git a/packages/ckeditor5-dev-manual-server/rolldown.config.js b/packages/ckeditor5-dev-manual-server/rolldown.config.js new file mode 100644 index 000000000..5c70b6606 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/rolldown.config.js @@ -0,0 +1,28 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { defineConfig } from 'rolldown'; +import { declarationFilesPlugin } from '../../scripts/plugin-declarations.js'; +import pkg from './package.json' with { type: 'json' }; + +const externals = [ + ...Object.keys( pkg.dependencies || {} ), + ...Object.keys( pkg.peerDependencies || {} ) +]; + +export default defineConfig( { + input: 'src/index.ts', + platform: 'node', + output: { + cleanDir: true, + format: 'esm', + dir: 'dist', + assetFileNames: '[name][extname]' + }, + plugins: [ + declarationFilesPlugin() + ], + external: id => externals.some( name => id.startsWith( name ) ) +} ); diff --git a/packages/ckeditor5-dev-manual-server/src/index.ts b/packages/ckeditor5-dev-manual-server/src/index.ts new file mode 100644 index 000000000..901242a0a --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/index.ts @@ -0,0 +1,9 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +export { createManualTestsPlugin } from './manual-test-plugin/plugin.js'; +export type { ManualData } from './manual-test-plugin/plugin.js'; + +export { stringifyValues } from './utils.js'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts new file mode 100644 index 000000000..04742ba4d --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts @@ -0,0 +1,100 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { globSync } from 'node:fs'; +import { stripLeadingSlash, toPosixPath, toPublicSpecifier } from '../utils.js'; +import type { ManualPageEntry, ManualTestAssetExtension } from './types.js'; + +interface ParsedManualTestAssetPath { + extension: ManualTestAssetExtension; + packageName: string; + packageRootPath: string; + slug: string; +} + +export function collectManualPages( patterns: Array, workspaceRoot: string ): Map { + const groupedFiles = new Map>>(); + const manualPages: Array = []; + + for ( const relativeFilePath of matchFiles( patterns, workspaceRoot ) ) { + if ( relativeFilePath.includes( '/_utils/' ) ) { + continue; + } + + const parsedPath = parseManualTestAssetPath( relativeFilePath ); + + if ( !parsedPath ) { + continue; + } + + const entryKey = `${ parsedPath.packageRootPath }/${ parsedPath.packageName }/${ parsedPath.slug }`; + const matchedFiles = groupedFiles.get( entryKey ) || {}; + + matchedFiles[ parsedPath.extension ] = relativeFilePath; + groupedFiles.set( entryKey, matchedFiles ); + } + + for ( const matchedFiles of groupedFiles.values() ) { + if ( !matchedFiles.html ) { + continue; + } + + const scriptFilePath = matchedFiles.ts || matchedFiles.js; + + if ( !scriptFilePath ) { + continue; + } + + const parsedPath = parseManualTestAssetPath( scriptFilePath )!; + + manualPages.push( { + displayName: humanizeSlug( parsedPath.slug ), + htmlFilePath: toPublicSpecifier( matchedFiles.html ), + instructionsFilePath: matchedFiles.md ? toPublicSpecifier( matchedFiles.md ) : undefined, + packageName: parsedPath.packageName, + scriptFilePath: toPublicSpecifier( scriptFilePath ), + slug: parsedPath.slug, + source: parsedPath.packageRootPath == 'packages' ? 'commercial' : 'oss' + } ); + } + + manualPages.sort( ( a, b ) => a.packageName.localeCompare( b.packageName ) || a.slug.localeCompare( b.slug ) ); + + return new Map( manualPages.map( entry => [ entry.htmlFilePath, entry ] ) ); +} + +function parseManualTestAssetPath( filePath: string ): ParsedManualTestAssetPath | null { + const normalizedFilePath = stripLeadingSlash( toPosixPath( filePath ) ); + const match = normalizedFilePath.match( + /^(packages|external\/ckeditor5\/packages)\/([^/]+)\/tests\/manual\/(.+)\.(html|js|md|ts)$/ + ); + + if ( !match ) { + return null; + } + + return { + extension: match[ 4 ]! as ParsedManualTestAssetPath[ 'extension' ], + packageName: match[ 2 ]!, + packageRootPath: match[ 1 ]!, + slug: match[ 3 ]! + }; +} + +function humanizeSlug( slug: string ): string { + return slug + .split( '/' ) + .map( pathPart => + pathPart + .split( '-' ) + .map( part => part.charAt( 0 ).toUpperCase() + part.slice( 1 ) ) + .join( ' ' ) + ) + .join( ' / ' ); +} + +function matchFiles( patterns: Array, workspaceRoot: string ): Array { + return patterns.flatMap( pattern => globSync( pattern, { cwd: workspaceRoot } ).map( match => toPosixPath( match ) ) ); +} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/parse-markdown.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/parse-markdown.ts new file mode 100644 index 000000000..650972a89 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/parse-markdown.ts @@ -0,0 +1,36 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { resolve } from 'node:path'; +import { readFileSync } from 'node:fs'; +import { unified } from 'unified'; +import remarkGfm from 'remark-gfm'; +import remarkParse from 'remark-parse'; +import remarkRehype from 'remark-rehype'; +import { toHtml } from 'hast-util-to-html'; +import type { ManualPageEntry } from './types.js'; + +const markdownProcessor = unified() + .use( remarkParse ) + .use( remarkGfm ) + .use( remarkRehype ); + +export function loadRenderedInstructions( entry: ManualPageEntry, workspaceRoot: string ): string { + if ( !entry.instructionsFilePath ) { + return ''; + } + + const absolutePath = resolve( workspaceRoot, entry.instructionsFilePath.slice( 1 ) ); + const markdown = readFileSync( absolutePath, 'utf8' ).trim(); + + if ( !markdown ) { + return ''; + } + + const tree = markdownProcessor.runSync( markdownProcessor.parse( markdown ) ); + + return toHtml( tree ); +} + diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts new file mode 100644 index 000000000..7b6f3a5e2 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -0,0 +1,155 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import path from 'node:path'; +import { collectManualPages } from './collect-pages.js'; +import { createManualStaticAssetsMiddleware } from './static-assets.js'; +import { createManualShellHtml } from './shell-html.js'; +import { toPublicFilePath, toPublicSpecifier } from '../utils.js'; +import type { Plugin } from 'vite'; +import type { ManualPageEntry } from './types.js'; +export type { ManualData } from './types.js'; + +interface ManualTestClientEntry { + displayName: string; + href: string; + packageName: string; + packageShortName: string; + slug: string; + source: 'commercial' | 'oss'; +} + +const MANUAL_TEST_PATTERNS = [ + 'packages/*/tests/manual/**/*.{html,js,md,ts}', + 'external/ckeditor5/packages/*/tests/manual/**/*.{html,js,md,ts}' +]; +const MANUAL_ENTRIES_VIRTUAL_ID = 'virtual:ckeditor5-manual-entries'; +const WORKSPACE_ROOT = process.cwd(); +const MANUAL_THEME_ROOT = path.resolve( import.meta.dirname, '..', 'theme' ); +const MANUAL_CATALOG_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'catalog.html' ); +const MANUAL_SHELL_TEMPLATE_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.html' ); +const MANUAL_SHELL_SCRIPT_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.ts' ); +const MANUAL_CATALOG_PUBLIC_PATH = toPublicFilePath( MANUAL_CATALOG_FILE_PATH, WORKSPACE_ROOT ); +const MANUAL_SHELL_SCRIPT_PUBLIC_PATH = toPublicFilePath( MANUAL_SHELL_SCRIPT_FILE_PATH, WORKSPACE_ROOT ); + +export function createManualTestsPlugin(): Plugin { + const manualPages = collectManualPages( MANUAL_TEST_PATTERNS, WORKSPACE_ROOT ); + const resolvedVirtualModuleId = `\0${ MANUAL_ENTRIES_VIRTUAL_ID }`; + const clientEntries: Array = [ ...manualPages.values() ].map( entry => ( { + displayName: entry.displayName, + href: entry.htmlFilePath, + packageName: entry.packageName, + packageShortName: entry.packageName.replace( /^ckeditor5-/, '' ), + slug: entry.slug, + source: entry.source + } ) ); + + return { + name: 'ckeditor5-manual-tests', + + configureServer( server ) { + server.middlewares.use( createManualStaticAssetsMiddleware( WORKSPACE_ROOT ) ); + + server.middlewares.use( ( request, _response, next ) => { + rewriteCatalogRequest( request ); + + next(); + } ); + }, + + configurePreviewServer( server ) { + server.middlewares.use( createManualStaticAssetsMiddleware( WORKSPACE_ROOT ) ); + + server.middlewares.use( ( request, _response, next ) => { + rewriteCatalogRequest( request ); + + next(); + } ); + }, + + config() { + return { + build: { + rolldownOptions: { + input: [ + MANUAL_CATALOG_FILE_PATH, + ...[ ...manualPages.values() ].map( entry => + path.resolve( WORKSPACE_ROOT, entry.htmlFilePath.slice( 1 ) ) + ) + ] + } + } + }; + }, + + resolveId( source ) { + if ( source == MANUAL_ENTRIES_VIRTUAL_ID ) { + return resolvedVirtualModuleId; + } + + return null; + }, + + load( id ) { + if ( id == resolvedVirtualModuleId ) { + return `export const manualTestEntries = ${ JSON.stringify( clientEntries, null, 2 ) };`; + } + + return null; + }, + + transformIndexHtml: { + order: 'pre', + + handler( html, context ) { + const entry = getManualPageEntryForHtmlPath( manualPages, context.path ); + + if ( !entry ) { + return undefined; + } + + return createManualShellHtml( { + entry, + html, + shellScriptPublicPath: MANUAL_SHELL_SCRIPT_PUBLIC_PATH, + shellTemplateFilePath: MANUAL_SHELL_TEMPLATE_FILE_PATH, + workspaceRoot: WORKSPACE_ROOT + } ); + } + } + }; +} + +function rewriteCatalogRequest( request: { url?: string } ): void { + const requestPath = request.url?.split( '?' )[ 0 ]; + + if ( requestPath == '/' || requestPath == '/index.html' ) { + request.url = MANUAL_CATALOG_PUBLIC_PATH; + } +} + +function getFilePathFromId( id: string ): string { + const queryIndex = id.indexOf( '?' ); + + return queryIndex >= 0 ? id.slice( 0, queryIndex ) : id; +} + +function getManualPageEntryForHtmlPath( + manualPages: Map, + requestPath: string +): ManualPageEntry | undefined { + const filePath = getFilePathFromId( requestPath ); + const entry = manualPages.get( filePath ); + + if ( entry ) { + return entry; + } + + if ( path.isAbsolute( filePath ) && filePath.startsWith( WORKSPACE_ROOT ) ) { + return manualPages.get( toPublicSpecifier( path.relative( WORKSPACE_ROOT, filePath ) ) ); + } + + return undefined; +} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts new file mode 100644 index 000000000..6d98b5f42 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts @@ -0,0 +1,120 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { posix } from 'node:path'; +import { readFileSync } from 'node:fs'; +import { parse, serialize } from 'parse5'; +import { + appendChild, + getAttribute, + getTextContent, + hasAttribute, + isElementNode, + query, + removeAttribute, + removeNode, + setAttribute, + setTextContent, + type ChildNode, + type Element, + type Node, + type ParentNode +} from '@parse5/tools'; +import { loadRenderedInstructions } from './parse-markdown.js'; +import type { ManualData, ManualPageEntry } from './types.js'; + +export interface CreateManualShellHtmlOptions { + entry: ManualPageEntry; + html: string; + shellScriptPublicPath: string; + shellTemplateFilePath: string; + workspaceRoot: string; +} + +export function createManualShellHtml( { + entry, + html, + shellScriptPublicPath, + shellTemplateFilePath, + workspaceRoot +}: CreateManualShellHtmlOptions ): string { + // Elements from shell template. + const shellHtml = readFileSync( shellTemplateFilePath, 'utf8' ); + const shellDocument = parse( shellHtml ); + const shellHead = getRequiredElementByTagName( shellDocument, 'head' ); + const shellBody = getRequiredElementByTagName( shellDocument, 'body' ); + const shellTitle = getRequiredElementByTagName( shellHead, 'title' ); + const shellDataElement = query( shellBody, candidate => { + return isElementNode( candidate ) && getAttribute( candidate, 'id' ) == 'manual-shell-data'; + } )!; + const shellScriptElement = getRequiredElementByAttribute( shellHead, 'data-manual-shell-script' ); + + // Elements from manual page. + const manualDocument = parse( html ); + const manualHead = getRequiredElementByTagName( manualDocument, 'head' ); + const manualBody = getRequiredElementByTagName( manualDocument, 'body' ); + const manualTitle = findChildElement( manualHead, 'title' ); + + const testScriptElement = getRequiredElementByAttribute( shellHead, 'data-manual-test-script' ); + const testScriptFileName = posix.basename( entry.scriptFilePath ); + + setTextContent( shellTitle, ( manualTitle ? getTextContent( manualTitle ) : '' ).trim() || entry.displayName ); + setAttribute( shellScriptElement, 'src', shellScriptPublicPath ); + removeAttribute( shellScriptElement, 'data-manual-shell-script' ); + setAttribute( testScriptElement, 'src', `./${ testScriptFileName }` ); + removeAttribute( testScriptElement, 'data-manual-test-script' ); + setTextContent( shellDataElement, stringifyHtmlScriptData( createManualData( entry, workspaceRoot ) ) ); + appendChildren( shellHead, manualHead.childNodes.filter( shouldMoveManualHeadNode ) ); + appendChildren( shellBody, [ ...manualBody.childNodes ] ); + + return serialize( shellDocument ); +} + +function createManualData( entry: ManualPageEntry, workspaceRoot: string ): ManualData { + return { + displayName: entry.displayName, + instructionsHtml: loadRenderedInstructions( entry, workspaceRoot ), + packageName: entry.packageName + }; +} + +function stringifyHtmlScriptData( value: ManualData ): string { + return JSON.stringify( value ).split( '<' ).join( '\\u003c' ); +} + +function shouldMoveManualHeadNode( node: ChildNode ): boolean { + if ( !isElementNode( node ) ) { + return true; + } + + if ( node.tagName == 'title' ) { + return false; + } + + if ( node.tagName != 'meta' ) { + return true; + } + + return !getAttribute( node, 'charset' ) && getAttribute( node, 'name' ) != 'viewport'; +} + +function appendChildren( parent: ParentNode, childNodes: Array ): void { + for ( const node of childNodes ) { + removeNode( node ); + appendChild( parent, node ); + } +} + +function getRequiredElementByTagName( root: Node, tagName: string ): Element { + return query( root, candidate => isElementNode( candidate ) && candidate.tagName == tagName )!; +} + +function getRequiredElementByAttribute( root: ParentNode, attributeName: string ): Element { + return query( root, candidate => isElementNode( candidate ) && hasAttribute( candidate, attributeName ) )!; +} + +function findChildElement( root: ParentNode, tagName: string ): Element | undefined { + return root.childNodes.find( ( node ): node is Element => isElementNode( node ) && node.tagName == tagName ); +} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts new file mode 100644 index 000000000..bf725cedd --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts @@ -0,0 +1,181 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import type { IncomingMessage, ServerResponse } from 'node:http'; +import { toPosixPath } from '../utils.js'; + +const PROCESSED_MANUAL_TEST_EXTENSIONS = new Set( [ '.html', '.js', '.md', '.ts' ] ); + +const VITE_MODULE_QUERY_PARAMETERS = new Set( [ + 'import', + 'raw', + 'url', + 'worker', + 'inline' +] ); + +type ManualStaticAssetsMiddleware = ( request: IncomingMessage, response: ServerResponse, next: () => void ) => void; + +export function createManualStaticAssetsMiddleware( workspaceRoot: string ): ManualStaticAssetsMiddleware { + return ( request, response, next ) => { + if ( request.method != 'GET' && request.method != 'HEAD' ) { + next(); + + return; + } + + const filePath = getManualStaticAssetFilePath( request.url, workspaceRoot ); + + if ( !filePath ) { + next(); + + return; + } + + let fileStats: fs.Stats; + + try { + fileStats = fs.statSync( filePath ); + } catch { + next(); + + return; + } + + if ( !fileStats.isFile() ) { + next(); + + return; + } + + response.statusCode = 200; + response.setHeader( 'Content-Length', fileStats.size ); + response.setHeader( 'Content-Type', getContentType( path.extname( filePath ) ) ); + + if ( request.method == 'HEAD' ) { + response.end(); + + return; + } + + fs.createReadStream( filePath ).pipe( response ); + }; +} + +export function getManualStaticAssetFilePath( requestUrl: string | undefined, workspaceRoot: string ): string | null { + if ( !requestUrl ) { + return null; + } + + let url: URL; + + try { + url = new URL( requestUrl, 'http://ckeditor5.local' ); + } catch { + return null; + } + + if ( [ ...url.searchParams.keys() ].some( key => VITE_MODULE_QUERY_PARAMETERS.has( key ) ) ) { + return null; + } + + let requestPath: string; + + try { + requestPath = decodeURIComponent( url.pathname ); + } catch { + return null; + } + + if ( requestPath.includes( '\0' ) ) { + return null; + } + + const filePath = path.resolve( workspaceRoot, toPosixPath( requestPath ).replace( /^\/+/, '' ) ); + const relativeResolvedPath = path.relative( workspaceRoot, filePath ); + + if ( relativeResolvedPath.startsWith( '..' ) || path.isAbsolute( relativeResolvedPath ) ) { + return null; + } + + const relativeFilePath = toPosixPath( relativeResolvedPath ); + + if ( !isManualStaticAssetPath( relativeFilePath ) ) { + return null; + } + + return filePath; +} + +function isManualStaticAssetPath( filePath: string ): boolean { + const pathParts = filePath.split( '/' ); + const manualDirectoryIndex = pathParts.findIndex( ( part, index ) => part == 'manual' && pathParts[ index - 1 ] == 'tests' ); + + if ( manualDirectoryIndex < 0 || manualDirectoryIndex == pathParts.length - 1 ) { + return false; + } + + const packageRootParts = pathParts.slice( 0, manualDirectoryIndex - 1 ); + const isCommercialPackage = packageRootParts.length == 2 && packageRootParts[ 0 ] == 'packages'; + const isOssPackage = packageRootParts.length == 4 && + packageRootParts[ 0 ] == 'external' && + packageRootParts[ 1 ] == 'ckeditor5' && + packageRootParts[ 2 ] == 'packages'; + + if ( !isCommercialPackage && !isOssPackage ) { + return false; + } + + const extension = path.posix.extname( filePath ); + + return extension != '' && !PROCESSED_MANUAL_TEST_EXTENSIONS.has( extension ); +} + +function getContentType( extension: string ): string { + switch ( extension ) { + case '.avif': + return 'image/avif'; + + case '.css': + return 'text/css; charset=utf-8'; + + case '.gif': + return 'image/gif'; + + case '.ico': + return 'image/x-icon'; + + case '.jpg': + case '.jpeg': + return 'image/jpeg'; + + case '.json': + case '.map': + return 'application/json; charset=utf-8'; + + case '.png': + return 'image/png'; + + case '.svg': + return 'image/svg+xml'; + + case '.txt': + return 'text/plain; charset=utf-8'; + + case '.webp': + return 'image/webp'; + + case '.woff': + return 'font/woff'; + + case '.woff2': + return 'font/woff2'; + + default: + return 'application/octet-stream'; + } +} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts new file mode 100644 index 000000000..1a9f912c0 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +export interface ManualData { + displayName: string; + instructionsHtml: string; + packageName: string; +} + +export interface ManualPageEntry { + displayName: string; + htmlFilePath: string; + instructionsFilePath?: string; + packageName: string; + scriptFilePath: string; + slug: string; + source: 'commercial' | 'oss'; +} + +export type ManualTestAssetExtension = 'html' | 'js' | 'md' | 'ts'; diff --git a/packages/ckeditor5-dev-manual-server/src/utils.ts b/packages/ckeditor5-dev-manual-server/src/utils.ts new file mode 100644 index 000000000..ea40522fb --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/utils.ts @@ -0,0 +1,39 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import path from 'node:path'; + +/** + * Stringifies the values of the given object. + */ +export function stringifyValues( obj: Record ): Record { + return Object.fromEntries( + Object + .entries( obj ) + .map( ( [ key, value ] ) => [ key, JSON.stringify( value ) ] ) + ); +} + +export function toPublicFilePath( filePath: string, workspaceRoot: string ): string { + const relativeFilePath = path.relative( workspaceRoot, filePath ); + + if ( !relativeFilePath.startsWith( '..' ) && !path.isAbsolute( relativeFilePath ) ) { + return toPublicSpecifier( relativeFilePath ); + } + + return `/@fs/${ toPosixPath( filePath ) }`; +} + +export function toPublicSpecifier( relativeFilePath: string ): string { + return `/${ stripLeadingSlash( toPosixPath( relativeFilePath ) ) }`; +} + +export function toPosixPath( value: string ): string { + return value.replace( /\\/g, '/' ); +} + +export function stripLeadingSlash( value: string ): string { + return value.startsWith( '/' ) ? value.slice( 1 ) : value; +} diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts new file mode 100644 index 000000000..34431b7bd --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts @@ -0,0 +1,64 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import path from 'node:path'; +import { describe, expect, test } from 'vitest'; +import { getManualStaticAssetFilePath } from '../../src/manual-test-plugin/static-assets.js'; + +describe( 'getManualStaticAssetFilePath()', () => { + const workspaceRoot = path.resolve( '/workspace' ); + + test( 'returns a workspace file path for a commercial manual test asset', () => { + expect( getManualStaticAssetFilePath( + '/packages/ckeditor5-foo/tests/manual/assets/image.png?v=1', + workspaceRoot + ) ).to.equal( path.resolve( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/assets/image.png' ) ); + } ); + + test( 'returns a workspace file path for an OSS manual test asset', () => { + expect( getManualStaticAssetFilePath( + '/external/ckeditor5/packages/ckeditor5-bar/tests/manual/fixtures/data.json', + workspaceRoot + ) ).to.equal( path.resolve( workspaceRoot, 'external/ckeditor5/packages/ckeditor5-bar/tests/manual/fixtures/data.json' ) ); + } ); + + test( 'does not return files processed by the manual server', () => { + for ( const extension of [ 'html', 'js', 'md', 'ts' ] ) { + expect( getManualStaticAssetFilePath( + `/packages/ckeditor5-foo/tests/manual/test.${ extension }`, + workspaceRoot + ) ).to.be.null; + } + } ); + + test( 'does not handle Vite module requests', () => { + expect( getManualStaticAssetFilePath( + '/packages/ckeditor5-foo/tests/manual/assets/image.png?import', + workspaceRoot + ) ).to.be.null; + } ); + + test( 'does not return assets outside supported manual test directories', () => { + expect( getManualStaticAssetFilePath( + '/packages/ckeditor5-foo/tests/automated/assets/image.png', + workspaceRoot + ) ).to.be.null; + + expect( getManualStaticAssetFilePath( + '/external/other-repository/packages/ckeditor5-bar/tests/manual/fixtures/data.json', + workspaceRoot + ) ).to.be.null; + } ); + + test( 'does not return paths with traversal segments', () => { + for ( const requestPath of [ + '/packages/ckeditor5-foo/tests/manual/../secret.png', + '/packages/ckeditor5-foo/tests/manual/%2e%2e/secret.png', + '/packages/ckeditor5-foo/tests/manual/..%5csecret.png' + ] ) { + expect( getManualStaticAssetFilePath( requestPath, workspaceRoot ) ).to.be.null; + } + } ); +} ); diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.css b/packages/ckeditor5-dev-manual-server/theme/catalog.css new file mode 100644 index 000000000..c48f06604 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.css @@ -0,0 +1,296 @@ +:root { + color-scheme: light dark; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + --bg: #fafafa; + --surface: #fff; + --surface-hover: #f5f5f5; + --border: #e5e5e5; + --border-strong: #d4d4d4; + --text: #0a0a0a; + --text-muted: #737373; + --text-subtle: #a3a3a3; + --accent: #171717; + --accent-ring: rgb(23 23 23 / 10%); + --tag-commercial-bg: #f5f3ff; + --tag-commercial-fg: #6d28d9; + --tag-oss-bg: #f0fdf4; + --tag-oss-fg: #15803d; + --radius-lg: 12px; + --radius-md: 8px; + --radius-sm: 6px; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #0a0a0a; + --surface: #141414; + --surface-hover: #1c1c1c; + --border: #262626; + --border-strong: #404040; + --text: #ededed; + --text-muted: #a3a3a3; + --text-subtle: #737373; + --accent: #fafafa; + --accent-ring: rgb(250 250 250 / 15%); + --tag-commercial-bg: rgb(124 58 237 / 15%); + --tag-commercial-fg: #c4b5fd; + --tag-oss-bg: rgb(34 197 94 / 15%); + --tag-oss-fg: #86efac; + } +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + background: var(--bg); + color: var(--text); + font-size: 14px; +} + +a { + color: inherit; +} + +.app { + max-width: 1280px; + margin: 0 auto; + padding: 0 24px 80px; +} + +.app__header { + position: sticky; + top: 0; + z-index: 10; + display: flex; + flex-direction: column; + gap: 20px; + padding: 32px 0 24px; + background: linear-gradient(var(--bg) 80%, transparent); + backdrop-filter: blur(8px); +} + +.app__titles { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 16px; +} + +.app__title { + margin: 0; + font-size: clamp(24px, 3vw, 32px); + font-weight: 700; + letter-spacing: -0.02em; +} + +.app__subtitle { + margin: 0; + font-size: 13px; + color: var(--text-muted); + font-variant-numeric: tabular-nums; +} + +.app__search { + display: flex; + align-items: center; + gap: 10px; + height: 44px; + padding: 0 14px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + transition: border-color 0.12s, box-shadow 0.12s; +} + +.app__search:focus-within { + border-color: var(--border-strong); + box-shadow: 0 0 0 4px var(--accent-ring); +} + +.app__search-icon { + flex-shrink: 0; + color: var(--text-subtle); +} + +.app__search-input { + flex: 1; + min-width: 0; + font: inherit; + font-size: 14px; + color: var(--text); + background: transparent; + border: 0; + outline: none; + padding: 0; +} + +.app__search-input::-webkit-search-cancel-button { + cursor: pointer; +} + +.app__search-input::placeholder { + color: var(--text-muted); +} + +.app__list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); + gap: 16px; + align-items: start; +} + +.app__empty { + grid-column: 1 / -1; + padding: 64px 24px; + text-align: center; + background: var(--surface); + border: 1px dashed var(--border); + border-radius: var(--radius-lg); +} + +.app__empty-title { + margin: 0 0 4px; + font-size: 15px; + font-weight: 600; +} + +.app__empty-hint { + margin: 0; + font-size: 13px; + color: var(--text-muted); +} + +.pkg { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + overflow: hidden; + transition: border-color 0.12s; +} + +.pkg:hover { + border-color: var(--border-strong); +} + +.pkg__header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + border-bottom: 1px solid var(--border); +} + +.pkg__name { + flex: 1; + min-width: 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.pkg__tag { + flex-shrink: 0; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + padding: 3px 8px; + border-radius: 999px; + line-height: 1.4; +} + +.pkg__tag--commercial { + color: var(--tag-commercial-fg); + background: var(--tag-commercial-bg); +} + +.pkg__tag--oss { + color: var(--tag-oss-fg); + background: var(--tag-oss-bg); +} + +.pkg__count { + flex-shrink: 0; + min-width: 24px; + text-align: right; + font-size: 12px; + color: var(--text-muted); + font-variant-numeric: tabular-nums; +} + +.pkg__list { + list-style: none; + margin: 0; + padding: 6px; +} + +.pkg__link { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + border-radius: var(--radius-sm); + text-decoration: none; + color: inherit; + transition: background 0.08s; +} + +.pkg__link:hover { + background: var(--surface-hover); +} + +.pkg__link:focus-visible { + outline: none; + background: var(--surface-hover); + box-shadow: inset 0 0 0 2px var(--border-strong); +} + +.pkg__test-name { + flex: 1; + min-width: 0; + font-size: 13px; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.pkg__test-slug { + flex-shrink: 0; + max-width: 50%; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 11px; + color: var(--text-subtle); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (max-width: 600px) { + .app { + padding: 0 16px 48px; + } + + .app__header { + padding: 20px 0 16px; + } + + .app__list { + grid-template-columns: 1fr; + } + + .pkg__test-slug { + display: none; + } +} diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.html b/packages/ckeditor5-dev-manual-server/theme/catalog.html new file mode 100644 index 000000000..d069bdec4 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.html @@ -0,0 +1,58 @@ + + + + + + + CKEditor 5 manual tests catalog + + + + + + + + + + + +
+ + diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.ts b/packages/ckeditor5-dev-manual-server/theme/catalog.ts new file mode 100644 index 000000000..761be7901 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.ts @@ -0,0 +1,126 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +// @ts-expect-error Virtual module provided by the manual test entries plugin. +import { manualTestEntries, type ManualTestEntry } from 'virtual:ckeditor5-manual-entries'; + +import './catalog.css'; + +/** + * Root element for the manual test index application. + */ +const appElement = document.querySelector( '#app' )!; + +renderApp( appElement, manualTestEntries ); + +/** + * Renders the index shell from the HTML template and wires filtering behavior. + */ +function renderApp( root: HTMLElement, entries: Array ): void { + const packageCount = new Set( entries.map( entry => entry.packageName ) ).size; + + root.replaceChildren( cloneTemplateElement( 'manual-index-template' ) ); + + root.querySelector( '.app__subtitle' )!.textContent = `${ entries.length } tests across ${ packageCount } packages`; + + const listElement = root.querySelector( '.app__list' )!; + const searchInput = root.querySelector( '.app__search-input' )!; + + const render = ( query: string ): void => { + listElement.replaceChildren( ...renderGroups( filterEntries( entries, query ) ) ); + }; + + searchInput.addEventListener( 'input', () => render( searchInput.value ) ); + + document.addEventListener( 'keydown', event => { + if ( event.key == '/' && document.activeElement != searchInput ) { + event.preventDefault(); + searchInput.focus(); + searchInput.select(); + } + } ); + + render( '' ); +} + +/** + * Returns entries matching the current search query. + */ +function filterEntries( entries: Array, query: string ): Array { + const normalized = query.trim().toLowerCase(); + + if ( !normalized ) { + return entries; + } + + return entries.filter( entry => + entry.packageName.toLowerCase().includes( normalized ) || + entry.slug.toLowerCase().includes( normalized ) || + entry.displayName.toLowerCase().includes( normalized ) + ); +} + +/** + * Groups visible entries by package and renders package cards. + */ +function renderGroups( entries: Array ): Array { + if ( !entries.length ) { + return [ cloneTemplateElement( 'manual-empty-template' ) ]; + } + + const groups = new Map>(); + + for ( const entry of entries ) { + const group = groups.get( entry.packageName ) || []; + + group.push( entry ); + groups.set( entry.packageName, group ); + } + + return [ ...groups.entries() ].map( ( [ packageName, packageEntries ] ) => renderPackageCard( packageName, packageEntries ) ); +} + +/** + * Renders a single package card with all tests belonging to that package. + */ +function renderPackageCard( packageName: string, entries: Array ): HTMLElement { + const source = entries[ 0 ].source; + const packageCard = cloneTemplateElement( 'manual-package-template' ); + const sourceTag = packageCard.querySelector( '.pkg__tag' )!; + const list = packageCard.querySelector( '.pkg__list' )!; + + packageCard.querySelector( '.pkg__name' )!.textContent = packageName; + sourceTag.textContent = source; + sourceTag.classList.add( `pkg__tag--${ source }` ); + packageCard.querySelector( '.pkg__count' )!.textContent = String( entries.length ); + list.replaceChildren( ...entries.map( entry => renderTestItem( entry ) ) ); + + return packageCard; +} + +/** + * Renders a single test link inside a package card. + */ +function renderTestItem( entry: ManualTestEntry ): HTMLElement { + const testItem = cloneTemplateElement( 'manual-test-template' ); + const link = testItem.querySelector( '.pkg__link' )!; + + link.href = entry.href; + testItem.querySelector( '.pkg__test-name' )!.textContent = entry.displayName; + testItem.querySelector( '.pkg__test-slug' )!.textContent = entry.slug; + + return testItem; +} + +/** + * Clones a one-root HTML template used by the index UI. + */ +function cloneTemplateElement( templateId: string ): HTMLElement { + const fragment = document + .querySelector( `#${ templateId }` )! + .content.cloneNode( true ) as DocumentFragment; + + return fragment.firstElementChild as HTMLElement; +} diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.css b/packages/ckeditor5-dev-manual-server/theme/shell.css new file mode 100644 index 000000000..b0650d021 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/shell.css @@ -0,0 +1,409 @@ +.manual-test-container { + grid-column: 1; + grid-row: 2; + order: 1; + flex: 1 1 auto; + min-width: 0; + box-sizing: border-box; + padding: 8px; +} + +body.shell-enabled { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + grid-template-rows: auto minmax(0, 1fr); + margin: 0; + min-height: 100vh; +} + +.shell { + grid-column: 1 / -1; + grid-row: 1; + position: relative; + z-index: 2147483647; + display: flex; + align-items: stretch; + box-sizing: border-box; + padding: 6px 8px; + width: 100%; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + font-size: 12px; + line-height: 1; + color: #0a0a0a; + background: rgb(255 255 255 / 85%); + backdrop-filter: saturate(180%) blur(20px); + -webkit-backdrop-filter: saturate(180%) blur(20px); + border-bottom: 1px solid rgb(0 0 0 / 8%); + box-shadow: 0 1px 2px rgb(0 0 0 / 4%); +} + +.shell__pill { + display: inline-flex; + align-items: center; + padding: 8px 14px; + border: 0; + background: transparent; + border-radius: 999px; + text-decoration: none; + color: inherit; + font: inherit; + font-weight: 500; + white-space: nowrap; + transition: background 0.1s; + cursor: pointer; +} + +.shell__pill:hover { + background: rgb(0 0 0 / 5%); +} + +.shell__pill:active { + background: rgb(0 0 0 / 10%); +} + +.shell__pill:focus-visible { + outline: 2px solid rgb(0 0 0 / 30%); + outline-offset: 1px; +} + +.shell__divider { + flex-shrink: 0; + align-self: center; + width: 1px; + height: 16px; + background: rgb(0 0 0 / 10%); + margin: 0 2px; +} + +.shell__label { + display: inline-flex; + align-items: center; + flex: 1 1 auto; + gap: 10px; + padding: 8px 14px; + overflow: hidden; + min-width: 0; +} + +.shell__label-name { + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.shell__label-package { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-weight: 400; + font-size: 11px; + opacity: 0.55; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (prefers-color-scheme: dark) { + .shell { + color: #ededed; + background: rgb(20 20 20 / 80%); + border-color: rgb(255 255 255 / 10%); + box-shadow: 0 1px 2px rgb(0 0 0 / 30%), 0 8px 24px rgb(0 0 0 / 50%); + } + + .shell__pill:hover { + background: rgb(255 255 255 / 8%); + } + + .shell__pill:active { + background: rgb(255 255 255 / 14%); + } + + .shell__pill:focus-visible { + outline-color: rgb(255 255 255 / 30%); + } + + .shell__divider { + background: rgb(255 255 255 / 12%); + } +} + +@media (max-width: 600px) { + .shell { + font-size: 11px; + } + + .shell__label-package { + display: none; + } + + .shell__label-name { + max-width: 20ch; + } +} + +.shell-instructions { + --shell-instructions-width: min(420px, 45vw); + --shell-instructions-transition-duration: 0.18s; + + grid-column: 2; + grid-row: 2; + order: 2; + align-self: start; + flex: 0 0 0; + width: 0; + height: 0; + min-height: 0; + padding: 0; + border: 0; + background: #fff; + color: #0a0a0a; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + line-height: 1.6; + overflow: hidden; + transition: + flex-basis var(--shell-instructions-transition-duration) ease-out, + width var(--shell-instructions-transition-duration) ease-out; +} + +.shell-instructions--open, +.shell-instructions--closing { + align-self: stretch; + height: auto; + border-left: 1px solid rgb(0 0 0 / 8%); + box-shadow: -8px 0 24px rgb(0 0 0 / 8%); +} + +.shell-instructions--open { + flex-basis: var(--shell-instructions-width); + width: var(--shell-instructions-width); +} + +.shell-instructions__inner { + display: flex; + flex-direction: column; + width: var(--shell-instructions-width); + height: 100%; +} + +.shell-instructions__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + padding: 20px 24px; + border-bottom: 1px solid rgb(0 0 0 / 8%); + flex-shrink: 0; +} + +.shell-instructions__titles { + min-width: 0; +} + +.shell-instructions__title { + margin: 0 0 4px; + font-size: 18px; + font-weight: 700; + letter-spacing: -0.01em; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.shell-instructions__package { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + color: #737373; +} + +.shell-instructions__close { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: 0; + border-radius: 8px; + background: transparent; + color: #737373; + cursor: pointer; + transition: background 0.1s, color 0.1s; +} + +.shell-instructions__close:hover { + background: rgb(0 0 0 / 5%); + color: #0a0a0a; +} + +.shell-instructions__close:focus-visible { + outline: 2px solid rgb(0 0 0 / 30%); + outline-offset: 1px; +} + +.shell-instructions__body { + padding: 20px 24px 28px; + font-size: 14px; + overflow-y: auto; + min-height: 0; +} + +.shell-instructions__body h1, +.shell-instructions__body h2, +.shell-instructions__body h3, +.shell-instructions__body h4, +.shell-instructions__body h5, +.shell-instructions__body h6 { + margin: 24px 0 8px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.3; +} + +.shell-instructions__body h1 { font-size: 20px; } +.shell-instructions__body h2 { font-size: 17px; } +.shell-instructions__body h3 { font-size: 15px; } +.shell-instructions__body h4, +.shell-instructions__body h5, +.shell-instructions__body h6 { font-size: 14px; } + +.shell-instructions__body > :first-child { margin-top: 0; } +.shell-instructions__body > :last-child { margin-bottom: 0; } + +.shell-instructions__body p { + margin: 8px 0; +} + +.shell-instructions__body ul, +.shell-instructions__body ol { + margin: 8px 0; + padding-left: 24px; +} + +.shell-instructions__body li { + margin: 3px 0; +} + +.shell-instructions__body code { + padding: 1px 6px; + border-radius: 4px; + background: #f0f0f0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 0.9em; +} + +.shell-instructions__body pre { + margin: 12px 0; + padding: 12px 14px; + border-radius: 8px; + background: #f5f5f5; + overflow-x: auto; + font-size: 12px; + line-height: 1.5; +} + +.shell-instructions__body pre code { + padding: 0; + background: transparent; + font-size: inherit; +} + +.shell-instructions__body a { + color: #1d4ed8; + text-decoration: underline; + text-underline-offset: 2px; +} + +.shell-instructions__body blockquote { + margin: 12px 0; + padding: 4px 14px; + border-left: 3px solid rgb(0 0 0 / 15%); + color: #525252; +} + +.shell-instructions__body table { + margin: 12px 0; + border-collapse: collapse; + font-size: 13px; +} + +.shell-instructions__body th, +.shell-instructions__body td { + padding: 6px 10px; + border: 1px solid rgb(0 0 0 / 10%); + text-align: left; +} + +.shell-instructions__body th { + background: #f5f5f5; + font-weight: 600; +} + +.shell-instructions__body hr { + margin: 16px 0; + border: 0; + border-top: 1px solid rgb(0 0 0 / 10%); +} + +@media (prefers-color-scheme: dark) { + .shell-instructions { + background: #141414; + color: #ededed; + border-left-color: rgb(255 255 255 / 10%); + box-shadow: -8px 0 24px rgb(0 0 0 / 40%); + } + + .shell-instructions__header { + border-color: rgb(255 255 255 / 10%); + } + + .shell-instructions__package { + color: #a3a3a3; + } + + .shell-instructions__close { + color: #a3a3a3; + } + + .shell-instructions__close:hover { + background: rgb(255 255 255 / 8%); + color: #ededed; + } + + .shell-instructions__body code { + background: #1e1e1e; + } + + .shell-instructions__body pre { + background: #1e1e1e; + } + + .shell-instructions__body a { + color: #93c5fd; + } + + .shell-instructions__body blockquote { + border-left-color: rgb(255 255 255 / 15%); + color: #a3a3a3; + } + + .shell-instructions__body th, + .shell-instructions__body td { + border-color: rgb(255 255 255 / 12%); + } + + .shell-instructions__body th { + background: #1e1e1e; + } + + .shell-instructions__body hr { + border-top-color: rgb(255 255 255 / 12%); + } +} + +@media (max-width: 800px) { + .shell-instructions { + --shell-instructions-width: min(360px, 70vw); + } +} diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.html b/packages/ckeditor5-dev-manual-server/theme/shell.html new file mode 100644 index 000000000..e8a8f9d57 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/shell.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts new file mode 100644 index 000000000..bdbf6a58f --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -0,0 +1,161 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import CKEditorInspector from '@ckeditor/ckeditor5-inspector'; +import type { ManualData } from '../src/manual-test-plugin/plugin.js'; + +import './shell.css'; + +declare const LICENSE_KEY: string; + +const globalTarget = window as any; + +renderManual(); +ensureManualTestContainer(); + +// In direct HTML manual tests, `id="editor"` creates `window.editor` as a named DOM property. +// Reset it so manual tests can safely reuse `window.editor` for the editor instance. +if ( globalTarget.editor instanceof Element ) { + globalTarget.editor = null; +} + +if ( !globalTarget.CKEditorInspector ) { + globalTarget.CKEditorInspector = CKEditorInspector; +} + +if ( !globalTarget.CKEDITOR_GLOBAL_LICENSE_KEY ) { + globalTarget.CKEDITOR_GLOBAL_LICENSE_KEY = LICENSE_KEY; +} + +/** + * Clones the injected Shell template, fills it with current test metadata, + * and prepends it to the page before the test content is wrapped. + */ +function renderManual(): void { + const template = document.querySelector( '#manual-shell-template' )!; + const dataElement = document.querySelector( '#manual-shell-data' )!; + const data = JSON.parse( dataElement.textContent! ) as ManualData; + const fragment = template.content.cloneNode( true ) as DocumentFragment; + + for ( const element of fragment.querySelectorAll( '[data-manual-shell-field="displayName"]' ) ) { + element.textContent = data.displayName; + } + + for ( const element of fragment.querySelectorAll( '[data-manual-shell-field="packageName"]' ) ) { + element.textContent = data.packageName; + } + + if ( data.instructionsHtml ) { + fragment.querySelector( '.shell-instructions__body' )!.innerHTML = data.instructionsHtml; + } else { + for ( const element of fragment.querySelectorAll( '[data-manual-shell-section="instructions"]' ) ) { + element.remove(); + } + } + + template.remove(); + dataElement.remove(); + document.body.prepend( fragment ); +} + +/** + * Wraps manual test content in a stable container while keeping Shell UI outside it. + */ +function ensureManualTestContainer(): void { + document.body.classList.add( 'shell-enabled' ); + + if ( document.querySelector( '.shell-instructions' ) ) { + document.body.classList.add( 'shell-has-instructions' ); + } + + const container = document.createElement( 'div' ); + container.className = 'manual-test-container'; + + for ( const node of Array.from( document.body.childNodes ) ) { + if ( + node instanceof Element && + ( node.classList.contains( 'shell' ) || node.classList.contains( 'shell-instructions' ) ) + ) { + continue; + } + + container.appendChild( node ); + } + + document.body.appendChild( container ); +} + +/** + * Opens or closes the instructions panel and synchronizes related accessibility state. + */ +function toggleInstructions( isOpen: boolean ): void { + const panel = document.querySelector( '.shell-instructions' ); + const trigger = document.querySelector( '[data-shell-action="show-instructions"]' ); + + if ( !panel ) { + return; + } + + panel.classList.remove( 'shell-instructions--closing' ); + + if ( !isOpen && panel.classList.contains( 'shell-instructions--open' ) ) { + panel.classList.add( 'shell-instructions--closing' ); + } + + panel.classList.toggle( 'shell-instructions--open', isOpen ); + panel.setAttribute( 'aria-hidden', String( !isOpen ) ); + panel.toggleAttribute( 'inert', !isOpen ); + trigger!.setAttribute( 'aria-expanded', String( isOpen ) ); +} + +/** + * Handles clicks on Shell action buttons. + */ +document.addEventListener( 'click', event => { + const target = event.target; + + if ( !( target instanceof Element ) ) { + return; + } + + const trigger = target.closest( '[data-shell-action]' ); + + if ( !trigger ) { + return; + } + + if ( trigger.dataset.shellAction == 'show-instructions' ) { + const panel = document.querySelector( '.shell-instructions' )!; + + toggleInstructions( !panel.classList.contains( 'shell-instructions--open' ) ); + } else if ( trigger.dataset.shellAction == 'close-instructions' ) { + toggleInstructions( false ); + } +} ); + +/** + * Cleans up the closing state after the instructions panel transition finishes. + */ +document.addEventListener( 'transitionend', event => { + const target = event.target; + + if ( + event.propertyName == 'width' && + target instanceof Element && + target.classList.contains( 'shell-instructions' ) && + !target.classList.contains( 'shell-instructions--open' ) + ) { + target.classList.remove( 'shell-instructions--closing' ); + } +} ); + +/** + * Allows closing the instructions panel with Escape. + */ +document.addEventListener( 'keydown', event => { + if ( event.key == 'Escape' ) { + toggleInstructions( false ); + } +} ); diff --git a/packages/ckeditor5-dev-manual-server/vitest.config.ts b/packages/ckeditor5-dev-manual-server/vitest.config.ts new file mode 100644 index 000000000..c3e9f1592 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/vitest.config.ts @@ -0,0 +1,27 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { defineConfig } from 'vitest/config'; + +export default defineConfig( { + test: { + testTimeout: 10000, + restoreMocks: true, + clearMocks: true, + mockReset: true, + unstubEnvs: true, + unstubGlobals: true, + include: [ + 'tests/**/*.ts' + ], + coverage: { + provider: 'v8', + include: [ + 'src/**' + ], + reporter: [ 'text', 'json', 'html', 'lcov' ] + } + } +} ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7b25c776..ecb1d0146 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,10 +110,10 @@ importers: version: 1.15.24 cssnano: specifier: ^7.1.4 - version: 7.1.4(postcss@8.5.10) + version: 7.1.4(postcss@8.5.15) cssnano-preset-lite: specifier: ^4.0.4 - version: 4.0.4(postcss@8.5.10) + version: 4.0.4(postcss@8.5.15) es-toolkit: specifier: ^1.45.1 version: 1.45.1 @@ -156,7 +156,7 @@ importers: version: 22.19.17 '@vitest/coverage-v8': specifier: ^4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@22.19.17)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@22.19.17)(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) rolldown: specifier: ^1.0.0 version: 1.0.0 @@ -165,7 +165,7 @@ importers: version: 4.41.0 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@22.19.17)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@22.19.17)(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-bump-year: dependencies: @@ -205,13 +205,13 @@ importers: version: 7.7.1 '@vitest/coverage-v8': specifier: ^4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) rolldown: specifier: ^1.0.0 version: 1.0.0 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-ci: dependencies: @@ -227,7 +227,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-dependency-checker: dependencies: @@ -255,7 +255,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-docs: dependencies: @@ -280,7 +280,7 @@ importers: version: 5.0.3 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-license-checker: dependencies: @@ -293,13 +293,59 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: ^4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) rolldown: specifier: ^1.0.0 version: 1.0.0 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) + + packages/ckeditor5-dev-manual-server: + dependencies: + '@ckeditor/ckeditor5-inspector': + specifier: ^5.0.0 + version: 5.0.0 + '@parse5/tools': + specifier: ^0.7.0 + version: 0.7.0(parse5@8.0.1) + hast-util-to-html: + specifier: ^9.0.5 + version: 9.0.5 + parse5: + specifier: ^8.0.1 + version: 8.0.1 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-rehype: + specifier: ^11.1.2 + version: 11.1.2 + unified: + specifier: ^11.0.5 + version: 11.0.5 + vite: + specifier: ^8.0.13 + version: 8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3) + vite-svg-loader: + specifier: ^5.1.1 + version: 5.1.1(vue@3.5.34(typescript@5.5.4)) + devDependencies: + '@vitest/coverage-v8': + specifier: ^4.1.2 + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) + rolldown: + specifier: ^1.0.0 + version: 1.0.0 + upath: + specifier: ^2.0.1 + version: 2.0.1 + vitest: + specifier: ^4.1.2 + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-release-tools: dependencies: @@ -342,7 +388,7 @@ importers: version: 5.5.0 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-stale-bot: dependencies: @@ -370,7 +416,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-tests: dependencies: @@ -466,7 +512,7 @@ importers: version: 0.4.0 karma-webpack: specifier: ^5.0.1 - version: 5.0.1(webpack@5.105.4) + version: 5.0.1(webpack@5.105.4(esbuild@0.27.7)) minimatch: specifier: ^10.2.5 version: 10.2.5 @@ -502,14 +548,14 @@ importers: version: 2.0.1 webpack: specifier: ^5.105.4 - version: 5.105.4 + version: 5.105.4(esbuild@0.27.7) devDependencies: jest-extended: specifier: ^5.0.3 version: 5.0.3 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-translations: dependencies: @@ -546,7 +592,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-utils: dependencies: @@ -555,7 +601,7 @@ importers: version: 2.0.41 babel-loader: specifier: ^10.1.1 - version: 10.1.1(@babel/core@7.29.0)(webpack@5.105.4) + version: 10.1.1(@babel/core@7.29.0)(webpack@5.105.4(esbuild@0.27.7)) cli-cursor: specifier: ^5.0.0 version: 5.0.0 @@ -564,10 +610,10 @@ importers: version: 3.4.0 css-loader: specifier: ^7.1.4 - version: 7.1.4(webpack@5.105.4) + version: 7.1.4(webpack@5.105.4(esbuild@0.27.7)) esbuild-loader: specifier: ^4.4.3 - version: 4.4.3(webpack@5.105.4) + version: 4.4.3(webpack@5.105.4(esbuild@0.27.7)) glob: specifier: ^13.0.6 version: 13.0.6 @@ -579,13 +625,13 @@ importers: version: 1.32.0 mini-css-extract-plugin: specifier: ^2.10.2 - version: 2.10.2(webpack@5.105.4) + version: 2.10.2(webpack@5.105.4(esbuild@0.27.7)) pacote: specifier: ^21.5.0 version: 21.5.0 raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.105.4) + version: 4.0.2(webpack@5.105.4(esbuild@0.27.7)) shelljs: specifier: ^0.10.0 version: 0.10.0 @@ -594,7 +640,7 @@ importers: version: 3.36.0 style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.105.4) + version: 4.0.0(webpack@5.105.4(esbuild@0.27.7)) through2: specifier: ^4.0.2 version: 4.0.2 @@ -607,7 +653,7 @@ importers: version: 11.1.8 '@vitest/coverage-v8': specifier: ^4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) jest-extended: specifier: ^5.0.3 version: 5.0.3 @@ -616,7 +662,7 @@ importers: version: 1.0.0 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/ckeditor5-dev-web-crawler: dependencies: @@ -629,7 +675,7 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: ^4.1.2 - version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))) + version: 4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))) rolldown: specifier: ^1.0.0 version: 1.0.0 @@ -638,7 +684,7 @@ importers: version: 2.0.1 vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages/typedoc-plugins: dependencies: @@ -660,7 +706,7 @@ importers: version: 0.7.3(typedoc@0.28.18(typescript@5.5.4)) vitest: specifier: ^4.1.2 - version: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) packages: @@ -723,6 +769,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -739,6 +790,9 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@ckeditor/ckeditor5-inspector@5.0.0': + resolution: {integrity: sha512-WVCa1mtePJkLtI81Rn2E6orV0B2Az/+O7f+corJzYapoH5koVEe9TcVyoKRquKUWeBXMG+D1m72vmR2kuoLNhA==} + '@ckeditor/ckeditor5-inspector@5.0.1': resolution: {integrity: sha512-O6iuP8EtDZvkkKpMJjJTY+ZZRQA/ZEpguaOmdg4J+mEXHZoI7fshNv8LA/g6cUoPO4W3eRfBZ1VPmrGWjZWdfg==} bundledDependencies: @@ -1404,6 +1458,9 @@ packages: '@oxc-project/types@0.129.0': resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.72.3': resolution: {integrity: sha512-CfAC4wrmMkUoISpQkFAIfMVvlPfQV3xg7ZlcqPXPOIMQhdKIId44G8W0mCPgtpWdFFAyJ+SFtiM+9vbyCkoVng==} @@ -1534,6 +1591,11 @@ packages: cpu: [x64] os: [win32] + '@parse5/tools@0.7.0': + resolution: {integrity: sha512-JDvrGhc8kYBq7/SM4obJkpgwWo6pRjF/fo9CCaiJyVOkDf203Ciq2UF6TjzCFXKs7Q/zS2sS4deyBx0XzRvh9Q==} + peerDependencies: + parse5: 7.x || 8.x + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1549,30 +1611,60 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0': resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0': resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0': resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0': resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1580,6 +1672,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0': resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1587,6 +1686,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0': resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1594,6 +1700,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0': resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1601,6 +1714,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0': resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1608,6 +1728,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0': resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1615,29 +1742,59 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0': resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0': resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0': resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0': resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0': resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} @@ -2221,6 +2378,9 @@ packages: resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + '@vitest/coverage-v8@4.1.2': resolution: {integrity: sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==} peerDependencies: @@ -2262,18 +2422,47 @@ packages: '@vue/compiler-core@3.5.32': resolution: {integrity: sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==} + '@vue/compiler-core@3.5.34': + resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} + '@vue/compiler-dom@3.5.32': resolution: {integrity: sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==} + '@vue/compiler-dom@3.5.34': + resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==} + '@vue/compiler-sfc@3.5.32': resolution: {integrity: sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==} + '@vue/compiler-sfc@3.5.34': + resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==} + '@vue/compiler-ssr@3.5.32': resolution: {integrity: sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==} + '@vue/compiler-ssr@3.5.34': + resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==} + + '@vue/reactivity@3.5.34': + resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} + + '@vue/runtime-core@3.5.34': + resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} + + '@vue/runtime-dom@3.5.34': + resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} + + '@vue/server-renderer@3.5.34': + resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} + peerDependencies: + vue: 3.5.34 + '@vue/shared@3.5.32': resolution: {integrity: sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==} + '@vue/shared@3.5.34': + resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -2477,6 +2666,9 @@ packages: resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} engines: {node: '>=12'} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2661,6 +2853,12 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} @@ -2746,6 +2944,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -2765,6 +2966,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + comment-parser@1.4.1: resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} engines: {node: '>= 12.0.0'} @@ -2846,6 +3051,10 @@ packages: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -2887,6 +3096,9 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + custom-event@1.0.1: resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==} @@ -3105,6 +3317,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -3562,6 +3778,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -3577,6 +3799,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -3749,6 +3974,10 @@ packages: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -4228,6 +4457,9 @@ packages: mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -4237,6 +4469,9 @@ packages: mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -4472,6 +4707,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4663,6 +4903,9 @@ packages: parse5@1.0.0: resolution: {integrity: sha512-NNdCAcwvTGOapmA/TF14m4HE4IMWiZBkCYKwaIyT5aq3COGzxdTn/u2Cvr/8lOYkwpaXvXEVx8RATtfzqOSYdw==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -4930,6 +5173,10 @@ packages: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4954,6 +5201,9 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -5040,6 +5290,18 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5108,6 +5370,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-svg-import@3.0.0: resolution: {integrity: sha512-5fUESTM5hdqJojrwO53JQUO7NespLNx4iLeMsToQfuaGGqGT5sz85Ns5gCDNxLO6yBPbn7p0A/6YA+Rq3clg4Q==} engines: {node: '>=18.0.0'} @@ -5306,6 +5573,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} @@ -5373,6 +5643,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@4.0.0: resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} engines: {node: '>=4'} @@ -5425,6 +5698,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svgo@3.3.3: + resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==} + engines: {node: '>=14.0.0'} + hasBin: true + svgo@4.0.1: resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} engines: {node: '>=16'} @@ -5489,6 +5767,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -5505,6 +5787,12 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -5592,9 +5880,15 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -5659,6 +5953,17 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-svg-loader@5.1.1: + resolution: {integrity: sha512-RPzcXA/EpKJA0585x58DBgs7my2VfeJ+j2j1EoHY4Zh82Y7hV4cR1fElgy2aZi85+QSrcLLoTStQ5uZjD68u+Q==} + peerDependencies: + vue: '>=3.2.13' + vite@7.3.2: resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5699,6 +6004,49 @@ packages: yaml: optional: true + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@4.1.2: resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -5738,6 +6086,14 @@ packages: resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==} engines: {node: '>=0.10.0'} + vue@3.5.34: + resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + watchpack@2.5.1: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} @@ -5968,6 +6324,10 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -5993,6 +6353,8 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@ckeditor/ckeditor5-inspector@5.0.0': {} + '@ckeditor/ckeditor5-inspector@5.0.1': {} '@colordx/core@5.0.3': {} @@ -6586,6 +6948,8 @@ snapshots: '@oxc-project/types@0.129.0': {} + '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.72.3': {} '@oxc-transform/binding-android-arm-eabi@0.112.0': @@ -6653,6 +7017,10 @@ snapshots: '@oxc-transform/binding-win32-x64-msvc@0.112.0': optional: true + '@parse5/tools@0.7.0(parse5@8.0.1)': + dependencies: + parse5: 8.0.1 + '@pkgjs/parseargs@0.11.0': optional: true @@ -6674,39 +7042,75 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0': optional: true + '@rolldown/binding-android-arm64@1.0.2': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0': optional: true + '@rolldown/binding-darwin-arm64@1.0.2': + optional: true + '@rolldown/binding-darwin-x64@1.0.0': optional: true + '@rolldown/binding-darwin-x64@1.0.2': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0': optional: true + '@rolldown/binding-freebsd-x64@1.0.2': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.2': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.2': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0': optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.2': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.2': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0': optional: true + '@rolldown/binding-linux-x64-musl@1.0.2': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0': optional: true + '@rolldown/binding-openharmony-arm64@1.0.2': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0': dependencies: '@emnapi/core': 1.10.0 @@ -6714,12 +7118,25 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@rolldown/binding-wasm32-wasi@1.0.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.2': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.2': + optional: true + '@rolldown/pluginutils@1.0.0': {} '@rollup/plugin-commonjs@28.0.9(rollup@4.60.1)': @@ -7283,7 +7700,9 @@ snapshots: '@typescript-eslint/types': 8.58.0 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@22.19.17)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)))': + '@ungap/structured-clone@1.3.1': {} + + '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@22.19.17)(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.2 @@ -7295,7 +7714,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.2(@types/node@22.19.17)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 4.1.2(@types/node@22.19.17)(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)))': dependencies: @@ -7311,6 +7730,20 @@ snapshots: tinyrainbow: 3.1.0 vitest: 4.1.2(@types/node@25.5.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.2 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: 4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 @@ -7320,21 +7753,29 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3) - '@vitest/mocker@4.1.2(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3) + vite: 8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3) + + '@vitest/mocker@4.1.2(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3) '@vitest/pretty-format@4.1.2': dependencies: @@ -7368,11 +7809,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.34': + dependencies: + '@babel/parser': 7.29.3 + '@vue/shared': 3.5.34 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.32': dependencies: '@vue/compiler-core': 3.5.32 '@vue/shared': 3.5.32 + '@vue/compiler-dom@3.5.34': + dependencies: + '@vue/compiler-core': 3.5.34 + '@vue/shared': 3.5.34 + '@vue/compiler-sfc@3.5.32': dependencies: '@babel/parser': 7.29.2 @@ -7385,13 +7839,54 @@ snapshots: postcss: 8.5.10 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.34': + dependencies: + '@babel/parser': 7.29.3 + '@vue/compiler-core': 3.5.34 + '@vue/compiler-dom': 3.5.34 + '@vue/compiler-ssr': 3.5.34 + '@vue/shared': 3.5.34 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.32': dependencies: '@vue/compiler-dom': 3.5.32 '@vue/shared': 3.5.32 + '@vue/compiler-ssr@3.5.34': + dependencies: + '@vue/compiler-dom': 3.5.34 + '@vue/shared': 3.5.34 + + '@vue/reactivity@3.5.34': + dependencies: + '@vue/shared': 3.5.34 + + '@vue/runtime-core@3.5.34': + dependencies: + '@vue/reactivity': 3.5.34 + '@vue/shared': 3.5.34 + + '@vue/runtime-dom@3.5.34': + dependencies: + '@vue/reactivity': 3.5.34 + '@vue/runtime-core': 3.5.34 + '@vue/shared': 3.5.34 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.5.4))': + dependencies: + '@vue/compiler-ssr': 3.5.34 + '@vue/shared': 3.5.34 + vue: 3.5.34(typescript@5.5.4) + '@vue/shared@3.5.32': {} + '@vue/shared@3.5.34': {} + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -7577,12 +8072,12 @@ snapshots: b4a@1.8.0: {} - babel-loader@10.1.1(@babel/core@7.29.0)(webpack@5.105.4): + babel-loader@10.1.1(@babel/core@7.29.0)(webpack@5.105.4(esbuild@0.27.7)): dependencies: '@babel/core': 7.29.0 find-up: 5.0.0 optionalDependencies: - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) babel-plugin-istanbul@7.0.1: dependencies: @@ -7594,6 +8089,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -7774,6 +8271,10 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + character-entities@2.0.2: {} chardet@2.1.1: {} @@ -7863,6 +8364,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} commander@11.1.0: {} @@ -7873,6 +8376,8 @@ snapshots: commander@2.20.3: {} + commander@7.2.0: {} + comment-parser@1.4.1: {} commondir@1.0.1: {} @@ -7935,11 +8440,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@7.3.1(postcss@8.5.10): + css-declaration-sorter@7.3.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - css-loader@7.1.4(webpack@5.105.4): + css-loader@7.1.4(webpack@5.105.4(esbuild@0.27.7)): dependencies: icss-utils: 5.1.0(postcss@8.5.10) postcss: 8.5.10 @@ -7950,7 +8455,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) css-select@5.2.2: dependencies: @@ -7965,6 +8470,11 @@ snapshots: mdn-data: 2.0.28 source-map-js: 1.2.1 + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -7974,62 +8484,64 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@7.0.12(postcss@8.5.10): + cssnano-preset-default@7.0.12(postcss@8.5.15): dependencies: browserslist: 4.28.2 - css-declaration-sorter: 7.3.1(postcss@8.5.10) - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 - postcss-calc: 10.1.1(postcss@8.5.10) - postcss-colormin: 7.0.7(postcss@8.5.10) - postcss-convert-values: 7.0.9(postcss@8.5.10) - postcss-discard-comments: 7.0.6(postcss@8.5.10) - postcss-discard-duplicates: 7.0.2(postcss@8.5.10) - postcss-discard-empty: 7.0.1(postcss@8.5.10) - postcss-discard-overridden: 7.0.1(postcss@8.5.10) - postcss-merge-longhand: 7.0.5(postcss@8.5.10) - postcss-merge-rules: 7.0.8(postcss@8.5.10) - postcss-minify-font-values: 7.0.1(postcss@8.5.10) - postcss-minify-gradients: 7.0.2(postcss@8.5.10) - postcss-minify-params: 7.0.6(postcss@8.5.10) - postcss-minify-selectors: 7.0.6(postcss@8.5.10) - postcss-normalize-charset: 7.0.1(postcss@8.5.10) - postcss-normalize-display-values: 7.0.1(postcss@8.5.10) - postcss-normalize-positions: 7.0.1(postcss@8.5.10) - postcss-normalize-repeat-style: 7.0.1(postcss@8.5.10) - postcss-normalize-string: 7.0.1(postcss@8.5.10) - postcss-normalize-timing-functions: 7.0.1(postcss@8.5.10) - postcss-normalize-unicode: 7.0.6(postcss@8.5.10) - postcss-normalize-url: 7.0.1(postcss@8.5.10) - postcss-normalize-whitespace: 7.0.1(postcss@8.5.10) - postcss-ordered-values: 7.0.2(postcss@8.5.10) - postcss-reduce-initial: 7.0.6(postcss@8.5.10) - postcss-reduce-transforms: 7.0.1(postcss@8.5.10) - postcss-svgo: 7.1.1(postcss@8.5.10) - postcss-unique-selectors: 7.0.5(postcss@8.5.10) - - cssnano-preset-lite@4.0.4(postcss@8.5.10): - dependencies: - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 - postcss-discard-comments: 7.0.6(postcss@8.5.10) - postcss-discard-empty: 7.0.1(postcss@8.5.10) - postcss-normalize-whitespace: 7.0.1(postcss@8.5.10) - - cssnano-utils@5.0.1(postcss@8.5.10): - dependencies: - postcss: 8.5.10 - - cssnano@7.1.4(postcss@8.5.10): - dependencies: - cssnano-preset-default: 7.0.12(postcss@8.5.10) + css-declaration-sorter: 7.3.1(postcss@8.5.15) + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-calc: 10.1.1(postcss@8.5.15) + postcss-colormin: 7.0.7(postcss@8.5.15) + postcss-convert-values: 7.0.9(postcss@8.5.15) + postcss-discard-comments: 7.0.6(postcss@8.5.15) + postcss-discard-duplicates: 7.0.2(postcss@8.5.15) + postcss-discard-empty: 7.0.1(postcss@8.5.15) + postcss-discard-overridden: 7.0.1(postcss@8.5.15) + postcss-merge-longhand: 7.0.5(postcss@8.5.15) + postcss-merge-rules: 7.0.8(postcss@8.5.15) + postcss-minify-font-values: 7.0.1(postcss@8.5.15) + postcss-minify-gradients: 7.0.2(postcss@8.5.15) + postcss-minify-params: 7.0.6(postcss@8.5.15) + postcss-minify-selectors: 7.0.6(postcss@8.5.15) + postcss-normalize-charset: 7.0.1(postcss@8.5.15) + postcss-normalize-display-values: 7.0.1(postcss@8.5.15) + postcss-normalize-positions: 7.0.1(postcss@8.5.15) + postcss-normalize-repeat-style: 7.0.1(postcss@8.5.15) + postcss-normalize-string: 7.0.1(postcss@8.5.15) + postcss-normalize-timing-functions: 7.0.1(postcss@8.5.15) + postcss-normalize-unicode: 7.0.6(postcss@8.5.15) + postcss-normalize-url: 7.0.1(postcss@8.5.15) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.15) + postcss-ordered-values: 7.0.2(postcss@8.5.15) + postcss-reduce-initial: 7.0.6(postcss@8.5.15) + postcss-reduce-transforms: 7.0.1(postcss@8.5.15) + postcss-svgo: 7.1.1(postcss@8.5.15) + postcss-unique-selectors: 7.0.5(postcss@8.5.15) + + cssnano-preset-lite@4.0.4(postcss@8.5.15): + dependencies: + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-discard-comments: 7.0.6(postcss@8.5.15) + postcss-discard-empty: 7.0.1(postcss@8.5.15) + postcss-normalize-whitespace: 7.0.1(postcss@8.5.15) + + cssnano-utils@5.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + cssnano@7.1.4(postcss@8.5.15): + dependencies: + cssnano-preset-default: 7.0.12(postcss@8.5.15) lilconfig: 3.1.3 - postcss: 8.5.10 + postcss: 8.5.15 csso@5.0.5: dependencies: css-tree: 2.2.1 + csstype@3.2.3: {} + custom-event@1.0.1: {} data-uri-to-buffer@6.0.2: {} @@ -8263,6 +8775,8 @@ snapshots: entities@7.0.1: {} + entities@8.0.0: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -8292,12 +8806,12 @@ snapshots: es6-error@4.1.1: {} - esbuild-loader@4.4.3(webpack@5.105.4): + esbuild-loader@4.4.3(webpack@5.105.4(esbuild@0.27.7)): dependencies: esbuild: 0.27.7 get-tsconfig: 4.13.7 loader-utils: 2.0.4 - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) webpack-sources: 3.3.4 esbuild@0.27.7: @@ -8794,6 +9308,24 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + he@1.2.0: {} homedir-polyfill@1.0.3: @@ -8806,6 +9338,8 @@ snapshots: html-escaper@2.0.2: {} + html-void-elements@3.0.0: {} + http-cache-semantics@4.2.0: {} http-errors@2.0.1: @@ -8946,6 +9480,8 @@ snapshots: is-plain-obj@2.1.0: {} + is-plain-obj@4.1.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 @@ -9162,11 +9698,11 @@ snapshots: dependencies: graceful-fs: 4.2.11 - karma-webpack@5.0.1(webpack@5.105.4): + karma-webpack@5.0.1(webpack@5.105.4(esbuild@0.27.7)): dependencies: glob: 7.2.3 minimatch: 9.0.9 - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) webpack-merge: 4.2.2 karma@6.4.4: @@ -9535,6 +10071,18 @@ snapshots: '@types/mdast': 4.0.4 unist-util-is: 6.0.1 + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 @@ -9553,6 +10101,8 @@ snapshots: mdn-data@2.0.28: {} + mdn-data@2.0.30: {} + mdn-data@2.27.1: {} mdurl@1.0.1: {} @@ -9780,11 +10330,11 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.10.2(webpack@5.105.4): + mini-css-extract-plugin@2.10.2(webpack@5.105.4(esbuild@0.27.7)): dependencies: schema-utils: 4.3.3 tapable: 2.3.2 - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) minimatch@10.2.5: dependencies: @@ -9897,6 +10447,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.12: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -10178,6 +10730,10 @@ snapshots: parse5@1.0.0: {} + parse5@8.0.1: + dependencies: + entities: 8.0.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -10232,80 +10788,80 @@ snapshots: pofile@1.1.4: {} - postcss-calc@10.1.1(postcss@8.5.10): + postcss-calc@10.1.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-colormin@7.0.7(postcss@8.5.10): + postcss-colormin@7.0.7(postcss@8.5.15): dependencies: '@colordx/core': 5.0.3 browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-convert-values@7.0.9(postcss@8.5.10): + postcss-convert-values@7.0.9(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-discard-comments@7.0.6(postcss@8.5.10): + postcss-discard-comments@7.0.6(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-discard-duplicates@7.0.2(postcss@8.5.10): + postcss-discard-duplicates@7.0.2(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - postcss-discard-empty@7.0.1(postcss@8.5.10): + postcss-discard-empty@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - postcss-discard-overridden@7.0.1(postcss@8.5.10): + postcss-discard-overridden@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - postcss-merge-longhand@7.0.5(postcss@8.5.10): + postcss-merge-longhand@7.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - stylehacks: 7.0.8(postcss@8.5.10) + stylehacks: 7.0.8(postcss@8.5.15) - postcss-merge-rules@7.0.8(postcss@8.5.10): + postcss-merge-rules@7.0.8(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-minify-font-values@7.0.1(postcss@8.5.10): + postcss-minify-font-values@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-gradients@7.0.2(postcss@8.5.10): + postcss-minify-gradients@7.0.2(postcss@8.5.15): dependencies: '@colordx/core': 5.0.3 - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-params@7.0.6(postcss@8.5.10): + postcss-minify-params@7.0.6(postcss@8.5.15): dependencies: browserslist: 4.28.2 - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-minify-selectors@7.0.6(postcss@8.5.10): + postcss-minify-selectors@7.0.6(postcss@8.5.15): dependencies: cssesc: 3.0.0 - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-modules-extract-imports@3.1.0(postcss@8.5.10): @@ -10329,66 +10885,66 @@ snapshots: icss-utils: 5.1.0(postcss@8.5.10) postcss: 8.5.10 - postcss-normalize-charset@7.0.1(postcss@8.5.10): + postcss-normalize-charset@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - postcss-normalize-display-values@7.0.1(postcss@8.5.10): + postcss-normalize-display-values@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-positions@7.0.1(postcss@8.5.10): + postcss-normalize-positions@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@7.0.1(postcss@8.5.10): + postcss-normalize-repeat-style@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-string@7.0.1(postcss@8.5.10): + postcss-normalize-string@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@7.0.1(postcss@8.5.10): + postcss-normalize-timing-functions@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@7.0.6(postcss@8.5.10): + postcss-normalize-unicode@7.0.6(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-url@7.0.1(postcss@8.5.10): + postcss-normalize-url@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@7.0.1(postcss@8.5.10): + postcss-normalize-whitespace@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-ordered-values@7.0.2(postcss@8.5.10): + postcss-ordered-values@7.0.2(postcss@8.5.15): dependencies: - cssnano-utils: 5.0.1(postcss@8.5.10) - postcss: 8.5.10 + cssnano-utils: 5.0.1(postcss@8.5.15) + postcss: 8.5.15 postcss-value-parser: 4.2.0 - postcss-reduce-initial@7.0.6(postcss@8.5.10): + postcss-reduce-initial@7.0.6(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 - postcss: 8.5.10 + postcss: 8.5.15 - postcss-reduce-transforms@7.0.1(postcss@8.5.10): + postcss-reduce-transforms@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 postcss-selector-parser@7.1.1: @@ -10396,15 +10952,15 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@7.1.1(postcss@8.5.10): + postcss-svgo@7.1.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-value-parser: 4.2.0 svgo: 4.0.1 - postcss-unique-selectors@7.0.5(postcss@8.5.10): + postcss-unique-selectors@7.0.5(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser@4.2.0: {} @@ -10415,6 +10971,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} presentable-error@0.0.1: {} @@ -10431,6 +10993,8 @@ snapshots: progress@2.0.3: {} + property-information@7.1.0: {} + proto-list@1.2.4: {} proxy-agent@6.5.0: @@ -10524,11 +11088,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.105.4): + raw-loader@4.0.2(webpack@5.105.4(esbuild@0.27.7)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) react-is@18.3.1: {} @@ -10546,6 +11110,40 @@ snapshots: regexp-tree@0.1.27: {} + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -10621,6 +11219,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0 '@rolldown/binding-win32-x64-msvc': 1.0.0 + rolldown@1.0.2: + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + rollup-plugin-svg-import@3.0.0(rollup@4.60.1): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) @@ -10877,6 +11496,8 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spdx-exceptions@2.5.0: {} spdx-expression-parse@4.0.0: @@ -10950,6 +11571,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@4.0.0: dependencies: ansi-regex: 3.0.1 @@ -10968,14 +11594,14 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@4.0.0(webpack@5.105.4): + style-loader@4.0.0(webpack@5.105.4(esbuild@0.27.7)): dependencies: - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) - stylehacks@7.0.8(postcss@8.5.10): + stylehacks@7.0.8(postcss@8.5.15): dependencies: browserslist: 4.28.2 - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 supports-color@5.5.0: @@ -10992,6 +11618,16 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svgo@3.3.3: + dependencies: + commander: 7.2.0 + css-select: 5.2.2 + css-tree: 2.3.1 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.6.0 + svgo@4.0.1: dependencies: commander: 11.1.0 @@ -11042,13 +11678,15 @@ snapshots: - bare-abort-controller - react-native-b4a - terser-webpack-plugin@5.4.0(webpack@5.105.4): + terser-webpack-plugin@5.4.0(esbuild@0.27.7)(webpack@5.105.4(esbuild@0.27.7)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.1 - webpack: 5.105.4 + webpack: 5.105.4(esbuild@0.27.7) + optionalDependencies: + esbuild: 0.27.7 terser@5.46.1: dependencies: @@ -11082,6 +11720,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyrainbow@3.1.0: {} tmp@0.2.5: {} @@ -11092,6 +11735,10 @@ snapshots: toidentifier@1.0.1: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@5.5.4): dependencies: typescript: 5.5.4 @@ -11167,10 +11814,24 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 @@ -11225,7 +11886,25 @@ snapshots: vary@1.1.2: {} - vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3): + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-svg-loader@5.1.1(vue@3.5.34(typescript@5.5.4)): + dependencies: + debug: 4.4.3(supports-color@8.1.1) + svgo: 3.3.3 + vue: 3.5.34(typescript@5.5.4) + transitivePeerDependencies: + - supports-color + + vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3): dependencies: esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) @@ -11234,33 +11913,47 @@ snapshots: rollup: 4.60.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.19.17 + '@types/node': 25.5.2 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 terser: 5.46.1 yaml: 2.8.3 - vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3): + vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3): dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 22.19.17 esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.46.1 + yaml: 2.8.3 + + vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.10 - rollup: 4.60.1 - tinyglobby: 0.2.15 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.5.2 + esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.32.0 terser: 5.46.1 yaml: 2.8.3 - vitest@4.1.2(@types/node@22.19.17)(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)): + vitest@4.1.2(@types/node@22.19.17)(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -11277,7 +11970,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 7.3.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(yaml@2.8.3) + vite: 8.0.14(@types/node@22.19.17)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.17 @@ -11311,8 +12004,45 @@ snapshots: transitivePeerDependencies: - msw + vitest@4.1.2(@types/node@25.5.2)(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(vite@8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 8.0.14(@types/node@25.5.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.1)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.2 + transitivePeerDependencies: + - msw + void-elements@2.0.1: {} + vue@3.5.34(typescript@5.5.4): + dependencies: + '@vue/compiler-dom': 3.5.34 + '@vue/compiler-sfc': 3.5.34 + '@vue/runtime-dom': 3.5.34 + '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@5.5.4)) + '@vue/shared': 3.5.34 + optionalDependencies: + typescript: 5.5.4 + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 @@ -11328,7 +12058,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.105.4: + webpack@5.105.4(esbuild@0.27.7): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -11352,7 +12082,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(webpack@5.105.4) + terser-webpack-plugin: 5.4.0(esbuild@0.27.7)(webpack@5.105.4(esbuild@0.27.7)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: From 638bdb3ad85c2269fb706080574c3d0396a6143a Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 27 May 2026 12:34:34 +0200 Subject: [PATCH 02/17] Prevent iframe reloads in manual tests. --- .../src/manual-test-plugin/shell-html.ts | 12 ++++- .../tests/manual-test-plugin/shell-html.ts | 48 +++++++++++++++++++ .../theme/shell.ts | 7 ++- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts index 6d98b5f42..02364c81e 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts @@ -8,6 +8,7 @@ import { readFileSync } from 'node:fs'; import { parse, serialize } from 'parse5'; import { appendChild, + createElement, getAttribute, getTextContent, hasAttribute, @@ -67,7 +68,8 @@ export function createManualShellHtml( { removeAttribute( testScriptElement, 'data-manual-test-script' ); setTextContent( shellDataElement, stringifyHtmlScriptData( createManualData( entry, workspaceRoot ) ) ); appendChildren( shellHead, manualHead.childNodes.filter( shouldMoveManualHeadNode ) ); - appendChildren( shellBody, [ ...manualBody.childNodes ] ); + // Wrap before the browser parses the page so iframe elements are not reparented after loading starts. + appendChild( shellBody, createManualTestContainer( manualBody ) ); return serialize( shellDocument ); } @@ -107,6 +109,14 @@ function appendChildren( parent: ParentNode, childNodes: Array ): voi } } +function createManualTestContainer( manualBody: ParentNode ): Element { + const container = createElement( 'div', { class: 'manual-test-container' } ); + + appendChildren( container, [ ...manualBody.childNodes ] ); + + return container; +} + function getRequiredElementByTagName( root: Node, tagName: string ): Element { return query( root, candidate => isElementNode( candidate ) && candidate.tagName == tagName )!; } diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts new file mode 100644 index 000000000..6c49840c6 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts @@ -0,0 +1,48 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import path from 'node:path'; +import { parse } from 'parse5'; +import { describe, expect, test } from 'vitest'; +import { getAttribute, isElementNode, query, queryAll, type Element } from '@parse5/tools'; +import { createManualShellHtml } from '../../src/manual-test-plugin/shell-html.js'; +import type { ManualPageEntry } from '../../src/manual-test-plugin/types.js'; + +describe( 'createManualShellHtml()', () => { + const shellTemplateFilePath = path.resolve( import.meta.dirname, '../../theme/shell.html' ); + const workspaceRoot = path.resolve( '/workspace' ); + const entry: ManualPageEntry = { + displayName: 'Iframe test', + htmlFilePath: '/packages/ckeditor5-foo/tests/manual/iframe.html', + packageName: 'ckeditor5-foo', + scriptFilePath: '/packages/ckeditor5-foo/tests/manual/iframe.js', + slug: 'iframe', + source: 'commercial' + }; + + test( 'wraps manual test content during HTML transform to avoid runtime iframe reparenting', () => { + const html = createManualShellHtml( { + entry, + html: '

Manual test

', + shellScriptPublicPath: '/theme/shell.ts', + shellTemplateFilePath, + workspaceRoot + } ); + const document = parse( html ); + const containers = [ ...queryAll( document, node => { + return isElementNode( node ) && getAttribute( node, 'class' ) == 'manual-test-container'; + } ) ]; + + expect( containers ).to.have.length( 1 ); + + const iframe = query( containers[ 0 ]!, node => isElementNode( node ) && node.tagName == 'iframe' ); + + expect( containers[ 0 ]!.parentNode ).to.equal( query( document, node => { + return isElementNode( node ) && node.tagName == 'body'; + } ) ); + expect( iframe ).not.to.be.null; + expect( getAttribute( iframe!, 'src' ) ).to.equal( 'assets/frame.html' ); + } ); +} ); diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts index bdbf6a58f..4fc54a9c9 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.ts +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -61,7 +61,7 @@ function renderManual(): void { } /** - * Wraps manual test content in a stable container while keeping Shell UI outside it. + * Sets up shell-related state for the manual test container injected by the HTML transform. */ function ensureManualTestContainer(): void { document.body.classList.add( 'shell-enabled' ); @@ -70,6 +70,11 @@ function ensureManualTestContainer(): void { document.body.classList.add( 'shell-has-instructions' ); } + if ( document.querySelector( '.manual-test-container' ) ) { + return; + } + + // The source transform injects this container. Keep this fallback for local setups using stale built plugin files. const container = document.createElement( 'div' ); container.className = 'manual-test-container'; From 47862c8759e6346aaa75ea0a2ff1d405c82bbdd8 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Fri, 29 May 2026 13:35:20 +0200 Subject: [PATCH 03/17] Update Rolldown configuration. --- packages/ckeditor5-dev-manual-server/package.json | 4 ++-- .../{rolldown.config.js => rolldown.config.ts} | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) rename packages/ckeditor5-dev-manual-server/{rolldown.config.js => rolldown.config.ts} (58%) diff --git a/packages/ckeditor5-dev-manual-server/package.json b/packages/ckeditor5-dev-manual-server/package.json index cb08f3785..36550b005 100644 --- a/packages/ckeditor5-dev-manual-server/package.json +++ b/packages/ckeditor5-dev-manual-server/package.json @@ -27,8 +27,8 @@ "ckeditor5-dev-manual-server": "dist/index.js" }, "scripts": { - "build": "rolldown -c rolldown.config.js", - "dev": "rolldown -c rolldown.config.js --watch", + "build": "rolldown -c rolldown.config.ts", + "dev": "rolldown -c rolldown.config.ts --watch", "test": "vitest run --config vitest.config.ts", "coverage": "vitest run --config vitest.config.ts --coverage", "test:dev": "vitest dev" diff --git a/packages/ckeditor5-dev-manual-server/rolldown.config.js b/packages/ckeditor5-dev-manual-server/rolldown.config.ts similarity index 58% rename from packages/ckeditor5-dev-manual-server/rolldown.config.js rename to packages/ckeditor5-dev-manual-server/rolldown.config.ts index 5c70b6606..30437262e 100644 --- a/packages/ckeditor5-dev-manual-server/rolldown.config.js +++ b/packages/ckeditor5-dev-manual-server/rolldown.config.ts @@ -4,12 +4,17 @@ */ import { defineConfig } from 'rolldown'; -import { declarationFilesPlugin } from '../../scripts/plugin-declarations.js'; +import { declarationFiles } from '../ckeditor5-dev-build-tools/src/plugins/declarations.js'; import pkg from './package.json' with { type: 'json' }; +const packageJson = pkg as { + dependencies?: Record; + peerDependencies?: Record; +}; + const externals = [ - ...Object.keys( pkg.dependencies || {} ), - ...Object.keys( pkg.peerDependencies || {} ) + ...Object.keys( packageJson.dependencies || {} ), + ...Object.keys( packageJson.peerDependencies || {} ) ]; export default defineConfig( { @@ -22,7 +27,9 @@ export default defineConfig( { assetFileNames: '[name][extname]' }, plugins: [ - declarationFilesPlugin() + declarationFiles( { + sourceDirectory: 'src' + } ) ], external: id => externals.some( name => id.startsWith( name ) ) } ); From e54c2591d4cf2a5961ded33a2a182fbd0b9e74e5 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Fri, 29 May 2026 16:03:57 +0200 Subject: [PATCH 04/17] Add `createManualRefreshPlugin`. --- .../ckeditor5-dev-manual-server/package.json | 4 +- .../ckeditor5-dev-manual-server/src/index.ts | 2 + .../src/manual-refresh-plugin/plugin.ts | 40 +++++++++++ .../theme/shell.css | 70 +++++++++++++++++-- .../theme/shell.ts | 34 +++++++++ pnpm-lock.yaml | 4 +- 6 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts diff --git a/packages/ckeditor5-dev-manual-server/package.json b/packages/ckeditor5-dev-manual-server/package.json index 36550b005..29482a8d2 100644 --- a/packages/ckeditor5-dev-manual-server/package.json +++ b/packages/ckeditor5-dev-manual-server/package.json @@ -34,7 +34,7 @@ "test:dev": "vitest dev" }, "dependencies": { - "@ckeditor/ckeditor5-inspector": "^5.0.0", + "@ckeditor/ckeditor5-inspector": "^5.0.1", "@parse5/tools": "^0.7.0", "hast-util-to-html": "^9.0.5", "parse5": "^8.0.1", @@ -47,7 +47,7 @@ }, "devDependencies": { "@vitest/coverage-v8": "^4.1.2", - "rolldown": "^1.0.0", + "rolldown": "^1.0.3", "upath": "^2.0.1", "vitest": "^4.1.2" } diff --git a/packages/ckeditor5-dev-manual-server/src/index.ts b/packages/ckeditor5-dev-manual-server/src/index.ts index 901242a0a..6aeea5bfd 100644 --- a/packages/ckeditor5-dev-manual-server/src/index.ts +++ b/packages/ckeditor5-dev-manual-server/src/index.ts @@ -6,4 +6,6 @@ export { createManualTestsPlugin } from './manual-test-plugin/plugin.js'; export type { ManualData } from './manual-test-plugin/plugin.js'; +export { createManualRefreshPlugin } from './manual-refresh-plugin/plugin.js'; + export { stringifyValues } from './utils.js'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts new file mode 100644 index 000000000..0e643e8d5 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts @@ -0,0 +1,40 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import type { HotPayload, Plugin } from 'vite'; + +const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; + +type HotSendArguments = [ payload: HotPayload ]; + +interface BundledDevClientEnvironment { + initialBuildCompleted: boolean; +} + +export function createManualRefreshPlugin(): Plugin { + return { + name: 'ckeditor5-manual-refresh', + apply: 'serve', + + configureServer( server ) { + const clientEnvironment = server.environments.client as typeof server.environments.client & BundledDevClientEnvironment; + const hot = clientEnvironment.hot; + const send = hot.send.bind( hot ); + + hot.send = ( ( ...args: HotSendArguments ) => { + const { type } = args[ 0 ]; + + if ( type == 'update' || ( type == 'full-reload' && clientEnvironment.initialBuildCompleted ) ) { + return send( { + type: 'custom', + event: MANUAL_REFRESH_EVENT_NAME + } ); + } + + send( ...args ); + } ); + } + }; +} diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.css b/packages/ckeditor5-dev-manual-server/theme/shell.css index b0650d021..93518cb8d 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.css +++ b/packages/ckeditor5-dev-manual-server/theme/shell.css @@ -20,15 +20,15 @@ body.shell-enabled { grid-column: 1 / -1; grid-row: 1; position: relative; - z-index: 2147483647; + z-index: 99999; display: flex; align-items: stretch; box-sizing: border-box; padding: 6px 8px; - width: 100%; + width: 100vw; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; font-size: 12px; - line-height: 1; + line-height: 1.25; color: #0a0a0a; background: rgb(255 255 255 / 85%); backdrop-filter: saturate(180%) blur(20px); @@ -102,6 +102,44 @@ body.shell-enabled { white-space: nowrap; } +.manual-refresh-prompt { + position: fixed; + top: calc( var( --shell-height, 44px ) / 1.25 ); + left: 50%; + z-index: 100000; + box-sizing: border-box; + padding: 8px 14px; + border: 1px solid #bfdbfe; + border-radius: 999px; + font: 600 12px/1.25 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + color: #1d4ed8; + background: #dbeafe; + cursor: pointer; + opacity: 0; + pointer-events: none; + transform: translate(-50%, calc( -100% - var( --shell-height, 44px ) ) ); + transition: transform 0.18s ease-out, opacity 0.18s ease-out, background 0.1s; +} + +.manual-refresh-prompt--visible { + opacity: 1; + pointer-events: auto; + transform: translate(-50%, -50%); +} + +.manual-refresh-prompt:hover { + background: #bfdbfe; +} + +.manual-refresh-prompt:active { + background: #93c5fd; +} + +.manual-refresh-prompt:focus-visible { + outline: 2px solid #2563eb; + outline-offset: 1px; +} + @media (prefers-color-scheme: dark) { .shell { color: #ededed; @@ -125,6 +163,25 @@ body.shell-enabled { .shell__divider { background: rgb(255 255 255 / 12%); } + + .manual-refresh-prompt { + color: #bfdbfe; + background: #1e3a8a; + border-color: #2563eb; + box-shadow: 0 8px 24px rgb(37 99 235 / 25%), 0 1px 2px rgb(0 0 0 / 30%); + } + + .manual-refresh-prompt:hover { + background: #1d4ed8; + } + + .manual-refresh-prompt:active { + background: #2563eb; + } + + .manual-refresh-prompt:focus-visible { + outline-color: #93c5fd; + } } @media (max-width: 600px) { @@ -145,8 +202,11 @@ body.shell-enabled { --shell-instructions-width: min(420px, 45vw); --shell-instructions-transition-duration: 0.18s; - grid-column: 2; - grid-row: 2; + position: fixed; + top: 44px; + right: 0; + bottom: 0; + z-index: 99998; order: 2; align-self: start; flex: 0 0 0; diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts index 4fc54a9c9..c18b92732 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.ts +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -10,10 +10,16 @@ import './shell.css'; declare const LICENSE_KEY: string; +interface ViteHotContextLike { + on( event: string, callback: () => void ): void; +} + +const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; const globalTarget = window as any; renderManual(); ensureManualTestContainer(); +setupManualRefreshPrompt(); // In direct HTML manual tests, `id="editor"` creates `window.editor` as a named DOM property. // Reset it so manual tests can safely reuse `window.editor` for the editor instance. @@ -92,6 +98,34 @@ function ensureManualTestContainer(): void { document.body.appendChild( container ); } +/** + * Shows a manual refresh prompt when the dev server reports source changes. + */ +function setupManualRefreshPrompt(): void { + const hot = ( import.meta as ImportMeta & { hot?: ViteHotContextLike } ).hot; + + if ( !hot ) { + return; + } + + const button = document.createElement( 'button' ); + + button.type = 'button'; + button.className = 'manual-refresh-prompt'; + button.textContent = 'Source changed. Click here to refresh the page.'; + button.tabIndex = -1; + button.setAttribute( 'aria-hidden', 'true' ); + button.addEventListener( 'click', () => window.location.reload() ); + + document.body.appendChild( button ); + + hot.on( MANUAL_REFRESH_EVENT_NAME, () => { + button.removeAttribute( 'aria-hidden' ); + button.removeAttribute( 'tabindex' ); + button.classList.add( 'manual-refresh-prompt--visible' ); + } ); +} + /** * Opens or closes the instructions panel and synchronizes related accessibility state. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23d2a564f..67bca310a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,7 +271,7 @@ importers: packages/ckeditor5-dev-manual-server: dependencies: '@ckeditor/ckeditor5-inspector': - specifier: ^5.0.0 + specifier: ^5.0.1 version: 5.0.1 '@parse5/tools': specifier: ^0.7.0 @@ -305,7 +305,7 @@ importers: specifier: ^4.1.2 version: 4.1.7(vitest@4.1.7) rolldown: - specifier: ^1.0.0 + specifier: ^1.0.3 version: 1.0.3 upath: specifier: ^2.0.1 From 62a6a4f4037f0d609a42455c43d9f65619593b6b Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 2 Jun 2026 14:08:11 +0200 Subject: [PATCH 05/17] Address PR comments. --- .../src/constants.ts | 6 ++ .../src/manual-refresh-plugin/plugin.ts | 3 +- .../src/manual-test-plugin/plugin.ts | 63 +++++++++++-------- .../theme/shell.ts | 6 +- 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/src/constants.ts diff --git a/packages/ckeditor5-dev-manual-server/src/constants.ts b/packages/ckeditor5-dev-manual-server/src/constants.ts new file mode 100644 index 000000000..b2b732c4e --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/constants.ts @@ -0,0 +1,6 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +export const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts index 0e643e8d5..5605a2438 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts @@ -4,8 +4,7 @@ */ import type { HotPayload, Plugin } from 'vite'; - -const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; +import { MANUAL_REFRESH_EVENT_NAME } from '../constants.js'; type HotSendArguments = [ payload: HotPayload ]; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts index 7b6f3a5e2..451958123 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -21,21 +21,27 @@ interface ManualTestClientEntry { source: 'commercial' | 'oss'; } +interface ManualTestServerLike { + middlewares: { + use( middleware: unknown ): void; + }; +} + const MANUAL_TEST_PATTERNS = [ 'packages/*/tests/manual/**/*.{html,js,md,ts}', 'external/ckeditor5/packages/*/tests/manual/**/*.{html,js,md,ts}' ]; const MANUAL_ENTRIES_VIRTUAL_ID = 'virtual:ckeditor5-manual-entries'; -const WORKSPACE_ROOT = process.cwd(); const MANUAL_THEME_ROOT = path.resolve( import.meta.dirname, '..', 'theme' ); const MANUAL_CATALOG_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'catalog.html' ); const MANUAL_SHELL_TEMPLATE_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.html' ); const MANUAL_SHELL_SCRIPT_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.ts' ); -const MANUAL_CATALOG_PUBLIC_PATH = toPublicFilePath( MANUAL_CATALOG_FILE_PATH, WORKSPACE_ROOT ); -const MANUAL_SHELL_SCRIPT_PUBLIC_PATH = toPublicFilePath( MANUAL_SHELL_SCRIPT_FILE_PATH, WORKSPACE_ROOT ); export function createManualTestsPlugin(): Plugin { - const manualPages = collectManualPages( MANUAL_TEST_PATTERNS, WORKSPACE_ROOT ); + const workspaceRoot = process.cwd(); + const manualCatalogPublicPath = toPublicFilePath( MANUAL_CATALOG_FILE_PATH, workspaceRoot ); + const manualShellScriptPublicPath = toPublicFilePath( MANUAL_SHELL_SCRIPT_FILE_PATH, workspaceRoot ); + const manualPages = collectManualPages( MANUAL_TEST_PATTERNS, workspaceRoot ); const resolvedVirtualModuleId = `\0${ MANUAL_ENTRIES_VIRTUAL_ID }`; const clientEntries: Array = [ ...manualPages.values() ].map( entry => ( { displayName: entry.displayName, @@ -50,23 +56,11 @@ export function createManualTestsPlugin(): Plugin { name: 'ckeditor5-manual-tests', configureServer( server ) { - server.middlewares.use( createManualStaticAssetsMiddleware( WORKSPACE_ROOT ) ); - - server.middlewares.use( ( request, _response, next ) => { - rewriteCatalogRequest( request ); - - next(); - } ); + useManualTestMiddlewares( server, workspaceRoot, manualCatalogPublicPath ); }, configurePreviewServer( server ) { - server.middlewares.use( createManualStaticAssetsMiddleware( WORKSPACE_ROOT ) ); - - server.middlewares.use( ( request, _response, next ) => { - rewriteCatalogRequest( request ); - - next(); - } ); + useManualTestMiddlewares( server, workspaceRoot, manualCatalogPublicPath ); }, config() { @@ -76,7 +70,7 @@ export function createManualTestsPlugin(): Plugin { input: [ MANUAL_CATALOG_FILE_PATH, ...[ ...manualPages.values() ].map( entry => - path.resolve( WORKSPACE_ROOT, entry.htmlFilePath.slice( 1 ) ) + path.resolve( workspaceRoot, entry.htmlFilePath.slice( 1 ) ) ) ] } @@ -104,7 +98,7 @@ export function createManualTestsPlugin(): Plugin { order: 'pre', handler( html, context ) { - const entry = getManualPageEntryForHtmlPath( manualPages, context.path ); + const entry = getManualPageEntryForHtmlPath( manualPages, context.path, workspaceRoot ); if ( !entry ) { return undefined; @@ -113,20 +107,34 @@ export function createManualTestsPlugin(): Plugin { return createManualShellHtml( { entry, html, - shellScriptPublicPath: MANUAL_SHELL_SCRIPT_PUBLIC_PATH, + shellScriptPublicPath: manualShellScriptPublicPath, shellTemplateFilePath: MANUAL_SHELL_TEMPLATE_FILE_PATH, - workspaceRoot: WORKSPACE_ROOT + workspaceRoot } ); } } }; } -function rewriteCatalogRequest( request: { url?: string } ): void { +function useManualTestMiddlewares( + server: ManualTestServerLike, + workspaceRoot: string, + manualCatalogPublicPath: string +): void { + server.middlewares.use( createManualStaticAssetsMiddleware( workspaceRoot ) ); + + server.middlewares.use( ( request: { url?: string }, _response: unknown, next: () => void ) => { + rewriteCatalogRequest( request, manualCatalogPublicPath ); + + next(); + } ); +} + +function rewriteCatalogRequest( request: { url?: string }, manualCatalogPublicPath: string ): void { const requestPath = request.url?.split( '?' )[ 0 ]; if ( requestPath == '/' || requestPath == '/index.html' ) { - request.url = MANUAL_CATALOG_PUBLIC_PATH; + request.url = manualCatalogPublicPath; } } @@ -138,7 +146,8 @@ function getFilePathFromId( id: string ): string { function getManualPageEntryForHtmlPath( manualPages: Map, - requestPath: string + requestPath: string, + workspaceRoot: string ): ManualPageEntry | undefined { const filePath = getFilePathFromId( requestPath ); const entry = manualPages.get( filePath ); @@ -147,8 +156,8 @@ function getManualPageEntryForHtmlPath( return entry; } - if ( path.isAbsolute( filePath ) && filePath.startsWith( WORKSPACE_ROOT ) ) { - return manualPages.get( toPublicSpecifier( path.relative( WORKSPACE_ROOT, filePath ) ) ); + if ( path.isAbsolute( filePath ) && filePath.startsWith( workspaceRoot ) ) { + return manualPages.get( toPublicSpecifier( path.relative( workspaceRoot, filePath ) ) ); } return undefined; diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts index c18b92732..573f5e082 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.ts +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -5,6 +5,7 @@ import CKEditorInspector from '@ckeditor/ckeditor5-inspector'; import type { ManualData } from '../src/manual-test-plugin/plugin.js'; +import { MANUAL_REFRESH_EVENT_NAME } from '../src/constants.js'; import './shell.css'; @@ -14,7 +15,6 @@ interface ViteHotContextLike { on( event: string, callback: () => void ): void; } -const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; const globalTarget = window as any; renderManual(); @@ -133,7 +133,7 @@ function toggleInstructions( isOpen: boolean ): void { const panel = document.querySelector( '.shell-instructions' ); const trigger = document.querySelector( '[data-shell-action="show-instructions"]' ); - if ( !panel ) { + if ( !panel || !trigger ) { return; } @@ -146,7 +146,7 @@ function toggleInstructions( isOpen: boolean ): void { panel.classList.toggle( 'shell-instructions--open', isOpen ); panel.setAttribute( 'aria-hidden', String( !isOpen ) ); panel.toggleAttribute( 'inert', !isOpen ); - trigger!.setAttribute( 'aria-expanded', String( isOpen ) ); + trigger.setAttribute( 'aria-expanded', String( isOpen ) ); } /** From 13d41949d0fbab3272d515a87079294b933d9e85 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 2 Jun 2026 14:54:53 +0200 Subject: [PATCH 06/17] Add `rawPlugin`. --- .../ckeditor5-dev-manual-server/src/index.ts | 6 +- .../src/manual-test-plugin/plugin.ts | 2 +- .../src/raw-plugin/plugin.ts | 56 ++++++++ .../plugin.ts | 2 +- .../tests/raw-plugin/plugin.ts | 126 ++++++++++++++++++ 5 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts rename packages/ckeditor5-dev-manual-server/src/{manual-refresh-plugin => refresh-plugin}/plugin.ts (94%) create mode 100644 packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts diff --git a/packages/ckeditor5-dev-manual-server/src/index.ts b/packages/ckeditor5-dev-manual-server/src/index.ts index 6aeea5bfd..db7cf7d60 100644 --- a/packages/ckeditor5-dev-manual-server/src/index.ts +++ b/packages/ckeditor5-dev-manual-server/src/index.ts @@ -3,9 +3,11 @@ * For licensing, see LICENSE.md. */ -export { createManualTestsPlugin } from './manual-test-plugin/plugin.js'; +export { manualTestsPlugin } from './manual-test-plugin/plugin.js'; export type { ManualData } from './manual-test-plugin/plugin.js'; -export { createManualRefreshPlugin } from './manual-refresh-plugin/plugin.js'; +export { refreshPlugin } from './refresh-plugin/plugin.js'; + +export { rawPlugin } from './raw-plugin/plugin.js'; export { stringifyValues } from './utils.js'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts index 451958123..d95505023 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -37,7 +37,7 @@ const MANUAL_CATALOG_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'catalog.html' const MANUAL_SHELL_TEMPLATE_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.html' ); const MANUAL_SHELL_SCRIPT_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.ts' ); -export function createManualTestsPlugin(): Plugin { +export function manualTestsPlugin(): Plugin { const workspaceRoot = process.cwd(); const manualCatalogPublicPath = toPublicFilePath( MANUAL_CATALOG_FILE_PATH, workspaceRoot ); const manualShellScriptPublicPath = toPublicFilePath( MANUAL_SHELL_SCRIPT_FILE_PATH, workspaceRoot ); diff --git a/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts new file mode 100644 index 000000000..e3d20cdbe --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts @@ -0,0 +1,56 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import type { Plugin } from 'vite'; +import path from 'node:path'; +import { readFile } from 'node:fs/promises'; + +const DEFAULT_EXTENSIONS = [ '.html' ]; +const RAW_QUERY = '?ckeditor5-raw'; + +export function rawPlugin( extensions: Array = DEFAULT_EXTENSIONS ): Plugin { + return { + name: 'ckeditor5-raw', + enforce: 'pre', + + async resolveId( source, importer ) { + if ( !importer || source.includes( '?' ) ) { + return null; + } + + if ( !extensions.includes( path.extname( source ) ) ) { + return null; + } + + const resolved = await this.resolve( source, importer, { skipSelf: true } ); + + if ( !resolved ) { + return null; + } + + return `${ getFilePathFromId( resolved.id ) }${ RAW_QUERY }`; + }, + + async load( id ) { + if ( !id.endsWith( RAW_QUERY ) ) { + return null; + } + + const filePath = id.slice( 0, -RAW_QUERY.length ); + const source = await readFile( filePath, 'utf8' ); + + return { + code: `export default ${ JSON.stringify( source ) };`, + map: null + }; + } + }; +} + +function getFilePathFromId( id: string ): string { + const queryIndex = id.indexOf( '?' ); + + return queryIndex >= 0 ? id.slice( 0, queryIndex ) : id; +} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts similarity index 94% rename from packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts rename to packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts index 5605a2438..c723802ca 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts @@ -12,7 +12,7 @@ interface BundledDevClientEnvironment { initialBuildCompleted: boolean; } -export function createManualRefreshPlugin(): Plugin { +export function refreshPlugin(): Plugin { return { name: 'ckeditor5-manual-refresh', apply: 'serve', diff --git a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts new file mode 100644 index 000000000..217cd1fa2 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts @@ -0,0 +1,126 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import os from 'node:os'; +import path from 'node:path'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { rawPlugin } from '../../src/raw-plugin/plugin.js'; +import type { PluginContext } from 'rollup'; + +interface RawPluginLoadResult { + code: string; + map: null; +} + +type ResolveIdHook = ( this: PluginContext, source: string, importer?: string ) => Promise; +type LoadHook = ( id: string ) => Promise; + +describe( 'rawPlugin()', () => { + let temporaryDirectory: string; + + beforeEach( async () => { + temporaryDirectory = await mkdtemp( path.join( os.tmpdir(), 'ckeditor5-raw-plugin-' ) ); + } ); + + afterEach( async () => { + await rm( temporaryDirectory, { recursive: true, force: true } ); + } ); + + test( 'runs before Vite built-in HTML handling', () => { + expect( rawPlugin().enforce ).to.equal( 'pre' ); + } ); + + test( 'resolves imported HTML files as raw JavaScript modules by default', async () => { + const plugin = rawPlugin(); + const htmlFilePath = path.join( temporaryDirectory, 'template.html' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + const load = plugin.load as LoadHook; + + await writeFile( htmlFilePath, '
Text
' ); + + const resolvedHtmlId = await resolveId.call( createPluginContext( htmlFilePath ), './template.html', importer ); + + expect( await load( resolvedHtmlId! ) ).to.deep.equal( { + code: 'export default "
Text
";', + map: null + } ); + } ); + + test( 'does not resolve SVG files by default', async () => { + const plugin = rawPlugin(); + const filePath = path.join( temporaryDirectory, 'icon.svg' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + + await writeFile( filePath, '' ); + + expect( await resolveId.call( createPluginContext( filePath ), './icon.svg', importer ) ).to.be.null; + } ); + + test( 'does not load plain HTML files directly', async () => { + const plugin = rawPlugin(); + const filePath = path.join( temporaryDirectory, 'manual.html' ); + const load = plugin.load as LoadHook; + + await writeFile( filePath, '

Manual test page

' ); + + expect( await load( filePath ) ).to.be.null; + } ); + + test( 'does not resolve HTML entry points without an importer', async () => { + const plugin = rawPlugin(); + const filePath = path.join( temporaryDirectory, 'manual.html' ); + const resolveId = plugin.resolveId as ResolveIdHook; + + expect( await resolveId.call( createPluginContext( filePath ), filePath ) ).to.be.null; + } ); + + test( 'ignores files with unsupported extensions', async () => { + const plugin = rawPlugin(); + const filePath = path.join( temporaryDirectory, 'script.js' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + + await writeFile( filePath, 'export default 1;' ); + + expect( await resolveId.call( createPluginContext( filePath ), './script.js', importer ) ).to.be.null; + } ); + + test( 'supports custom extensions without leading dots', async () => { + const plugin = rawPlugin( [ 'txt' ] ); + const filePath = path.join( temporaryDirectory, 'sample.txt' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + const load = plugin.load as LoadHook; + + await writeFile( filePath, 'Raw text' ); + + const resolvedId = await resolveId.call( createPluginContext( filePath ), './sample.txt', importer ); + + expect( await load( resolvedId! ) ).to.deep.equal( { + code: 'export default "Raw text";', + map: null + } ); + } ); + + test( 'ignores imports with explicit request queries', async () => { + const plugin = rawPlugin(); + const filePath = path.join( temporaryDirectory, 'template.html' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + + await writeFile( filePath, '

Query

' ); + + expect( await resolveId.call( createPluginContext( filePath ), './template.html?url', importer ) ).to.be.null; + } ); +} ); + +function createPluginContext( resolvedId: string ): PluginContext { + return { + resolve: async () => ( { id: resolvedId } ) + } as unknown as PluginContext; +} From f45d6b6ad64a971771e4507b646d754956b025a7 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 2 Jun 2026 15:58:54 +0200 Subject: [PATCH 07/17] Rename `rawPlugin` to `rawHtmlPlugin`. --- .../ckeditor5-dev-manual-server/src/index.ts | 2 +- .../src/raw-plugin/plugin.ts | 5 +- .../tests/raw-plugin/plugin.ts | 62 +++++++++++-------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/packages/ckeditor5-dev-manual-server/src/index.ts b/packages/ckeditor5-dev-manual-server/src/index.ts index db7cf7d60..333e91047 100644 --- a/packages/ckeditor5-dev-manual-server/src/index.ts +++ b/packages/ckeditor5-dev-manual-server/src/index.ts @@ -8,6 +8,6 @@ export type { ManualData } from './manual-test-plugin/plugin.js'; export { refreshPlugin } from './refresh-plugin/plugin.js'; -export { rawPlugin } from './raw-plugin/plugin.js'; +export { rawHtmlPlugin } from './raw-plugin/plugin.js'; export { stringifyValues } from './utils.js'; diff --git a/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts index e3d20cdbe..cebc46a77 100644 --- a/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts @@ -7,10 +7,9 @@ import type { Plugin } from 'vite'; import path from 'node:path'; import { readFile } from 'node:fs/promises'; -const DEFAULT_EXTENSIONS = [ '.html' ]; const RAW_QUERY = '?ckeditor5-raw'; -export function rawPlugin( extensions: Array = DEFAULT_EXTENSIONS ): Plugin { +export function rawHtmlPlugin(): Plugin { return { name: 'ckeditor5-raw', enforce: 'pre', @@ -20,7 +19,7 @@ export function rawPlugin( extensions: Array = DEFAULT_EXTENSIONS ): Plu return null; } - if ( !extensions.includes( path.extname( source ) ) ) { + if ( path.extname( source ) != '.html' ) { return null; } diff --git a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts index 217cd1fa2..8da3890a0 100644 --- a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts @@ -7,7 +7,7 @@ import os from 'node:os'; import path from 'node:path'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import { rawPlugin } from '../../src/raw-plugin/plugin.js'; +import { rawHtmlPlugin } from '../../src/raw-plugin/plugin.js'; import type { PluginContext } from 'rollup'; interface RawPluginLoadResult { @@ -18,7 +18,7 @@ interface RawPluginLoadResult { type ResolveIdHook = ( this: PluginContext, source: string, importer?: string ) => Promise; type LoadHook = ( id: string ) => Promise; -describe( 'rawPlugin()', () => { +describe( 'rawHtmlPlugin()', () => { let temporaryDirectory: string; beforeEach( async () => { @@ -30,11 +30,11 @@ describe( 'rawPlugin()', () => { } ); test( 'runs before Vite built-in HTML handling', () => { - expect( rawPlugin().enforce ).to.equal( 'pre' ); + expect( rawHtmlPlugin().enforce ).to.equal( 'pre' ); } ); test( 'resolves imported HTML files as raw JavaScript modules by default', async () => { - const plugin = rawPlugin(); + const plugin = rawHtmlPlugin(); const htmlFilePath = path.join( temporaryDirectory, 'template.html' ); const importer = path.join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; @@ -50,8 +50,25 @@ describe( 'rawPlugin()', () => { } ); } ); + test( 'strips resolved import queries before loading raw HTML', async () => { + const plugin = rawHtmlPlugin(); + const htmlFilePath = path.join( temporaryDirectory, 'template.html' ); + const importer = path.join( temporaryDirectory, 'manual.js' ); + const resolveId = plugin.resolveId as ResolveIdHook; + const load = plugin.load as LoadHook; + + await writeFile( htmlFilePath, '

Template

' ); + + const resolvedHtmlId = await resolveId.call( createPluginContext( `${ htmlFilePath }?v=1` ), './template.html', importer ); + + expect( await load( resolvedHtmlId! ) ).to.deep.equal( { + code: 'export default "

Template

";', + map: null + } ); + } ); + test( 'does not resolve SVG files by default', async () => { - const plugin = rawPlugin(); + const plugin = rawHtmlPlugin(); const filePath = path.join( temporaryDirectory, 'icon.svg' ); const importer = path.join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; @@ -62,7 +79,7 @@ describe( 'rawPlugin()', () => { } ); test( 'does not load plain HTML files directly', async () => { - const plugin = rawPlugin(); + const plugin = rawHtmlPlugin(); const filePath = path.join( temporaryDirectory, 'manual.html' ); const load = plugin.load as LoadHook; @@ -72,43 +89,34 @@ describe( 'rawPlugin()', () => { } ); test( 'does not resolve HTML entry points without an importer', async () => { - const plugin = rawPlugin(); + const plugin = rawHtmlPlugin(); const filePath = path.join( temporaryDirectory, 'manual.html' ); const resolveId = plugin.resolveId as ResolveIdHook; expect( await resolveId.call( createPluginContext( filePath ), filePath ) ).to.be.null; } ); - test( 'ignores files with unsupported extensions', async () => { - const plugin = rawPlugin(); - const filePath = path.join( temporaryDirectory, 'script.js' ); + test( 'does not resolve HTML imports that Vite cannot resolve', async () => { + const plugin = rawHtmlPlugin(); const importer = path.join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; - await writeFile( filePath, 'export default 1;' ); - - expect( await resolveId.call( createPluginContext( filePath ), './script.js', importer ) ).to.be.null; + expect( await resolveId.call( createPluginContext( null ), './missing.html', importer ) ).to.be.null; } ); - test( 'supports custom extensions without leading dots', async () => { - const plugin = rawPlugin( [ 'txt' ] ); - const filePath = path.join( temporaryDirectory, 'sample.txt' ); + test( 'ignores files with unsupported extensions', async () => { + const plugin = rawHtmlPlugin(); + const filePath = path.join( temporaryDirectory, 'script.js' ); const importer = path.join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; - const load = plugin.load as LoadHook; - - await writeFile( filePath, 'Raw text' ); - const resolvedId = await resolveId.call( createPluginContext( filePath ), './sample.txt', importer ); + await writeFile( filePath, 'export default 1;' ); - expect( await load( resolvedId! ) ).to.deep.equal( { - code: 'export default "Raw text";', - map: null - } ); + expect( await resolveId.call( createPluginContext( filePath ), './script.js', importer ) ).to.be.null; } ); test( 'ignores imports with explicit request queries', async () => { - const plugin = rawPlugin(); + const plugin = rawHtmlPlugin(); const filePath = path.join( temporaryDirectory, 'template.html' ); const importer = path.join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; @@ -119,8 +127,8 @@ describe( 'rawPlugin()', () => { } ); } ); -function createPluginContext( resolvedId: string ): PluginContext { +function createPluginContext( resolvedId: string | null ): PluginContext { return { - resolve: async () => ( { id: resolvedId } ) + resolve: async () => resolvedId ? { id: resolvedId } : null } as unknown as PluginContext; } From 7ead5471f7ce7de707c9ed380c03e423d6a3c9a5 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Tue, 2 Jun 2026 16:35:39 +0200 Subject: [PATCH 08/17] Fix test harness styling. --- packages/ckeditor5-dev-manual-server/theme/shell.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.css b/packages/ckeditor5-dev-manual-server/theme/shell.css index 93518cb8d..51bc39ee0 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.css +++ b/packages/ckeditor5-dev-manual-server/theme/shell.css @@ -3,8 +3,10 @@ grid-row: 2; order: 1; flex: 1 1 auto; + min-height: 0; min-width: 0; box-sizing: border-box; + overflow: auto; padding: 8px; } @@ -12,8 +14,12 @@ body.shell-enabled { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto minmax(0, 1fr); + box-sizing: border-box; + height: 100vh; margin: 0; - min-height: 100vh; + min-height: 0; + overflow: hidden; + padding: 0; } .shell { From 450ae2b4e312f0c02bac091a1a2e90750f4b410f Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 14:55:34 +0200 Subject: [PATCH 09/17] Use `optimizeDeps` instead of `experimental.bundledDev`. --- .../ckeditor5-dev-manual-server/src/index.ts | 2 +- .../src/manual-test-plugin/collect-pages.ts | 3 +- .../src/manual-test-plugin/plugin.ts | 77 +++++-------- .../src/manual-test-plugin/static-assets.ts | 102 ++++++----------- .../src/manual-test-plugin/types.ts | 1 - .../src/raw-plugin/plugin.ts | 34 +----- .../src/refresh-plugin/plugin.ts | 32 ++---- .../ckeditor5-dev-manual-server/src/utils.ts | 23 +++- .../tests/manual-test-plugin/plugin.ts | 82 +++++++++++++ .../tests/manual-test-plugin/shell-html.ts | 9 +- .../tests/manual-test-plugin/static-assets.ts | 108 +++++++++++------- .../tests/raw-plugin/plugin.ts | 81 +++++-------- .../tests/refresh-plugin/plugin.ts | 93 +++++++++++++++ .../tests/utils.ts | 58 ++++++++++ .../theme/catalog.css | 29 ----- .../theme/catalog.html | 1 - .../theme/catalog.ts | 4 - .../theme/shell.ts | 25 +--- 18 files changed, 430 insertions(+), 334 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts create mode 100644 packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts create mode 100644 packages/ckeditor5-dev-manual-server/tests/utils.ts diff --git a/packages/ckeditor5-dev-manual-server/src/index.ts b/packages/ckeditor5-dev-manual-server/src/index.ts index 333e91047..f6345e2f8 100644 --- a/packages/ckeditor5-dev-manual-server/src/index.ts +++ b/packages/ckeditor5-dev-manual-server/src/index.ts @@ -10,4 +10,4 @@ export { refreshPlugin } from './refresh-plugin/plugin.js'; export { rawHtmlPlugin } from './raw-plugin/plugin.js'; -export { stringifyValues } from './utils.js'; +export { getOptimizedPackageIncludes, stringifyValues } from './utils.js'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts index 04742ba4d..95f4e14a1 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/collect-pages.ts @@ -55,8 +55,7 @@ export function collectManualPages( patterns: Array, workspaceRoot: stri instructionsFilePath: matchedFiles.md ? toPublicSpecifier( matchedFiles.md ) : undefined, packageName: parsedPath.packageName, scriptFilePath: toPublicSpecifier( scriptFilePath ), - slug: parsedPath.slug, - source: parsedPath.packageRootPath == 'packages' ? 'commercial' : 'oss' + slug: parsedPath.slug } ); } diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts index d95505023..89985c3da 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -3,9 +3,10 @@ * For licensing, see LICENSE.md. */ -import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve, relative } from 'node:path'; import { collectManualPages } from './collect-pages.js'; -import { createManualStaticAssetsMiddleware } from './static-assets.js'; +import { collectManualStaticAssets, createManualStaticAssetsMiddleware } from './static-assets.js'; import { createManualShellHtml } from './shell-html.js'; import { toPublicFilePath, toPublicSpecifier } from '../utils.js'; import type { Plugin } from 'vite'; @@ -18,7 +19,6 @@ interface ManualTestClientEntry { packageName: string; packageShortName: string; slug: string; - source: 'commercial' | 'oss'; } interface ManualTestServerLike { @@ -27,40 +27,42 @@ interface ManualTestServerLike { }; } -const MANUAL_TEST_PATTERNS = [ - 'packages/*/tests/manual/**/*.{html,js,md,ts}', - 'external/ckeditor5/packages/*/tests/manual/**/*.{html,js,md,ts}' -]; +const PACKAGE_ROOT = dirname( fileURLToPath( import.meta.resolve( '@ckeditor/ckeditor5-dev-manual-server/package.json' ) ) ); const MANUAL_ENTRIES_VIRTUAL_ID = 'virtual:ckeditor5-manual-entries'; -const MANUAL_THEME_ROOT = path.resolve( import.meta.dirname, '..', 'theme' ); -const MANUAL_CATALOG_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'catalog.html' ); -const MANUAL_SHELL_TEMPLATE_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.html' ); -const MANUAL_SHELL_SCRIPT_FILE_PATH = path.resolve( MANUAL_THEME_ROOT, 'shell.ts' ); +const MANUAL_THEME_ROOT = resolve( PACKAGE_ROOT, 'theme' ); +const MANUAL_CATALOG_FILE_PATH = resolve( MANUAL_THEME_ROOT, 'catalog.html' ); +const MANUAL_CATALOG_SCRIPT_FILE_PATH = resolve( MANUAL_THEME_ROOT, 'catalog.ts' ); +const MANUAL_SHELL_TEMPLATE_FILE_PATH = resolve( MANUAL_THEME_ROOT, 'shell.html' ); +const MANUAL_SHELL_SCRIPT_FILE_PATH = resolve( MANUAL_THEME_ROOT, 'shell.ts' ); -export function manualTestsPlugin(): Plugin { +export function manualTestsPlugin( manualTestPatterns: Array ): Plugin { const workspaceRoot = process.cwd(); const manualCatalogPublicPath = toPublicFilePath( MANUAL_CATALOG_FILE_PATH, workspaceRoot ); + const manualCatalogScriptPublicPath = toPublicFilePath( MANUAL_CATALOG_SCRIPT_FILE_PATH, workspaceRoot ); const manualShellScriptPublicPath = toPublicFilePath( MANUAL_SHELL_SCRIPT_FILE_PATH, workspaceRoot ); - const manualPages = collectManualPages( MANUAL_TEST_PATTERNS, workspaceRoot ); + const manualPages = collectManualPages( manualTestPatterns.map( pattern => `${ pattern }.{html,js,md,ts}` ), workspaceRoot ); + const manualStaticAssets = collectManualStaticAssets( manualTestPatterns, workspaceRoot ); + const getManualPageEntryForFile = ( filePath: string ): ManualPageEntry | undefined => { + return manualPages.get( toPublicSpecifier( relative( workspaceRoot, filePath ) ) ); + }; const resolvedVirtualModuleId = `\0${ MANUAL_ENTRIES_VIRTUAL_ID }`; const clientEntries: Array = [ ...manualPages.values() ].map( entry => ( { displayName: entry.displayName, href: entry.htmlFilePath, packageName: entry.packageName, packageShortName: entry.packageName.replace( /^ckeditor5-/, '' ), - slug: entry.slug, - source: entry.source + slug: entry.slug } ) ); return { name: 'ckeditor5-manual-tests', configureServer( server ) { - useManualTestMiddlewares( server, workspaceRoot, manualCatalogPublicPath ); + useManualTestMiddlewares( server, manualCatalogPublicPath, manualStaticAssets ); }, configurePreviewServer( server ) { - useManualTestMiddlewares( server, workspaceRoot, manualCatalogPublicPath ); + useManualTestMiddlewares( server, manualCatalogPublicPath, manualStaticAssets ); }, config() { @@ -69,9 +71,7 @@ export function manualTestsPlugin(): Plugin { rolldownOptions: { input: [ MANUAL_CATALOG_FILE_PATH, - ...[ ...manualPages.values() ].map( entry => - path.resolve( workspaceRoot, entry.htmlFilePath.slice( 1 ) ) - ) + ...[ ...manualPages.values() ].map( entry => resolve( workspaceRoot, entry.htmlFilePath.slice( 1 ) ) ) ] } } @@ -98,7 +98,11 @@ export function manualTestsPlugin(): Plugin { order: 'pre', handler( html, context ) { - const entry = getManualPageEntryForHtmlPath( manualPages, context.path, workspaceRoot ); + if ( context.filename == MANUAL_CATALOG_FILE_PATH ) { + return html.replace( './catalog.ts', manualCatalogScriptPublicPath ); + } + + const entry = getManualPageEntryForFile( context.filename ); if ( !entry ) { return undefined; @@ -118,10 +122,10 @@ export function manualTestsPlugin(): Plugin { function useManualTestMiddlewares( server: ManualTestServerLike, - workspaceRoot: string, - manualCatalogPublicPath: string + manualCatalogPublicPath: string, + manualStaticAssets: Map ): void { - server.middlewares.use( createManualStaticAssetsMiddleware( workspaceRoot ) ); + server.middlewares.use( createManualStaticAssetsMiddleware( manualStaticAssets ) ); server.middlewares.use( ( request: { url?: string }, _response: unknown, next: () => void ) => { rewriteCatalogRequest( request, manualCatalogPublicPath ); @@ -137,28 +141,3 @@ function rewriteCatalogRequest( request: { url?: string }, manualCatalogPublicPa request.url = manualCatalogPublicPath; } } - -function getFilePathFromId( id: string ): string { - const queryIndex = id.indexOf( '?' ); - - return queryIndex >= 0 ? id.slice( 0, queryIndex ) : id; -} - -function getManualPageEntryForHtmlPath( - manualPages: Map, - requestPath: string, - workspaceRoot: string -): ManualPageEntry | undefined { - const filePath = getFilePathFromId( requestPath ); - const entry = manualPages.get( filePath ); - - if ( entry ) { - return entry; - } - - if ( path.isAbsolute( filePath ) && filePath.startsWith( workspaceRoot ) ) { - return manualPages.get( toPublicSpecifier( path.relative( workspaceRoot, filePath ) ) ); - } - - return undefined; -} diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts index bf725cedd..949df2273 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts @@ -3,13 +3,12 @@ * For licensing, see LICENSE.md. */ -import fs from 'node:fs'; -import path from 'node:path'; +import { resolve, extname } from 'node:path'; +import { globSync, statSync, createReadStream } from 'node:fs'; import type { IncomingMessage, ServerResponse } from 'node:http'; -import { toPosixPath } from '../utils.js'; +import { toPublicSpecifier } from '../utils.js'; const PROCESSED_MANUAL_TEST_EXTENSIONS = new Set( [ '.html', '.js', '.md', '.ts' ] ); - const VITE_MODULE_QUERY_PARAMETERS = new Set( [ 'import', 'raw', @@ -20,7 +19,20 @@ const VITE_MODULE_QUERY_PARAMETERS = new Set( [ type ManualStaticAssetsMiddleware = ( request: IncomingMessage, response: ServerResponse, next: () => void ) => void; -export function createManualStaticAssetsMiddleware( workspaceRoot: string ): ManualStaticAssetsMiddleware { +export function collectManualStaticAssets( patterns: Array, workspaceRoot: string ): Map { + return new Map( patterns + .flatMap( pattern => globSync( pattern, { cwd: workspaceRoot } ) ) + .sort() + .filter( relativeFilePath => statSync( resolve( workspaceRoot, relativeFilePath ) ).isFile() ) + .filter( isManualStaticAssetPath ) + .map( relativeFilePath => [ + toPublicSpecifier( relativeFilePath ), + resolve( workspaceRoot, relativeFilePath ) + ] ) + ); +} + +export function createManualStaticAssetsMiddleware( staticAssets: Map ): ManualStaticAssetsMiddleware { return ( request, response, next ) => { if ( request.method != 'GET' && request.method != 'HEAD' ) { next(); @@ -28,7 +40,7 @@ export function createManualStaticAssetsMiddleware( workspaceRoot: string ): Man return; } - const filePath = getManualStaticAssetFilePath( request.url, workspaceRoot ); + const filePath = getManualStaticAssetFilePath( request.url, staticAssets ); if ( !filePath ) { next(); @@ -36,21 +48,7 @@ export function createManualStaticAssetsMiddleware( workspaceRoot: string ): Man return; } - let fileStats: fs.Stats; - - try { - fileStats = fs.statSync( filePath ); - } catch { - next(); - - return; - } - - if ( !fileStats.isFile() ) { - next(); - - return; - } + const fileStats = statSync( filePath ); response.statusCode = 200; response.setHeader( 'Content-Length', fileStats.size ); @@ -62,11 +60,14 @@ export function createManualStaticAssetsMiddleware( workspaceRoot: string ): Man return; } - fs.createReadStream( filePath ).pipe( response ); + createReadStream( filePath ).pipe( response ); }; } -export function getManualStaticAssetFilePath( requestUrl: string | undefined, workspaceRoot: string ): string | null { +export function getManualStaticAssetFilePath( + requestUrl: string | undefined, + staticAssets: Map +): string | null { if ( !requestUrl ) { return null; } @@ -83,56 +84,11 @@ export function getManualStaticAssetFilePath( requestUrl: string | undefined, wo return null; } - let requestPath: string; - - try { - requestPath = decodeURIComponent( url.pathname ); - } catch { - return null; - } - - if ( requestPath.includes( '\0' ) ) { - return null; - } - - const filePath = path.resolve( workspaceRoot, toPosixPath( requestPath ).replace( /^\/+/, '' ) ); - const relativeResolvedPath = path.relative( workspaceRoot, filePath ); - - if ( relativeResolvedPath.startsWith( '..' ) || path.isAbsolute( relativeResolvedPath ) ) { - return null; - } - - const relativeFilePath = toPosixPath( relativeResolvedPath ); - - if ( !isManualStaticAssetPath( relativeFilePath ) ) { - return null; - } - - return filePath; + return staticAssets.get( url.pathname ) || null; } function isManualStaticAssetPath( filePath: string ): boolean { - const pathParts = filePath.split( '/' ); - const manualDirectoryIndex = pathParts.findIndex( ( part, index ) => part == 'manual' && pathParts[ index - 1 ] == 'tests' ); - - if ( manualDirectoryIndex < 0 || manualDirectoryIndex == pathParts.length - 1 ) { - return false; - } - - const packageRootParts = pathParts.slice( 0, manualDirectoryIndex - 1 ); - const isCommercialPackage = packageRootParts.length == 2 && packageRootParts[ 0 ] == 'packages'; - const isOssPackage = packageRootParts.length == 4 && - packageRootParts[ 0 ] == 'external' && - packageRootParts[ 1 ] == 'ckeditor5' && - packageRootParts[ 2 ] == 'packages'; - - if ( !isCommercialPackage && !isOssPackage ) { - return false; - } - - const extension = path.posix.extname( filePath ); - - return extension != '' && !PROCESSED_MANUAL_TEST_EXTENSIONS.has( extension ); + return extname( filePath ) != '' && !PROCESSED_MANUAL_TEST_EXTENSIONS.has( extname( filePath ) ); } function getContentType( extension: string ): string { @@ -157,6 +113,12 @@ function getContentType( extension: string ): string { case '.map': return 'application/json; charset=utf-8'; + case '.mp3': + return 'audio/mpeg'; + + case '.mp4': + return 'video/mp4'; + case '.png': return 'image/png'; diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts index 1a9f912c0..e1db27023 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/types.ts @@ -16,7 +16,6 @@ export interface ManualPageEntry { packageName: string; scriptFilePath: string; slug: string; - source: 'commercial' | 'oss'; } export type ManualTestAssetExtension = 'html' | 'js' | 'md' | 'ts'; diff --git a/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts index cebc46a77..85193cde9 100644 --- a/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/raw-plugin/plugin.ts @@ -4,10 +4,8 @@ */ import type { Plugin } from 'vite'; -import path from 'node:path'; -import { readFile } from 'node:fs/promises'; -const RAW_QUERY = '?ckeditor5-raw'; +const RAW_QUERY = '?raw'; export function rawHtmlPlugin(): Plugin { return { @@ -19,37 +17,11 @@ export function rawHtmlPlugin(): Plugin { return null; } - if ( path.extname( source ) != '.html' ) { + if ( !source.endsWith( '.html' ) ) { return null; } - const resolved = await this.resolve( source, importer, { skipSelf: true } ); - - if ( !resolved ) { - return null; - } - - return `${ getFilePathFromId( resolved.id ) }${ RAW_QUERY }`; - }, - - async load( id ) { - if ( !id.endsWith( RAW_QUERY ) ) { - return null; - } - - const filePath = id.slice( 0, -RAW_QUERY.length ); - const source = await readFile( filePath, 'utf8' ); - - return { - code: `export default ${ JSON.stringify( source ) };`, - map: null - }; + return this.resolve( `${ source }${ RAW_QUERY }`, importer, { skipSelf: true } ); } }; } - -function getFilePathFromId( id: string ): string { - const queryIndex = id.indexOf( '?' ); - - return queryIndex >= 0 ? id.slice( 0, queryIndex ) : id; -} diff --git a/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts index c723802ca..1d010108c 100644 --- a/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts @@ -3,37 +3,25 @@ * For licensing, see LICENSE.md. */ -import type { HotPayload, Plugin } from 'vite'; +import type { Plugin } from 'vite'; import { MANUAL_REFRESH_EVENT_NAME } from '../constants.js'; -type HotSendArguments = [ payload: HotPayload ]; - -interface BundledDevClientEnvironment { - initialBuildCompleted: boolean; -} - export function refreshPlugin(): Plugin { return { name: 'ckeditor5-manual-refresh', apply: 'serve', - configureServer( server ) { - const clientEnvironment = server.environments.client as typeof server.environments.client & BundledDevClientEnvironment; - const hot = clientEnvironment.hot; - const send = hot.send.bind( hot ); + handleHotUpdate( { file, server } ) { + if ( file.endsWith( '.css' ) ) { + return; + } - hot.send = ( ( ...args: HotSendArguments ) => { - const { type } = args[ 0 ]; - - if ( type == 'update' || ( type == 'full-reload' && clientEnvironment.initialBuildCompleted ) ) { - return send( { - type: 'custom', - event: MANUAL_REFRESH_EVENT_NAME - } ); - } - - send( ...args ); + server.hot.send( { + type: 'custom', + event: MANUAL_REFRESH_EVENT_NAME } ); + + return []; } }; } diff --git a/packages/ckeditor5-dev-manual-server/src/utils.ts b/packages/ckeditor5-dev-manual-server/src/utils.ts index ea40522fb..e2114d96d 100644 --- a/packages/ckeditor5-dev-manual-server/src/utils.ts +++ b/packages/ckeditor5-dev-manual-server/src/utils.ts @@ -3,7 +3,8 @@ * For licensing, see LICENSE.md. */ -import path from 'node:path'; +import { resolve, relative, isAbsolute } from 'node:path'; +import { globSync, readFileSync } from 'node:fs'; /** * Stringifies the values of the given object. @@ -16,10 +17,26 @@ export function stringifyValues( obj: Record ): Record ): Array { + const packageNames = globSync( packageJsonGlobs, { cwd: process.cwd() } ) + .map( packageJsonPath => { + const resolvedPath = resolve( process.cwd(), packageJsonPath ); + const packageJson = JSON.parse( readFileSync( resolvedPath, 'utf8' ) ); + + return packageJson.name; + } ) + .sort(); + + return [ ...new Set( packageNames ) ]; +} + export function toPublicFilePath( filePath: string, workspaceRoot: string ): string { - const relativeFilePath = path.relative( workspaceRoot, filePath ); + const relativeFilePath = relative( workspaceRoot, filePath ); - if ( !relativeFilePath.startsWith( '..' ) && !path.isAbsolute( relativeFilePath ) ) { + if ( !relativeFilePath.startsWith( '..' ) && !isAbsolute( relativeFilePath ) ) { return toPublicSpecifier( relativeFilePath ); } diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts new file mode 100644 index 000000000..eab6c7b38 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts @@ -0,0 +1,82 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { tmpdir } from 'node:os'; +import { join, resolve, dirname } from 'node:path'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { manualTestsPlugin } from '../../src/manual-test-plugin/plugin.js'; +import type { Plugin } from 'vite'; + +type ConfigHook = Exclude; +type ResolveIdHook = Exclude; +type LoadHook = Exclude; +type TransformIndexHtmlHook = { + handler( html: string, context: { filename: string } ): string | undefined; +}; + +describe( 'manualTestsPlugin()', () => { + let workspaceRoot: string; + + beforeEach( async () => { + workspaceRoot = await mkdtemp( join( tmpdir(), 'ckeditor5-manual-test-plugin-' ) ); + vi.spyOn( process, 'cwd' ).mockReturnValue( workspaceRoot ); + } ); + + afterEach( async () => { + await rm( workspaceRoot, { recursive: true, force: true } ); + } ); + + test( 'uses provided broad manual test globs for page entries', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/foo.html' ), + createFile( 'packages/ckeditor5-foo/tests/manual/foo.js' ), + createFile( 'packages/ckeditor5-bar/tests/manual/bar.html' ), + createFile( 'packages/ckeditor5-bar/tests/manual/bar.js' ) + ] ); + + const plugin = manualTestsPlugin( [ 'packages/ckeditor5-foo/tests/manual/**/*' ] ); + const config = ( plugin.config as ConfigHook ).call( {}, {}, { command: 'build', mode: 'production' } ); + const input = config!.build!.rolldownOptions!.input as Array; + + expect( input ).to.include( join( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/foo.html' ) ); + expect( input ).not.to.include( join( workspaceRoot, 'packages/ckeditor5-bar/tests/manual/bar.html' ) ); + } ); + + test( 'exposes entries collected from provided broad manual test globs', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/foo.html' ), + createFile( 'packages/ckeditor5-foo/tests/manual/foo.ts' ), + createFile( 'packages/ckeditor5-bar/tests/manual/bar.html' ), + createFile( 'packages/ckeditor5-bar/tests/manual/bar.ts' ) + ] ); + + const plugin = manualTestsPlugin( [ 'packages/ckeditor5-foo/tests/manual/**/*' ] ); + const resolvedId = ( plugin.resolveId as ResolveIdHook ).call( {}, 'virtual:ckeditor5-manual-entries', undefined, {} ); + const source = ( plugin.load as LoadHook ).call( {}, resolvedId as string, {} ) as string; + + expect( source ).to.contain( '/packages/ckeditor5-foo/tests/manual/foo.html' ); + expect( source ).not.to.contain( '/packages/ckeditor5-bar/tests/manual/bar.html' ); + } ); + + test( 'rewrites the catalog script to a public file path', () => { + const plugin = manualTestsPlugin( [] ); + const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; + const catalogFilePath = resolve( import.meta.dirname, '../../theme/catalog.html' ); + const catalogScriptFilePath = resolve( import.meta.dirname, '../../theme/catalog.ts' ).replace( /\\/g, '/' ); + + expect( transformIndexHtml.handler( + '', + { filename: catalogFilePath } + ) ).to.equal( `` ); + } ); + + async function createFile( relativeFilePath: string ): Promise { + const filePath = join( workspaceRoot, relativeFilePath ); + + await mkdir( dirname( filePath ), { recursive: true } ); + await writeFile( filePath, '' ); + } +} ); diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts index 6c49840c6..14febe16c 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -import path from 'node:path'; +import { resolve } from 'node:path'; import { parse } from 'parse5'; import { describe, expect, test } from 'vitest'; import { getAttribute, isElementNode, query, queryAll, type Element } from '@parse5/tools'; @@ -11,15 +11,14 @@ import { createManualShellHtml } from '../../src/manual-test-plugin/shell-html.j import type { ManualPageEntry } from '../../src/manual-test-plugin/types.js'; describe( 'createManualShellHtml()', () => { - const shellTemplateFilePath = path.resolve( import.meta.dirname, '../../theme/shell.html' ); - const workspaceRoot = path.resolve( '/workspace' ); + const shellTemplateFilePath = resolve( import.meta.dirname, '../../theme/shell.html' ); + const workspaceRoot = resolve( '/workspace' ); const entry: ManualPageEntry = { displayName: 'Iframe test', htmlFilePath: '/packages/ckeditor5-foo/tests/manual/iframe.html', packageName: 'ckeditor5-foo', scriptFilePath: '/packages/ckeditor5-foo/tests/manual/iframe.js', - slug: 'iframe', - source: 'commercial' + slug: 'iframe' }; test( 'wraps manual test content during HTML transform to avoid runtime iframe reparenting', () => { diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts index 34431b7bd..89a14732c 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts @@ -3,62 +3,90 @@ * For licensing, see LICENSE.md. */ -import path from 'node:path'; -import { describe, expect, test } from 'vitest'; -import { getManualStaticAssetFilePath } from '../../src/manual-test-plugin/static-assets.js'; +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { + collectManualStaticAssets, + getManualStaticAssetFilePath +} from '../../src/manual-test-plugin/static-assets.js'; -describe( 'getManualStaticAssetFilePath()', () => { - const workspaceRoot = path.resolve( '/workspace' ); +describe( 'manual static assets', () => { + let workspaceRoot: string; - test( 'returns a workspace file path for a commercial manual test asset', () => { - expect( getManualStaticAssetFilePath( - '/packages/ckeditor5-foo/tests/manual/assets/image.png?v=1', - workspaceRoot - ) ).to.equal( path.resolve( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/assets/image.png' ) ); + beforeEach( async () => { + workspaceRoot = await mkdtemp( join( tmpdir(), 'ckeditor5-manual-static-assets-' ) ); } ); - test( 'returns a workspace file path for an OSS manual test asset', () => { - expect( getManualStaticAssetFilePath( - '/external/ckeditor5/packages/ckeditor5-bar/tests/manual/fixtures/data.json', - workspaceRoot - ) ).to.equal( path.resolve( workspaceRoot, 'external/ckeditor5/packages/ckeditor5-bar/tests/manual/fixtures/data.json' ) ); + afterEach( async () => { + await rm( workspaceRoot, { recursive: true, force: true } ); } ); - test( 'does not return files processed by the manual server', () => { - for ( const extension of [ 'html', 'js', 'md', 'ts' ] ) { - expect( getManualStaticAssetFilePath( - `/packages/ckeditor5-foo/tests/manual/test.${ extension }`, - workspaceRoot - ) ).to.be.null; - } + test( 'collects manual test assets from configured patterns', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/assets/image.png' ), + createFile( 'external/ckeditor5/packages/ckeditor5-bar/tests/manual/sample.jpg' ), + createFile( 'packages/ckeditor5-foo/tests/manual/test.html' ), + createFile( 'packages/ckeditor5-foo/tests/manual/test.js' ) + ] ); + + const staticAssets = collectManualStaticAssets( [ + 'packages/*/tests/manual/**/*', + 'external/ckeditor5/packages/*/tests/manual/**/*' + ], workspaceRoot ); + + expect( [ ...staticAssets.entries() ] ).to.deep.equal( [ + [ + '/external/ckeditor5/packages/ckeditor5-bar/tests/manual/sample.jpg', + join( workspaceRoot, 'external/ckeditor5/packages/ckeditor5-bar/tests/manual/sample.jpg' ) + ], + [ + '/packages/ckeditor5-foo/tests/manual/assets/image.png', + join( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/assets/image.png' ) + ] + ] ); } ); - test( 'does not handle Vite module requests', () => { + test( 'returns the collected file path for a request URL', () => { + const staticAssets = new Map( [ + [ '/packages/ckeditor5-foo/tests/manual/assets/image.png', '/workspace/packages/ckeditor5-foo/tests/manual/assets/image.png' ] + ] ); + expect( getManualStaticAssetFilePath( - '/packages/ckeditor5-foo/tests/manual/assets/image.png?import', - workspaceRoot - ) ).to.be.null; + '/packages/ckeditor5-foo/tests/manual/assets/image.png?v=1', + staticAssets + ) ).to.equal( '/workspace/packages/ckeditor5-foo/tests/manual/assets/image.png' ); } ); - test( 'does not return assets outside supported manual test directories', () => { + test( 'does not handle Vite module requests', () => { + const staticAssets = new Map( [ + [ '/packages/ckeditor5-foo/tests/manual/assets/image.png', '/workspace/packages/ckeditor5-foo/tests/manual/assets/image.png' ] + ] ); + expect( getManualStaticAssetFilePath( - '/packages/ckeditor5-foo/tests/automated/assets/image.png', - workspaceRoot + '/packages/ckeditor5-foo/tests/manual/assets/image.png?url', + staticAssets ) ).to.be.null; + } ); + + test( 'ignores unknown and invalid request URLs', () => { + const staticAssets = new Map( [ + [ '/packages/ckeditor5-foo/tests/manual/assets/image.png', '/workspace/packages/ckeditor5-foo/tests/manual/assets/image.png' ] + ] ); expect( getManualStaticAssetFilePath( - '/external/other-repository/packages/ckeditor5-bar/tests/manual/fixtures/data.json', - workspaceRoot + '/packages/ckeditor5-foo/tests/manual/missing.png', + staticAssets ) ).to.be.null; + expect( getManualStaticAssetFilePath( 'http://%', staticAssets ) ).to.be.null; + expect( getManualStaticAssetFilePath( undefined, staticAssets ) ).to.be.null; } ); - test( 'does not return paths with traversal segments', () => { - for ( const requestPath of [ - '/packages/ckeditor5-foo/tests/manual/../secret.png', - '/packages/ckeditor5-foo/tests/manual/%2e%2e/secret.png', - '/packages/ckeditor5-foo/tests/manual/..%5csecret.png' - ] ) { - expect( getManualStaticAssetFilePath( requestPath, workspaceRoot ) ).to.be.null; - } - } ); + async function createFile( relativeFilePath: string ): Promise { + const filePath = join( workspaceRoot, relativeFilePath ); + + await mkdir( dirname( filePath ), { recursive: true } ); + await writeFile( filePath, '' ); + } } ); diff --git a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts index 8da3890a0..d56cfd0cd 100644 --- a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts @@ -3,26 +3,20 @@ * For licensing, see LICENSE.md. */ -import os from 'node:os'; -import path from 'node:path'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { rawHtmlPlugin } from '../../src/raw-plugin/plugin.js'; -import type { PluginContext } from 'rollup'; +import type { PluginContext, ResolveIdResult } from 'rollup'; -interface RawPluginLoadResult { - code: string; - map: null; -} - -type ResolveIdHook = ( this: PluginContext, source: string, importer?: string ) => Promise; -type LoadHook = ( id: string ) => Promise; +type ResolveIdHook = ( this: PluginContext, source: string, importer?: string ) => Promise; describe( 'rawHtmlPlugin()', () => { let temporaryDirectory: string; beforeEach( async () => { - temporaryDirectory = await mkdtemp( path.join( os.tmpdir(), 'ckeditor5-raw-plugin-' ) ); + temporaryDirectory = await mkdtemp( join( tmpdir(), 'ckeditor5-raw-plugin-' ) ); } ); afterEach( async () => { @@ -33,44 +27,35 @@ describe( 'rawHtmlPlugin()', () => { expect( rawHtmlPlugin().enforce ).to.equal( 'pre' ); } ); - test( 'resolves imported HTML files as raw JavaScript modules by default', async () => { + test( 'resolves imported HTML files as Vite raw imports by default', async () => { const plugin = rawHtmlPlugin(); - const htmlFilePath = path.join( temporaryDirectory, 'template.html' ); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const htmlFilePath = join( temporaryDirectory, 'template.html' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; - const load = plugin.load as LoadHook; await writeFile( htmlFilePath, '
Text
' ); - const resolvedHtmlId = await resolveId.call( createPluginContext( htmlFilePath ), './template.html', importer ); - - expect( await load( resolvedHtmlId! ) ).to.deep.equal( { - code: 'export default "
Text
";', - map: null - } ); + expect( await resolveId.call( createPluginContext( `${ htmlFilePath }?raw` ), './template.html', importer ) ) + .to.equal( `${ htmlFilePath }?raw` ); } ); - test( 'strips resolved import queries before loading raw HTML', async () => { + test( 'delegates raw import resolution to Vite', async () => { const plugin = rawHtmlPlugin(); - const htmlFilePath = path.join( temporaryDirectory, 'template.html' ); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const htmlFilePath = join( temporaryDirectory, 'template.html' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; - const load = plugin.load as LoadHook; + const context = createPluginContext( `${ htmlFilePath }?raw` ); await writeFile( htmlFilePath, '

Template

' ); + await resolveId.call( context, './template.html', importer ); - const resolvedHtmlId = await resolveId.call( createPluginContext( `${ htmlFilePath }?v=1` ), './template.html', importer ); - - expect( await load( resolvedHtmlId! ) ).to.deep.equal( { - code: 'export default "

Template

";', - map: null - } ); + expect( context.resolve ).toHaveBeenCalledWith( './template.html?raw', importer, { skipSelf: true } ); } ); test( 'does not resolve SVG files by default', async () => { const plugin = rawHtmlPlugin(); - const filePath = path.join( temporaryDirectory, 'icon.svg' ); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const filePath = join( temporaryDirectory, 'icon.svg' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; await writeFile( filePath, '' ); @@ -78,19 +63,9 @@ describe( 'rawHtmlPlugin()', () => { expect( await resolveId.call( createPluginContext( filePath ), './icon.svg', importer ) ).to.be.null; } ); - test( 'does not load plain HTML files directly', async () => { - const plugin = rawHtmlPlugin(); - const filePath = path.join( temporaryDirectory, 'manual.html' ); - const load = plugin.load as LoadHook; - - await writeFile( filePath, '

Manual test page

' ); - - expect( await load( filePath ) ).to.be.null; - } ); - test( 'does not resolve HTML entry points without an importer', async () => { const plugin = rawHtmlPlugin(); - const filePath = path.join( temporaryDirectory, 'manual.html' ); + const filePath = join( temporaryDirectory, 'manual.html' ); const resolveId = plugin.resolveId as ResolveIdHook; expect( await resolveId.call( createPluginContext( filePath ), filePath ) ).to.be.null; @@ -98,7 +73,7 @@ describe( 'rawHtmlPlugin()', () => { test( 'does not resolve HTML imports that Vite cannot resolve', async () => { const plugin = rawHtmlPlugin(); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; expect( await resolveId.call( createPluginContext( null ), './missing.html', importer ) ).to.be.null; @@ -106,8 +81,8 @@ describe( 'rawHtmlPlugin()', () => { test( 'ignores files with unsupported extensions', async () => { const plugin = rawHtmlPlugin(); - const filePath = path.join( temporaryDirectory, 'script.js' ); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const filePath = join( temporaryDirectory, 'script.js' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; await writeFile( filePath, 'export default 1;' ); @@ -117,8 +92,8 @@ describe( 'rawHtmlPlugin()', () => { test( 'ignores imports with explicit request queries', async () => { const plugin = rawHtmlPlugin(); - const filePath = path.join( temporaryDirectory, 'template.html' ); - const importer = path.join( temporaryDirectory, 'manual.js' ); + const filePath = join( temporaryDirectory, 'template.html' ); + const importer = join( temporaryDirectory, 'manual.js' ); const resolveId = plugin.resolveId as ResolveIdHook; await writeFile( filePath, '

Query

' ); @@ -127,8 +102,8 @@ describe( 'rawHtmlPlugin()', () => { } ); } ); -function createPluginContext( resolvedId: string | null ): PluginContext { +function createPluginContext( resolvedId: string | null ): PluginContext & { resolve: ReturnType } { return { - resolve: async () => resolvedId ? { id: resolvedId } : null - } as unknown as PluginContext; + resolve: vi.fn( async () => resolvedId ) + } as unknown as PluginContext & { resolve: ReturnType }; } diff --git a/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts new file mode 100644 index 000000000..f3d05c7d7 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts @@ -0,0 +1,93 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { describe, expect, test, vi } from 'vitest'; +import { MANUAL_REFRESH_EVENT_NAME } from '../../src/constants.js'; +import { refreshPlugin } from '../../src/refresh-plugin/plugin.js'; +import type { ModuleNode } from 'vite'; + +interface TestHmrContext { + file: string; + modules: Array; + server: { + hot: { + send: ReturnType; + }; + }; +} + +interface TestModule { + type: ModuleNode[ 'type' ]; +} + +type TestHandleHotUpdateHook = ( context: TestHmrContext ) => unknown; + +describe( 'refreshPlugin()', () => { + test( 'applies only in the dev server', () => { + expect( refreshPlugin().apply ).to.equal( 'serve' ); + } ); + + test( 'keeps regular Vite HMR for CSS file updates', () => { + const handleHotUpdate = getHandleHotUpdate(); + const context = createHmrContext( '/workspace/theme/styles.css', [ createModule( 'js' ) ] ); + + expect( handleHotUpdate.call( {}, context ) ).to.be.undefined; + expect( context.server.hot.send ).not.toHaveBeenCalled(); + } ); + + test( 'shows the manual refresh prompt for non-CSS updates', () => { + const handleHotUpdate = getHandleHotUpdate(); + const context = createHmrContext( '/workspace/tests/manual/sample.ts', [ createModule( 'js' ) ] ); + + expect( handleHotUpdate.call( {}, context ) ).to.deep.equal( [] ); + expect( context.server.hot.send ).toHaveBeenCalledWith( { + type: 'custom', + event: MANUAL_REFRESH_EVENT_NAME + } ); + } ); + + test( 'shows the manual refresh prompt for full reload updates', () => { + const handleHotUpdate = getHandleHotUpdate(); + const context = createHmrContext( '/workspace/tests/manual/sample.html', [] ); + + expect( handleHotUpdate.call( {}, context ) ).to.deep.equal( [] ); + expect( context.server.hot.send ).toHaveBeenCalledWith( { + type: 'custom', + event: MANUAL_REFRESH_EVENT_NAME + } ); + } ); +} ); + +function getHandleHotUpdate(): TestHandleHotUpdateHook { + const hook = refreshPlugin().handleHotUpdate!; + + if ( typeof hook == 'function' ) { + return context => hook.call( {}, context as never ); + } + + return context => hook.handler.call( {}, context as never ); +} + +function createHmrContext( file: string, modules: Array ): TestHmrContext { + const context: TestHmrContext = { + file, + modules, + server: { + hot: { + send: vi.fn() + } + } + }; + + return context; +} + +function createModule( type: ModuleNode[ 'type' ] ): TestModule { + const module: TestModule = { + type + }; + + return module; +} diff --git a/packages/ckeditor5-dev-manual-server/tests/utils.ts b/packages/ckeditor5-dev-manual-server/tests/utils.ts new file mode 100644 index 000000000..d2536518d --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/utils.ts @@ -0,0 +1,58 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { getOptimizedPackageIncludes } from '../src/utils.js'; + +describe( 'getOptimizedPackageIncludes()', () => { + let temporaryDirectory: string; + + beforeEach( async () => { + temporaryDirectory = await mkdtemp( join( tmpdir(), 'ckeditor5-optimized-package-includes-' ) ); + } ); + + afterEach( async () => { + await rm( temporaryDirectory, { recursive: true, force: true } ); + } ); + + test( 'returns sorted unique package names', async () => { + await Promise.all( [ + createPackageJson( 'alpha', { name: '@ckeditor/ckeditor5-zeta' } ), + createPackageJson( 'bravo', { name: '@ckeditor/ckeditor5-alpha' } ), + createPackageJson( 'charlie', { name: '@ckeditor/ckeditor5-zeta' } ) + ] ); + + expect( getOptimizedPackageIncludes( [ join( temporaryDirectory, '*.json' ) ] ) ).to.deep.equal( [ + '@ckeditor/ckeditor5-alpha', + '@ckeditor/ckeditor5-zeta' + ] ); + } ); + + test( 'uses all provided globs', async () => { + await Promise.all( [ + createPackageJson( 'alpha', { name: '@ckeditor/ckeditor5-alpha' } ), + createPackageJson( 'bravo', { name: '@ckeditor/ckeditor5-bravo' } ), + createPackageJson( 'charlie', { name: '@ckeditor/ckeditor5-charlie' } ) + ] ); + + expect( getOptimizedPackageIncludes( [ + join( temporaryDirectory, 'alpha.json' ), + join( temporaryDirectory, 'bravo.json' ) + ] ) ).to.deep.equal( [ + '@ckeditor/ckeditor5-alpha', + '@ckeditor/ckeditor5-bravo' + ] ); + } ); + + function createPackageJson( directoryName: string, packageJson: Record ): Promise { + const packageJsonPath = join( temporaryDirectory, `${ directoryName }.json` ); + + return writeFile( packageJsonPath, JSON.stringify( packageJson ) ) + .then( () => packageJsonPath ); + } +} ); diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.css b/packages/ckeditor5-dev-manual-server/theme/catalog.css index c48f06604..c93b1bab5 100644 --- a/packages/ckeditor5-dev-manual-server/theme/catalog.css +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.css @@ -15,10 +15,6 @@ --text-subtle: #a3a3a3; --accent: #171717; --accent-ring: rgb(23 23 23 / 10%); - --tag-commercial-bg: #f5f3ff; - --tag-commercial-fg: #6d28d9; - --tag-oss-bg: #f0fdf4; - --tag-oss-fg: #15803d; --radius-lg: 12px; --radius-md: 8px; --radius-sm: 6px; @@ -36,10 +32,6 @@ --text-subtle: #737373; --accent: #fafafa; --accent-ring: rgb(250 250 250 / 15%); - --tag-commercial-bg: rgb(124 58 237 / 15%); - --tag-commercial-fg: #c4b5fd; - --tag-oss-bg: rgb(34 197 94 / 15%); - --tag-oss-fg: #86efac; } } @@ -199,27 +191,6 @@ a { white-space: nowrap; } -.pkg__tag { - flex-shrink: 0; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.05em; - text-transform: uppercase; - padding: 3px 8px; - border-radius: 999px; - line-height: 1.4; -} - -.pkg__tag--commercial { - color: var(--tag-commercial-fg); - background: var(--tag-commercial-bg); -} - -.pkg__tag--oss { - color: var(--tag-oss-fg); - background: var(--tag-oss-bg); -} - .pkg__count { flex-shrink: 0; min-width: 24px; diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.html b/packages/ckeditor5-dev-manual-server/theme/catalog.html index d069bdec4..3c93a9816 100644 --- a/packages/ckeditor5-dev-manual-server/theme/catalog.html +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.html @@ -37,7 +37,6 @@

CKEditor 5 manual tests

-
    diff --git a/packages/ckeditor5-dev-manual-server/theme/catalog.ts b/packages/ckeditor5-dev-manual-server/theme/catalog.ts index 761be7901..f0c0cf5d8 100644 --- a/packages/ckeditor5-dev-manual-server/theme/catalog.ts +++ b/packages/ckeditor5-dev-manual-server/theme/catalog.ts @@ -86,14 +86,10 @@ function renderGroups( entries: Array ): Array { * Renders a single package card with all tests belonging to that package. */ function renderPackageCard( packageName: string, entries: Array ): HTMLElement { - const source = entries[ 0 ].source; const packageCard = cloneTemplateElement( 'manual-package-template' ); - const sourceTag = packageCard.querySelector( '.pkg__tag' )!; const list = packageCard.querySelector( '.pkg__list' )!; packageCard.querySelector( '.pkg__name' )!.textContent = packageName; - sourceTag.textContent = source; - sourceTag.classList.add( `pkg__tag--${ source }` ); packageCard.querySelector( '.pkg__count' )!.textContent = String( entries.length ); list.replaceChildren( ...entries.map( entry => renderTestItem( entry ) ) ); diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts index 573f5e082..de731a15e 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.ts +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -18,7 +18,7 @@ interface ViteHotContextLike { const globalTarget = window as any; renderManual(); -ensureManualTestContainer(); +setupManualTestContainerState(); setupManualRefreshPrompt(); // In direct HTML manual tests, `id="editor"` creates `window.editor` as a named DOM property. @@ -69,33 +69,12 @@ function renderManual(): void { /** * Sets up shell-related state for the manual test container injected by the HTML transform. */ -function ensureManualTestContainer(): void { +function setupManualTestContainerState(): void { document.body.classList.add( 'shell-enabled' ); if ( document.querySelector( '.shell-instructions' ) ) { document.body.classList.add( 'shell-has-instructions' ); } - - if ( document.querySelector( '.manual-test-container' ) ) { - return; - } - - // The source transform injects this container. Keep this fallback for local setups using stale built plugin files. - const container = document.createElement( 'div' ); - container.className = 'manual-test-container'; - - for ( const node of Array.from( document.body.childNodes ) ) { - if ( - node instanceof Element && - ( node.classList.contains( 'shell' ) || node.classList.contains( 'shell-instructions' ) ) - ) { - continue; - } - - container.appendChild( node ); - } - - document.body.appendChild( container ); } /** From 0aa09f50b0d4618807e56854b8306355ecf023b1 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:21:36 +0200 Subject: [PATCH 10/17] Address PR comments and fix CI issue. --- package.json | 2 +- .../src/manual-test-plugin/plugin.ts | 2 - pnpm-lock.yaml | 192 +++++------------- 3 files changed, 53 insertions(+), 143 deletions(-) diff --git a/package.json b/package.json index c07c5e286..9ce0db947 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "semver": "^7.7.4", "typescript": "5.5.4", "upath": "^2.0.1", - "vite": ">=7.3.2 <8", + "vite": "^8.0.13", "vitest": "^4.1.2" }, "scripts": { diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts index 89985c3da..dbe5d2396 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -17,7 +17,6 @@ interface ManualTestClientEntry { displayName: string; href: string; packageName: string; - packageShortName: string; slug: string; } @@ -50,7 +49,6 @@ export function manualTestsPlugin( manualTestPatterns: Array ): Plugin { displayName: entry.displayName, href: entry.htmlFilePath, packageName: entry.packageName, - packageShortName: entry.packageName.replace( /^ckeditor5-/, '' ), slug: entry.slug } ) ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2697f3312..e4079f451 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,11 +73,11 @@ importers: specifier: ^2.0.1 version: 2.0.1 vite: - specifier: '>=7.3.2 <8' - version: 7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) + specifier: ^8.0.13 + version: 8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0) vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-build-tools: dependencies: @@ -135,7 +135,7 @@ importers: version: 4.41.0 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-bump-year: dependencies: @@ -181,7 +181,7 @@ importers: version: 1.0.3 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-ci: dependencies: @@ -197,7 +197,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-dependency-checker: dependencies: @@ -225,7 +225,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-docs: dependencies: @@ -250,7 +250,7 @@ importers: version: 5.0.3 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-license-checker: dependencies: @@ -269,7 +269,7 @@ importers: version: 1.0.3 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-manual-server: dependencies: @@ -358,7 +358,7 @@ importers: version: 5.5.0 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-stale-bot: dependencies: @@ -386,7 +386,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-tests: dependencies: @@ -482,7 +482,7 @@ importers: version: 0.4.0 karma-webpack: specifier: ^5.0.1 - version: 5.0.1(webpack@5.107.2(lightningcss@1.32.0)) + version: 5.0.1(webpack@5.107.2(esbuild@0.27.7)) minimatch: specifier: ^10.2.5 version: 10.2.5 @@ -518,14 +518,14 @@ importers: version: 2.0.1 webpack: specifier: ^5.105.4 - version: 5.107.2(lightningcss@1.32.0) + version: 5.107.2(esbuild@0.27.7) devDependencies: jest-extended: specifier: ^5.0.3 version: 5.0.3 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-translations: dependencies: @@ -562,7 +562,7 @@ importers: devDependencies: vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-utils: dependencies: @@ -574,7 +574,7 @@ importers: version: 2.0.41 babel-loader: specifier: ^10.1.1 - version: 10.1.1(@babel/core@7.29.7)(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 10.1.1(@babel/core@7.29.7)(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) cli-cursor: specifier: ^5.0.0 version: 5.0.0 @@ -583,10 +583,10 @@ importers: version: 3.4.0 css-loader: specifier: ^7.1.4 - version: 7.1.4(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 7.1.4(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) esbuild-loader: specifier: ^4.4.3 - version: 4.4.3(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 4.4.3(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) glob: specifier: ^13.0.6 version: 13.0.6 @@ -598,13 +598,13 @@ importers: version: 1.32.0 mini-css-extract-plugin: specifier: ^2.10.2 - version: 2.10.2(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 2.10.2(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) pacote: specifier: ^21.5.0 version: 21.5.0 raw-loader: specifier: ^4.0.2 - version: 4.0.2(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 4.0.2(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) shelljs: specifier: ^0.10.0 version: 0.10.0 @@ -613,7 +613,7 @@ importers: version: 3.36.0 style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + version: 4.0.0(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) through2: specifier: ^4.0.2 version: 4.0.2 @@ -635,7 +635,7 @@ importers: version: 1.0.3 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/ckeditor5-dev-web-crawler: dependencies: @@ -657,7 +657,7 @@ importers: version: 2.0.1 vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages/typedoc-plugins: dependencies: @@ -679,7 +679,7 @@ importers: version: 0.7.3(typedoc@0.28.19(typescript@5.5.4)) vitest: specifier: ^4.1.2 - version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) packages: @@ -5754,46 +5754,6 @@ packages: peerDependencies: vue: '>=3.2.13' - vite@7.3.3: - resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@8.0.14: resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7173,7 +7133,8 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.8': + optional: true '@types/estree@1.0.9': {} @@ -7383,7 +7344,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) + vitest: 4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)) '@vitest/expect@4.1.7': dependencies: @@ -7394,14 +7355,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.7(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.7 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) - '@vitest/mocker@4.1.7(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.7 @@ -7673,12 +7626,12 @@ snapshots: b4a@1.8.1: {} - babel-loader@10.1.1(@babel/core@7.29.7)(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + babel-loader@10.1.1(@babel/core@7.29.7)(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@babel/core': 7.29.7 find-up: 5.0.0 optionalDependencies: - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) babel-plugin-istanbul@7.0.1: dependencies: @@ -8045,7 +7998,7 @@ snapshots: dependencies: postcss: 8.5.15 - css-loader@7.1.4(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + css-loader@7.1.4(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: icss-utils: 5.1.0(postcss@8.5.15) postcss: 8.5.15 @@ -8056,7 +8009,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.8.1 optionalDependencies: - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) css-select@5.2.2: dependencies: @@ -8405,12 +8358,12 @@ snapshots: es6-error@4.1.1: {} - esbuild-loader@4.4.3(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + esbuild-loader@4.4.3(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: esbuild: 0.27.7 get-tsconfig: 4.14.0 loader-utils: 2.0.4 - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) webpack-sources: 3.5.0 esbuild@0.27.7: @@ -9284,11 +9237,11 @@ snapshots: dependencies: graceful-fs: 4.2.11 - karma-webpack@5.0.1(webpack@5.107.2(lightningcss@1.32.0)): + karma-webpack@5.0.1(webpack@5.107.2(esbuild@0.27.7)): dependencies: glob: 7.2.3 minimatch: 9.0.9 - webpack: 5.107.2(lightningcss@1.32.0) + webpack: 5.107.2(esbuild@0.27.7) webpack-merge: 4.2.2 karma@6.4.4: @@ -9918,11 +9871,11 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.10.2(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + mini-css-extract-plugin@2.10.2(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) minimatch@10.2.5: dependencies: @@ -10665,11 +10618,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-loader@4.0.2(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + raw-loader@4.0.2(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) react-is@18.3.1: {} @@ -10848,6 +10801,7 @@ snapshots: '@rollup/rollup-win32-x64-gnu': 4.60.4 '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 + optional: true run-async@4.0.6: {} @@ -11165,9 +11119,9 @@ snapshots: strip-json-comments@3.1.1: {} - style-loader@4.0.0(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + style-loader@4.0.0(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) stylehacks@7.0.11(postcss@8.5.15): dependencies: @@ -11249,26 +11203,27 @@ snapshots: - bare-abort-controller - react-native-b4a - terser-webpack-plugin@5.6.0(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)): + terser-webpack-plugin@5.6.0(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.48.0 - webpack: 5.107.2(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15) optionalDependencies: + esbuild: 0.27.7 lightningcss: 1.32.0 postcss: 8.5.15 - terser-webpack-plugin@5.6.0(lightningcss@1.32.0)(webpack@5.107.2(lightningcss@1.32.0)): + terser-webpack-plugin@5.6.0(esbuild@0.27.7)(webpack@5.107.2(esbuild@0.27.7)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.48.0 - webpack: 5.107.2(lightningcss@1.32.0) + webpack: 5.107.2(esbuild@0.27.7) optionalDependencies: - lightningcss: 1.32.0 + esbuild: 0.27.7 terser@5.48.0: dependencies: @@ -11480,21 +11435,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0): - dependencies: - esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.15 - rollup: 4.60.4 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 22.19.19 - fsevents: 2.3.3 - lightningcss: 1.32.0 - terser: 5.48.0 - yaml: 2.9.0 - vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -11509,34 +11449,6 @@ snapshots: terser: 5.48.0 yaml: 2.9.0 - vitest@4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(vite@7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.7 - '@vitest/runner': 4.1.7 - '@vitest/snapshot': 4.1.7 - '@vitest/spy': 4.1.7 - '@vitest/utils': 4.1.7 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 - tinybench: 2.9.0 - tinyexec: 1.2.2 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.3(@types/node@22.19.19)(lightningcss@1.32.0)(terser@5.48.0)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.19.19 - '@vitest/coverage-v8': 4.1.7(vitest@4.1.7) - transitivePeerDependencies: - - msw - vitest@4.1.7(@types/node@22.19.19)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@22.19.19)(esbuild@0.27.7)(terser@5.48.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.7 @@ -11592,7 +11504,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.107.2(lightningcss@1.32.0): + webpack@5.107.2(esbuild@0.27.7): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -11614,7 +11526,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(lightningcss@1.32.0)(webpack@5.107.2(lightningcss@1.32.0)) + terser-webpack-plugin: 5.6.0(esbuild@0.27.7)(webpack@5.107.2(esbuild@0.27.7)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -11631,7 +11543,7 @@ snapshots: - postcss - uglify-js - webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15): + webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -11653,7 +11565,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(lightningcss@1.32.0)(postcss@8.5.15)) + terser-webpack-plugin: 5.6.0(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.2(esbuild@0.27.7)(lightningcss@1.32.0)(postcss@8.5.15)) watchpack: 2.5.1 webpack-sources: 3.5.0 transitivePeerDependencies: From 1b7e3b607b183aa9b71e5638eef9a495eaba5561 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:27:10 +0200 Subject: [PATCH 11/17] Fix type errors. --- .../src/manual-test-plugin/static-assets.ts | 2 +- .../tests/manual-test-plugin/plugin.ts | 20 ++++++++++++------- .../tests/raw-plugin/plugin.ts | 14 ++++++------- .../tests/refresh-plugin/plugin.ts | 4 ++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts index 949df2273..01587ca31 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts @@ -52,7 +52,7 @@ export function createManualStaticAssetsMiddleware( staticAssets: Map; -type ResolveIdHook = Exclude; -type LoadHook = Exclude; +type ConfigHook = ( this: unknown, config: Record, env: { command: 'build'; mode: string } ) => TestConfig; +type ResolveIdHook = ( this: unknown, source: string, importer: string | undefined, options: Record ) => string | null; +type LoadHook = ( this: unknown, id: string, options: Record ) => string | null; +type TestConfig = { + build?: { + rolldownOptions?: { + input?: unknown; + }; + }; +}; type TransformIndexHtmlHook = { handler( html: string, context: { filename: string } ): string | undefined; }; @@ -38,7 +44,7 @@ describe( 'manualTestsPlugin()', () => { ] ); const plugin = manualTestsPlugin( [ 'packages/ckeditor5-foo/tests/manual/**/*' ] ); - const config = ( plugin.config as ConfigHook ).call( {}, {}, { command: 'build', mode: 'production' } ); + const config = ( plugin.config as unknown as ConfigHook ).call( {}, {}, { command: 'build', mode: 'production' } ); const input = config!.build!.rolldownOptions!.input as Array; expect( input ).to.include( join( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/foo.html' ) ); @@ -54,8 +60,8 @@ describe( 'manualTestsPlugin()', () => { ] ); const plugin = manualTestsPlugin( [ 'packages/ckeditor5-foo/tests/manual/**/*' ] ); - const resolvedId = ( plugin.resolveId as ResolveIdHook ).call( {}, 'virtual:ckeditor5-manual-entries', undefined, {} ); - const source = ( plugin.load as LoadHook ).call( {}, resolvedId as string, {} ) as string; + const resolvedId = ( plugin.resolveId as unknown as ResolveIdHook ).call( {}, 'virtual:ckeditor5-manual-entries', undefined, {} ); + const source = ( plugin.load as unknown as LoadHook ).call( {}, resolvedId as string, {} ) as string; expect( source ).to.contain( '/packages/ckeditor5-foo/tests/manual/foo.html' ); expect( source ).not.to.contain( '/packages/ckeditor5-bar/tests/manual/bar.html' ); diff --git a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts index d56cfd0cd..293c8bd2a 100644 --- a/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/raw-plugin/plugin.ts @@ -31,7 +31,7 @@ describe( 'rawHtmlPlugin()', () => { const plugin = rawHtmlPlugin(); const htmlFilePath = join( temporaryDirectory, 'template.html' ); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; await writeFile( htmlFilePath, '
    Text
    ' ); @@ -43,7 +43,7 @@ describe( 'rawHtmlPlugin()', () => { const plugin = rawHtmlPlugin(); const htmlFilePath = join( temporaryDirectory, 'template.html' ); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; const context = createPluginContext( `${ htmlFilePath }?raw` ); await writeFile( htmlFilePath, '

    Template

    ' ); @@ -56,7 +56,7 @@ describe( 'rawHtmlPlugin()', () => { const plugin = rawHtmlPlugin(); const filePath = join( temporaryDirectory, 'icon.svg' ); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; await writeFile( filePath, '' ); @@ -66,7 +66,7 @@ describe( 'rawHtmlPlugin()', () => { test( 'does not resolve HTML entry points without an importer', async () => { const plugin = rawHtmlPlugin(); const filePath = join( temporaryDirectory, 'manual.html' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; expect( await resolveId.call( createPluginContext( filePath ), filePath ) ).to.be.null; } ); @@ -74,7 +74,7 @@ describe( 'rawHtmlPlugin()', () => { test( 'does not resolve HTML imports that Vite cannot resolve', async () => { const plugin = rawHtmlPlugin(); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; expect( await resolveId.call( createPluginContext( null ), './missing.html', importer ) ).to.be.null; } ); @@ -83,7 +83,7 @@ describe( 'rawHtmlPlugin()', () => { const plugin = rawHtmlPlugin(); const filePath = join( temporaryDirectory, 'script.js' ); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; await writeFile( filePath, 'export default 1;' ); @@ -94,7 +94,7 @@ describe( 'rawHtmlPlugin()', () => { const plugin = rawHtmlPlugin(); const filePath = join( temporaryDirectory, 'template.html' ); const importer = join( temporaryDirectory, 'manual.js' ); - const resolveId = plugin.resolveId as ResolveIdHook; + const resolveId = plugin.resolveId as unknown as ResolveIdHook; await writeFile( filePath, '

    Query

    ' ); diff --git a/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts index f3d05c7d7..b49544962 100644 --- a/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts @@ -64,10 +64,10 @@ function getHandleHotUpdate(): TestHandleHotUpdateHook { const hook = refreshPlugin().handleHotUpdate!; if ( typeof hook == 'function' ) { - return context => hook.call( {}, context as never ); + return context => hook.call( {} as never, context as never ); } - return context => hook.handler.call( {}, context as never ); + return context => hook.handler.call( {} as never, context as never ); } function createHmrContext( file: string, modules: Array ): TestHmrContext { From 57795472a3d854fb9f961b8f4cae2e6ac3895661 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:44:11 +0200 Subject: [PATCH 12/17] Address PR feedback. --- .../ckeditor5-dev-manual-server/package.json | 3 -- .../src/manual-test-plugin/shell-html.ts | 32 +++++++++++++++++++ .../tests/manual-test-plugin/shell-html.ts | 25 +++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-dev-manual-server/package.json b/packages/ckeditor5-dev-manual-server/package.json index 29482a8d2..5ad70724b 100644 --- a/packages/ckeditor5-dev-manual-server/package.json +++ b/packages/ckeditor5-dev-manual-server/package.json @@ -23,9 +23,6 @@ "dist", "theme" ], - "bin": { - "ckeditor5-dev-manual-server": "dist/index.js" - }, "scripts": { "build": "rolldown -c rolldown.config.ts", "dev": "rolldown -c rolldown.config.ts --watch", diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts index 02364c81e..bdbd6c5e9 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts @@ -61,6 +61,8 @@ export function createManualShellHtml( { const testScriptElement = getRequiredElementByAttribute( shellHead, 'data-manual-test-script' ); const testScriptFileName = posix.basename( entry.scriptFilePath ); + removeManualTestScriptNodes( manualDocument, testScriptFileName ); + setTextContent( shellTitle, ( manualTitle ? getTextContent( manualTitle ) : '' ).trim() || entry.displayName ); setAttribute( shellScriptElement, 'src', shellScriptPublicPath ); removeAttribute( shellScriptElement, 'data-manual-shell-script' ); @@ -117,6 +119,36 @@ function createManualTestContainer( manualBody: ParentNode ): Element { return container; } +function removeManualTestScriptNodes( parent: ParentNode, testScriptFileName: string ): void { + for ( const node of [ ...parent.childNodes ] ) { + if ( !isElementNode( node ) ) { + continue; + } + + if ( isManualTestScriptNode( node, testScriptFileName ) ) { + removeNode( node ); + + continue; + } + + removeManualTestScriptNodes( node, testScriptFileName ); + } +} + +function isManualTestScriptNode( node: Element, testScriptFileName: string ): boolean { + if ( node.tagName != 'script' ) { + return false; + } + + const source = getAttribute( node, 'src' ); + + if ( !source ) { + return false; + } + + return posix.basename( source.split( '?' )[ 0 ]!.split( '#' )[ 0 ]! ) == testScriptFileName; +} + function getRequiredElementByTagName( root: Node, tagName: string ): Element { return query( root, candidate => isElementNode( candidate ) && candidate.tagName == tagName )!; } diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts index 14febe16c..f95103722 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts @@ -44,4 +44,29 @@ describe( 'createManualShellHtml()', () => { expect( iframe ).not.to.be.null; expect( getAttribute( iframe!, 'src' ) ).to.equal( 'assets/frame.html' ); } ); + + test( 'keeps only the shell-injected manual test script', () => { + const html = createManualShellHtml( { + entry, + html: [ + '', + '', + '', + '', + '', + '', + '' + ].join( '' ), + shellScriptPublicPath: '/theme/shell.ts', + shellTemplateFilePath, + workspaceRoot + } ); + const document = parse( html ); + const scriptSources = [ ...queryAll( document, node => isElementNode( node ) && node.tagName == 'script' ) ] + .map( script => getAttribute( script, 'src' ) ) + .filter( ( source ): source is string => Boolean( source ) ); + + expect( scriptSources.filter( source => source == './iframe.js' ) ).to.have.length( 1 ); + expect( scriptSources ).to.include( './helper.js' ); + } ); } ); From 880cbd16388850295ef7d02ea1dfba9fdcd0b161 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:44:35 +0200 Subject: [PATCH 13/17] Improve code coverage. --- .../manual-test-plugin/parse-markdown.ts | 61 +++++++++++++ .../tests/manual-test-plugin/plugin.ts | 64 +++++++++++++- .../tests/manual-test-plugin/static-assets.ts | 87 ++++++++++++++++++- 3 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/parse-markdown.ts diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/parse-markdown.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/parse-markdown.ts new file mode 100644 index 000000000..1fd3b23cf --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/parse-markdown.ts @@ -0,0 +1,61 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { loadRenderedInstructions } from '../../src/manual-test-plugin/parse-markdown.js'; +import type { ManualPageEntry } from '../../src/manual-test-plugin/types.js'; + +describe( 'loadRenderedInstructions()', () => { + let workspaceRoot: string; + + beforeEach( async () => { + workspaceRoot = await mkdtemp( join( tmpdir(), 'ckeditor5-manual-instructions-' ) ); + } ); + + afterEach( async () => { + await rm( workspaceRoot, { recursive: true, force: true } ); + } ); + + test( 'returns an empty string for entries without instructions', () => { + expect( loadRenderedInstructions( createEntry(), workspaceRoot ) ).to.equal( '' ); + } ); + + test( 'returns an empty string for empty instructions', async () => { + await createFile( 'packages/ckeditor5-foo/tests/manual/sample.md', ' \n' ); + + expect( loadRenderedInstructions( createEntry( { + instructionsFilePath: '/packages/ckeditor5-foo/tests/manual/sample.md' + } ), workspaceRoot ) ).to.equal( '' ); + } ); + + test( 'renders markdown instructions to HTML', async () => { + await createFile( 'packages/ckeditor5-foo/tests/manual/sample.md', '## Steps\n\n- Click **Bold**' ); + + expect( loadRenderedInstructions( createEntry( { + instructionsFilePath: '/packages/ckeditor5-foo/tests/manual/sample.md' + } ), workspaceRoot ) ).to.equal( '

    Steps

    \n
      \n
    • Click Bold
    • \n
    ' ); + } ); + + async function createFile( relativeFilePath: string, content: string ): Promise { + const filePath = join( workspaceRoot, relativeFilePath ); + + await mkdir( dirname( filePath ), { recursive: true } ); + await writeFile( filePath, content ); + } +} ); + +function createEntry( overrides: Partial = {} ): ManualPageEntry { + return { + displayName: 'Sample', + htmlFilePath: '/packages/ckeditor5-foo/tests/manual/sample.html', + packageName: 'ckeditor5-foo', + scriptFilePath: '/packages/ckeditor5-foo/tests/manual/sample.js', + slug: 'sample', + ...overrides + }; +} diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts index 1808cecd2..52d428a50 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts @@ -12,6 +12,7 @@ import { manualTestsPlugin } from '../../src/manual-test-plugin/plugin.js'; type ConfigHook = ( this: unknown, config: Record, env: { command: 'build'; mode: string } ) => TestConfig; type ResolveIdHook = ( this: unknown, source: string, importer: string | undefined, options: Record ) => string | null; type LoadHook = ( this: unknown, id: string, options: Record ) => string | null; +type ServerHook = ( server: TestServer ) => void; type TestConfig = { build?: { rolldownOptions?: { @@ -22,6 +23,11 @@ type TestConfig = { type TransformIndexHtmlHook = { handler( html: string, context: { filename: string } ): string | undefined; }; +type TestServer = { + middlewares: { + use: ReturnType; + }; +}; describe( 'manualTestsPlugin()', () => { let workspaceRoot: string; @@ -67,6 +73,25 @@ describe( 'manualTestsPlugin()', () => { expect( source ).not.to.contain( '/packages/ckeditor5-bar/tests/manual/bar.html' ); } ); + test( 'returns null for unknown virtual module requests', () => { + const plugin = manualTestsPlugin( [] ); + + expect( ( plugin.resolveId as unknown as ResolveIdHook ).call( {}, 'virtual:other', undefined, {} ) ).to.be.null; + expect( ( plugin.load as unknown as LoadHook ).call( {}, 'virtual:other', {} ) ).to.be.null; + } ); + + test( 'registers manual test middlewares for dev and preview servers', () => { + const plugin = manualTestsPlugin( [] ); + const devServer = createServer(); + const previewServer = createServer(); + + ( plugin.configureServer as unknown as ServerHook )( devServer ); + ( plugin.configurePreviewServer as unknown as ServerHook )( previewServer ); + + expect( devServer.middlewares.use ).toHaveBeenCalledTimes( 2 ); + expect( previewServer.middlewares.use ).toHaveBeenCalledTimes( 2 ); + } ); + test( 'rewrites the catalog script to a public file path', () => { const plugin = manualTestsPlugin( [] ); const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; @@ -79,10 +104,45 @@ describe( 'manualTestsPlugin()', () => { ) ).to.equal( `` ); } ); - async function createFile( relativeFilePath: string ): Promise { + test( 'passes through HTML files that are not manual pages', () => { + const plugin = manualTestsPlugin( [] ); + const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; + + expect( transformIndexHtml.handler( '

    Sample

    ', { + filename: join( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/missing.html' ) + } ) ).to.be.undefined; + } ); + + test( 'wraps manual page HTML with the shell', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/foo.html', 'Foo

    Manual test

    ' ), + createFile( 'packages/ckeditor5-foo/tests/manual/foo.js' ) + ] ); + + const plugin = manualTestsPlugin( [ 'packages/ckeditor5-foo/tests/manual/**/*' ] ); + const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; + const html = transformIndexHtml.handler( + 'Foo

    Manual test

    ', + { filename: join( workspaceRoot, 'packages/ckeditor5-foo/tests/manual/foo.html' ) } + )!; + + expect( html ).to.contain( 'manual-test-container' ); + expect( html ).to.contain( 'Foo' ); + expect( html ).to.contain( '

    Manual test

    ' ); + } ); + + async function createFile( relativeFilePath: string, content = '' ): Promise { const filePath = join( workspaceRoot, relativeFilePath ); await mkdir( dirname( filePath ), { recursive: true } ); - await writeFile( filePath, '' ); + await writeFile( filePath, content ); } } ); + +function createServer(): TestServer { + return { + middlewares: { + use: vi.fn() + } + }; +} diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts index 89a14732c..c9ac81c9c 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts @@ -5,10 +5,12 @@ import { tmpdir } from 'node:os'; import { join, dirname } from 'node:path'; +import { Writable } from 'node:stream'; import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { collectManualStaticAssets, + createManualStaticAssetsMiddleware, getManualStaticAssetFilePath } from '../../src/manual-test-plugin/static-assets.js'; @@ -83,10 +85,89 @@ describe( 'manual static assets', () => { expect( getManualStaticAssetFilePath( undefined, staticAssets ) ).to.be.null; } ); - async function createFile( relativeFilePath: string ): Promise { + test( 'serves collected static assets', async () => { + const filePath = await createFile( 'packages/ckeditor5-foo/tests/manual/assets/image.svg', '' ); + const response = createResponse(); + const next = vi.fn(); + const middleware = createManualStaticAssetsMiddleware( new Map( [ + [ '/packages/ckeditor5-foo/tests/manual/assets/image.svg', filePath ] + ] ) ); + const finished = new Promise( resolve => response.on( 'finish', resolve ) ); + + middleware( { + method: 'GET', + url: '/packages/ckeditor5-foo/tests/manual/assets/image.svg' + } as never, response as never, next ); + + await finished; + + expect( next ).not.toHaveBeenCalled(); + expect( response.statusCode ).to.equal( 200 ); + expect( response.setHeader ).toHaveBeenCalledWith( 'Content-Length', 11 ); + expect( response.setHeader ).toHaveBeenCalledWith( 'Content-Type', 'image/svg+xml' ); + expect( response.getBody() ).to.equal( '' ); + } ); + + test( 'ends HEAD requests without streaming the static asset body', async () => { + const filePath = await createFile( 'packages/ckeditor5-foo/tests/manual/assets/data.json', '{ "ok": true }' ); + const response = createResponse(); + const next = vi.fn(); + const middleware = createManualStaticAssetsMiddleware( new Map( [ + [ '/packages/ckeditor5-foo/tests/manual/assets/data.json', filePath ] + ] ) ); + const finished = new Promise( resolve => response.on( 'finish', resolve ) ); + + middleware( { + method: 'HEAD', + url: '/packages/ckeditor5-foo/tests/manual/assets/data.json' + } as never, response as never, next ); + + await finished; + + expect( next ).not.toHaveBeenCalled(); + expect( response.setHeader ).toHaveBeenCalledWith( 'Content-Type', 'application/json; charset=utf-8' ); + expect( response.getBody() ).to.equal( '' ); + } ); + + test( 'passes through unsupported static asset requests', () => { + const middleware = createManualStaticAssetsMiddleware( new Map() ); + const response = createResponse(); + const next = vi.fn(); + + middleware( { method: 'POST', url: '/asset.png' } as never, response as never, next ); + + expect( next ).toHaveBeenCalledOnce(); + } ); + + async function createFile( relativeFilePath: string, content = '' ): Promise { const filePath = join( workspaceRoot, relativeFilePath ); await mkdir( dirname( filePath ), { recursive: true } ); - await writeFile( filePath, '' ); + await writeFile( filePath, content ); + + return filePath; } } ); + +function createResponse(): Writable & { + statusCode?: number; + setHeader: ReturnType; + getBody(): string; +} { + const chunks: Array = []; + const response = new Writable( { + write( chunk: Buffer, _encoding, callback ) { + chunks.push( Buffer.from( chunk ) ); + callback(); + } + } ) as Writable & { + statusCode?: number; + setHeader: ReturnType; + getBody(): string; + }; + + response.setHeader = vi.fn(); + response.getBody = () => Buffer.concat( chunks ).toString( 'utf8' ); + + return response; +} From f019776b3c1ef97d83a259a25304895d2c2faccc Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:49:05 +0200 Subject: [PATCH 14/17] Address PR feedback. --- .../src/manual-test-plugin/plugin.ts | 4 ++-- .../src/manual-test-plugin/static-assets.ts | 2 +- .../tests/manual-test-plugin/plugin.ts | 12 ++++++++++++ .../tests/manual-test-plugin/static-assets.ts | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts index dbe5d2396..d78cf106b 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/plugin.ts @@ -8,7 +8,7 @@ import { dirname, resolve, relative } from 'node:path'; import { collectManualPages } from './collect-pages.js'; import { collectManualStaticAssets, createManualStaticAssetsMiddleware } from './static-assets.js'; import { createManualShellHtml } from './shell-html.js'; -import { toPublicFilePath, toPublicSpecifier } from '../utils.js'; +import { toPosixPath, toPublicFilePath, toPublicSpecifier } from '../utils.js'; import type { Plugin } from 'vite'; import type { ManualPageEntry } from './types.js'; export type { ManualData } from './types.js'; @@ -96,7 +96,7 @@ export function manualTestsPlugin( manualTestPatterns: Array ): Plugin { order: 'pre', handler( html, context ) { - if ( context.filename == MANUAL_CATALOG_FILE_PATH ) { + if ( toPosixPath( context.filename ) == toPosixPath( MANUAL_CATALOG_FILE_PATH ) ) { return html.replace( './catalog.ts', manualCatalogScriptPublicPath ); } diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts index 01587ca31..525d4f4f6 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/static-assets.ts @@ -8,7 +8,7 @@ import { globSync, statSync, createReadStream } from 'node:fs'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { toPublicSpecifier } from '../utils.js'; -const PROCESSED_MANUAL_TEST_EXTENSIONS = new Set( [ '.html', '.js', '.md', '.ts' ] ); +const PROCESSED_MANUAL_TEST_EXTENSIONS = new Set( [ '.css', '.html', '.js', '.md', '.ts' ] ); const VITE_MODULE_QUERY_PARAMETERS = new Set( [ 'import', 'raw', diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts index 52d428a50..e24110ef6 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts @@ -104,6 +104,18 @@ describe( 'manualTestsPlugin()', () => { ) ).to.equal( `` ); } ); + test( 'rewrites the catalog script when the context filename uses Windows separators', () => { + const plugin = manualTestsPlugin( [] ); + const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; + const catalogFilePath = resolve( import.meta.dirname, '../../theme/catalog.html' ); + const catalogScriptFilePath = resolve( import.meta.dirname, '../../theme/catalog.ts' ).replace( /\\/g, '/' ); + + expect( transformIndexHtml.handler( + '', + { filename: catalogFilePath.replace( /\//g, '\\' ) } + ) ).to.equal( `` ); + } ); + test( 'passes through HTML files that are not manual pages', () => { const plugin = manualTestsPlugin( [] ); const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts index c9ac81c9c..6a563956f 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts @@ -29,6 +29,7 @@ describe( 'manual static assets', () => { await Promise.all( [ createFile( 'packages/ckeditor5-foo/tests/manual/assets/image.png' ), createFile( 'external/ckeditor5/packages/ckeditor5-bar/tests/manual/sample.jpg' ), + createFile( 'packages/ckeditor5-foo/tests/manual/styles.css' ), createFile( 'packages/ckeditor5-foo/tests/manual/test.html' ), createFile( 'packages/ckeditor5-foo/tests/manual/test.js' ) ] ); From bdc81187fdcf8aa53976699eb4a281602566d375 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:52:09 +0200 Subject: [PATCH 15/17] Improve code coverage. --- .../tests/manual-test-plugin/collect-pages.ts | 88 +++++++++++++++++++ .../tests/manual-test-plugin/plugin.ts | 26 ++++++ .../tests/manual-test-plugin/static-assets.ts | 36 +++++++- .../tests/utils.ts | 34 ++++++- 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/collect-pages.ts diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/collect-pages.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/collect-pages.ts new file mode 100644 index 000000000..4e205f113 --- /dev/null +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/collect-pages.ts @@ -0,0 +1,88 @@ +/** + * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { collectManualPages } from '../../src/manual-test-plugin/collect-pages.js'; + +describe( 'collectManualPages()', () => { + let workspaceRoot: string; + + beforeEach( async () => { + workspaceRoot = await mkdtemp( join( tmpdir(), 'ckeditor5-manual-pages-' ) ); + } ); + + afterEach( async () => { + await rm( workspaceRoot, { recursive: true, force: true } ); + } ); + + test( 'collects sorted manual page entries from package and external package paths', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-zeta/tests/manual/nested/demo-case.html' ), + createFile( 'packages/ckeditor5-zeta/tests/manual/nested/demo-case.js' ), + createFile( 'packages/ckeditor5-zeta/tests/manual/nested/demo-case.md' ), + createFile( 'external/ckeditor5/packages/ckeditor5-alpha/tests/manual/sample.html' ), + createFile( 'external/ckeditor5/packages/ckeditor5-alpha/tests/manual/sample.ts' ) + ] ); + + const pages = collectManualPages( [ + 'packages/*/tests/manual/**/*.{html,js,md,ts}', + 'external/ckeditor5/packages/*/tests/manual/**/*.{html,js,md,ts}' + ], workspaceRoot ); + + expect( [ ...pages.values() ] ).to.deep.equal( [ + { + displayName: 'Sample', + htmlFilePath: '/external/ckeditor5/packages/ckeditor5-alpha/tests/manual/sample.html', + instructionsFilePath: undefined, + packageName: 'ckeditor5-alpha', + scriptFilePath: '/external/ckeditor5/packages/ckeditor5-alpha/tests/manual/sample.ts', + slug: 'sample' + }, + { + displayName: 'Nested / Demo Case', + htmlFilePath: '/packages/ckeditor5-zeta/tests/manual/nested/demo-case.html', + instructionsFilePath: '/packages/ckeditor5-zeta/tests/manual/nested/demo-case.md', + packageName: 'ckeditor5-zeta', + scriptFilePath: '/packages/ckeditor5-zeta/tests/manual/nested/demo-case.js', + slug: 'nested/demo-case' + } + ] ); + } ); + + test( 'ignores utility files, unsupported paths, and incomplete manual pages', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/_utils/helper.js' ), + createFile( 'packages/ckeditor5-foo/tests/manual/html-only.html' ), + createFile( 'packages/ckeditor5-foo/tests/manual/script-only.js' ), + createFile( 'packages/ckeditor5-foo/tests/other/sample.html' ), + createFile( 'packages/ckeditor5-foo/tests/other/sample.js' ) + ] ); + + expect( collectManualPages( [ 'packages/**/*.{html,js,md,ts}' ], workspaceRoot ) ).to.deep.equal( new Map() ); + } ); + + test( 'prefers TypeScript test scripts over JavaScript scripts', async () => { + await Promise.all( [ + createFile( 'packages/ckeditor5-foo/tests/manual/sample.html' ), + createFile( 'packages/ckeditor5-foo/tests/manual/sample.js' ), + createFile( 'packages/ckeditor5-foo/tests/manual/sample.ts' ) + ] ); + + const pages = collectManualPages( [ 'packages/*/tests/manual/**/*.{html,js,md,ts}' ], workspaceRoot ); + + expect( pages.get( '/packages/ckeditor5-foo/tests/manual/sample.html' )!.scriptFilePath ) + .to.equal( '/packages/ckeditor5-foo/tests/manual/sample.ts' ); + } ); + + async function createFile( relativeFilePath: string ): Promise { + const filePath = join( workspaceRoot, relativeFilePath ); + + await mkdir( dirname( filePath ), { recursive: true } ); + await writeFile( filePath, '' ); + } +} ); diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts index e24110ef6..a4049a132 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/plugin.ts @@ -92,6 +92,32 @@ describe( 'manualTestsPlugin()', () => { expect( previewServer.middlewares.use ).toHaveBeenCalledTimes( 2 ); } ); + test( 'rewrites root and index requests to the manual test catalog', () => { + const plugin = manualTestsPlugin( [] ); + const server = createServer(); + + ( plugin.configureServer as unknown as ServerHook )( server ); + + const middleware = server.middlewares.use.mock.calls[ 1 ]![ 0 ] as ( + request: { url?: string }, + response: unknown, + next: () => void + ) => void; + const rootRequest = { url: '/?q=1' }; + const indexRequest = { url: '/index.html' }; + const otherRequest = { url: '/manual.html' }; + const next = vi.fn(); + + middleware( rootRequest, {}, next ); + middleware( indexRequest, {}, next ); + middleware( otherRequest, {}, next ); + + expect( rootRequest.url ).to.contain( '/theme/catalog.html' ); + expect( indexRequest.url ).to.contain( '/theme/catalog.html' ); + expect( otherRequest.url ).to.equal( '/manual.html' ); + expect( next ).toHaveBeenCalledTimes( 3 ); + } ); + test( 'rewrites the catalog script to a public file path', () => { const plugin = manualTestsPlugin( [] ); const transformIndexHtml = plugin.transformIndexHtml as TransformIndexHtmlHook; diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts index 6a563956f..11f2631e7 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/static-assets.ts @@ -136,8 +136,42 @@ describe( 'manual static assets', () => { const next = vi.fn(); middleware( { method: 'POST', url: '/asset.png' } as never, response as never, next ); + middleware( { method: 'GET', url: '/missing.png' } as never, response as never, next ); - expect( next ).toHaveBeenCalledOnce(); + expect( next ).toHaveBeenCalledTimes( 2 ); + } ); + + test( 'sets content types for supported static asset extensions', async () => { + const cases = [ + [ 'image.avif', 'image/avif' ], + [ 'styles.css', 'text/css; charset=utf-8' ], + [ 'animation.gif', 'image/gif' ], + [ 'favicon.ico', 'image/x-icon' ], + [ 'photo.jpg', 'image/jpeg' ], + [ 'photo.jpeg', 'image/jpeg' ], + [ 'sound.mp3', 'audio/mpeg' ], + [ 'video.mp4', 'video/mp4' ], + [ 'image.png', 'image/png' ], + [ 'readme.txt', 'text/plain; charset=utf-8' ], + [ 'image.webp', 'image/webp' ], + [ 'font.woff', 'font/woff' ], + [ 'font.woff2', 'font/woff2' ], + [ 'file.bin', 'application/octet-stream' ] + ] as const; + + for ( const [ fileName, contentType ] of cases ) { + const requestPath = `/packages/ckeditor5-foo/tests/manual/assets/${ fileName }`; + const filePath = await createFile( `packages/ckeditor5-foo/tests/manual/assets/${ fileName }`, '' ); + const response = createResponse(); + const middleware = createManualStaticAssetsMiddleware( new Map( [ [ requestPath, filePath ] ] ) ); + const finished = new Promise( resolve => response.on( 'finish', resolve ) ); + + middleware( { method: 'HEAD', url: requestPath } as never, response as never, vi.fn() ); + + await finished; + + expect( response.setHeader ).toHaveBeenCalledWith( 'Content-Type', contentType ); + } } ); async function createFile( relativeFilePath: string, content = '' ): Promise { diff --git a/packages/ckeditor5-dev-manual-server/tests/utils.ts b/packages/ckeditor5-dev-manual-server/tests/utils.ts index d2536518d..825248cdb 100644 --- a/packages/ckeditor5-dev-manual-server/tests/utils.ts +++ b/packages/ckeditor5-dev-manual-server/tests/utils.ts @@ -7,7 +7,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; -import { getOptimizedPackageIncludes } from '../src/utils.js'; +import { getOptimizedPackageIncludes, stringifyValues, toPublicFilePath, toPublicSpecifier } from '../src/utils.js'; describe( 'getOptimizedPackageIncludes()', () => { let temporaryDirectory: string; @@ -56,3 +56,35 @@ describe( 'getOptimizedPackageIncludes()', () => { .then( () => packageJsonPath ); } } ); + +describe( 'stringifyValues()', () => { + test( 'JSON-stringifies object values', () => { + expect( stringifyValues( { + array: [ 'foo' ], + boolean: true, + number: 5, + string: 'bar' + } ) ).to.deep.equal( { + array: '["foo"]', + boolean: 'true', + number: '5', + string: '"bar"' + } ); + } ); +} ); + +describe( 'public path utilities', () => { + test( 'returns a public path for files inside the workspace', () => { + expect( toPublicFilePath( '/workspace/packages/foo/manual.html', '/workspace' ) ) + .to.equal( '/packages/foo/manual.html' ); + } ); + + test( 'returns a Vite file-system path for files outside the workspace', () => { + expect( toPublicFilePath( '/external/theme/shell.ts', '/workspace' ) ) + .to.equal( '/@fs//external/theme/shell.ts' ); + } ); + + test( 'normalizes public specifiers', () => { + expect( toPublicSpecifier( '\\packages\\foo\\manual.html' ) ).to.equal( '/packages/foo/manual.html' ); + } ); +} ); From a0a2565b1c9dd708b229ce0b9de5b09331c98752 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 15:56:26 +0200 Subject: [PATCH 16/17] Address PR feedback. --- .../src/manual-test-plugin/shell-html.ts | 8 +++++- .../tests/manual-test-plugin/shell-html.ts | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts index bdbd6c5e9..5d9be6517 100644 --- a/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/src/manual-test-plugin/shell-html.ts @@ -146,7 +146,13 @@ function isManualTestScriptNode( node: Element, testScriptFileName: string ): bo return false; } - return posix.basename( source.split( '?' )[ 0 ]!.split( '#' )[ 0 ]! ) == testScriptFileName; + const sourceFileName = posix.basename( source.split( '?' )[ 0 ]!.split( '#' )[ 0 ]! ); + + return getScriptModuleName( sourceFileName ) == getScriptModuleName( testScriptFileName ); +} + +function getScriptModuleName( fileName: string ): string { + return fileName.replace( /\.[jt]s$/, '' ); } function getRequiredElementByTagName( root: Node, tagName: string ): Element { diff --git a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts index f95103722..0e4c6d972 100644 --- a/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts +++ b/packages/ckeditor5-dev-manual-server/tests/manual-test-plugin/shell-html.ts @@ -69,4 +69,30 @@ describe( 'createManualShellHtml()', () => { expect( scriptSources.filter( source => source == './iframe.js' ) ).to.have.length( 1 ); expect( scriptSources ).to.include( './helper.js' ); } ); + + test( 'removes stale JavaScript test script when TypeScript test script is selected', () => { + const html = createManualShellHtml( { + entry: { + ...entry, + scriptFilePath: '/packages/ckeditor5-foo/tests/manual/iframe.ts' + }, + html: [ + '', + '', + '', + '' + ].join( '' ), + shellScriptPublicPath: '/theme/shell.ts', + shellTemplateFilePath, + workspaceRoot + } ); + const document = parse( html ); + const scriptSources = [ ...queryAll( document, node => isElementNode( node ) && node.tagName == 'script' ) ] + .map( script => getAttribute( script, 'src' ) ) + .filter( ( source ): source is string => Boolean( source ) ); + + expect( scriptSources ).to.include( './iframe.ts' ); + expect( scriptSources ).to.include( './iframe-helper.js' ); + expect( scriptSources ).not.to.include( './iframe.js' ); + } ); } ); From f4d9c8638feafa395e66a1c2741b3fd46f9215c2 Mon Sep 17 00:00:00 2001 From: Filip Sobol Date: Wed, 3 Jun 2026 16:02:05 +0200 Subject: [PATCH 17/17] Address PR feedback. --- packages/ckeditor5-dev-manual-server/src/constants.ts | 6 ------ .../src/refresh-plugin/plugin.ts | 3 ++- .../tests/refresh-plugin/plugin.ts | 3 +-- packages/ckeditor5-dev-manual-server/theme/shell.ts | 9 +++++++-- 4 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 packages/ckeditor5-dev-manual-server/src/constants.ts diff --git a/packages/ckeditor5-dev-manual-server/src/constants.ts b/packages/ckeditor5-dev-manual-server/src/constants.ts deleted file mode 100644 index b2b732c4e..000000000 --- a/packages/ckeditor5-dev-manual-server/src/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md. - */ - -export const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; diff --git a/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts index 1d010108c..3b6a368c7 100644 --- a/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/src/refresh-plugin/plugin.ts @@ -4,7 +4,8 @@ */ import type { Plugin } from 'vite'; -import { MANUAL_REFRESH_EVENT_NAME } from '../constants.js'; + +export const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; export function refreshPlugin(): Plugin { return { diff --git a/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts index b49544962..cb65e9a4c 100644 --- a/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts +++ b/packages/ckeditor5-dev-manual-server/tests/refresh-plugin/plugin.ts @@ -4,8 +4,7 @@ */ import { describe, expect, test, vi } from 'vitest'; -import { MANUAL_REFRESH_EVENT_NAME } from '../../src/constants.js'; -import { refreshPlugin } from '../../src/refresh-plugin/plugin.js'; +import { refreshPlugin, MANUAL_REFRESH_EVENT_NAME } from '../../src/refresh-plugin/plugin.js'; import type { ModuleNode } from 'vite'; interface TestHmrContext { diff --git a/packages/ckeditor5-dev-manual-server/theme/shell.ts b/packages/ckeditor5-dev-manual-server/theme/shell.ts index de731a15e..a9072b99f 100644 --- a/packages/ckeditor5-dev-manual-server/theme/shell.ts +++ b/packages/ckeditor5-dev-manual-server/theme/shell.ts @@ -4,17 +4,22 @@ */ import CKEditorInspector from '@ckeditor/ckeditor5-inspector'; -import type { ManualData } from '../src/manual-test-plugin/plugin.js'; -import { MANUAL_REFRESH_EVENT_NAME } from '../src/constants.js'; import './shell.css'; declare const LICENSE_KEY: string; +interface ManualData { + displayName: string; + instructionsHtml: string; + packageName: string; +} + interface ViteHotContextLike { on( event: string, callback: () => void ): void; } +const MANUAL_REFRESH_EVENT_NAME = 'ckeditor5-manual:refresh-available'; const globalTarget = window as any; renderManual();