@ckeditor/ckeditor5-dev-manual-server 59.1.0 → 61.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/README.md CHANGED
@@ -8,6 +8,21 @@ Used to extend Vite to create a manual test server for CKEditor 5.
8
8
 
9
9
  More information about development tools packages can be found at the following URL: <https://github.com/ckeditor/ckeditor5-dev>.
10
10
 
11
+ ## Loading translations
12
+
13
+ Pass a language code to `manualTestsPlugin()` to load and combine that language's TypeScript translation files from the configured
14
+ package paths. The selected language becomes the default UI and content language for editors and contexts created by manual tests.
15
+ An explicit language in an editor or context configuration still takes precedence.
16
+
17
+ ```ts
18
+ manualTestsPlugin( {
19
+ paths: [ 'packages/*' ],
20
+ language: process.env.CK_LANGUAGE || 'en'
21
+ } )
22
+ ```
23
+
24
+ For example, start the manual test server in Polish using `CK_LANGUAGE=pl pnpm manual`.
25
+
11
26
  ## Changelog
12
27
 
13
28
  See the [`CHANGELOG.md`](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/ckeditor5-dev-manual-server/CHANGELOG.md) file.
package/dist/index.js CHANGED
@@ -88,6 +88,47 @@ function toManualPageEntry(relativeFilePath, workspaceRoot) {
88
88
  };
89
89
  }
90
90
  //#endregion
91
+ //#region src/manual-test-plugin/translations.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_TRANSLATIONS_VIRTUAL_ID = "virtual:ckeditor5-manual-translations";
97
+ const RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID = `\0${MANUAL_TRANSLATIONS_VIRTUAL_ID}`;
98
+ /**
99
+ * Generates the virtual module that loads translations from every configured package root.
100
+ */
101
+ function createManualTranslationsModule(paths, language) {
102
+ const translationPatterns = paths.map((packagePath) => {
103
+ return posix.join("/", toPosixPath(packagePath), `lang/translations/${language}.ts`);
104
+ });
105
+ return `
106
+ import { add } from '@ckeditor/ckeditor5-utils';
107
+ import { Context, Editor } from '@ckeditor/ckeditor5-core';
108
+
109
+ const translationModules = import.meta.glob(
110
+ ${JSON.stringify(translationPatterns)},
111
+ { eager: true, import: 'default' }
112
+ );
113
+ const language = ${JSON.stringify(language)};
114
+ const translations = Object.values( translationModules ).map( module => module[ language ] );
115
+
116
+ if ( !translations.length ) {
117
+ throw new Error( \`No CKEditor 5 translations found for "\${ language }".\` );
118
+ }
119
+
120
+ if ( !translations.some( translation => translation.getPluralForm ) ) {
121
+ throw new Error( \`The ckeditor5-core translations for "\${ language }" were not loaded.\` );
122
+ }
123
+
124
+ for ( const translation of translations ) {
125
+ add( language, translation.dictionary, translation.getPluralForm );
126
+ }
127
+
128
+ Editor.defaultConfig = { ...Editor.defaultConfig, language: ${JSON.stringify(language)} };
129
+ Context.defaultConfig = { ...Context.defaultConfig, language: ${JSON.stringify(language)} };`;
130
+ }
131
+ //#endregion
91
132
  //#region src/manual-test-plugin/plugin.ts
92
133
  /**
93
134
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
@@ -137,33 +178,38 @@ function manualTestsPlugin(options) {
137
178
  slug: entry.slug
138
179
  }));
139
180
  const getManualEntriesJson = () => JSON.stringify(getClientEntries(), null, 2);
181
+ const translationsModuleCode = options.language ? createManualTranslationsModule(options.paths, options.language) : null;
140
182
  return {
141
183
  name: "ckeditor5-manual-tests",
142
- config() {
143
- return { build: { rolldownOptions: { input: getManualBuildInputs() } } };
184
+ config(config = {}) {
185
+ workspaceRoot = resolve(config.root || process.cwd());
186
+ return {
187
+ input: getManualBuildInputs(),
188
+ build: { rolldownOptions: { output: { strictExecutionOrder: true } } }
189
+ };
144
190
  },
145
191
  configResolved(config) {
146
192
  workspaceRoot = config.root;
147
193
  base = config.base || "/";
148
194
  manualPagesCache.invalidate();
149
195
  packageThemeEntryCache.clear();
150
- config.build.rolldownOptions.input = getManualBuildInputs();
151
196
  },
152
197
  configureServer(server) {
153
198
  server.middlewares.use((request, _response, next) => {
154
199
  rewriteCatalogRequest(request, getManualCatalogPublicPath());
155
200
  next();
156
201
  });
157
- const clientEnvironment = server.environments.client;
158
- keepManualHtmlSourceFresh(clientEnvironment.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
202
+ keepManualHtmlSourceFresh(server.environments.client.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
159
203
  },
160
204
  resolveId(source) {
161
205
  if (source == MANUAL_ENTRIES_VIRTUAL_ID) return resolvedVirtualModuleId;
206
+ if (source == "virtual:ckeditor5-manual-translations" && translationsModuleCode) return RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID;
162
207
  if (isManualCatalogBuildInputSpecifier(source, getManualCatalogBuildInputFilePath(), workspaceRoot)) return getManualCatalogBuildInputFilePath();
163
208
  return null;
164
209
  },
165
210
  load(id) {
166
211
  if (id == resolvedVirtualModuleId) return `export const manualTestEntries = ${getManualEntriesJson()};`;
212
+ if (id == RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID) return translationsModuleCode;
167
213
  if (toPosixPath(id) == toPosixPath(getManualCatalogBuildInputFilePath())) return readFileSync(MANUAL_CATALOG_FILE_PATH, "utf8");
168
214
  return null;
169
215
  },
@@ -174,12 +220,13 @@ function manualTestsPlugin(options) {
174
220
  const entry = getManualPageEntryForScriptSpecifier(scriptSpecifier);
175
221
  if (!entry) return;
176
222
  const packageRootSpecifier = entry.htmlFilePath.slice(0, entry.htmlFilePath.indexOf(MANUAL_TESTS_DIRECTORY));
177
- const themeEntries = getPackageThemeEntries(packageRootSpecifier);
178
- if (!themeEntries.length) return;
223
+ const imports = getPackageThemeEntries(packageRootSpecifier).map((themeEntryFilePath) => {
224
+ return `import '${posix.relative(posix.dirname(scriptSpecifier), `${packageRootSpecifier}/${themeEntryFilePath}`)}';`;
225
+ });
226
+ if (options.language) imports.push(`import '${MANUAL_TRANSLATIONS_VIRTUAL_ID}';`);
227
+ if (!imports.length) return;
179
228
  return {
180
- code: `${code}\n${themeEntries.map((themeEntryFilePath) => {
181
- return `import '${posix.relative(posix.dirname(scriptSpecifier), `${packageRootSpecifier}/${themeEntryFilePath}`)}';`;
182
- }).join("\n")}\n`,
229
+ code: `${code}\n${imports.join("\n")}\n`,
183
230
  map: null
184
231
  };
185
232
  }
@@ -207,7 +254,7 @@ function manualTestsPlugin(options) {
207
254
  * produced by the rolldown dev engine. That engine emits the HTML output only during the initial
208
255
  * build and never regenerates it when the source `.html` changes: its bundle state reports no
209
256
  * stale output for HTML entries, `devEngine.invalidate()` throws on a non-JS module, and even a
210
- * forced full build leaves the HTML memory file untouched (verified against Vite 8.1.0). A `.html`
257
+ * forced full build leaves the HTML memory file untouched (verified against Vite 8.2.1). A `.html`
211
258
  * edit still triggers a full page reload, so without this the browser reloads into the same stale
212
259
  * HTML until the server is restarted.
213
260
  *
@@ -368,39 +415,40 @@ function getDebugFlags(debugOption = "") {
368
415
  * For licensing, see LICENSE.md.
369
416
  */
370
417
  const MANUAL_REFRESH_EVENT_NAME = "ckeditor5-manual:refresh-available";
371
- const isManualRefreshWrappedClient = Symbol("isManualRefreshWrappedClient");
418
+ const wrappedClients = /* @__PURE__ */ new WeakSet();
372
419
  function refreshPlugin() {
373
420
  return {
374
421
  name: "ckeditor5-manual-refresh",
375
422
  apply: "serve",
376
423
  configureServer(server) {
377
- const bundledDev = server.environments.client.bundledDev;
378
- if (!bundledDev) return;
379
- wrapBundledDevClientSend(bundledDev.clients);
424
+ const { bundledDev } = server.environments.client;
425
+ wrapBundledDevClientSend(bundledDev);
380
426
  wrapBundledDevFullReloads(bundledDev, server.config.root);
427
+ },
428
+ hotUpdate({ file, modules }) {
429
+ if (file.endsWith(".manual.html")) return modules;
381
430
  }
382
431
  };
383
432
  }
384
- function wrapBundledDevClientSend(clients) {
385
- if (!clients || typeof clients.setupIfNeeded != "function") return;
433
+ function wrapBundledDevClientSend(bundledDev) {
434
+ const clients = bundledDev.clients;
386
435
  const setupIfNeeded = clients.setupIfNeeded.bind(clients);
387
436
  clients.setupIfNeeded = (client, clientId) => {
388
- if (!client[isManualRefreshWrappedClient]) {
437
+ if (!wrappedClients.has(client)) {
389
438
  const send = client.send.bind(client);
390
439
  client.send = (payload) => {
391
- sendManualRefreshPayload(payload, send);
440
+ sendManualRefreshPayload(bundledDev, payload, send);
392
441
  };
393
- client[isManualRefreshWrappedClient] = true;
442
+ wrappedClients.add(client);
394
443
  }
395
444
  return setupIfNeeded(client, clientId);
396
445
  };
397
446
  }
398
447
  function wrapBundledDevFullReloads(bundledDev, workspaceRoot) {
399
- if (typeof bundledDev.handleHmrOutput != "function") return;
400
448
  const handleHmrOutput = bundledDev.handleHmrOutput.bind(bundledDev);
401
- bundledDev.handleHmrOutput = (client, files, hmrOutput, invalidateInformation) => {
402
- if (hmrOutput.type != "FullReload") return handleHmrOutput(client, files, hmrOutput, invalidateInformation);
403
- if (!shouldShowManualRefreshPromptForFiles(files)) {
449
+ bundledDev.handleHmrOutput = (client, files, hmrOutput) => {
450
+ if (hmrOutput.type != "FullReload") return handleHmrOutput(client, files, hmrOutput);
451
+ if (!shouldShowManualRefreshPrompt(files)) {
404
452
  reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot);
405
453
  return;
406
454
  }
@@ -413,25 +461,26 @@ function wrapBundledDevFullReloads(bundledDev, workspaceRoot) {
413
461
  }
414
462
  async function reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot) {
415
463
  try {
416
- await bundledDev.devEngine?.ensureLatestBuildOutput();
464
+ await bundledDev.devEngine.ensureLatestBuildOutput();
417
465
  } catch {}
466
+ const htmlFile = files.find((file) => isHtmlFile(file));
418
467
  client.send({
419
468
  type: "full-reload",
420
- path: getChangedHtmlPublicPath(files, workspaceRoot)
469
+ path: htmlFile ? toPublicFilePath(htmlFile, workspaceRoot) : void 0
421
470
  });
422
471
  }
423
- function getChangedHtmlPublicPath(files, workspaceRoot) {
424
- const htmlFile = files.find((file) => file.endsWith(".html"));
425
- return htmlFile ? toPublicFilePath(htmlFile, workspaceRoot) : void 0;
426
- }
427
472
  function ensureLatestBuildOutput(bundledDev) {
428
473
  try {
429
- bundledDev.devEngine?.ensureLatestBuildOutput().catch(() => {});
474
+ bundledDev.devEngine.ensureLatestBuildOutput().catch(() => {});
430
475
  } catch {}
431
476
  }
432
- function sendManualRefreshPayload(payload, send) {
433
- if (payload.type == "update" && !payload.updates.length) return;
434
- if (shouldShowManualRefreshPrompt(payload)) {
477
+ function sendManualRefreshPayload(bundledDev, payload, send) {
478
+ if (payload.type == "bundled-dev-update" && shouldShowManualRefreshPrompt(payload.changedIds)) {
479
+ ensureLatestBuildOutput(bundledDev);
480
+ send({
481
+ ...payload,
482
+ changedIds: []
483
+ });
435
484
  send({
436
485
  type: "custom",
437
486
  event: MANUAL_REFRESH_EVENT_NAME
@@ -440,17 +489,14 @@ function sendManualRefreshPayload(payload, send) {
440
489
  }
441
490
  send(payload);
442
491
  }
443
- function shouldShowManualRefreshPrompt(payload) {
444
- if (payload.type == "update") return !isCssUpdatePayload(payload);
445
- return false;
492
+ function shouldShowManualRefreshPrompt(filePaths) {
493
+ return filePaths.some((filePath) => !isCssFile(filePath) && !isHtmlFile(filePath));
446
494
  }
447
- function isCssUpdatePayload(payload) {
448
- return payload.type == "update" && payload.updates.length > 0 && payload.updates.every((update) => {
449
- return update.type == "css-update" || update.type == "js-update" && update.acceptedPath.endsWith(".css");
450
- });
495
+ function isCssFile(filePath) {
496
+ return filePath.split("?", 1)[0].endsWith(".css");
451
497
  }
452
- function shouldShowManualRefreshPromptForFiles(files) {
453
- return files.some((file) => !file.endsWith(".css") && !file.endsWith(".html"));
498
+ function isHtmlFile(filePath) {
499
+ return filePath.split("?", 1)[0].endsWith(".html");
454
500
  }
455
501
  //#endregion
456
502
  //#region src/svg-plugin/plugin.ts
@@ -6,5 +6,6 @@ import type { Plugin } from "vite";
6
6
  export interface ManualTestsPluginOptions {
7
7
  paths: Array<string>;
8
8
  include?: Array<string>;
9
+ language?: string;
9
10
  }
10
11
  export declare function manualTestsPlugin(options: ManualTestsPluginOptions): Plugin;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ export declare const MANUAL_TRANSLATIONS_VIRTUAL_ID: string;
6
+ export declare const RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID: string;
7
+ /**
8
+ * Generates the virtual module that loads translations from every configured package root.
9
+ */
10
+ export declare function createManualTranslationsModule(paths: Array<string>, language: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-dev-manual-server",
3
- "version": "59.1.0",
3
+ "version": "61.0.0",
4
4
  "description": "Used to extend Vite to create a manual test server for CKEditor 5.",
5
5
  "keywords": [],
6
6
  "author": "CKSource (http://cksource.com/)",
@@ -25,6 +25,6 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@ckeditor/ckeditor5-inspector": "^5.0.1",
28
- "vite": "^8.1.0"
28
+ "vite": "^8.2.1"
29
29
  }
30
30
  }