extension-develop 4.1.25 → 4.1.26

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.
@@ -1,5 +1,5 @@
1
1
  import { createRequire as __extjsCreateRequire } from "node:module"; const require = __extjsCreateRequire(import.meta.url);
2
- import { randomUUID } from "node:crypto";
2
+ import { createHash, randomUUID } from "node:crypto";
3
3
  import node_fs from "node:fs";
4
4
  import { Writable } from "node:stream";
5
5
  import { rspack } from "@rspack/core";
@@ -7,14 +7,13 @@ import { RspackDevServer } from "@rspack/dev-server";
7
7
  import { merge } from "webpack-merge";
8
8
  import { WebSocket, WebSocketServer } from "ws";
9
9
  import { classifyReloadFromSources, attachLifecycleStream, readContentScriptCount, humanLine, createChangedSourcesTracker, buildSourceFeatureIndex, applySplitChunksGuard, createLifecycleStream, dispatchReload } from "./840~0.mjs";
10
- import { runnerCleanupError, autoExitForceKill, loadCommandConfig, sanitize, extensionJsRunnerError, portInUse, devServerStartTimeout, autoExitTriggered, loadProjectConfigDefaults, runnerUncaughtException, shouldWarnPortConflict, devServerRestarting, DEV_COMMAND_DEFAULTS, mergeOptionLayers, runnerUnhandledRejection, createPlaywrightMetadataWriter, autoExitModeEnabled, getSessionRunId, loadCustomConfig, loadBrowserConfig, resolveCompanionExtensionsConfig, getSpecialFoldersDataForProjectRoot } from "./832~0.mjs";
10
+ import { runnerCleanupError, autoExitForceKill, loadCommandConfig, sanitize, extensionJsRunnerError, portInUse, devServerStartTimeout, autoExitTriggered, loadProjectConfigDefaults, runnerUncaughtException, shouldWarnPortConflict, devServerRestarting, DEV_COMMAND_DEFAULTS, mergeOptionLayers, runnerUnhandledRejection, createPlaywrightMetadataWriter, autoExitModeEnabled, emulatorViewerUrl, getSessionRunId, loadCustomConfig, loadBrowserConfig, resolveCompanionExtensionsConfig, getSpecialFoldersDataForProjectRoot } from "./832~0.mjs";
11
11
  import { CONTROL_WS_PATH, writePersistedControlPort, writeControlToken, clearControlToken, readPersistedControlPort } from "./616~0.mjs";
12
- import { hasProjectDependency, findNearestProjectManifestDirSync } from "./731~0.mjs";
12
+ import { isGeckoBasedBrowser, hasProjectDependency, isEmulatorBrowser, findNearestProjectManifestDirSync, isWebkitBasedBrowser } from "./474~0.mjs";
13
13
  import { publicFolderOrDefault } from "./105~0.mjs";
14
14
  import { setupNoBrowserBannerOnFirstDone, DevSessionRestartScheduler, resolveConnectableHost, default as webpackConfig, bindDevSessionRestart, unbindDevSessionRestart, setupCompilerLifecycleHooks } from "./rspack-config~0.mjs";
15
15
  import { logsPath, controlPortFilePath, legacyControlPortFilePath, asAbsolute, getDistPath, ensureSessionStateInProjectGitignore, actionsPath, ensureSessionArtifactsIgnoreFile } from "./950~0.mjs";
16
16
  import { isDebug } from "./852~0.mjs";
17
- import { isGeckoBasedBrowser, isWebkitBasedBrowser } from "./331~0.mjs";
18
17
  import * as __rspack_external_node_os_4f3c9d58 from "node:os";
19
18
  import * as __rspack_external_node_path_806ed179 from "node:path";
20
19
  import * as __rspack_external_node_fs_1b05aee1 from "node:fs";
@@ -181,6 +180,173 @@ function fnv1aHex(input) {
181
180
  }
182
181
  return (hash >>> 0).toString(16).padStart(8, '0');
183
182
  }
183
+ const EMULATOR_FILES_PATH = '/__extjs-emulator/files.json';
184
+ const EMULATOR_FILES_VERSION = 1;
185
+ const EMULATOR_WIRE_VERSION = '1';
186
+ const DEFAULT_EMULATOR_ORIGIN = 'https://browsers.extension.land';
187
+ function normalizeWebOrigin(value) {
188
+ const raw = String(value ?? '').trim();
189
+ if (!raw) return null;
190
+ let url;
191
+ try {
192
+ url = new URL(raw);
193
+ } catch {
194
+ return null;
195
+ }
196
+ if ('http:' !== url.protocol && 'https:' !== url.protocol) return null;
197
+ if (url.username || url.password) return null;
198
+ return url.origin;
199
+ }
200
+ function resolveEmulatorOrigin(env = process.env) {
201
+ const configured = String(env.EXTENSION_EMULATOR_ORIGIN || '').trim();
202
+ if (!configured) return DEFAULT_EMULATOR_ORIGIN;
203
+ const origin = normalizeWebOrigin(configured);
204
+ if (!origin) throw new Error(`EXTENSION_EMULATOR_ORIGIN must be an http or https origin, got: ${configured}`);
205
+ return origin;
206
+ }
207
+ function isSameWebOrigin(candidate, expected) {
208
+ if (!expected) return false;
209
+ const left = normalizeWebOrigin(candidate);
210
+ const right = normalizeWebOrigin(expected);
211
+ return null !== left && left === right;
212
+ }
213
+ function buildEmulatorViewerUrl(options) {
214
+ const host = options.host.includes(':') ? `[${options.host}]` : options.host;
215
+ const params = new URLSearchParams();
216
+ params.set('files', `http://${host}:${options.port}${EMULATOR_FILES_PATH}`);
217
+ if (null != options.controlPort) params.set('control', `ws://${host}:${options.controlPort}${options.controlPath}`);
218
+ params.set('instance', options.instanceId);
219
+ params.set('v', EMULATOR_WIRE_VERSION);
220
+ return `${options.origin}/chromium/#${params.toString()}`;
221
+ }
222
+ function hotModuleReplacementNames(assets) {
223
+ const names = new Set();
224
+ for (const asset of assets)if (asset.info?.hotModuleReplacement) {
225
+ names.add(asset.name);
226
+ for (const related of Object.values(asset.info.related || {}))for (const name of [].concat(related))if (name) names.add(name);
227
+ }
228
+ return names;
229
+ }
230
+ function isServableAssetName(name) {
231
+ if (!name || name.startsWith('/') || name.includes('\\')) return false;
232
+ return !/(?:^|\/)\.\.(?:\/|$)/.test(name);
233
+ }
234
+ function servedAssetPath(name) {
235
+ return name.split('/').filter((segment, index)=>'' !== segment && !('.' === segment && index > 0)).join('/');
236
+ }
237
+ function buildEmulatorFileIndex(assets, instanceId) {
238
+ const list = Array.from(assets);
239
+ const hotNames = hotModuleReplacementNames(list);
240
+ const files = [];
241
+ const listed = new Set();
242
+ for (const asset of list){
243
+ const name = String(asset.name || '');
244
+ if (!isServableAssetName(name) || hotNames.has(name)) continue;
245
+ const servedPath = servedAssetPath(name);
246
+ if (!servedPath || listed.has(servedPath)) continue;
247
+ listed.add(servedPath);
248
+ const bytes = asset.source.buffer();
249
+ files.push({
250
+ path: servedPath,
251
+ size: bytes.length,
252
+ sha256: createHash('sha256').update(bytes).digest('hex')
253
+ });
254
+ }
255
+ files.sort((a, b)=>a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
256
+ return {
257
+ version: EMULATOR_FILES_VERSION,
258
+ instanceId,
259
+ root: '/',
260
+ files
261
+ };
262
+ }
263
+ function resolveLivereloadPath(webSocketServer) {
264
+ if (!webSocketServer || 'object' != typeof webSocketServer) return null;
265
+ const options = webSocketServer.options;
266
+ const socketPath = options?.path;
267
+ return 'string' == typeof socketPath && socketPath.startsWith('/') ? socketPath : null;
268
+ }
269
+ function createEmulatorFileIndexHolder(instanceId) {
270
+ let latest = null;
271
+ let livereloadPath = null;
272
+ let waiters = [];
273
+ const withLivereload = (index)=>livereloadPath ? {
274
+ version: index.version,
275
+ instanceId: index.instanceId,
276
+ root: index.root,
277
+ livereload: {
278
+ path: livereloadPath
279
+ },
280
+ files: index.files
281
+ } : index;
282
+ return {
283
+ publish (assets) {
284
+ latest = buildEmulatorFileIndex(assets, instanceId);
285
+ const ready = waiters;
286
+ waiters = [];
287
+ for (const resolve of ready)resolve(withLivereload(latest));
288
+ return withLivereload(latest);
289
+ },
290
+ reset () {
291
+ latest = null;
292
+ },
293
+ setLivereloadPath (socketPath) {
294
+ livereloadPath = socketPath;
295
+ },
296
+ current () {
297
+ if (latest) return Promise.resolve(withLivereload(latest));
298
+ return new Promise((resolve)=>{
299
+ waiters.push(resolve);
300
+ });
301
+ }
302
+ };
303
+ }
304
+ function attachEmulatorFileIndex(compiler, holder) {
305
+ const done = compiler?.hooks?.done;
306
+ if (!done) return;
307
+ holder.reset();
308
+ done.tap({
309
+ name: 'extjs-emulator-files',
310
+ stage: -100
311
+ }, (stats)=>{
312
+ if (stats.compilation.errors?.length) return;
313
+ holder.publish(stats.compilation.getAssets());
314
+ });
315
+ }
316
+ function createEmulatorFilesMiddlewareEntry(holder, devServer) {
317
+ const options = devServer?.options;
318
+ holder.setLivereloadPath(resolveLivereloadPath(options?.webSocketServer));
319
+ return {
320
+ name: 'extjs-emulator-files',
321
+ path: EMULATOR_FILES_PATH,
322
+ middleware: createEmulatorFilesMiddleware(holder)
323
+ };
324
+ }
325
+ function createEmulatorFilesMiddleware(holder) {
326
+ return async (req, res, next)=>{
327
+ const method = String(req.method || 'GET').toUpperCase();
328
+ res.setHeader('Access-Control-Allow-Origin', '*');
329
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
330
+ res.setHeader('Cache-Control', 'no-store');
331
+ if ('OPTIONS' === method) {
332
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
333
+ res.statusCode = 204;
334
+ res.end();
335
+ return;
336
+ }
337
+ if ('GET' !== method && 'HEAD' !== method) return void next();
338
+ try {
339
+ const index = await holder.current();
340
+ const body = JSON.stringify(index);
341
+ res.statusCode = 200;
342
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
343
+ res.setHeader('Content-Length', Buffer.byteLength(body));
344
+ res.end('HEAD' === method ? void 0 : body);
345
+ } catch (error) {
346
+ next(error);
347
+ }
348
+ };
349
+ }
184
350
  const DEFAULT_RING_CAPACITY = 5000;
185
351
  class LogRingBuffer {
186
352
  capacity;
@@ -263,10 +429,16 @@ const UNDELIVERED_RELOAD_WARN = {
263
429
  'recently-disconnected': "SW not attached, your edit compiled but no page received it. The extension's service worker disconnected (MV3 workers idle out, or the browser may have exited). Reloads resume automatically once it reconnects."
264
430
  };
265
431
  const UNDELIVERED_RELOAD_WARN_WEBKIT_NEVER_CONNECTED = "Extension not connected, your edit compiled but no page received it. The extension has not connected to the dev server this session. Open Safari > Settings > Extensions and turn it on. Safari restarts the extension itself once it is enabled, and reloads resume automatically.";
432
+ const UNDELIVERED_RELOAD_WARN_EMULATOR = {
433
+ 'never-connected': "No emulated Chromium page is connected, your edit compiled but no page received it. Open the emulated Chromium URL the dev server printed; reloads resume automatically once the page connects.",
434
+ 'recently-disconnected': "The emulated Chromium page disconnected, your edit compiled but no page received it. Reopen the emulated Chromium URL the dev server printed; reloads resume automatically once the page reconnects."
435
+ };
266
436
  class BridgeBroker {
267
437
  instanceId;
268
438
  runId;
269
439
  engine;
440
+ viewerOrigin;
441
+ viewerEverConnected = false;
270
442
  ring;
271
443
  file;
272
444
  roles = new Map();
@@ -293,6 +465,7 @@ class BridgeBroker {
293
465
  this.instanceId = options.instanceId;
294
466
  this.runId = options.runId;
295
467
  this.engine = options.engine;
468
+ this.viewerOrigin = normalizeWebOrigin(options.viewerOrigin);
296
469
  this.ring = options.ring ?? new LogRingBuffer();
297
470
  this.file = options.file;
298
471
  this.allowEval = options.allowEval ?? false;
@@ -316,6 +489,9 @@ class BridgeBroker {
316
489
  get controllerCount() {
317
490
  return this.countRole('controller');
318
491
  }
492
+ get viewerCount() {
493
+ return this.countRole('viewer');
494
+ }
319
495
  get pendingCount() {
320
496
  return this.pending.size;
321
497
  }
@@ -334,7 +510,7 @@ class BridgeBroker {
334
510
  if ('producer' === this.roles.get(conn)) this.onResult(frame);
335
511
  return;
336
512
  case 'reload-ack':
337
- if ('producer' === this.roles.get(conn) && this.pendingReload?.reloadType === frame.reloadType) this.pendingReload = void 0;
513
+ if (this.isReloadReceiver(this.roles.get(conn)) && this.pendingReload?.reloadType === frame.reloadType) this.pendingReload = void 0;
338
514
  return;
339
515
  default:
340
516
  return;
@@ -368,7 +544,7 @@ class BridgeBroker {
368
544
  } : {}
369
545
  };
370
546
  let notified = 0;
371
- for (const [conn, role] of this.roles)if ('producer' === role) try {
547
+ for (const [conn, role] of this.roles)if (this.isReloadReceiver(role)) try {
372
548
  conn.send(frame);
373
549
  notified++;
374
550
  } catch {}
@@ -379,11 +555,12 @@ class BridgeBroker {
379
555
  const now = this.now();
380
556
  if (now - this.sessionStartedAt < UNDELIVERED_RELOAD_GRACE_MS) return null;
381
557
  if (null != this.lastStaleHelloAt && now - this.lastStaleHelloAt < STALE_RESYNC_HINT_MS) return null;
382
- const kind = this.producerEverConnected ? 'recently-disconnected' : 'never-connected';
558
+ const kind = this.producerEverConnected || this.viewerEverConnected ? 'recently-disconnected' : 'never-connected';
383
559
  if (options?.producerRestartExpected && 'recently-disconnected' === kind) return null;
384
560
  if (this.lastUndeliveredWarnKind === kind) return null;
385
561
  this.lastUndeliveredWarnKind = kind;
386
562
  if ('never-connected' === kind && 'webkit' === this.engine) return UNDELIVERED_RELOAD_WARN_WEBKIT_NEVER_CONNECTED;
563
+ if ('emulator' === this.engine) return UNDELIVERED_RELOAD_WARN_EMULATOR[kind];
387
564
  return UNDELIVERED_RELOAD_WARN[kind];
388
565
  }
389
566
  pingProducers() {
@@ -423,6 +600,26 @@ class BridgeBroker {
423
600
  }
424
601
  onHello(conn, hello) {
425
602
  if (1 !== hello.v) return void conn.close(4002, 'unsupported envelope version');
603
+ const fromViewerOrigin = isSameWebOrigin(conn.origin, this.viewerOrigin);
604
+ if ('viewer' === hello.role && !fromViewerOrigin) return void conn.close(4002, 'viewer not allowed from this origin');
605
+ if (fromViewerOrigin && 'viewer' !== hello.role) return void conn.close(4002, 'only the viewer role is allowed from a web origin');
606
+ if ('viewer' === hello.role) {
607
+ if (hello.instanceId !== this.instanceId) return void conn.close(4001, 'instanceId mismatch');
608
+ this.roles.set(conn, 'viewer');
609
+ this.viewerEverConnected = true;
610
+ this.lastUndeliveredWarnKind = null;
611
+ conn.send({
612
+ type: 'ready',
613
+ runId: this.runId,
614
+ engine: this.engine
615
+ });
616
+ if (this.pendingReload) {
617
+ const frame = this.pendingReload;
618
+ this.pendingReload = void 0;
619
+ conn.send(frame);
620
+ }
621
+ return;
622
+ }
426
623
  if (hello.instanceId !== this.instanceId) {
427
624
  if ('producer' === hello.role) {
428
625
  this.lastStaleHelloAt = this.now();
@@ -632,6 +829,9 @@ class BridgeBroker {
632
829
  conn.send(frame);
633
830
  } catch {}
634
831
  }
832
+ isReloadReceiver(role) {
833
+ return 'producer' === role || 'viewer' === role;
834
+ }
635
835
  countRole(role) {
636
836
  let n = 0;
637
837
  for (const r of this.roles.values())if (r === role) n++;
@@ -766,6 +966,10 @@ function isWebOrigin(origin) {
766
966
  const value = String(origin || '').trim();
767
967
  return 'null' === value.toLowerCase() || /^https?:\/\//i.test(value);
768
968
  }
969
+ function isAdmittedOrigin(origin, viewerOrigin) {
970
+ if (!isWebOrigin(origin)) return true;
971
+ return isSameWebOrigin(origin, viewerOrigin);
972
+ }
769
973
  function startControlServer(options) {
770
974
  const { broker } = options;
771
975
  const host = options.host ?? '127.0.0.1';
@@ -775,12 +979,14 @@ function startControlServer(options) {
775
979
  host,
776
980
  port: options.port ?? 0,
777
981
  path,
778
- verifyClient: (info)=>!isWebOrigin(info.origin)
982
+ verifyClient: (info)=>isAdmittedOrigin(info.origin, options.viewerOrigin)
779
983
  });
780
984
  wss.on('error', reject);
781
- wss.on('connection', (socket)=>{
985
+ wss.on('connection', (socket, request)=>{
986
+ const origin = request?.headers?.origin;
782
987
  const conn = {
783
988
  id: `c${++connSeq}`,
989
+ origin: 'string' == typeof origin ? origin : void 0,
784
990
  send (frame) {
785
991
  if (socket.readyState !== WebSocket.OPEN) return;
786
992
  if (socket.bufferedAmount > SLOW_CONSUMER_BYTES) {
@@ -909,6 +1115,35 @@ class PortManager {
909
1115
  this.currentInstance = null;
910
1116
  }
911
1117
  }
1118
+ function createResponseDataRepair(ref) {
1119
+ return (req, res, data, byteLength)=>{
1120
+ const untouched = {
1121
+ data,
1122
+ byteLength
1123
+ };
1124
+ if (0 !== byteLength) return untouched;
1125
+ if (!data || Buffer.isBuffer(data)) return untouched;
1126
+ if (req.headers.range || 206 === res.statusCode) return untouched;
1127
+ const outputPath = ref.outputPath();
1128
+ const outputFileSystem = ref.outputFileSystem();
1129
+ if (!outputPath || 'function' != typeof outputFileSystem?.readFileSync) return untouched;
1130
+ const pathname = decodeURIComponent(String(req.url || '').split('?')[0].split('#')[0]);
1131
+ const filename = __rspack_external_node_path_806ed179.join(outputPath, pathname);
1132
+ if (!filename.startsWith(__rspack_external_node_path_806ed179.join(outputPath, __rspack_external_node_path_806ed179.sep))) return untouched;
1133
+ let buffer;
1134
+ try {
1135
+ buffer = outputFileSystem.readFileSync(filename);
1136
+ } catch {
1137
+ return untouched;
1138
+ }
1139
+ if (0 === buffer.length) return untouched;
1140
+ if ('function' == typeof data.destroy) data.destroy();
1141
+ return {
1142
+ data: buffer,
1143
+ byteLength: buffer.length
1144
+ };
1145
+ };
1146
+ }
912
1147
  function shouldWriteAssetToDisk(filePath) {
913
1148
  if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(filePath)) return false;
914
1149
  return !/(?:^|[/\\])manifest\.json$/i.test(filePath);
@@ -1124,6 +1359,8 @@ async function dev_server_devServer(projectStructure, devOptions) {
1124
1359
  process.env.EXTENSION_DEV_SERVER_PORT = String(port);
1125
1360
  process.env.EXTENSION_DEV_SERVER_PATH = '/ws';
1126
1361
  const browserName = String(devOptions.browser || 'chromium');
1362
+ const emulatorLane = isEmulatorBrowser(browserName);
1363
+ const emulatorOrigin = emulatorLane ? resolveEmulatorOrigin() : null;
1127
1364
  const primaryDistPath = getDistPath(asAbsolute(packageJsonDir), browserName);
1128
1365
  const bridgeLogsAbsPath = logsPath(packageJsonDir, browserName);
1129
1366
  ensureSessionArtifactsIgnoreFile(packageJsonDir);
@@ -1146,7 +1383,8 @@ async function dev_server_devServer(projectStructure, devOptions) {
1146
1383
  const bridgeBroker = new BridgeBroker({
1147
1384
  instanceId: currentInstance.instanceId,
1148
1385
  runId: sessionRunId,
1149
- engine: isGeckoBasedBrowser(browserName) ? 'firefox' : isWebkitBasedBrowser(browserName) ? 'webkit' : 'chromium',
1386
+ engine: emulatorLane ? 'emulator' : isGeckoBasedBrowser(browserName) ? 'firefox' : isWebkitBasedBrowser(browserName) ? 'webkit' : 'chromium',
1387
+ viewerOrigin: emulatorOrigin,
1150
1388
  ring: new LogRingBuffer(),
1151
1389
  file: bridgeLogFile,
1152
1390
  allowControl,
@@ -1195,13 +1433,15 @@ async function dev_server_devServer(projectStructure, devOptions) {
1195
1433
  controlServer = await startControlServer({
1196
1434
  broker: bridgeBroker,
1197
1435
  host: devServerHost,
1198
- port: preferredControlPort ?? 0
1436
+ port: preferredControlPort ?? 0,
1437
+ viewerOrigin: emulatorOrigin
1199
1438
  });
1200
1439
  } catch (error) {
1201
1440
  if (null == preferredControlPort) throw error;
1202
1441
  controlServer = await startControlServer({
1203
1442
  broker: bridgeBroker,
1204
- host: devServerHost
1443
+ host: devServerHost,
1444
+ viewerOrigin: emulatorOrigin
1205
1445
  });
1206
1446
  }
1207
1447
  bridgeControlPort = controlServer.port;
@@ -1213,6 +1453,16 @@ async function dev_server_devServer(projectStructure, devOptions) {
1213
1453
  }
1214
1454
  process.env.EXTENSION_INSTANCE_ID = currentInstance.instanceId;
1215
1455
  process.env.EXTENSION_CONTROL_PORT = null != bridgeControlPort ? String(bridgeControlPort) : '';
1456
+ const emulatorFiles = emulatorLane ? createEmulatorFileIndexHolder(currentInstance.instanceId) : null;
1457
+ const emulatorViewer = emulatorLane && emulatorOrigin ? buildEmulatorViewerUrl({
1458
+ origin: emulatorOrigin,
1459
+ host: connectableHost,
1460
+ port,
1461
+ controlPort: bridgeControlPort,
1462
+ controlPath: CONTROL_WS_PATH,
1463
+ instanceId: currentInstance.instanceId
1464
+ }) : null;
1465
+ if (emulatorViewer && launchedPlugin && 'function' == typeof launchedPlugin.setEmulatorViewerUrl) launchedPlugin.setEmulatorViewerUrl(emulatorViewer);
1216
1466
  process.once('exit', ()=>{
1217
1467
  try {
1218
1468
  bridgeLogFile.close();
@@ -1281,6 +1531,7 @@ async function dev_server_devServer(projectStructure, devOptions) {
1281
1531
  port
1282
1532
  });
1283
1533
  if (shouldWarnPortConflict(devOptions.port, port)) humanLine(portInUse(Number(devOptions.port), port));
1534
+ let activeCompiler = null;
1284
1535
  const serverConfig = {
1285
1536
  host: devServerHost,
1286
1537
  allowedHosts: 'all',
@@ -1293,7 +1544,11 @@ async function dev_server_devServer(projectStructure, devOptions) {
1293
1544
  writeToDisk: shouldWriteAssetToDisk,
1294
1545
  stats: {
1295
1546
  all: false
1296
- }
1547
+ },
1548
+ modifyResponseData: createResponseDataRepair({
1549
+ outputPath: ()=>activeCompiler?.options?.output?.path,
1550
+ outputFileSystem: ()=>activeCompiler?.outputFileSystem
1551
+ })
1297
1552
  },
1298
1553
  watchFiles: {
1299
1554
  paths: [
@@ -1317,6 +1572,12 @@ async function dev_server_devServer(projectStructure, devOptions) {
1317
1572
  headers: {
1318
1573
  'Access-Control-Allow-Origin': '*'
1319
1574
  },
1575
+ ...emulatorFiles ? {
1576
+ setupMiddlewares: (...args)=>[
1577
+ createEmulatorFilesMiddlewareEntry(emulatorFiles, args[1]),
1578
+ ...args[0]
1579
+ ]
1580
+ } : {},
1320
1581
  port,
1321
1582
  hot: true,
1322
1583
  liveReload: true
@@ -1327,6 +1588,7 @@ async function dev_server_devServer(projectStructure, devOptions) {
1327
1588
  const customWebpackConfig = await loadCustomConfig(packageJsonDir);
1328
1589
  const compilerConfig = applySplitChunksGuard(merge(customWebpackConfig(baseConfig), {}));
1329
1590
  const compiler = rspack(compilerConfig);
1591
+ activeCompiler = compiler;
1330
1592
  const uninstallManifestGuard = installManifestDiskWriteGuard(manifestOutputPath);
1331
1593
  const releaseOutputFsGuard = suppressManifestOutputWrites(compiler, manifestOutputPath);
1332
1594
  if (!extendedOptions.browsersPlugin && compiler?.hooks?.watchRun) {
@@ -1354,6 +1616,15 @@ async function dev_server_devServer(projectStructure, devOptions) {
1354
1616
  });
1355
1617
  });
1356
1618
  }
1619
+ if (emulatorFiles) attachEmulatorFileIndex(compiler, emulatorFiles);
1620
+ if (emulatorViewer && !opts.isRestart && devOptions.noBrowser) {
1621
+ let printedViewer = false;
1622
+ compiler.hooks.done.tap('extjs-emulator-viewer-url', (stats)=>{
1623
+ if (printedViewer || stats?.hasErrors?.()) return;
1624
+ printedViewer = true;
1625
+ humanLine(emulatorViewerUrl(emulatorViewer));
1626
+ });
1627
+ }
1357
1628
  attachLifecycleStream(compiler, lifecycle);
1358
1629
  setupCompilerLifecycleHooks(compiler);
1359
1630
  if (!opts.isRestart && devOptions.noBrowser) setupNoBrowserBannerOnFirstDone({
@@ -4,7 +4,7 @@ import node_path from "node:path";
4
4
  import { parse } from "es-module-lexer";
5
5
  import { validate } from "schema-utils";
6
6
  import { filterKeysForThisBrowser, reservedScriptsFolder } from "./672~0.mjs";
7
- import { stripBom, findNearestProjectManifestSync } from "./731~0.mjs";
7
+ import { stripBom, findNearestProjectManifestSync } from "./474~0.mjs";
8
8
  import { canonicalizeDir, canonicalizeResourcePath } from "./938~0.mjs";
9
9
  import { getCanonicalContentScriptEntryName, CANONICAL_CONTENT_SCRIPT_ENTRY_PREFIX, getContentScriptCssProbeMarker } from "./640~0.mjs";
10
10
  import { EXTENSION_ROOT_PLACEHOLDER } from "./747~0.mjs";
@@ -4,7 +4,7 @@ import node_path from "node:path";
4
4
  import { parse } from "es-module-lexer";
5
5
  import { validate } from "schema-utils";
6
6
  import { filterKeysForThisBrowser, reservedScriptsFolder } from "./672~0.mjs";
7
- import { stripBom, findNearestProjectManifestSync } from "./731~0.mjs";
7
+ import { stripBom, findNearestProjectManifestSync } from "./474~0.mjs";
8
8
  import { canonicalizeDir, canonicalizeResourcePath } from "./938~0.mjs";
9
9
  import { getCanonicalContentScriptEntryName, CANONICAL_CONTENT_SCRIPT_ENTRY_PREFIX, getContentScriptCssProbeMarker } from "./640~0.mjs";
10
10
  import { EXTENSION_ROOT_PLACEHOLDER } from "./747~0.mjs";
@@ -4,6 +4,9 @@ export declare const GECKO_BASED_BROWSERS: string[];
4
4
  export declare const WEBKIT_BASED_BROWSERS: string[];
5
5
  export declare const CHROMIUM_FAMILY_ALIASES: string[];
6
6
  export declare const GECKO_FAMILY_ALIASES: string[];
7
+ export declare const EMULATOR_BROWSER = "chromium-emulator";
8
+ export declare function isEmulatorLaneEnabled(env?: NodeJS.ProcessEnv): boolean;
9
+ export declare function isEmulatorBrowser(browser: unknown): boolean;
7
10
  export declare const SUPPORTED_BROWSERS: string[];
8
11
  export declare const SUPPORTED_PACKAGE_MANAGERS: readonly ['npm', 'pnpm', 'yarn', 'bun', 'deno'];
9
12
  export declare const SUPPORTED_RUNTIMES: readonly ['node', 'deno', 'bun'];
@@ -3,6 +3,7 @@ export interface ManagedExtensionRecord {
3
3
  id?: string;
4
4
  }
5
5
  export declare function chromiumExtensionIdFromKey(manifestKey: string): string;
6
+ export declare function chromiumExtensionIdFromRegisteredPath(registeredPath: string, platform?: NodeJS.Platform): string;
6
7
  export declare function chromiumExtensionIdFromPath(extensionDir: string): string;
7
8
  export declare function chromiumExtensionId(extensionDir: string): string;
8
9
  export declare function geckoExtensionId(extensionDir: string): string | undefined;
@@ -20,6 +20,7 @@ export declare function projectInstallScriptsDisabled(pmName: string): string;
20
20
  export declare function anotherDevSessionActive(browser: string, pid: number, runId: string): string;
21
21
  export declare function buildAssetsTree(stats: Stats | undefined): string;
22
22
  export declare function buildComplete(browser: DevOptions['browser'], distDisplayPath: string, totalBytes?: number, mode?: 'development' | 'production' | 'none'): string;
23
+ export declare function operaBuildUnminified(): string;
23
24
  export declare function addonLintSummary(errorCount: number, warningCount: number, distDisplay: string): string;
24
25
  export declare function addonLintFinding(level: 'error' | 'warning', code: string, message: string, location: string, attribution?: string): string;
25
26
  export declare function addonLintDependencyAttribution(packages: string[], onlyDependencies: boolean): string;
@@ -2,8 +2,8 @@ import type { ProjectStructure } from './project.js';
2
2
  export type AbsolutePath = string & {
3
3
  readonly __brand: 'AbsolutePath';
4
4
  };
5
- export type BrowserInput = 'chrome' | 'edge' | 'chromium' | 'firefox' | 'brave' | 'opera' | 'vivaldi' | 'yandex' | 'waterfox' | 'librewolf' | 'chromium-based' | 'gecko-based' | 'firefox-based' | 'safari' | 'webkit-based' | undefined;
6
- export type NormalizedBrowser = 'chrome' | 'edge' | 'chromium' | 'firefox' | 'brave' | 'opera' | 'vivaldi' | 'yandex' | 'waterfox' | 'librewolf' | 'chromium-based' | 'gecko-based' | 'safari' | 'webkit-based';
5
+ export type BrowserInput = 'chrome' | 'edge' | 'chromium' | 'firefox' | 'brave' | 'opera' | 'vivaldi' | 'yandex' | 'waterfox' | 'librewolf' | 'zen' | 'floorp' | 'chromium-based' | 'gecko-based' | 'firefox-based' | 'safari' | 'webkit-based' | 'chromium-emulator' | undefined;
6
+ export type NormalizedBrowser = 'chrome' | 'edge' | 'chromium' | 'firefox' | 'brave' | 'opera' | 'vivaldi' | 'yandex' | 'waterfox' | 'librewolf' | 'zen' | 'floorp' | 'chromium-based' | 'gecko-based' | 'safari' | 'webkit-based' | 'chromium-emulator';
7
7
  export declare function asAbsolute(p: string): AbsolutePath;
8
8
  export declare function toPosixPath(p: string): string;
9
9
  export declare function collapseHomeDir(value: string): string;
@@ -47,6 +47,7 @@ export interface BrowserLaunchOptions {
47
47
  logColor?: boolean;
48
48
  logUrl?: string;
49
49
  logTab?: number | string;
50
+ emulatorViewerUrl?: string;
50
51
  logSink?: BrowserLogSink;
51
52
  }
52
53
  /** Browser-generated log entry normalized by the launcher's CDP controller. */
@@ -118,6 +119,7 @@ export declare class BrowsersPlugin implements RunnerPlugin {
118
119
  private controller;
119
120
  private reloadBroker;
120
121
  private logSink;
122
+ private emulatorViewerUrl;
121
123
  private lastReportedRefusal;
122
124
  constructor(options: BrowsersPluginOptions);
123
125
  /**
@@ -127,6 +129,7 @@ export declare class BrowsersPlugin implements RunnerPlugin {
127
129
  */
128
130
  setReloadBroker(broker: ReloadBroker): void;
129
131
  setLogSink(sink: BrowserLogSink): void;
132
+ setEmulatorViewerUrl(url: string): void;
130
133
  private retryRefusedExtensionLoad;
131
134
  apply(compiler: Compiler): void;
132
135
  }
@@ -29,7 +29,8 @@ export type ReadyMetadata = {
29
29
  cdpPort?: number;
30
30
  rdpPort?: number;
31
31
  profilePath?: string;
32
- browserPid?: number;
32
+ browserPid?: number | null;
33
+ engine?: 'emulator';
33
34
  launcherPid?: number;
34
35
  toolchainVersion: string;
35
36
  extensionName?: string;
@@ -41,6 +41,7 @@ export declare class TraceRuntimeLoadedFiles {
41
41
  });
42
42
  apply(compiler: Compiler): void;
43
43
  private readManifest;
44
+ private traceManifestNamedFiles;
44
45
  private traceWorkerImportScripts;
45
46
  private traceFetchedFiles;
46
47
  private traceGetURLFiles;
@@ -0,0 +1,2 @@
1
+ import type { FilepathList } from '../../types.js';
2
+ export declare function omitOwnedPages(pages: FilepathList | undefined, owned: FilepathList): FilepathList;