@ckeditor/ckeditor5-dev-manual-server 57.0.0-alpha.0 → 57.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -3
- package/dist/index.js +287 -396
- package/dist/manual-test-plugin/collect-pages.d.ts +7 -0
- package/dist/manual-test-plugin/plugin.d.ts +0 -1
- package/dist/manual-test-plugin/types.d.ts +0 -9
- package/dist/static-assets-plugin/plugin.d.ts +10 -0
- package/dist/{manual-test-plugin → static-assets-plugin}/static-assets.d.ts +0 -1
- package/dist/{raw-plugin → svg-plugin}/plugin.d.ts +1 -1
- package/dist/utils.d.ts +8 -0
- package/package.json +2 -10
- package/theme/catalog.css +2 -16
- package/theme/catalog.html +0 -1
- package/theme/catalog.ts +2 -4
- package/theme/manual-bootstrap.css +44 -0
- package/theme/manual-bootstrap.ts +105 -0
- package/theme/manual-header.css +217 -0
- package/theme/manual-header.ts +193 -0
- package/theme/manual-instructions.css +130 -0
- package/dist/manual-test-plugin/parse-markdown.d.ts +0 -6
- package/dist/manual-test-plugin/shell-html.d.ts +0 -14
- package/theme/shell.css +0 -361
- package/theme/shell.html +0 -46
- package/theme/shell.ts +0 -199
package/dist/index.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import { createReadStream,
|
|
1
|
+
import { createReadStream, globSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import
|
|
4
|
-
import { parse, serialize } from "parse5";
|
|
5
|
-
import { appendChild, createElement, getAttribute, getTextContent, hasAttribute, isElementNode, query, removeAttribute, removeNode, setAttribute, setTextContent } from "@parse5/tools";
|
|
6
|
-
import { unified } from "unified";
|
|
7
|
-
import remarkGfm from "remark-gfm";
|
|
8
|
-
import remarkParse from "remark-parse";
|
|
9
|
-
import remarkRehype from "remark-rehype";
|
|
10
|
-
import { toHtml } from "hast-util-to-html";
|
|
11
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, extname, isAbsolute, posix, relative, resolve } from "node:path";
|
|
12
4
|
//#region src/utils.ts
|
|
13
5
|
/**
|
|
14
6
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
@@ -35,6 +27,20 @@ function cacheValue(compute) {
|
|
|
35
27
|
function stringifyValues(obj) {
|
|
36
28
|
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, JSON.stringify(value)]));
|
|
37
29
|
}
|
|
30
|
+
function normalizePackageName(packageName) {
|
|
31
|
+
return packageName.replace(/^ckeditor5-/, "");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Builds a predicate that decides whether a package is part of the `include` selection. When the
|
|
35
|
+
* selection is empty every package is included. Package names are matched by their normalized form,
|
|
36
|
+
* so both short (`case-change`) and full (`ckeditor5-case-change`) names work. A `null` package name
|
|
37
|
+
* (an asset without a package segment) is only kept when the selection is empty.
|
|
38
|
+
*/
|
|
39
|
+
function createPackageNameFilter(includePackageNames) {
|
|
40
|
+
if (includePackageNames.length == 0) return () => true;
|
|
41
|
+
const normalizedIncludePackageNames = new Set(includePackageNames.map(normalizePackageName));
|
|
42
|
+
return (packageName) => packageName != null && normalizedIncludePackageNames.has(normalizePackageName(packageName));
|
|
43
|
+
}
|
|
38
44
|
function toPublicFilePath(filePath, workspaceRoot) {
|
|
39
45
|
const relativeFilePath = relative(workspaceRoot, filePath);
|
|
40
46
|
if (!relativeFilePath.startsWith("..") && !isAbsolute(relativeFilePath)) return toPublicSpecifier(relativeFilePath);
|
|
@@ -55,63 +61,246 @@ function stripLeadingSlash(value) {
|
|
|
55
61
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
56
62
|
* For licensing, see LICENSE.md.
|
|
57
63
|
*/
|
|
64
|
+
const MANUAL_TESTS_DIRECTORY = "tests/manual/";
|
|
65
|
+
const MANUAL_TEST_SUFFIX$1 = ".manual.html";
|
|
66
|
+
/**
|
|
67
|
+
* Discovers manual test pages. A manual test is a single full HTML document named
|
|
68
|
+
* `<slug>.manual.html` located under a package's `tests/manual/` directory. Plain `.html`
|
|
69
|
+
* files are treated as static fixtures and are never collected here.
|
|
70
|
+
*
|
|
71
|
+
* The `patterns` are package root globs, for example `packages/*`.
|
|
72
|
+
*/
|
|
58
73
|
function collectManualPages(patterns, workspaceRoot) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
const manualPages = matchManualPageFiles(patterns, workspaceRoot).map((relativeFilePath) => toManualPageEntry(relativeFilePath, workspaceRoot)).toSorted((a, b) => a.packageName.localeCompare(b.packageName) || a.slug.localeCompare(b.slug)).map((entry) => [entry.htmlFilePath, entry]);
|
|
75
|
+
return new Map(manualPages);
|
|
76
|
+
}
|
|
77
|
+
function matchManualPageFiles(patterns, workspaceRoot) {
|
|
78
|
+
return patterns.map((pattern) => `${pattern.replace(/\/+$/, "")}/${MANUAL_TESTS_DIRECTORY}**/*${MANUAL_TEST_SUFFIX$1}`).flatMap((pattern) => globSync(pattern, { cwd: workspaceRoot }).map((match) => toPosixPath(match)));
|
|
79
|
+
}
|
|
80
|
+
function toManualPageEntry(relativeFilePath, workspaceRoot) {
|
|
81
|
+
const directoryStartIndex = relativeFilePath.startsWith(MANUAL_TESTS_DIRECTORY) ? 0 : relativeFilePath.indexOf(`/${MANUAL_TESTS_DIRECTORY}`) + 1;
|
|
82
|
+
const packagePath = relativeFilePath.slice(0, Math.max(directoryStartIndex - 1, 0));
|
|
83
|
+
const slugPath = relativeFilePath.slice(directoryStartIndex + 13);
|
|
84
|
+
return {
|
|
85
|
+
htmlFilePath: toPublicSpecifier(relativeFilePath),
|
|
86
|
+
packageName: packagePath ? packagePath.slice(packagePath.lastIndexOf("/") + 1) : basename(workspaceRoot),
|
|
87
|
+
slug: slugPath.slice(0, -12)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/manual-test-plugin/plugin.ts
|
|
92
|
+
/**
|
|
93
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
94
|
+
* For licensing, see LICENSE.md.
|
|
95
|
+
*/
|
|
96
|
+
const MANUAL_HEADER_ELEMENT = "ck-manual-header";
|
|
97
|
+
const MANUAL_ENTRIES_VIRTUAL_ID = "virtual:ckeditor5-manual-entries";
|
|
98
|
+
const MANUAL_THEME_ROOT = realpathSync(fileURLToPath(import.meta.resolve("@ckeditor/ckeditor5-dev-manual-server/theme")));
|
|
99
|
+
const MANUAL_CATALOG_FILE_PATH = resolve(MANUAL_THEME_ROOT, "catalog.html");
|
|
100
|
+
const MANUAL_CATALOG_SCRIPT_FILE_PATH = resolve(MANUAL_THEME_ROOT, "catalog.ts");
|
|
101
|
+
const MANUAL_BOOTSTRAP_SCRIPT_FILE_PATH = resolve(MANUAL_THEME_ROOT, "manual-bootstrap.ts");
|
|
102
|
+
const MANUAL_HEADER_SCRIPT_FILE_PATH = resolve(MANUAL_THEME_ROOT, "manual-header.ts");
|
|
103
|
+
function manualTestsPlugin(options) {
|
|
104
|
+
let workspaceRoot = process.cwd();
|
|
105
|
+
let base = "/";
|
|
106
|
+
const includePackageNames = (options.include || []).filter(Boolean);
|
|
107
|
+
const manualPagesCache = cacheValue(() => filterManualPages(collectManualPages(options.paths, workspaceRoot), includePackageNames));
|
|
108
|
+
const getManualPages = manualPagesCache.get;
|
|
109
|
+
const getManualPageEntryForFile = (filePath) => {
|
|
110
|
+
return getManualPages().get(toPublicSpecifier(relative(workspaceRoot, filePath)));
|
|
111
|
+
};
|
|
112
|
+
const getManualCatalogBuildInputFilePath = () => resolve(workspaceRoot, "index.html");
|
|
113
|
+
const getManualCatalogPublicPath = () => toPublicFilePath(getManualCatalogBuildInputFilePath(), workspaceRoot);
|
|
114
|
+
const getManualCatalogScriptPublicPath = () => toPublicFilePath(MANUAL_CATALOG_SCRIPT_FILE_PATH, workspaceRoot);
|
|
115
|
+
const getManualBootstrapScriptPublicPath = () => toPublicFilePath(MANUAL_BOOTSTRAP_SCRIPT_FILE_PATH, workspaceRoot);
|
|
116
|
+
const getManualHeaderScriptPublicPath = () => toPublicFilePath(MANUAL_HEADER_SCRIPT_FILE_PATH, workspaceRoot);
|
|
117
|
+
const getManualBuildInputs = () => [getManualCatalogBuildInputFilePath(), ...[...getManualPages().values()].map((entry) => resolve(workspaceRoot, stripLeadingSlash(entry.htmlFilePath)))];
|
|
118
|
+
const resolvedVirtualModuleId = `\0${MANUAL_ENTRIES_VIRTUAL_ID}`;
|
|
119
|
+
const getClientEntries = () => [...getManualPages().values()].map((entry) => ({
|
|
120
|
+
href: toBasePublicPath(entry.htmlFilePath, base),
|
|
121
|
+
packageName: entry.packageName,
|
|
122
|
+
slug: entry.slug
|
|
123
|
+
}));
|
|
124
|
+
const getManualEntriesJson = () => JSON.stringify(getClientEntries(), null, 2);
|
|
125
|
+
return {
|
|
126
|
+
name: "ckeditor5-manual-tests",
|
|
127
|
+
config() {
|
|
128
|
+
return { build: { rolldownOptions: { input: getManualBuildInputs() } } };
|
|
129
|
+
},
|
|
130
|
+
configResolved(config) {
|
|
131
|
+
workspaceRoot = config.root;
|
|
132
|
+
base = config.base || "/";
|
|
133
|
+
manualPagesCache.invalidate();
|
|
134
|
+
config.build.rolldownOptions.input = getManualBuildInputs();
|
|
135
|
+
},
|
|
136
|
+
configureServer(server) {
|
|
137
|
+
server.middlewares.use((request, _response, next) => {
|
|
138
|
+
rewriteCatalogRequest(request, getManualCatalogPublicPath());
|
|
139
|
+
next();
|
|
140
|
+
});
|
|
141
|
+
const clientEnvironment = server.environments.client;
|
|
142
|
+
keepManualHtmlSourceFresh(clientEnvironment.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
|
|
143
|
+
},
|
|
144
|
+
resolveId(source) {
|
|
145
|
+
if (source == MANUAL_ENTRIES_VIRTUAL_ID) return resolvedVirtualModuleId;
|
|
146
|
+
if (isManualCatalogBuildInputSpecifier(source, getManualCatalogBuildInputFilePath(), workspaceRoot)) return getManualCatalogBuildInputFilePath();
|
|
147
|
+
return null;
|
|
148
|
+
},
|
|
149
|
+
load(id) {
|
|
150
|
+
if (id == resolvedVirtualModuleId) return `export const manualTestEntries = ${getManualEntriesJson()};`;
|
|
151
|
+
if (toPosixPath(id) == toPosixPath(getManualCatalogBuildInputFilePath())) return readFileSync(MANUAL_CATALOG_FILE_PATH, "utf8");
|
|
152
|
+
return null;
|
|
153
|
+
},
|
|
154
|
+
transformIndexHtml: {
|
|
155
|
+
order: "pre",
|
|
156
|
+
handler(html, context) {
|
|
157
|
+
if (isManualCatalogHtmlFile(context.filename, getManualCatalogBuildInputFilePath())) return html.replace("./catalog.ts", getManualCatalogScriptPublicPath());
|
|
158
|
+
const entry = getManualPageEntryForFile(context.filename);
|
|
159
|
+
if (!entry) return;
|
|
160
|
+
const tags = [createModuleScriptTag(getManualBootstrapScriptPublicPath(), "head-prepend")];
|
|
161
|
+
if (html.includes(`<${MANUAL_HEADER_ELEMENT}`)) tags.push(...createManualHeaderTags(entry, getManualHeaderScriptPublicPath(), toBaseCatalogPath(base, entry.htmlFilePath)));
|
|
162
|
+
return {
|
|
163
|
+
html,
|
|
164
|
+
tags
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Keeps the served manual test HTML in sync with the source file while the dev server runs.
|
|
172
|
+
*
|
|
173
|
+
* Under `experimental.bundledDev`, Vite serves each manual test's HTML from an in-memory bundle
|
|
174
|
+
* produced by the rolldown dev engine. That engine emits the HTML output only during the initial
|
|
175
|
+
* build and never regenerates it when the source `.html` changes: its bundle state reports no
|
|
176
|
+
* stale output for HTML entries, `devEngine.invalidate()` throws on a non-JS module, and even a
|
|
177
|
+
* forced full build leaves the HTML memory file untouched (verified against Vite 8.1.0). A `.html`
|
|
178
|
+
* edit still triggers a full page reload, so without this the browser reloads into the same stale
|
|
179
|
+
* HTML until the server is restarted.
|
|
180
|
+
*
|
|
181
|
+
* The built-in-memory HTML rewrites the entry script, injects the asset tags and manual chrome,
|
|
182
|
+
* and copies the source `<head>` — all inside `<head>`. The markup after `</head>` is a verbatim
|
|
183
|
+
* copy of the source. So on every request we keep the freshly built `<head>` and splice in the
|
|
184
|
+
* current post-`</head>` markup read from disk. Splicing unconditionally (rather than only after a
|
|
185
|
+
* detected change) also covers sources that changed between the initial build and the first request
|
|
186
|
+
* for the page, and it is idempotent for unchanged sources. This reflects edits to the test body
|
|
187
|
+
* without re-running Vite's HTML transform or parsing the document. Any edit that would change
|
|
188
|
+
* what belongs in `<head>` is not picked up this way and still needs a server restart, because
|
|
189
|
+
* reproducing it would require re-running the build pipeline the dev engine refuses to run for HTML
|
|
190
|
+
* entries. That covers edits confined to the `<head>` (styles, meta, extra scripts) as well as
|
|
191
|
+
* adding or removing `<ck-manual-header>` in the body: toggling it changes the injected header
|
|
192
|
+
* chrome (`<meta>` and the component script) in `<head>`, so the chrome only updates after a
|
|
193
|
+
* restart even though the body splice already reflects the element itself.
|
|
194
|
+
*
|
|
195
|
+
* When `bundledDev` is not enabled the store is absent and this is a no-op: Vite's normal dev
|
|
196
|
+
* pipeline already serves fresh HTML on every request.
|
|
197
|
+
*/
|
|
198
|
+
function keepManualHtmlSourceFresh(memoryFiles, getManualPages, workspaceRoot) {
|
|
199
|
+
if (!memoryFiles) return;
|
|
200
|
+
const getBundledFile = memoryFiles.get.bind(memoryFiles);
|
|
201
|
+
memoryFiles.get = (filePath) => {
|
|
202
|
+
const file = getBundledFile(filePath);
|
|
203
|
+
if (!file || !getManualPages().has(toPublicSpecifier(filePath))) return file;
|
|
204
|
+
try {
|
|
205
|
+
const sourceFilePath = resolve(workspaceRoot, stripLeadingSlash(filePath));
|
|
206
|
+
const freshHtml = composeFreshManualHtml(file.source, readFileSync(sourceFilePath, "utf8"));
|
|
207
|
+
return freshHtml == null ? file : { source: freshHtml };
|
|
208
|
+
} catch {
|
|
209
|
+
return file;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Combines the freshly built `<head>` (asset tags and injected manual chrome) with the current
|
|
215
|
+
* post-`</head>` markup from the source file. Returns `null` when either document is missing a
|
|
216
|
+
* `</head>`, so the caller can fall back to the built output.
|
|
217
|
+
*/
|
|
218
|
+
function composeFreshManualHtml(builtHtml, sourceHtml) {
|
|
219
|
+
const builtHeadEnd = findHeadCloseTagIndex(builtHtml);
|
|
220
|
+
const sourceHeadEnd = findHeadCloseTagIndex(sourceHtml);
|
|
221
|
+
if (builtHeadEnd == -1 || sourceHeadEnd == -1) return null;
|
|
222
|
+
return builtHtml.slice(0, builtHeadEnd + 7) + sourceHtml.slice(sourceHeadEnd + 7);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Finds the index of the `</head>` closing tag, or `-1` when the document has none. A `</head>`
|
|
226
|
+
* literal may also appear earlier inside a `<head>` comment or an inline script string, so of all
|
|
227
|
+
* the occurrences preceding the `<body>` tag the last one is taken. Without a `<body>` tag (or
|
|
228
|
+
* when every occurrence follows it) the first occurrence wins, matching the pre-heuristic
|
|
229
|
+
* behavior for such malformed documents.
|
|
230
|
+
*/
|
|
231
|
+
function findHeadCloseTagIndex(html) {
|
|
232
|
+
const firstHeadCloseIndex = /<\/head>/i.exec(html)?.index ?? -1;
|
|
233
|
+
const bodyMatch = /<body[\s>]/i.exec(html);
|
|
234
|
+
if (!bodyMatch) return firstHeadCloseIndex;
|
|
235
|
+
const headClosePattern = /<\/head>/gi;
|
|
236
|
+
let lastHeadCloseIndex = -1;
|
|
237
|
+
let match;
|
|
238
|
+
while ((match = headClosePattern.exec(html)) && match.index < bodyMatch.index) lastHeadCloseIndex = match.index;
|
|
239
|
+
return lastHeadCloseIndex == -1 ? firstHeadCloseIndex : lastHeadCloseIndex;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Injection contract for the `<ck-manual-header>` component, added to `<head>` only when the
|
|
243
|
+
* page source contains the element:
|
|
244
|
+
* - a `<meta>` carrying the package name and the base-aware catalog href the component reads;
|
|
245
|
+
* - the module `<script>` that defines the custom element (folded into the page bundle under
|
|
246
|
+
* `bundledDev`, which is fine — it still executes).
|
|
247
|
+
*/
|
|
248
|
+
function createManualHeaderTags(entry, headerScriptPublicPath, catalogHref) {
|
|
249
|
+
return [{
|
|
250
|
+
tag: "meta",
|
|
251
|
+
attrs: {
|
|
252
|
+
"name": MANUAL_HEADER_ELEMENT,
|
|
253
|
+
"data-package-name": entry.packageName,
|
|
254
|
+
"data-catalog-href": catalogHref
|
|
255
|
+
},
|
|
256
|
+
injectTo: "head"
|
|
257
|
+
}, createModuleScriptTag(headerScriptPublicPath)];
|
|
86
258
|
}
|
|
87
|
-
function
|
|
88
|
-
const match = stripLeadingSlash(toPosixPath(filePath)).match(/^(?:(.*?)\/)?([^/]+)\/tests\/manual\/(.+)\.(html|js|md|ts)$/);
|
|
89
|
-
if (!match) return null;
|
|
259
|
+
function createModuleScriptTag(src, injectTo = "head") {
|
|
90
260
|
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
261
|
+
tag: "script",
|
|
262
|
+
attrs: {
|
|
263
|
+
type: "module",
|
|
264
|
+
src
|
|
265
|
+
},
|
|
266
|
+
injectTo
|
|
95
267
|
};
|
|
96
268
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
269
|
+
function filterManualPages(manualPages, includePackageNames) {
|
|
270
|
+
const isIncluded = createPackageNameFilter(includePackageNames);
|
|
271
|
+
return new Map([...manualPages].filter(([, entry]) => isIncluded(entry.packageName)));
|
|
272
|
+
}
|
|
273
|
+
function isManualCatalogHtmlFile(filePath, catalogBuildInputFilePath) {
|
|
274
|
+
const normalizedFilePath = toPosixPath(filePath);
|
|
275
|
+
return normalizedFilePath == toPosixPath(MANUAL_CATALOG_FILE_PATH) || normalizedFilePath == toPosixPath(catalogBuildInputFilePath);
|
|
276
|
+
}
|
|
277
|
+
function isManualCatalogBuildInputSpecifier(source, catalogBuildInputFilePath, workspaceRoot) {
|
|
278
|
+
return toPosixPath(resolve(workspaceRoot, source)) == toPosixPath(catalogBuildInputFilePath);
|
|
279
|
+
}
|
|
280
|
+
function toBasePublicPath(publicPath, base) {
|
|
281
|
+
if (base == "" || base == "./") return `.${publicPath}`;
|
|
282
|
+
return `${base.replace(/\/$/, "")}${publicPath}`;
|
|
283
|
+
}
|
|
284
|
+
function toBaseCatalogPath(base, entryHtmlFilePath) {
|
|
285
|
+
if (base == "" || base == "./") return posix.relative(posix.dirname(stripLeadingSlash(entryHtmlFilePath)), "index.html");
|
|
286
|
+
return base;
|
|
287
|
+
}
|
|
288
|
+
function rewriteCatalogRequest(request, manualCatalogPublicPath) {
|
|
289
|
+
const url = parseRequestUrl(request.url);
|
|
290
|
+
if (!url) return;
|
|
291
|
+
if (url.pathname == "/" || url.pathname == "/index.html") request.url = `${manualCatalogPublicPath}${url.search}`;
|
|
99
292
|
}
|
|
100
|
-
function
|
|
101
|
-
return
|
|
293
|
+
function parseRequestUrl(requestUrl) {
|
|
294
|
+
return URL.parse(requestUrl || "", "http://localhost");
|
|
102
295
|
}
|
|
103
296
|
//#endregion
|
|
104
|
-
//#region src/
|
|
297
|
+
//#region src/static-assets-plugin/static-assets.ts
|
|
105
298
|
/**
|
|
106
299
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
107
300
|
* For licensing, see LICENSE.md.
|
|
108
301
|
*/
|
|
109
|
-
const PROCESSED_MANUAL_TEST_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
110
|
-
|
|
111
|
-
".js",
|
|
112
|
-
".md",
|
|
113
|
-
".ts"
|
|
114
|
-
]);
|
|
302
|
+
const PROCESSED_MANUAL_TEST_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts"]);
|
|
303
|
+
const MANUAL_TEST_SUFFIX = ".manual.html";
|
|
115
304
|
const VITE_MODULE_QUERY_PARAMETERS = /* @__PURE__ */ new Set([
|
|
116
305
|
"import",
|
|
117
306
|
"raw",
|
|
@@ -120,7 +309,7 @@ const VITE_MODULE_QUERY_PARAMETERS = /* @__PURE__ */ new Set([
|
|
|
120
309
|
"inline"
|
|
121
310
|
]);
|
|
122
311
|
function collectManualStaticAssets(patterns, workspaceRoot) {
|
|
123
|
-
return new Map(patterns.flatMap((pattern) => globSync(pattern, { cwd: workspaceRoot })).sort().filter((relativeFilePath) => statSync(resolve(workspaceRoot, relativeFilePath), { throwIfNoEntry: false })?.isFile()).filter(isManualStaticAssetPath).map((relativeFilePath) => [toPublicSpecifier(relativeFilePath), resolve(workspaceRoot, relativeFilePath)]));
|
|
312
|
+
return new Map(patterns.map((pattern) => `${pattern.replace(/\/+$/, "")}/tests/manual/**/*`).flatMap((pattern) => globSync(pattern, { cwd: workspaceRoot })).sort().filter((relativeFilePath) => statSync(resolve(workspaceRoot, relativeFilePath), { throwIfNoEntry: false })?.isFile()).filter(isManualStaticAssetPath).map((relativeFilePath) => [toPublicSpecifier(relativeFilePath), resolve(workspaceRoot, relativeFilePath)]));
|
|
124
313
|
}
|
|
125
314
|
function createManualStaticAssetsMiddleware(collectStaticAssets) {
|
|
126
315
|
return (request, response, next) => {
|
|
@@ -163,6 +352,7 @@ function decodePathname(pathname) {
|
|
|
163
352
|
}
|
|
164
353
|
}
|
|
165
354
|
function isManualStaticAssetPath(filePath) {
|
|
355
|
+
if (filePath.endsWith(MANUAL_TEST_SUFFIX)) return false;
|
|
166
356
|
return extname(filePath) != "" && !PROCESSED_MANUAL_TEST_EXTENSIONS.has(extname(filePath));
|
|
167
357
|
}
|
|
168
358
|
function getContentType(extension) {
|
|
@@ -170,6 +360,8 @@ function getContentType(extension) {
|
|
|
170
360
|
case ".avif": return "image/avif";
|
|
171
361
|
case ".css": return "text/css; charset=utf-8";
|
|
172
362
|
case ".gif": return "image/gif";
|
|
363
|
+
case ".htm":
|
|
364
|
+
case ".html": return "text/html; charset=utf-8";
|
|
173
365
|
case ".ico": return "image/x-icon";
|
|
174
366
|
case ".jpg":
|
|
175
367
|
case ".jpeg": return "image/jpeg";
|
|
@@ -187,187 +379,22 @@ function getContentType(extension) {
|
|
|
187
379
|
}
|
|
188
380
|
}
|
|
189
381
|
//#endregion
|
|
190
|
-
//#region src/
|
|
191
|
-
/**
|
|
192
|
-
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
193
|
-
* For licensing, see LICENSE.md.
|
|
194
|
-
*/
|
|
195
|
-
const markdownProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true });
|
|
196
|
-
function loadRenderedInstructions(entry, workspaceRoot) {
|
|
197
|
-
if (!entry.instructionsFilePath) return "";
|
|
198
|
-
const markdown = readFileSync(resolve(workspaceRoot, entry.instructionsFilePath.slice(1)), "utf8").trim();
|
|
199
|
-
if (!markdown) return "";
|
|
200
|
-
return toHtml(markdownProcessor.runSync(markdownProcessor.parse(markdown)), { allowDangerousHtml: true });
|
|
201
|
-
}
|
|
202
|
-
//#endregion
|
|
203
|
-
//#region src/manual-test-plugin/shell-html.ts
|
|
204
|
-
/**
|
|
205
|
-
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
206
|
-
* For licensing, see LICENSE.md.
|
|
207
|
-
*/
|
|
208
|
-
function createManualShellHtml({ catalogPublicPath = "/", entry, html, shellScriptPublicPath, shellTemplateFilePath, workspaceRoot }) {
|
|
209
|
-
const shellDocument = parse(readFileSync(shellTemplateFilePath, "utf8").replace("href=\"/\" title=\"Back to test index\"", `href="${catalogPublicPath}" title="Back to test index"`));
|
|
210
|
-
const shellHead = getRequiredElementByTagName$1(shellDocument, "head");
|
|
211
|
-
const shellBody = getRequiredElementByTagName$1(shellDocument, "body");
|
|
212
|
-
const shellTitle = getRequiredElementByTagName$1(shellHead, "title");
|
|
213
|
-
const shellDataElement = query(shellBody, (candidate) => {
|
|
214
|
-
return isElementNode(candidate) && getAttribute(candidate, "id") == "manual-shell-data";
|
|
215
|
-
});
|
|
216
|
-
const shellScriptElement = getRequiredElementByAttribute(shellHead, "data-manual-shell-script");
|
|
217
|
-
const manualDocument = parse(html);
|
|
218
|
-
const manualHead = getRequiredElementByTagName$1(manualDocument, "head");
|
|
219
|
-
const manualBody = getRequiredElementByTagName$1(manualDocument, "body");
|
|
220
|
-
const manualTitle = findChildElement(manualHead, "title");
|
|
221
|
-
const testScriptElement = getRequiredElementByAttribute(shellHead, "data-manual-test-script");
|
|
222
|
-
const testScriptFileName = posix.basename(entry.scriptFilePath);
|
|
223
|
-
removeManualTestScriptNodes(manualDocument, testScriptFileName);
|
|
224
|
-
setTextContent(shellTitle, (manualTitle ? getTextContent(manualTitle) : "").trim() || entry.displayName);
|
|
225
|
-
setAttribute(shellScriptElement, "src", shellScriptPublicPath);
|
|
226
|
-
removeAttribute(shellScriptElement, "data-manual-shell-script");
|
|
227
|
-
setAttribute(testScriptElement, "src", `./${testScriptFileName}`);
|
|
228
|
-
removeAttribute(testScriptElement, "data-manual-test-script");
|
|
229
|
-
setTextContent(shellDataElement, stringifyHtmlScriptData(createManualData(entry, workspaceRoot)));
|
|
230
|
-
copyManualBodyAttributes(manualBody, shellBody);
|
|
231
|
-
appendChildren(shellHead, manualHead.childNodes.filter(shouldMoveManualHeadNode));
|
|
232
|
-
appendChild(shellBody, createManualTestContainer(manualBody));
|
|
233
|
-
return serialize(shellDocument);
|
|
234
|
-
}
|
|
235
|
-
function createManualData(entry, workspaceRoot) {
|
|
236
|
-
return {
|
|
237
|
-
displayName: entry.displayName,
|
|
238
|
-
instructionsHtml: loadRenderedInstructions(entry, workspaceRoot),
|
|
239
|
-
packageName: entry.packageName
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function stringifyHtmlScriptData(value) {
|
|
243
|
-
return JSON.stringify(value).split("<").join("\\u003c");
|
|
244
|
-
}
|
|
245
|
-
function copyManualBodyAttributes(manualBody, shellBody) {
|
|
246
|
-
for (const { name, value } of manualBody.attrs) {
|
|
247
|
-
if (name == "class") {
|
|
248
|
-
setAttribute(shellBody, "class", [getAttribute(shellBody, "class"), value].filter(Boolean).join(" "));
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (!hasAttribute(shellBody, name)) setAttribute(shellBody, name, value);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
function shouldMoveManualHeadNode(node) {
|
|
255
|
-
if (!isElementNode(node)) return true;
|
|
256
|
-
if (node.tagName == "title") return false;
|
|
257
|
-
if (node.tagName != "meta") return true;
|
|
258
|
-
return !getAttribute(node, "charset") && getAttribute(node, "name") != "viewport";
|
|
259
|
-
}
|
|
260
|
-
function appendChildren(parent, childNodes) {
|
|
261
|
-
for (const node of childNodes) {
|
|
262
|
-
removeNode(node);
|
|
263
|
-
appendChild(parent, node);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function createManualTestContainer(manualBody) {
|
|
267
|
-
const container = createElement("div", { class: "manual-test-container" });
|
|
268
|
-
appendChildren(container, [...manualBody.childNodes]);
|
|
269
|
-
return container;
|
|
270
|
-
}
|
|
271
|
-
function removeManualTestScriptNodes(parent, testScriptFileName) {
|
|
272
|
-
for (const node of [...parent.childNodes]) {
|
|
273
|
-
if (!isElementNode(node)) continue;
|
|
274
|
-
if (isManualTestScriptNode(node, testScriptFileName)) {
|
|
275
|
-
removeNode(node);
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
removeManualTestScriptNodes(node, testScriptFileName);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function isManualTestScriptNode(node, testScriptFileName) {
|
|
282
|
-
if (node.tagName != "script") return false;
|
|
283
|
-
const source = getAttribute(node, "src");
|
|
284
|
-
if (!source) return false;
|
|
285
|
-
if (isExternalScriptSource$1(source)) return false;
|
|
286
|
-
return getScriptModuleName(posix.basename(source.split("?")[0].split("#")[0])) == getScriptModuleName(testScriptFileName);
|
|
287
|
-
}
|
|
288
|
-
function isExternalScriptSource$1(source) {
|
|
289
|
-
return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("//");
|
|
290
|
-
}
|
|
291
|
-
function getScriptModuleName(fileName) {
|
|
292
|
-
return fileName.replace(/\.[jt]s$/, "");
|
|
293
|
-
}
|
|
294
|
-
function getRequiredElementByTagName$1(root, tagName) {
|
|
295
|
-
return query(root, (candidate) => isElementNode(candidate) && candidate.tagName == tagName);
|
|
296
|
-
}
|
|
297
|
-
function getRequiredElementByAttribute(root, attributeName) {
|
|
298
|
-
return query(root, (candidate) => isElementNode(candidate) && hasAttribute(candidate, attributeName));
|
|
299
|
-
}
|
|
300
|
-
function findChildElement(root, tagName) {
|
|
301
|
-
return root.childNodes.find((node) => isElementNode(node) && node.tagName == tagName);
|
|
302
|
-
}
|
|
303
|
-
//#endregion
|
|
304
|
-
//#region src/manual-test-plugin/plugin.ts
|
|
382
|
+
//#region src/static-assets-plugin/plugin.ts
|
|
305
383
|
/**
|
|
306
384
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
307
385
|
* For licensing, see LICENSE.md.
|
|
308
386
|
*/
|
|
309
|
-
|
|
310
|
-
const MANUAL_THEME_ROOT = realpathSync(fileURLToPath(import.meta.resolve("@ckeditor/ckeditor5-dev-manual-server/theme")));
|
|
311
|
-
const MANUAL_CATALOG_FILE_PATH = resolve(MANUAL_THEME_ROOT, "catalog.html");
|
|
312
|
-
const MANUAL_CATALOG_SCRIPT_FILE_PATH = resolve(MANUAL_THEME_ROOT, "catalog.ts");
|
|
313
|
-
const MANUAL_SHELL_TEMPLATE_FILE_PATH = resolve(MANUAL_THEME_ROOT, "shell.html");
|
|
314
|
-
const MANUAL_SHELL_SCRIPT_FILE_PATH = resolve(MANUAL_THEME_ROOT, "shell.ts");
|
|
315
|
-
function manualTestsPlugin(options) {
|
|
387
|
+
function manualStaticAssetsPlugin(options) {
|
|
316
388
|
let workspaceRoot = process.cwd();
|
|
317
|
-
let base = "/";
|
|
318
|
-
const manualTestPatterns = options.paths;
|
|
319
|
-
const manualPagePatterns = manualTestPatterns.map(toManualPagePattern);
|
|
320
389
|
const includePackageNames = (options.include || []).filter(Boolean);
|
|
321
|
-
const
|
|
322
|
-
const getManualPages = manualPagesCache.get;
|
|
323
|
-
const manualStaticAssetsCache = cacheValue(() => collectManualStaticAssets(getManualStaticAssetPatterns(getManualPages(), manualTestPatterns, includePackageNames), workspaceRoot));
|
|
324
|
-
const getManualStaticAssets = manualStaticAssetsCache.get;
|
|
325
|
-
const invalidateManualFileCaches = () => {
|
|
326
|
-
manualPagesCache.invalidate();
|
|
327
|
-
manualStaticAssetsCache.invalidate();
|
|
328
|
-
};
|
|
329
|
-
const getManualPageEntryForFile = (filePath) => {
|
|
330
|
-
return getManualPages().get(toPublicSpecifier(relative(workspaceRoot, filePath)));
|
|
331
|
-
};
|
|
332
|
-
const getManualCatalogBuildInputFilePath = () => resolve(workspaceRoot, "index.html");
|
|
333
|
-
const getManualCatalogPublicPath = () => toPublicFilePath(getManualCatalogBuildInputFilePath(), workspaceRoot);
|
|
334
|
-
const getManualCatalogScriptPublicPath = () => toPublicFilePath(MANUAL_CATALOG_SCRIPT_FILE_PATH, workspaceRoot);
|
|
335
|
-
const getManualShellScriptPublicPath = () => toPublicFilePath(MANUAL_SHELL_SCRIPT_FILE_PATH, workspaceRoot);
|
|
336
|
-
const getManualCatalogClientPath = (entry) => toBaseCatalogPath(base, entry.htmlFilePath);
|
|
337
|
-
const getManualBuildInputs = () => [getManualCatalogBuildInputFilePath(), ...[...getManualPages().values()].map((entry) => resolve(workspaceRoot, entry.htmlFilePath.slice(1)))];
|
|
338
|
-
const resolvedVirtualModuleId = `\0${MANUAL_ENTRIES_VIRTUAL_ID}`;
|
|
339
|
-
const getClientEntries = () => [...getManualPages().values()].map((entry) => ({
|
|
340
|
-
displayName: entry.displayName,
|
|
341
|
-
href: toBasePublicPath(entry.htmlFilePath, base),
|
|
342
|
-
packageName: entry.packageName,
|
|
343
|
-
slug: entry.slug
|
|
344
|
-
}));
|
|
345
|
-
const getManualEntriesJson = () => JSON.stringify(getClientEntries(), null, 2);
|
|
390
|
+
const getManualStaticAssets = cacheValue(() => filterManualStaticAssets(collectManualStaticAssets(options.paths, workspaceRoot), includePackageNames, workspaceRoot)).get;
|
|
346
391
|
return {
|
|
347
|
-
name: "ckeditor5-manual-
|
|
348
|
-
config() {
|
|
349
|
-
return { build: { rolldownOptions: { input: getManualBuildInputs() } } };
|
|
350
|
-
},
|
|
392
|
+
name: "ckeditor5-manual-static-assets",
|
|
351
393
|
configResolved(config) {
|
|
352
394
|
workspaceRoot = config.root;
|
|
353
|
-
base = config.base || "/";
|
|
354
|
-
invalidateManualFileCaches();
|
|
355
|
-
config.build.rolldownOptions.input = getManualBuildInputs();
|
|
356
395
|
},
|
|
357
396
|
configureServer(server) {
|
|
358
|
-
|
|
359
|
-
useManualTestMiddlewares(server, getManualCatalogPublicPath, getManualStaticAssets);
|
|
360
|
-
useBundledDevManualHtmlSource(memoryFiles, getManualPages, getManualShellScriptPublicPath, getManualCatalogClientPath, () => workspaceRoot);
|
|
361
|
-
},
|
|
362
|
-
resolveId(source) {
|
|
363
|
-
if (source == MANUAL_ENTRIES_VIRTUAL_ID) return resolvedVirtualModuleId;
|
|
364
|
-
if (isManualCatalogBuildInputSpecifier(source, getManualCatalogBuildInputFilePath(), workspaceRoot)) return getManualCatalogBuildInputFilePath();
|
|
365
|
-
return null;
|
|
366
|
-
},
|
|
367
|
-
load(id) {
|
|
368
|
-
if (id == resolvedVirtualModuleId) return `export const manualTestEntries = ${getManualEntriesJson()};`;
|
|
369
|
-
if (toPosixPath(id) == toPosixPath(getManualCatalogBuildInputFilePath())) return readFileSync(MANUAL_CATALOG_FILE_PATH, "utf8");
|
|
370
|
-
return null;
|
|
397
|
+
server.middlewares.use(createManualStaticAssetsMiddleware(getManualStaticAssets));
|
|
371
398
|
},
|
|
372
399
|
generateBundle() {
|
|
373
400
|
for (const [publicPath, filePath] of getManualStaticAssets()) this.emitFile({
|
|
@@ -375,143 +402,16 @@ function manualTestsPlugin(options) {
|
|
|
375
402
|
fileName: stripLeadingSlash(publicPath),
|
|
376
403
|
source: readFileSync(filePath)
|
|
377
404
|
});
|
|
378
|
-
},
|
|
379
|
-
transformIndexHtml: {
|
|
380
|
-
order: "pre",
|
|
381
|
-
handler(html, context) {
|
|
382
|
-
if (isManualCatalogHtmlFile(context.filename, getManualCatalogBuildInputFilePath())) return html.replace("./catalog.ts", getManualCatalogScriptPublicPath());
|
|
383
|
-
const entry = getManualPageEntryForFile(context.filename);
|
|
384
|
-
if (!entry) return;
|
|
385
|
-
return createManualShellHtml({
|
|
386
|
-
catalogPublicPath: getManualCatalogClientPath(entry),
|
|
387
|
-
entry,
|
|
388
|
-
html,
|
|
389
|
-
shellScriptPublicPath: getManualShellScriptPublicPath(),
|
|
390
|
-
shellTemplateFilePath: MANUAL_SHELL_TEMPLATE_FILE_PATH,
|
|
391
|
-
workspaceRoot
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
405
|
}
|
|
395
406
|
};
|
|
396
407
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
408
|
+
function filterManualStaticAssets(staticAssets, includePackageNames, workspaceRoot) {
|
|
409
|
+
const isIncluded = createPackageNameFilter(includePackageNames);
|
|
410
|
+
return new Map([...staticAssets].filter(([publicPath]) => isIncluded(getManualTestPackageName(publicPath, workspaceRoot))));
|
|
400
411
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
next();
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
function useBundledDevManualHtmlSource(memoryFiles, getManualPages, getManualShellScriptPublicPath, getManualCatalogClientPath, getWorkspaceRoot) {
|
|
408
|
-
if (!memoryFiles) return;
|
|
409
|
-
const getBundledFile = memoryFiles.get.bind(memoryFiles);
|
|
410
|
-
memoryFiles.get = (filePath) => {
|
|
411
|
-
const file = getBundledFile(filePath);
|
|
412
|
-
const entry = getManualPages().get(toPublicSpecifier(filePath));
|
|
413
|
-
if (!file || !entry) return file;
|
|
414
|
-
const workspaceRoot = getWorkspaceRoot();
|
|
415
|
-
const shellScriptPublicPath = getManualShellScriptPublicPath();
|
|
416
|
-
const bundledHtml = getFileSource(file);
|
|
417
|
-
return { source: mergeBundledAssetTags(createManualShellHtml({
|
|
418
|
-
catalogPublicPath: getManualCatalogClientPath(entry),
|
|
419
|
-
entry,
|
|
420
|
-
html: readFileSync(resolve(workspaceRoot, stripLeadingSlash(entry.htmlFilePath)), "utf8"),
|
|
421
|
-
shellScriptPublicPath,
|
|
422
|
-
shellTemplateFilePath: MANUAL_SHELL_TEMPLATE_FILE_PATH,
|
|
423
|
-
workspaceRoot
|
|
424
|
-
}), bundledHtml, entry, shellScriptPublicPath) };
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
function getFileSource(file) {
|
|
428
|
-
return typeof file.source == "string" ? file.source : Buffer.from(file.source).toString("utf8");
|
|
429
|
-
}
|
|
430
|
-
function toManualPagePattern(pattern) {
|
|
431
|
-
if (/\.(?:html|js|md|ts|\{[^}]*\})$/.test(pattern)) return pattern;
|
|
432
|
-
return `${pattern}.{html,js,md,ts}`;
|
|
433
|
-
}
|
|
434
|
-
function filterManualPages(manualPages, includePackageNames) {
|
|
435
|
-
if (includePackageNames.length == 0) return manualPages;
|
|
436
|
-
const normalizedIncludePackageNames = new Set(includePackageNames.map(normalizePackageName));
|
|
437
|
-
return new Map([...manualPages].filter(([, entry]) => normalizedIncludePackageNames.has(normalizePackageName(entry.packageName))));
|
|
438
|
-
}
|
|
439
|
-
function normalizePackageName(packageName) {
|
|
440
|
-
return packageName.replace(/^ckeditor5-/, "");
|
|
441
|
-
}
|
|
442
|
-
function getManualStaticAssetPatterns(manualPages, manualTestPatterns, includePackageNames) {
|
|
443
|
-
if (includePackageNames.length == 0) return manualTestPatterns;
|
|
444
|
-
return [...new Set([...manualPages.values()].map(getManualTestRootDirectory))].map((manualTestRootDirectory) => `${manualTestRootDirectory}/**/*`);
|
|
445
|
-
}
|
|
446
|
-
function getManualTestRootDirectory(entry) {
|
|
447
|
-
const htmlFilePath = stripLeadingSlash(entry.htmlFilePath);
|
|
448
|
-
const manualDirectoryIndex = htmlFilePath.indexOf("/tests/manual/");
|
|
449
|
-
return htmlFilePath.slice(0, manualDirectoryIndex + 14 - 1);
|
|
450
|
-
}
|
|
451
|
-
function isManualCatalogHtmlFile(filePath, catalogBuildInputFilePath) {
|
|
452
|
-
const normalizedFilePath = toPosixPath(filePath);
|
|
453
|
-
return normalizedFilePath == toPosixPath(MANUAL_CATALOG_FILE_PATH) || normalizedFilePath == toPosixPath(catalogBuildInputFilePath);
|
|
454
|
-
}
|
|
455
|
-
function isManualCatalogBuildInputSpecifier(source, catalogBuildInputFilePath, workspaceRoot) {
|
|
456
|
-
return toPosixPath(resolve(workspaceRoot, source)) == toPosixPath(catalogBuildInputFilePath);
|
|
457
|
-
}
|
|
458
|
-
function toBasePublicPath(publicPath, base) {
|
|
459
|
-
if (base == "" || base == "./") return `.${publicPath}`;
|
|
460
|
-
return `${base.replace(/\/$/, "")}${publicPath}`;
|
|
461
|
-
}
|
|
462
|
-
function toBaseCatalogPath(base, entryHtmlFilePath) {
|
|
463
|
-
if (base == "" || base == "./") return posix.relative(posix.dirname(stripLeadingSlash(entryHtmlFilePath)), "index.html");
|
|
464
|
-
return base;
|
|
465
|
-
}
|
|
466
|
-
function mergeBundledAssetTags(sourceHtml, bundledHtml, entry, shellScriptPublicPath) {
|
|
467
|
-
const sourceDocument = parse(sourceHtml);
|
|
468
|
-
const bundledDocument = parse(bundledHtml);
|
|
469
|
-
const sourceHead = getRequiredElementByTagName(sourceDocument, "head");
|
|
470
|
-
const bundledHead = getRequiredElementByTagName(bundledDocument, "head");
|
|
471
|
-
const testScriptFileName = posix.basename(entry.scriptFilePath);
|
|
472
|
-
const hasBundledShellAsset = bundledHead.childNodes.some((node) => isBundledModuleAsset(node, "shell"));
|
|
473
|
-
for (const node of [...sourceHead.childNodes]) if (isSourceTestScript(node, testScriptFileName) || hasBundledShellAsset && isSourceShellScript(node, shellScriptPublicPath)) removeNode(node);
|
|
474
|
-
for (const node of bundledHead.childNodes.filter(isBundledAssetTag)) {
|
|
475
|
-
removeNode(node);
|
|
476
|
-
appendChild(sourceHead, node);
|
|
477
|
-
}
|
|
478
|
-
return serialize(sourceDocument);
|
|
479
|
-
}
|
|
480
|
-
function isSourceTestScript(node, testScriptFileName) {
|
|
481
|
-
if (!isElementNode(node) || node.tagName != "script") return false;
|
|
482
|
-
const source = getAttribute(node, "src");
|
|
483
|
-
if (!source) return false;
|
|
484
|
-
if (isExternalScriptSource(source)) return false;
|
|
485
|
-
return posix.basename(source) == testScriptFileName;
|
|
486
|
-
}
|
|
487
|
-
function isExternalScriptSource(source) {
|
|
488
|
-
return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("//");
|
|
489
|
-
}
|
|
490
|
-
function isSourceShellScript(node, shellScriptPublicPath) {
|
|
491
|
-
return isElementNode(node) && node.tagName == "script" && getAttribute(node, "src") == shellScriptPublicPath;
|
|
492
|
-
}
|
|
493
|
-
function isBundledModuleAsset(node, moduleName) {
|
|
494
|
-
if (!isElementNode(node)) return false;
|
|
495
|
-
if (node.tagName == "script") return isBundledModulePath(getAttribute(node, "src"), moduleName);
|
|
496
|
-
return node.tagName == "link" && getAttribute(node, "rel") == "modulepreload" && isBundledModulePath(getAttribute(node, "href"), moduleName);
|
|
497
|
-
}
|
|
498
|
-
function isBundledModulePath(path, moduleName) {
|
|
499
|
-
return Boolean(path?.startsWith(`/assets/${moduleName}-`));
|
|
500
|
-
}
|
|
501
|
-
function isBundledAssetTag(node) {
|
|
502
|
-
if (!isElementNode(node)) return false;
|
|
503
|
-
return Boolean(node.tagName == "script" && getAttribute(node, "src")?.startsWith("/assets/") || node.tagName == "link" && getAttribute(node, "href")?.startsWith("/assets/"));
|
|
504
|
-
}
|
|
505
|
-
function rewriteCatalogRequest(request, manualCatalogPublicPath) {
|
|
506
|
-
const url = parseRequestUrl(request.url);
|
|
507
|
-
if (!url) return;
|
|
508
|
-
if (url.pathname == "/" || url.pathname == "/index.html") request.url = `${manualCatalogPublicPath}${url.search}`;
|
|
509
|
-
}
|
|
510
|
-
function parseRequestUrl(requestUrl) {
|
|
511
|
-
return URL.parse(requestUrl || "", "http://localhost");
|
|
512
|
-
}
|
|
513
|
-
function getRequiredElementByTagName(root, tagName) {
|
|
514
|
-
return query(root, (candidate) => isElementNode(candidate) && candidate.tagName == tagName);
|
|
412
|
+
function getManualTestPackageName(publicPath, workspaceRoot) {
|
|
413
|
+
if (publicPath.startsWith("/tests/manual/")) return basename(workspaceRoot);
|
|
414
|
+
return publicPath.match(/^\/(?:.*\/)?([^/]+)\/tests\/manual\//)?.[1] || null;
|
|
515
415
|
}
|
|
516
416
|
//#endregion
|
|
517
417
|
//#region src/debug-plugin/plugin.ts
|
|
@@ -561,14 +461,15 @@ function refreshPlugin() {
|
|
|
561
461
|
name: "ckeditor5-manual-refresh",
|
|
562
462
|
apply: "serve",
|
|
563
463
|
configureServer(server) {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
464
|
+
const bundledDev = server.environments.client.bundledDev;
|
|
465
|
+
if (!bundledDev) return;
|
|
466
|
+
wrapBundledDevClientSend(bundledDev.clients);
|
|
467
|
+
wrapBundledDevFullReloads(bundledDev);
|
|
567
468
|
}
|
|
568
469
|
};
|
|
569
470
|
}
|
|
570
471
|
function wrapBundledDevClientSend(clients) {
|
|
571
|
-
if (!clients) return;
|
|
472
|
+
if (!clients || typeof clients.setupIfNeeded != "function") return;
|
|
572
473
|
const setupIfNeeded = clients.setupIfNeeded.bind(clients);
|
|
573
474
|
clients.setupIfNeeded = (client, clientId) => {
|
|
574
475
|
if (!client[isManualRefreshWrappedClient]) {
|
|
@@ -581,12 +482,12 @@ function wrapBundledDevClientSend(clients) {
|
|
|
581
482
|
return setupIfNeeded(client, clientId);
|
|
582
483
|
};
|
|
583
484
|
}
|
|
584
|
-
function wrapBundledDevFullReloads(
|
|
585
|
-
if (
|
|
586
|
-
const handleHmrOutput =
|
|
587
|
-
|
|
485
|
+
function wrapBundledDevFullReloads(bundledDev) {
|
|
486
|
+
if (typeof bundledDev.handleHmrOutput != "function") return;
|
|
487
|
+
const handleHmrOutput = bundledDev.handleHmrOutput.bind(bundledDev);
|
|
488
|
+
bundledDev.handleHmrOutput = (client, files, hmrOutput, invalidateInformation) => {
|
|
588
489
|
if (hmrOutput.type == "FullReload" && shouldShowManualRefreshPromptForFiles(files)) {
|
|
589
|
-
|
|
490
|
+
ensureLatestBuildOutput(bundledDev);
|
|
590
491
|
client.send({
|
|
591
492
|
type: "custom",
|
|
592
493
|
event: MANUAL_REFRESH_EVENT_NAME
|
|
@@ -596,6 +497,11 @@ function wrapBundledDevFullReloads(clientEnvironment) {
|
|
|
596
497
|
return handleHmrOutput(client, files, hmrOutput, invalidateInformation);
|
|
597
498
|
};
|
|
598
499
|
}
|
|
500
|
+
function ensureLatestBuildOutput(bundledDev) {
|
|
501
|
+
try {
|
|
502
|
+
bundledDev.devEngine?.ensureLatestBuildOutput().catch(() => {});
|
|
503
|
+
} catch {}
|
|
504
|
+
}
|
|
599
505
|
function sendManualRefreshPayload(payload, send) {
|
|
600
506
|
if (shouldShowManualRefreshPrompt(payload)) {
|
|
601
507
|
send({
|
|
@@ -619,42 +525,27 @@ function shouldShowManualRefreshPromptForFiles(files) {
|
|
|
619
525
|
return files.some((file) => !file.endsWith(".css") && !file.endsWith(".html"));
|
|
620
526
|
}
|
|
621
527
|
//#endregion
|
|
622
|
-
//#region src/
|
|
528
|
+
//#region src/svg-plugin/plugin.ts
|
|
623
529
|
/**
|
|
624
530
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
625
531
|
* For licensing, see LICENSE.md.
|
|
626
532
|
*/
|
|
627
|
-
|
|
628
|
-
function rawHtmlPlugin() {
|
|
533
|
+
function rawSvgPlugin() {
|
|
629
534
|
return {
|
|
630
|
-
name: "ckeditor5-raw",
|
|
535
|
+
name: "ckeditor5-raw-svg",
|
|
631
536
|
enforce: "pre",
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const source = await readFile(id.slice(0, -14), "utf8");
|
|
642
|
-
return {
|
|
643
|
-
code: `export default ${JSON.stringify(source)};`,
|
|
644
|
-
map: null
|
|
645
|
-
};
|
|
537
|
+
load: {
|
|
538
|
+
filter: { id: { include: /\.svg$/ } },
|
|
539
|
+
handler(id) {
|
|
540
|
+
const content = readFileSync(id, "utf-8");
|
|
541
|
+
return {
|
|
542
|
+
code: `export default ${JSON.stringify(content)};`,
|
|
543
|
+
map: null
|
|
544
|
+
};
|
|
545
|
+
}
|
|
646
546
|
}
|
|
647
547
|
};
|
|
648
548
|
}
|
|
649
|
-
function resolveHtmlFilePath(source, importer) {
|
|
650
|
-
if (!source.startsWith(".")) return null;
|
|
651
|
-
const filePath = path.resolve(path.dirname(getFilePathFromId(importer)), source);
|
|
652
|
-
return existsSync(filePath) ? toPosixPath(filePath) : null;
|
|
653
|
-
}
|
|
654
|
-
function getFilePathFromId(id) {
|
|
655
|
-
const queryIndex = id.indexOf("?");
|
|
656
|
-
return queryIndex >= 0 ? id.slice(0, queryIndex) : id;
|
|
657
|
-
}
|
|
658
549
|
//#endregion
|
|
659
550
|
//#region src/index.ts
|
|
660
551
|
/**
|
|
@@ -662,4 +553,4 @@ function getFilePathFromId(id) {
|
|
|
662
553
|
* For licensing, see LICENSE.md.
|
|
663
554
|
*/
|
|
664
555
|
//#endregion
|
|
665
|
-
export { ckDebugPlugin, manualTestsPlugin,
|
|
556
|
+
export { ckDebugPlugin, manualStaticAssetsPlugin, manualTestsPlugin, rawSvgPlugin, refreshPlugin, stringifyValues };
|