@weapp-vite/miniprogram-automator 1.2.14 → 1.2.16

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.mts CHANGED
@@ -269,7 +269,7 @@ declare class Page {
269
269
  $(selector: string, options?: PageQueryOptions): Promise<Element | null>;
270
270
  $$(selector: string, options?: PageQueryOptions): Promise<any>;
271
271
  getElementByXpath(selector: string, options?: PageQueryOptions): Promise<Element | null>;
272
- getElementsByXpath(selector: string, options?: PageQueryOptions): Promise<any>;
272
+ getElementsByXpath(selector: string, options?: PageQueryOptions): Promise<Element[]>;
273
273
  xpath(selector: string): Promise<Element | null>;
274
274
  wxml(): Promise<any>;
275
275
  renderedNodes(selector: string, options?: PageQueryOptions): Promise<RenderedNodeSnapshot[]>;
@@ -351,10 +351,10 @@ declare class MiniProgram extends EventEmitter {
351
351
  close(): Promise<void>;
352
352
  remote(auto?: boolean): Promise<void>;
353
353
  disconnect(): void;
354
- enableLog(): Promise<void>;
354
+ enableLog(timeout?: number): Promise<void>;
355
355
  on(event: string | symbol, listener: (...args: any[]) => void): this;
356
356
  exposeFunction(name: string, bindingFunction: AutomatorCallable): Promise<void>;
357
- checkVersion(): Promise<void>;
357
+ checkVersion(timeout?: number): Promise<void>;
358
358
  /**
359
359
  * @description 等待小程序 App 域协议可用,避免返回半就绪自动化会话。
360
360
  */
package/dist/index.mjs CHANGED
@@ -217,10 +217,6 @@ function cmpVersion(versionA, versionB) {
217
217
  }
218
218
  return 0;
219
219
  }
220
- /** toStr 的方法封装。 */
221
- function toStr(value) {
222
- return value == null ? "" : String(value);
223
- }
224
220
  //#endregion
225
221
  //#region src/pageRouteSnapshot.ts
226
222
  const ROUTE_ELEMENT_SNAPSHOT_TIMEOUT = 5e3;
@@ -946,7 +942,7 @@ var Connection = class Connection extends EventEmitter {
946
942
  //#endregion
947
943
  //#region src/headless.ts
948
944
  async function launchHeadlessAutomator(options) {
949
- return await (await import("./launch-DKcPk1N2.mjs")).launch({ projectPath: options.projectPath });
945
+ return await (await import("./launch-Dqnd-Lqe.mjs")).launch({ projectPath: options.projectPath });
950
946
  }
951
947
  //#endregion
952
948
  //#region src/launcher/portLease.ts
@@ -1044,8 +1040,64 @@ async function acquireAutomatorPortLease(preferredPort) {
1044
1040
  return createLease(await getPort(0, "127.0.0.1"));
1045
1041
  }
1046
1042
  //#endregion
1043
+ //#region src/launcher/wechatCliFallback.ts
1044
+ const WECHAT_DEVTOOLS_SERVICE_PORT_RE = /listening\s+on\s+http:\/\/127\.0\.0\.1:(\d{2,5})/i;
1045
+ function extractWechatDevtoolsServicePort(output) {
1046
+ const port = Number(output.match(WECHAT_DEVTOOLS_SERVICE_PORT_RE)?.[1]);
1047
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
1048
+ }
1049
+ async function enableAutomatorViaHttp(options) {
1050
+ const endpoint = new URL("/auto", `http://127.0.0.1:${options.servicePort}`);
1051
+ endpoint.searchParams.set("project", options.projectPath);
1052
+ endpoint.searchParams.set("port", String(options.autoPort));
1053
+ endpoint.searchParams.set("autoPort", String(options.autoPort));
1054
+ if (options.account) endpoint.searchParams.set("account", options.account);
1055
+ if (options.ticket) endpoint.searchParams.set("ticket", options.ticket);
1056
+ if (options.trustProject) endpoint.searchParams.set("trustProject", "true");
1057
+ const response = await fetch(endpoint, { redirect: "follow" });
1058
+ const body = await response.text();
1059
+ if (!response.ok) throw new Error(`WeChat DevTools HTTP automator fallback failed with status ${response.status}${body.trim() ? `: ${body.trim().slice(0, 400)}` : ""}`);
1060
+ let result;
1061
+ try {
1062
+ result = JSON.parse(body);
1063
+ } catch (error) {
1064
+ throw new Error("WeChat DevTools HTTP automator fallback returned invalid JSON", { cause: error });
1065
+ }
1066
+ if (result && typeof result === "object" && "autoPort" in result) {
1067
+ const autoPort = Number(result.autoPort);
1068
+ if (!Number.isInteger(autoPort) || autoPort <= 0 || autoPort > 65535) throw new Error(`WeChat DevTools HTTP automator fallback returned invalid autoPort: ${String(result.autoPort)}`);
1069
+ return autoPort;
1070
+ }
1071
+ if (typeof result === "string" && /^s\d+$/.test(result)) return options.autoPort;
1072
+ throw new Error(`WeChat DevTools HTTP automator fallback returned unsupported response: ${body.trim().slice(0, 400)}`);
1073
+ }
1074
+ const AUTOMATOR_VALUE_OPTIONS = /* @__PURE__ */ new Set([
1075
+ "--auto-account",
1076
+ "--auto-port",
1077
+ "--autoAccount",
1078
+ "--autoPort",
1079
+ "--project",
1080
+ "--test-ticket",
1081
+ "--ticket"
1082
+ ]);
1083
+ const AUTOMATOR_FLAG_OPTIONS = /* @__PURE__ */ new Set(["--trust-project"]);
1084
+ function resolveWechatDevtoolsBootstrapArgs(args) {
1085
+ const bootstrapArgs = [];
1086
+ for (let index = 0; index < args.length; index += 1) {
1087
+ const arg = args[index];
1088
+ if (AUTOMATOR_VALUE_OPTIONS.has(arg)) {
1089
+ index += 1;
1090
+ continue;
1091
+ }
1092
+ if (AUTOMATOR_FLAG_OPTIONS.has(arg) || arg === "auto") continue;
1093
+ bootstrapArgs.push(arg);
1094
+ }
1095
+ bootstrapArgs.push("islogin");
1096
+ return bootstrapArgs;
1097
+ }
1098
+ //#endregion
1047
1099
  //#region package.json
1048
- var version = "1.2.14";
1100
+ var version = "1.2.16";
1049
1101
  //#endregion
1050
1102
  //#region src/Native.ts
1051
1103
  /** Native 的实现。 */
@@ -1400,7 +1452,8 @@ var Page = class Page {
1400
1452
  }
1401
1453
  }
1402
1454
  async getElementsByXpath(selector, options = {}) {
1403
- const { elements } = await this.send("Page.getElementsByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT });
1455
+ const elements = (await this.send("Page.getElementsByXpath", { selector }, { timeout: options.timeout ?? PAGE_QUERY_TIMEOUT }))?.elements;
1456
+ if (!Array.isArray(elements)) throw new TypeError("DevTools Page.getElementsByXpath 返回了无效响应:缺少 elements 数组。");
1404
1457
  return elements.map((element) => {
1405
1458
  return Element.create(this.connection, {
1406
1459
  ...element,
@@ -2447,8 +2500,9 @@ var MiniProgram = class extends EventEmitter {
2447
2500
  disconnect() {
2448
2501
  this.connection.dispose();
2449
2502
  }
2450
- async enableLog() {
2451
- await this.send("App.enableLog");
2503
+ async enableLog(timeout) {
2504
+ if (timeout === void 0) await this.send("App.enableLog");
2505
+ else await this.send("App.enableLog", {}, { timeout });
2452
2506
  this.logEnabled = true;
2453
2507
  }
2454
2508
  on(event, listener) {
@@ -2461,8 +2515,8 @@ var MiniProgram = class extends EventEmitter {
2461
2515
  this.appBindings.set(name, bindingFunction);
2462
2516
  await this.send("App.addBinding", { name });
2463
2517
  }
2464
- async checkVersion() {
2465
- const toolInfo = await this.send("Tool.getInfo");
2518
+ async checkVersion(timeout) {
2519
+ const toolInfo = timeout === void 0 ? await this.send("Tool.getInfo") : await this.send("Tool.getInfo", {}, { timeout });
2466
2520
  this.connection.configureToolInfo(toolInfo);
2467
2521
  const sdkVersion = toolInfo.SDKVersion;
2468
2522
  if (typeof sdkVersion !== "string" || !sdkVersion.trim()) return;
@@ -2474,12 +2528,16 @@ var MiniProgram = class extends EventEmitter {
2474
2528
  async waitForAppReady(timeout = APP_READY_TIMEOUT) {
2475
2529
  const startedAt = Date.now();
2476
2530
  let lastError;
2477
- while (Date.now() - startedAt <= timeout) try {
2478
- await this.send("App.captureScreenshot", {}, { timeout: APP_READY_PROBE_TIMEOUT });
2479
- return;
2480
- } catch (error) {
2481
- lastError = error;
2482
- await sleep(APP_READY_POLL_DELAY);
2531
+ while (Date.now() - startedAt < timeout) {
2532
+ const remainingTimeout = timeout - (Date.now() - startedAt);
2533
+ try {
2534
+ await this.send("App.captureScreenshot", {}, { timeout: Math.min(APP_READY_PROBE_TIMEOUT, remainingTimeout) });
2535
+ return;
2536
+ } catch (error) {
2537
+ lastError = error;
2538
+ const remainingAfterProbe = timeout - (Date.now() - startedAt);
2539
+ if (remainingAfterProbe > 0) await sleep(Math.min(APP_READY_POLL_DELAY, remainingAfterProbe));
2540
+ }
2483
2541
  }
2484
2542
  throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Timed out waiting App domain readiness after ${timeout}ms`);
2485
2543
  }
@@ -3128,6 +3186,7 @@ var SwanLauncher = class {
3128
3186
  * @file 开发者工具启动与连接流程。
3129
3187
  */
3130
3188
  const DEFAULT_TIMEOUT = 3e4;
3189
+ const VERSION_CHECK_TIMEOUT = 3e4;
3131
3190
  const AUTOMATOR_LAUNCH_RETRIES = 3;
3132
3191
  const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
3133
3192
  const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
@@ -3243,24 +3302,23 @@ var Launcher = class {
3243
3302
  let processError = null;
3244
3303
  let processExitCode = null;
3245
3304
  let processSignal = null;
3246
- args = [
3247
- ...args,
3248
- "auto",
3249
- "--project",
3250
- resolvedProjectPath,
3251
- "--auto-port",
3252
- toStr(port)
3253
- ];
3254
- if (account) args.push("--auto-account", account);
3255
- else if (ticket) args.push("--ticket", ticket);
3256
- if (trustProject) args.push("--trust-project");
3305
+ let successfulCliExitSettled = false;
3306
+ let httpFallbackAttempted = false;
3307
+ let httpFallbackError = null;
3308
+ let targetPort = port;
3309
+ const cliOutput = [];
3310
+ args = resolveWechatDevtoolsBootstrapArgs(args);
3257
3311
  try {
3258
3312
  const spawnTarget = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, args) : {
3259
3313
  file: cliPath,
3260
3314
  args
3261
3315
  };
3262
3316
  const child = spawn(spawnTarget.file, spawnTarget.args, {
3263
- stdio: "ignore",
3317
+ stdio: [
3318
+ "ignore",
3319
+ "pipe",
3320
+ "pipe"
3321
+ ],
3264
3322
  cwd: cwd || void 0,
3265
3323
  ...shouldUseWindowsCommandShell(cliPath) ? {
3266
3324
  windowsHide: true,
@@ -3270,10 +3328,17 @@ var Launcher = class {
3270
3328
  child.on("error", (error) => {
3271
3329
  processError = error;
3272
3330
  });
3331
+ child.stdout?.on("data", (chunk) => {
3332
+ cliOutput.push(String(chunk));
3333
+ });
3334
+ child.stderr?.on("data", (chunk) => {
3335
+ cliOutput.push(String(chunk));
3336
+ });
3273
3337
  child.on("exit", (code, signal) => {
3274
3338
  processExitCode = code;
3275
3339
  processSignal = signal;
3276
3340
  if (code !== 0 || signal) processError = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
3341
+ else successfulCliExitSettled = true;
3277
3342
  });
3278
3343
  child.unref();
3279
3344
  } catch (error) {
@@ -3281,16 +3346,51 @@ var Launcher = class {
3281
3346
  }
3282
3347
  let miniProgram = null;
3283
3348
  let lastConnectError = null;
3349
+ const readinessStartedAt = Date.now();
3350
+ const resolveRemainingTimeout = () => Math.max(0, timeout - (Date.now() - readinessStartedAt));
3284
3351
  await waitUntil(async () => {
3285
3352
  try {
3286
3353
  if (processError) return true;
3354
+ if (successfulCliExitSettled && !httpFallbackAttempted) {
3355
+ const servicePort = extractWechatDevtoolsServicePort(cliOutput.join("\n"));
3356
+ if (servicePort) {
3357
+ httpFallbackAttempted = true;
3358
+ try {
3359
+ targetPort = await enableAutomatorViaHttp({
3360
+ account,
3361
+ autoPort: port,
3362
+ projectPath: resolvedProjectPath,
3363
+ servicePort,
3364
+ ticket,
3365
+ trustProject
3366
+ });
3367
+ } catch (error) {
3368
+ httpFallbackError = error;
3369
+ return true;
3370
+ }
3371
+ }
3372
+ }
3373
+ const connectTimeout = resolveRemainingTimeout();
3374
+ if (connectTimeout <= 0) return false;
3287
3375
  const candidate = await this.connectTool({
3288
- timeout: 3e3,
3289
- wsEndpoint: `ws://127.0.0.1:${port}`
3376
+ timeout: Math.min(3e3, connectTimeout),
3377
+ wsEndpoint: `ws://127.0.0.1:${targetPort}`
3290
3378
  });
3291
3379
  try {
3292
- await candidate.checkVersion();
3293
- if (typeof candidate.waitForAppReady === "function") await candidate.waitForAppReady(timeout);
3380
+ const checkVersionTimeout = resolveRemainingTimeout();
3381
+ if (checkVersionTimeout <= 0) {
3382
+ candidate.disconnect();
3383
+ return false;
3384
+ }
3385
+ await candidate.checkVersion(Math.min(VERSION_CHECK_TIMEOUT, checkVersionTimeout));
3386
+ if (typeof candidate.waitForAppReady === "function") {
3387
+ const appReadyTimeout = resolveRemainingTimeout();
3388
+ if (appReadyTimeout <= 0) {
3389
+ candidate.disconnect();
3390
+ return false;
3391
+ }
3392
+ await candidate.waitForAppReady(appReadyTimeout);
3393
+ }
3294
3394
  } catch (error) {
3295
3395
  candidate.disconnect();
3296
3396
  lastConnectError = error;
@@ -3305,6 +3405,7 @@ var Launcher = class {
3305
3405
  }
3306
3406
  }, timeout, 1e3);
3307
3407
  if (!miniProgram) {
3408
+ if (httpFallbackError) throw httpFallbackError;
3308
3409
  if (processError) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified");
3309
3410
  if (lastConnectError) throw lastConnectError;
3310
3411
  if (processExitCode !== null || processSignal) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
@@ -3312,9 +3413,9 @@ var Launcher = class {
3312
3413
  }
3313
3414
  const resolvedMiniProgram = miniProgram;
3314
3415
  Reflect.set(resolvedMiniProgram, "__WEAPP_VITE_SESSION_METADATA", {
3315
- port,
3416
+ port: targetPort,
3316
3417
  projectPath: resolvedProjectPath,
3317
- wsEndpoint: `ws://127.0.0.1:${port}`
3418
+ wsEndpoint: `ws://127.0.0.1:${targetPort}`
3318
3419
  });
3319
3420
  releasePortLeaseOnExit = !retainPortLeaseUntilSessionClose(resolvedMiniProgram, portLease);
3320
3421
  await sleep$1(5e3);
@@ -3326,7 +3427,7 @@ var Launcher = class {
3326
3427
  async connect(options) {
3327
3428
  if (normalizePlatform(options.platform) === "swan") return await new SwanLauncher().connect(options);
3328
3429
  const miniProgram = await this.connectTool(options);
3329
- await miniProgram.checkVersion();
3430
+ await miniProgram.checkVersion(options.timeout);
3330
3431
  return miniProgram;
3331
3432
  }
3332
3433
  async extendProjectConfig(projectConfig, projectPath) {
@@ -12036,6 +12036,42 @@ function resolveTemplateData(node, source) {
12036
12036
  return resolved && typeof resolved === "object" ? resolved : {};
12037
12037
  }
12038
12038
  //#endregion
12039
+ //#region ../../mpcore/packages/simulator/src/project/plugins.ts
12040
+ const HEADLESS_PLUGIN_VIRTUAL_ROOT = "__plugins__";
12041
+ const PLUGIN_URL_RE = /^plugin:\/\/([^/]+)\/(.+)$/;
12042
+ function createPluginVirtualRoot(alias) {
12043
+ return `${HEADLESS_PLUGIN_VIRTUAL_ROOT}/${alias}`;
12044
+ }
12045
+ function resolvePluginRequest(plugins, request, kind) {
12046
+ const match = PLUGIN_URL_RE.exec(request);
12047
+ if (!match) return;
12048
+ const [, alias, publicName] = match;
12049
+ const plugin = plugins.find((item) => item.alias === alias);
12050
+ const registry = kind === "page" ? plugin?.config.pages : plugin?.config.publicComponents;
12051
+ const resourcePath = registry && typeof registry === "object" && !Array.isArray(registry) ? registry[publicName] : void 0;
12052
+ if (!plugin || typeof resourcePath !== "string" || !resourcePath) return;
12053
+ return {
12054
+ plugin,
12055
+ publicName,
12056
+ resourcePath: `${plugin.virtualRoot}/${resourcePath}`
12057
+ };
12058
+ }
12059
+ function createPluginArtifactSource(fallback, miniprogramRootPath, plugins) {
12060
+ function resolvePhysicalPath(filePath) {
12061
+ const normalizedPath = path.resolve(filePath);
12062
+ for (const plugin of plugins) {
12063
+ const virtualRootPath = path.resolve(miniprogramRootPath, plugin.virtualRoot);
12064
+ const relativePath = path.relative(virtualRootPath, normalizedPath);
12065
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)) return path.resolve(plugin.rootPath, relativePath);
12066
+ }
12067
+ return normalizedPath;
12068
+ }
12069
+ return {
12070
+ has: (filePath) => fallback.has(resolvePhysicalPath(filePath)),
12071
+ readText: (filePath) => fallback.readText(resolvePhysicalPath(filePath))
12072
+ };
12073
+ }
12074
+ //#endregion
12039
12075
  //#region ../../mpcore/packages/simulator/src/view/eventBinding.ts
12040
12076
  const LEGACY_EVENT_NAME_RE = /^\w+$/;
12041
12077
  const EVENT_BINDING_PREFIXES = [
@@ -14583,11 +14619,12 @@ function isMustacheOnly(value) {
14583
14619
  function toDatasetKey$3(attributeName) {
14584
14620
  return attributeName.slice(5).replace(DATASET_NAME_RE$3, (_match, char) => char.toUpperCase());
14585
14621
  }
14586
- function collectDataset$3(node) {
14622
+ function collectDataset$3(node, source) {
14587
14623
  const dataset = {};
14588
14624
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
14589
14625
  if (!key.startsWith("data-") || key === "data-sim-scope" || key === "data-sim-tap" || key === "data-sim-component") continue;
14590
- dataset[toDatasetKey$3(key)] = String(value);
14626
+ const resolvedValue = source && isMustacheOnly(value) ? resolveTemplateExpression(source, value) : value;
14627
+ dataset[toDatasetKey$3(key)] = String(resolvedValue ?? "");
14591
14628
  }
14592
14629
  return dataset;
14593
14630
  }
@@ -14698,7 +14735,7 @@ function createLoopScope(scope, itemName, indexName, item, index) {
14698
14735
  //#endregion
14699
14736
  //#region ../../mpcore/packages/simulator/src/runtime/render/component.ts
14700
14737
  function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, alias, genericComponentBasePath) {
14701
- const usingComponents = resolveUsingComponents(context.artifactSource, ownerJsonPath, ownerFilePath);
14738
+ const usingComponents = resolveUsingComponents(context, ownerJsonPath, ownerFilePath);
14702
14739
  const componentBasePath = genericComponentBasePath ?? usingComponents.get(alias);
14703
14740
  if (!componentBasePath) return null;
14704
14741
  const filePath = `${componentBasePath}.js`;
@@ -14720,14 +14757,14 @@ function readComponentConfig(artifactSource, jsonPath) {
14720
14757
  return {};
14721
14758
  }
14722
14759
  }
14723
- function resolveUsingComponents(artifactSource, ownerJsonPath, ownerFilePath) {
14760
+ function resolveUsingComponents(context, ownerJsonPath, ownerFilePath) {
14724
14761
  try {
14725
- const usingComponents = readComponentConfig(artifactSource, ownerJsonPath).usingComponents;
14762
+ const usingComponents = readComponentConfig(context.artifactSource, ownerJsonPath).usingComponents;
14726
14763
  if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
14727
14764
  const resolved = /* @__PURE__ */ new Map();
14728
14765
  for (const [alias, rawPath] of Object.entries(usingComponents)) {
14729
14766
  if (typeof rawPath !== "string") continue;
14730
- const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
14767
+ const basePath = resolvePluginRequest(context.project.plugins, rawPath, "publicComponent")?.resourcePath ?? (rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath)));
14731
14768
  resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
14732
14769
  }
14733
14770
  return resolved;
@@ -14739,7 +14776,7 @@ function resolveComponentGenerics(context, hostNode, ownerJsonPath, ownerFilePat
14739
14776
  const componentJsonPath = path.resolve(context.project.miniprogramRootPath, `${componentFilePath.replace(JS_FILE_RE, "")}.json`);
14740
14777
  const componentGenerics = readComponentConfig(context.artifactSource, componentJsonPath).componentGenerics;
14741
14778
  if (!componentGenerics || typeof componentGenerics !== "object" || Array.isArray(componentGenerics)) return;
14742
- const ownerComponents = resolveUsingComponents(context.artifactSource, ownerJsonPath, ownerFilePath);
14779
+ const ownerComponents = resolveUsingComponents(context, ownerJsonPath, ownerFilePath);
14743
14780
  const resolved = /* @__PURE__ */ new Map();
14744
14781
  for (const [genericName, definition] of Object.entries(componentGenerics)) {
14745
14782
  const selectedAlias = hostNode.attribs?.[`generic:${genericName}`];
@@ -14764,9 +14801,10 @@ function buildComponentTrigger(componentScopeId, context, hostNode) {
14764
14801
  return (instance, eventName, detail, triggerOptions) => {
14765
14802
  const interactionTarget = instance.__lastInteractionEvent__?.target;
14766
14803
  const interactionCurrentTarget = instance.__lastInteractionEvent__?.currentTarget;
14804
+ const componentDataset = context.componentScopes.get(componentScopeId)?.dataset ?? hostDataset;
14767
14805
  const interactionMark = instance.__lastInteractionEvent__?.mark;
14768
14806
  const target = {
14769
- dataset: interactionTarget?.dataset ?? hostDataset,
14807
+ dataset: interactionTarget?.dataset ?? componentDataset,
14770
14808
  id: interactionTarget?.id ?? hostId
14771
14809
  };
14772
14810
  let currentScopeId = componentScopeId;
@@ -14822,7 +14860,7 @@ function createComponentScope(clonedNode, scope, componentScopeId, componentInst
14822
14860
  alias: clonedNode.name,
14823
14861
  classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
14824
14862
  data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
14825
- dataset: collectDataset$3(clonedNode),
14863
+ dataset: collectDataset$3(clonedNode, scope.data),
14826
14864
  eventBindings: collectComponentEventBindings(clonedNode),
14827
14865
  getMethod: (methodName) => {
14828
14866
  const method = componentInstance?.[methodName];
@@ -15051,7 +15089,8 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
15051
15089
  }
15052
15090
  function renderRuntimePageTree(context, page) {
15053
15091
  const route = page.route.replace(LEADING_SLASH_RE$4, "");
15054
- const templatePath = path.resolve(context.project.miniprogramRootPath, `${route}.wxml`);
15092
+ const resourcePath = context.project.routes.find((item) => item.route === route)?.resourcePath ?? route;
15093
+ const templatePath = path.resolve(context.project.miniprogramRootPath, `${resourcePath}.wxml`);
15055
15094
  const document = parseTemplateDocument(readTemplateSource(context.artifactSource, templatePath));
15056
15095
  const pageScopeId = `page:${route}`;
15057
15096
  const pageScope = {
@@ -15068,7 +15107,7 @@ function renderRuntimePageTree(context, page) {
15068
15107
  const seenComponentScopes = /* @__PURE__ */ new Set();
15069
15108
  const root = (document.children ?? [])[0] ?? document;
15070
15109
  const templateRenderState = createTemplateRenderState(root);
15071
- const renderedRoot = renderNodeTree(root, pageScope, context, path.resolve(context.project.miniprogramRootPath, `${route}.json`), `${route}.js`, pageScopeId, seenComponentScopes, templateRenderState);
15110
+ const renderedRoot = renderNodeTree(root, pageScope, context, path.resolve(context.project.miniprogramRootPath, `${resourcePath}.json`), `${resourcePath}.js`, pageScopeId, seenComponentScopes, templateRenderState);
15072
15111
  for (const [scopeId, instance] of [...context.componentCache.entries()]) if (!seenComponentScopes.has(scopeId)) {
15073
15112
  runComponentLifecycle(instance, "detached");
15074
15113
  context.componentCache.delete(scopeId);
@@ -15819,7 +15858,7 @@ function pushRoute(records, seen, record) {
15819
15858
  seen.add(record.route);
15820
15859
  records.push(record);
15821
15860
  }
15822
- function resolveRoutesFromAppConfig(config) {
15861
+ function resolveRoutesFromAppConfig(config, plugins = []) {
15823
15862
  const records = [];
15824
15863
  const seen = /* @__PURE__ */ new Set();
15825
15864
  if (Array.isArray(config.pages)) for (const page of config.pages) {
@@ -15849,6 +15888,19 @@ function resolveRoutesFromAppConfig(config) {
15849
15888
  });
15850
15889
  }
15851
15890
  }
15891
+ for (const plugin of plugins) {
15892
+ const pages = plugin.config.pages;
15893
+ if (!pages || typeof pages !== "object" || Array.isArray(pages)) continue;
15894
+ for (const resourcePath of Object.values(pages)) {
15895
+ if (typeof resourcePath !== "string" || !resourcePath) continue;
15896
+ pushRoute(records, seen, {
15897
+ kind: "page",
15898
+ resourcePath: `${plugin.virtualRoot}/${resourcePath}`,
15899
+ route: `plugin-private://${plugin.provider}/${resourcePath}`,
15900
+ source: "plugin"
15901
+ });
15902
+ }
15903
+ }
15852
15904
  return records;
15853
15905
  }
15854
15906
  //#endregion
@@ -15860,9 +15912,10 @@ function createProjectDescriptor(options) {
15860
15912
  appConfigPath: options.appConfigPath,
15861
15913
  miniprogramRoot: options.miniprogramRoot,
15862
15914
  miniprogramRootPath: options.miniprogramRootPath,
15915
+ plugins: options.plugins ?? [],
15863
15916
  projectPath: options.projectPath,
15864
15917
  projectConfigFiles: options.projectConfigFiles ?? [],
15865
- routes: resolveRoutesFromAppConfig(options.appConfig)
15918
+ routes: resolveRoutesFromAppConfig(options.appConfig, options.plugins ?? [])
15866
15919
  };
15867
15920
  }
15868
15921
  //#endregion
@@ -15979,6 +16032,36 @@ function resolveMiniprogramRoot(projectPath, configFiles) {
15979
16032
  }
15980
16033
  throw new Error(`Failed to resolve miniprogramRoot for headless runtime project: ${projectPath}. Expected project.config.json or project.private.config.json to define miniprogramRoot.`);
15981
16034
  }
16035
+ function resolveConfiguredRoot(configFiles, key) {
16036
+ for (const configFile of [...configFiles].reverse()) {
16037
+ const value = configFile.value[key];
16038
+ if (typeof value === "string" && value.trim()) return value.trim();
16039
+ }
16040
+ }
16041
+ function resolveLocalPlugins(projectPath, projectConfigFiles, appConfig) {
16042
+ const pluginRoot = resolveConfiguredRoot(projectConfigFiles, "pluginRoot");
16043
+ const configuredPlugins = appConfig.plugins;
16044
+ if (!pluginRoot || !configuredPlugins || typeof configuredPlugins !== "object" || Array.isArray(configuredPlugins)) return [];
16045
+ const rootPath = path.resolve(projectPath, pluginRoot);
16046
+ const pluginConfig = readJsonObject$1(path.resolve(rootPath, "plugin.json"));
16047
+ if (!pluginConfig) return [];
16048
+ const plugins = [];
16049
+ const projectAppId = resolveConfiguredRoot(projectConfigFiles, "appid");
16050
+ for (const [alias, definition] of Object.entries(configuredPlugins)) {
16051
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) continue;
16052
+ const provider = definition.provider;
16053
+ const isLocalPlugin = definition.version === "dev" || projectAppId && provider === projectAppId;
16054
+ if (typeof provider !== "string" || !provider || !isLocalPlugin) continue;
16055
+ plugins.push({
16056
+ alias,
16057
+ config: pluginConfig,
16058
+ provider,
16059
+ rootPath,
16060
+ virtualRoot: createPluginVirtualRoot(alias)
16061
+ });
16062
+ }
16063
+ return plugins;
16064
+ }
15982
16065
  function ensureFileExists(filePath, label) {
15983
16066
  if (fs.existsSync(filePath)) return;
15984
16067
  throw new Error(`Missing ${label} for headless runtime project: ${normalize(filePath)}`);
@@ -15997,11 +16080,15 @@ function loadProject(projectPath) {
15997
16080
  ensureFileExists(miniprogramRootPath, "miniprogramRoot directory");
15998
16081
  const appConfigPath = path.resolve(miniprogramRootPath, "app.json");
15999
16082
  ensureFileExists(appConfigPath, "built app.json");
16083
+ const appConfig = loadAppConfig(appConfigPath);
16084
+ const plugins = resolveLocalPlugins(normalizedProjectPath, projectConfigFiles, appConfig);
16000
16085
  return createProjectDescriptor({
16001
- appConfig: loadAppConfig(appConfigPath),
16086
+ appConfig,
16002
16087
  appConfigPath,
16088
+ artifactSource: createPluginArtifactSource(createFileSystemArtifactSource(), miniprogramRootPath, plugins),
16003
16089
  miniprogramRoot,
16004
16090
  miniprogramRootPath,
16091
+ plugins,
16005
16092
  projectPath: normalizedProjectPath,
16006
16093
  projectConfigFiles
16007
16094
  });
@@ -17371,6 +17458,12 @@ function createModuleLoader(registries, getCurrentPages, getApp, wxDriver, optio
17371
17458
  registries.currentLoadContext = previousLoadContext;
17372
17459
  }
17373
17460
  }
17461
+ executionContext.requirePlugin = (alias) => {
17462
+ const plugin = options.plugins.find((item) => item.alias === alias);
17463
+ if (!plugin) throw new Error(`Unknown local plugin "${alias}" in headless runtime.`);
17464
+ const main = typeof plugin.config.main === "string" && plugin.config.main ? plugin.config.main : "index.js";
17465
+ return executeModule(path.resolve(options.miniprogramRootPath, plugin.virtualRoot, main), null).exports;
17466
+ };
17374
17467
  return {
17375
17468
  close() {
17376
17469
  moduleCache.clear();
@@ -19283,7 +19376,9 @@ var HeadlessSession = class {
19283
19376
  artifactSource: this.project.artifactSource,
19284
19377
  globals: options.globals,
19285
19378
  kernel: this.kernel,
19286
- onConsole: (entry) => this.emit("console", entry)
19379
+ miniprogramRootPath: this.project.miniprogramRootPath,
19380
+ onConsole: (entry) => this.emit("console", entry),
19381
+ plugins: this.project.plugins
19287
19382
  });
19288
19383
  }
19289
19384
  assertActive() {
@@ -19902,8 +19997,9 @@ var HeadlessSession = class {
19902
19997
  });
19903
19998
  }
19904
19999
  createFreshPage(target) {
19905
- const pageModulePath = path.resolve(this.project.miniprogramRootPath, `${target.routeRecord.route}.js`);
19906
- const pageConfigPath = path.resolve(this.project.miniprogramRootPath, `${target.routeRecord.route}.json`);
20000
+ const resourcePath = target.routeRecord.resourcePath ?? target.routeRecord.route;
20001
+ const pageModulePath = path.resolve(this.project.miniprogramRootPath, `${resourcePath}.js`);
20002
+ const pageConfigPath = path.resolve(this.project.miniprogramRootPath, `${resourcePath}.json`);
19907
20003
  const pageDefinition = this.moduleLoader.executePageModule(pageModulePath, target.routeRecord.route);
19908
20004
  const pageConfig = readJsonObject(this.project, pageConfigPath);
19909
20005
  const pageInstance = createPageInstance(`/${target.routeRecord.route}`, pageDefinition, target.query, {
@@ -19938,7 +20034,9 @@ var HeadlessSession = class {
19938
20034
  }
19939
20035
  resolveNavigationTarget(url) {
19940
20036
  const { pathname, query } = parseNavigationUrl(url);
19941
- const normalizedRoute = resolveNavigationPath(pathname, this.currentPageInstance ? stripLeadingSlash(this.currentPageInstance.route) : void 0);
20037
+ const baseRoute = this.currentPageInstance ? stripLeadingSlash(this.currentPageInstance.route) : void 0;
20038
+ const pluginRequest = resolvePluginRequest(this.project.plugins, pathname, "page");
20039
+ const normalizedRoute = pluginRequest ? `plugin-private://${pluginRequest.plugin.provider}/${pluginRequest.resourcePath.slice(pluginRequest.plugin.virtualRoot.length + 1)}` : resolveNavigationPath(pathname, baseRoute);
19942
20040
  const routeRecord = this.project.routes.find((item) => item.route === normalizedRoute);
19943
20041
  if (!routeRecord) {
19944
20042
  this.bootstrap(createAppLaunchOptions(normalizedRoute, query));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@weapp-vite/miniprogram-automator",
3
3
  "type": "module",
4
- "version": "1.2.14",
4
+ "version": "1.2.16",
5
5
  "description": "完全兼容微信 miniprogram-automator 的现代化替代实现",
6
6
  "author": "ice breaker <1324318532@qq.com>",
7
7
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "obug": "^2.1.4",
45
45
  "ws": "^8.21.3",
46
- "@weapp-vite/qr": "1.1.1"
46
+ "@weapp-vite/qr": "1.1.2"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/ws": "^8.18.1",
54
- "sharp": "^0.35.3"
54
+ "sharp": "^0.35.4"
55
55
  },
56
56
  "scripts": {
57
57
  "dev": "tsdown -w --sourcemap",