@heybox/hb-sdk 0.8.1-alpha.2 → 0.8.1-alpha.6

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +58 -4
  3. package/dist/cli-chunks/{build-FQoALfGC.cjs → build-B0X9pazW.cjs} +16 -10
  4. package/dist/cli-chunks/{context-CA_Q1lyH.cjs → context-DVfWZ6d1.cjs} +8 -3
  5. package/dist/cli-chunks/{create-BjIl4hy4.cjs → create-CyQWVxCA.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-CiPQYsW_.cjs → dev-DP7WtQOo.cjs} +465 -117
  7. package/dist/cli-chunks/{doctor-CS6c3n-W.cjs → doctor-D6l-ONq-.cjs} +1 -1
  8. package/dist/cli-chunks/{index-BEn7WaqA.cjs → index-CDwwaNvP.cjs} +87 -15
  9. package/dist/cli-chunks/{index-CsP_x6y6.cjs → index-DvOPzPxS.cjs} +2 -2
  10. package/dist/cli-chunks/{index.esm-DeEc_-uV.cjs → index.esm-B8pX0BrZ.cjs} +9 -8
  11. package/dist/cli-chunks/{login-BENCVgaQ.cjs → login-Dt-FHzpL.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-BO8oOfcs.cjs → project-vite-CE7kyqxl.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-IiVCXcSq.cjs → remote-Be6PeR-n.cjs} +41 -28
  14. package/dist/cli-chunks/{runtime-gate-BMSY22AL.cjs → runtime-gate-BWb8QIcX.cjs} +0 -107
  15. package/dist/cli-chunks/{runtime-permission-env-BPRwn4Yc.cjs → runtime-permission-env-DWSscWBT.cjs} +304 -23
  16. package/dist/cli-chunks/{session-C0eIKZ6V.cjs → session-DZXRQ4Dp.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-DpInwbiA.cjs → skill-B8GWL2KT.cjs} +83 -27
  18. package/dist/cli-chunks/{version-BONZQWPR.cjs → version-D19lZmYN.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/browser-dev-host-Bb43BPMS.js +101 -0
  21. package/dist/devtools/browser-dev-host/assets/desktop-app-launch-DnPRjCa3.js +6 -0
  22. package/dist/devtools/browser-dev-host/assets/index-D-aNERAr.css +1 -0
  23. package/dist/devtools/browser-dev-host/assets/index-Dh5wRj5c.js +567 -0
  24. package/dist/devtools/browser-dev-host/index.html +3 -3
  25. package/dist/index.cjs.js +1893 -1562
  26. package/dist/index.esm.js +1893 -1563
  27. package/dist/miniapp-publish.cjs.js +61 -0
  28. package/dist/miniapp-publish.esm.js +59 -1
  29. package/dist/protocol.cjs.js +64 -13
  30. package/dist/protocol.esm.js +64 -13
  31. package/dist/templates/vanilla-vite-js/README.md.ejs +1 -1
  32. package/dist/templates/vanilla-vite-js/package.json.ejs +1 -0
  33. package/dist/templates/vanilla-vite-js/vite.config.js +1 -1
  34. package/dist/vite.cjs.js +1333 -18
  35. package/dist/vite.esm.js +1334 -20
  36. package/package.json +8 -8
  37. package/skill/SKILL.md +10 -5
  38. package/skill/references/api-protocol.md +3 -3
  39. package/skill/references/api-root.md +225 -210
  40. package/skill/references/cli.md +9 -3
  41. package/skill/references/examples.md +13 -1
  42. package/skill/references/recipes.md +161 -0
  43. package/skill/references/safety-boundaries.md +7 -1
  44. package/skill/skill.json +5 -5
  45. package/types/core/client.d.ts +2 -0
  46. package/types/core/sdk.d.ts +3 -0
  47. package/types/core/singleton.d.ts +3 -0
  48. package/types/devtools/device-logs.d.ts +48 -0
  49. package/types/index.d.ts +3 -1
  50. package/types/miniapp-manifest/companion-directory.d.ts +2 -0
  51. package/types/miniapp-manifest/companion-executable.d.ts +4 -0
  52. package/types/miniapp-manifest/companion-types.d.ts +38 -0
  53. package/types/miniapp-manifest/companions.d.ts +17 -0
  54. package/types/miniapp-manifest/index.d.ts +2 -0
  55. package/types/miniapp-manifest/node.d.ts +9 -0
  56. package/types/miniapp-manifest/permissions.d.ts +1 -1
  57. package/types/miniapp-manifest/schema.d.ts +5 -1
  58. package/types/miniapp-publish/index.d.ts +17 -2
  59. package/types/modules/companion/index.d.ts +57 -0
  60. package/types/protocol/dev-session.d.ts +6 -0
  61. package/types/vite/index.d.ts +11 -4
  62. package/dist/devtools/browser-dev-host/assets/browser-dev-host-DzEst9n7.js +0 -99
  63. package/dist/devtools/browser-dev-host/assets/index-B-NHlLsr.js +0 -567
  64. package/dist/devtools/browser-dev-host/assets/index-P-ra4m1y.css +0 -1
  65. package/dist/devtools/browser-dev-host/assets/workbench-state-BwV7bm4n.js +0 -5
@@ -1,24 +1,25 @@
1
1
  'use strict';
2
2
 
3
+ var node_crypto = require('node:crypto');
3
4
  var fs = require('node:fs');
4
5
  var os = require('node:os');
5
6
  var path = require('node:path');
6
7
  var net = require('node:net');
7
- var node_crypto = require('node:crypto');
8
8
  var promises = require('node:dns/promises');
9
9
  var node_http = require('node:http');
10
10
  var node_module = require('node:module');
11
- var context = require('./context-CA_Q1lyH.cjs');
12
- var runtimePermissionEnv = require('./runtime-permission-env-BPRwn4Yc.cjs');
11
+ var context = require('./context-DVfWZ6d1.cjs');
12
+ var runtimePermissionEnv = require('./runtime-permission-env-DWSscWBT.cjs');
13
13
  var undici = require('undici');
14
14
  var fs$1 = require('node:fs/promises');
15
- var index = require('./index-BEn7WaqA.cjs');
15
+ var index = require('./index-CDwwaNvP.cjs');
16
16
  var require$$0$1 = require('crypto');
17
17
  var require$$0 = require('buffer');
18
- var session = require('./session-C0eIKZ6V.cjs');
18
+ var session = require('./session-DZXRQ4Dp.cjs');
19
19
  var browser = require('./browser-RAy8e8cV.cjs');
20
- var projectVite = require('./project-vite-BO8oOfcs.cjs');
21
- var version = require('./version-BONZQWPR.cjs');
20
+ var projectVite = require('./project-vite-CE7kyqxl.cjs');
21
+ var version = require('./version-D19lZmYN.cjs');
22
+ require('node:zlib');
22
23
 
23
24
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
24
25
  class Locked extends Error {
@@ -183,6 +184,82 @@ async function getPorts(options) {
183
184
  throw new Error('No available ports found');
184
185
  }
185
186
 
187
+ /**
188
+ * network.request 诊断日志的纯脱敏工具。
189
+ *
190
+ * @remarks
191
+ * 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
192
+ * modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
193
+ *(devtools 不得直接 import runtime capability modules)。
194
+ */
195
+ /** 敏感字段名:header / query / body key 共用。 */
196
+ const SENSITIVE_KEY_PATTERN$1 = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
197
+ /**
198
+ * 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
199
+ */
200
+ function sanitizeNetworkRequestUrl(url) {
201
+ if (!url) {
202
+ return url;
203
+ }
204
+ try {
205
+ const parsed = new URL(url);
206
+ parsed.username = '';
207
+ parsed.password = '';
208
+ redactUrlSearchParams(parsed.searchParams);
209
+ if (parsed.hash && parsed.hash.length > 1) {
210
+ const hashBody = parsed.hash.slice(1);
211
+ if (hashBody.includes('=')) {
212
+ const hashParams = new URLSearchParams(hashBody);
213
+ if (redactUrlSearchParams(hashParams)) {
214
+ parsed.hash = hashParams.toString();
215
+ }
216
+ }
217
+ }
218
+ return parsed.toString();
219
+ }
220
+ catch {
221
+ return redactSensitivePairsInRawText(url);
222
+ }
223
+ }
224
+ /**
225
+ * 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
226
+ */
227
+ function sanitizeDiagnosticMessage(message) {
228
+ if (!message) {
229
+ return message;
230
+ }
231
+ return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
232
+ }
233
+ function isSensitiveNetworkKey(key) {
234
+ return SENSITIVE_KEY_PATTERN$1.test(key.trim());
235
+ }
236
+ function redactUrlSearchParams(params) {
237
+ let changed = false;
238
+ for (const key of [...params.keys()]) {
239
+ if (isSensitiveNetworkKey(key)) {
240
+ params.set(key, '[redacted]');
241
+ changed = true;
242
+ }
243
+ }
244
+ return changed;
245
+ }
246
+ function redactSensitivePairsInRawText(text) {
247
+ return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
248
+ if (!isSensitiveNetworkKey(key)) {
249
+ return full;
250
+ }
251
+ return `${prefix}${key}=[redacted]`;
252
+ });
253
+ }
254
+
255
+ const DEVICE_LOG_PATH = '/__hb_sdk__/device-logs';
256
+ const registryKey = Symbol.for('@heybox/hb-sdk/device-logs');
257
+ const registry = globalThis;
258
+ const stores = (registry[registryKey] ??= new Map());
259
+ function getDeviceLogs(appUrl) {
260
+ return stores.get(Number(new URL(appUrl).port));
261
+ }
262
+
186
263
  /** Dev Shell 读取局域网 Dev Session endpoint 的 query 参数。 */
187
264
  const MINI_PROGRAM_DEV_CONTEXT_QUERY_PARAM = 'dev_context_url';
188
265
  /** Browser Dev Host 创建和读取 Dev Session 的固定路径。 */
@@ -546,74 +623,6 @@ function readErrorMessage(error) {
546
623
  return error instanceof Error ? error.message : String(error);
547
624
  }
548
625
 
549
- /**
550
- * network.request 诊断日志的纯脱敏工具。
551
- *
552
- * @remarks
553
- * 放在 `core` 而非 `modules/network`:无错误类型依赖,供 SDK 错误文案、
554
- * modules observability、devtools Browser Dev Host / proxy 共用,并满足 boundary
555
- *(devtools 不得直接 import runtime capability modules)。
556
- */
557
- /** 敏感字段名:header / query / body key 共用。 */
558
- const SENSITIVE_KEY_PATTERN$1 = /(authorization|cookie|password|passwd|secret|token|pkey|private[_-]?key|access[_-]?key|client[_-]?secret|api[_-]?key|apikey|session|jwt|refresh[_-]?token|csrf)/i;
559
- /**
560
- * 脱敏请求 URL:去掉 userinfo,并将 query / fragment 中的敏感参数值替换为 `[redacted]`。
561
- */
562
- function sanitizeNetworkRequestUrl(url) {
563
- if (!url) {
564
- return url;
565
- }
566
- try {
567
- const parsed = new URL(url);
568
- parsed.username = '';
569
- parsed.password = '';
570
- redactUrlSearchParams(parsed.searchParams);
571
- if (parsed.hash && parsed.hash.length > 1) {
572
- const hashBody = parsed.hash.slice(1);
573
- if (hashBody.includes('=')) {
574
- const hashParams = new URLSearchParams(hashBody);
575
- if (redactUrlSearchParams(hashParams)) {
576
- parsed.hash = hashParams.toString();
577
- }
578
- }
579
- }
580
- return parsed.toString();
581
- }
582
- catch {
583
- return redactSensitivePairsInRawText(url);
584
- }
585
- }
586
- /**
587
- * 诊断 message 中的 URL 片段做脱敏,避免 error.message 二次泄漏 query secrets。
588
- */
589
- function sanitizeDiagnosticMessage(message) {
590
- if (!message) {
591
- return message;
592
- }
593
- return message.replace(/https?:\/\/[^\s]+/gi, matched => sanitizeNetworkRequestUrl(matched.replace(/[),.;]+$/g, '')));
594
- }
595
- function isSensitiveNetworkKey(key) {
596
- return SENSITIVE_KEY_PATTERN$1.test(key.trim());
597
- }
598
- function redactUrlSearchParams(params) {
599
- let changed = false;
600
- for (const key of [...params.keys()]) {
601
- if (isSensitiveNetworkKey(key)) {
602
- params.set(key, '[redacted]');
603
- changed = true;
604
- }
605
- }
606
- return changed;
607
- }
608
- function redactSensitivePairsInRawText(text) {
609
- return text.replace(/([?&#]|^|[?&])([^=&#\s]+)=([^&#\s]*)/g, (full, prefix, key, value) => {
610
- if (!isSensitiveNetworkKey(key)) {
611
- return full;
612
- }
613
- return `${prefix}${key}=[redacted]`;
614
- });
615
- }
616
-
617
626
  const MAX_BROWSER_DEV_HOST_NETWORK_REQUEST_BODY = 2 * 1024 * 1024;
618
627
  const MAX_BROWSER_DEV_HOST_NETWORK_RESPONSE_BODY = 5 * 1024 * 1024;
619
628
  const DENIED_NETWORK_TARGETS = createDeniedNetworkTargets();
@@ -1851,8 +1860,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
1851
1860
  };
1852
1861
  const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
1853
1862
  IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
1854
- const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CiPQYsW_.cjs', document.baseURI).href)))('ws');
1855
- const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CiPQYsW_.cjs', document.baseURI).href)))('parse5');
1863
+ const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DP7WtQOo.cjs', document.baseURI).href)))('ws');
1864
+ const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-DP7WtQOo.cjs', document.baseURI).href)))('parse5');
1856
1865
  const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
1857
1866
  path.resolve(__dirname, 'devtools/browser-dev-host'),
1858
1867
  path.resolve(__dirname, '../devtools/browser-dev-host'),
@@ -1886,6 +1895,7 @@ async function startBrowserDevHostServer(options) {
1886
1895
  mobileLaunchRedirect: initialMobileLaunchRedirect,
1887
1896
  nativeAppLaunchUnavailableReason: options.nativeAppLaunchUnavailableReason,
1888
1897
  runtimePermissions: options.runtimePermissions,
1898
+ devReadiness: options.devReadiness,
1889
1899
  };
1890
1900
  let bootstrapJson = serializeBrowserDevHostBootstrap(bootstrap);
1891
1901
  const serverRuntime = createBrowserDevHostServer(root, {
@@ -1950,6 +1960,8 @@ async function startBrowserDevHostServer(options) {
1950
1960
  return closePromise;
1951
1961
  },
1952
1962
  updateBootstrap: (next) => {
1963
+ if (next.devReadiness !== undefined)
1964
+ bootstrap.devReadiness = next.devReadiness;
1953
1965
  if (next.devContext !== undefined)
1954
1966
  bootstrap.devContext = next.devContext;
1955
1967
  if (next.lanAddresses !== undefined) {
@@ -2172,6 +2184,44 @@ function createBrowserDevHostServer(root, runtime) {
2172
2184
  writeSerializedJsonResponse(response, 200, runtime.readBootstrapJson());
2173
2185
  return;
2174
2186
  }
2187
+ if (pathname === '/__hb_sdk__/sandbox') {
2188
+ if (!isValidSensitiveRequest(request, loopbackPeer, runtime.sessionController.csrfToken)) {
2189
+ writeForbidden(response);
2190
+ return;
2191
+ }
2192
+ if (request.method !== 'POST') {
2193
+ writeMethodNotAllowed(response, 'POST');
2194
+ return;
2195
+ }
2196
+ try {
2197
+ const input = await readJsonRequestBody(request);
2198
+ if (!isRecord$2(input) || typeof input.device !== 'string' || typeof input.path !== 'string') {
2199
+ writeBadRequest(response);
2200
+ return;
2201
+ }
2202
+ const store = getDeviceLogs(runtime.appUrl);
2203
+ writeJsonResponse(response, 200, store ? await store.listSandbox(input.device, input.path) : { error: '设备服务不可用' });
2204
+ }
2205
+ catch {
2206
+ writeBadRequest(response);
2207
+ }
2208
+ return;
2209
+ }
2210
+ if (pathname === DEVICE_LOG_PATH) {
2211
+ const origin = request.headers.origin;
2212
+ if (!loopbackPeer || !hasNumericLoopbackHost(request) ||
2213
+ (origin && origin !== `http://${request.headers.host}`)) {
2214
+ writeForbidden(response);
2215
+ return;
2216
+ }
2217
+ if (request.method !== 'GET') {
2218
+ writeMethodNotAllowed(response, 'GET');
2219
+ return;
2220
+ }
2221
+ const store = getDeviceLogs(runtime.appUrl);
2222
+ writeJsonResponse(response, 200, store ? store.query(requestUrl.searchParams) : { epoch: '', items: [], devices: [], nextCursor: 0, hasMore: false, gap: false });
2223
+ return;
2224
+ }
2175
2225
  if (pathname === '/session/status') {
2176
2226
  if (!loopbackPeer || !hasNumericLoopbackHost(request)) {
2177
2227
  writeForbidden(response);
@@ -10902,7 +10952,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
10902
10952
  function createNodeRealtimeDriverRegistry() {
10903
10953
  const registry = createRuntimeRealtimeDriverRegistry();
10904
10954
  registry.registerLazy('node-websocket', async () => {
10905
- const mod = await Promise.resolve().then(function () { return require('./index.esm-DeEc_-uV.cjs'); });
10955
+ const mod = await Promise.resolve().then(function () { return require('./index.esm-B8pX0BrZ.cjs'); });
10906
10956
  return mod.createNodeWebSocketRuntimeDriver();
10907
10957
  });
10908
10958
  return registry;
@@ -14862,7 +14912,7 @@ function containsSecret(value, secret) {
14862
14912
 
14863
14913
  const LAUNCH_CORS_HEADERS = {
14864
14914
  'access-control-allow-methods': 'GET',
14865
- 'access-control-allow-origin': '*',
14915
+ 'access-control-allow-origin': 'https://www.xiaoheihe.cn',
14866
14916
  'access-control-allow-private-network': 'true',
14867
14917
  'cache-control': 'no-store',
14868
14918
  };
@@ -14877,6 +14927,10 @@ function tryHandleMiniProgramDevLaunchRequest(request, response, readSource) {
14877
14927
  if (requestUrl.pathname !== MINI_PROGRAM_DEV_LAUNCH_PATH || requestUrl.search !== '') {
14878
14928
  return false;
14879
14929
  }
14930
+ if (!isAllowedLaunchOrigin(request.headers.origin)) {
14931
+ writeLaunchResponse(response, 403);
14932
+ return true;
14933
+ }
14880
14934
  if (request.method !== 'GET' && request.method !== 'OPTIONS') {
14881
14935
  response.writeHead(405, { allow: 'GET', ...LAUNCH_CORS_HEADERS });
14882
14936
  response.end();
@@ -14911,6 +14965,16 @@ function createLaunchSnapshot(source, requestOrigin) {
14911
14965
  const localDevLaunchToken = source.localDevLaunchToken?.trim();
14912
14966
  if (localDevLaunchToken)
14913
14967
  snapshot.local_dev_launch_token = localDevLaunchToken;
14968
+ if (source.devSessionId)
14969
+ snapshot.dev_session_id = source.devSessionId;
14970
+ if (source.expiresAt)
14971
+ snapshot.expires_at = source.expiresAt;
14972
+ const companionManifestSha256 = source.companionManifestSha256?.trim();
14973
+ if (companionManifestSha256)
14974
+ snapshot.companion_manifest_sha256 = companionManifestSha256;
14975
+ const companionArtifactAccessToken = source.companionArtifactAccessToken?.trim();
14976
+ if (companionArtifactAccessToken)
14977
+ snapshot.companion_artifact_access_token = companionArtifactAccessToken;
14914
14978
  const storedContextUrl = source.devContextUrlsByMiniUrlOrigin.get(requestOrigin.origin);
14915
14979
  if (storedContextUrl) {
14916
14980
  snapshot.dev_context_url = projectStoredDevContextUrl(storedContextUrl, requestOrigin.hostname);
@@ -14954,6 +15018,9 @@ function readNumericRequestOrigin(request) {
14954
15018
  function isAllowedLaunchPeer(address) {
14955
15019
  return isLoopbackOrPrivateLanAddress(address);
14956
15020
  }
15021
+ function isAllowedLaunchOrigin(origin) {
15022
+ return origin === undefined || origin === 'https://www.xiaoheihe.cn';
15023
+ }
14957
15024
  function writeLaunchResponse(response, status, body) {
14958
15025
  response.writeHead(status, {
14959
15026
  ...LAUNCH_CORS_HEADERS,
@@ -14970,6 +15037,10 @@ const LOCAL_DEV_URL_HOST = '127.0.0.1';
14970
15037
  // 与 Heybox H5 Vite 插件共享,让 hb-sdk 统一管理启动输出。
14971
15038
  const MANAGED_DEV_OUTPUT_ENV = 'HEYBOX_DEV_SERVER_MANAGED_OUTPUT';
14972
15039
  const VITE_LOG_LEVEL = 'warn';
15040
+ const COMPANION_DEV_MANIFEST_PATH = '/__hb_sdk__/companion/manifest.json';
15041
+ const COMPANION_DEV_MANIFEST_MAX_BYTES = 1024 * 1024;
15042
+ const COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TOKEN';
15043
+ const COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER = 'x-hb-sdk-companion-dev-token';
14973
15044
  async function runDevCommand(options, runtime = {}) {
14974
15045
  const logger = runtime.logger ?? index.createCliLogger();
14975
15046
  const fetchImpl = runtime.fetchImpl ?? fetch;
@@ -14991,6 +15062,9 @@ async function runDevCommand(options, runtime = {}) {
14991
15062
  port: appPort,
14992
15063
  };
14993
15064
  const projectRoot = projectVite.findProjectRoot(runtime.cwd ?? process.cwd());
15065
+ const companionArtifactAccessToken = (runtime.randomBytes ?? node_crypto.randomBytes)(32).toString('base64url');
15066
+ const devSessionId = node_crypto.randomBytes(16).toString('hex');
15067
+ runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
14994
15068
  const { context: devLaunchContext, miniProgram } = await loadDevLaunchContext({
14995
15069
  env: runtime.env,
14996
15070
  fetchImpl,
@@ -14998,22 +15072,58 @@ async function runDevCommand(options, runtime = {}) {
14998
15072
  projectRoot,
14999
15073
  timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
15000
15074
  });
15001
- const launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
15002
- const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
15003
- const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions)), runtime.env), runtime.env), '已创建 Vite 开发服务器');
15075
+ let launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
15004
15076
  const mobileDevContextUrlsByOrigin = new Map();
15077
+ let currentCompanionManifestSha256;
15005
15078
  let currentLocalDevLaunchToken = devLaunchContext.localDevLaunchToken;
15006
- appServer.middlewares.use((request, response, next) => {
15007
- const handled = tryHandleMiniProgramDevLaunchRequest(request, response, () => ({
15008
- devContextUrlsByMiniUrlOrigin: mobileDevContextUrlsByOrigin,
15009
- localDevLaunchToken: currentLocalDevLaunchToken,
15010
- miniProgramId: devLaunchContext.miniProgramId,
15011
- runtimePermissions: launchRuntimePermissions,
15012
- sdkVersion: version.HB_SDK_VERSION,
15013
- }));
15014
- if (!handled)
15015
- next();
15079
+ let currentExpiresAt;
15080
+ let tokenSession;
15081
+ const readLaunchSource = () => ({
15082
+ companionArtifactAccessToken,
15083
+ companionManifestSha256: currentCompanionManifestSha256,
15084
+ devContextUrlsByMiniUrlOrigin: mobileDevContextUrlsByOrigin,
15085
+ localDevLaunchToken: currentLocalDevLaunchToken,
15086
+ devSessionId,
15087
+ expiresAt: currentExpiresAt,
15088
+ miniProgramId: devLaunchContext.miniProgramId,
15089
+ runtimePermissions: launchRuntimePermissions,
15090
+ sdkVersion: version.HB_SDK_VERSION,
15091
+ });
15092
+ const launchPlugin = createMiniProgramDevLaunchVitePlugin(readLaunchSource, async (request, response) => {
15093
+ if (request.url !== MINI_PROGRAM_DEV_LAUNCH_PATH || request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER] === undefined)
15094
+ return false;
15095
+ response.setHeader('Cache-Control', 'no-store');
15096
+ const token = request.headers[COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER];
15097
+ const remote = request.socket.remoteAddress;
15098
+ if (typeof token !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(token) ||
15099
+ !node_crypto.timingSafeEqual(Buffer.from(token), Buffer.from(companionArtifactAccessToken)) ||
15100
+ !['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(remote ?? '') ||
15101
+ !/^(127\.0\.0\.1|\[::1\]):\d{1,5}$/.test(request.headers.host ?? '')) {
15102
+ response.writeHead(403).end();
15103
+ return true;
15104
+ }
15105
+ if (request.method !== 'GET') {
15106
+ response.writeHead(405, { Allow: 'GET' }).end();
15107
+ return true;
15108
+ }
15109
+ try {
15110
+ const issued = await tokenSession?.getTicket(currentCompanionManifestSha256);
15111
+ if (!issued)
15112
+ response.writeHead(503).end();
15113
+ else
15114
+ response.writeHead(200, { 'Content-Type': 'application/json', 'X-Content-Type-Options': 'nosniff' }).end(JSON.stringify(createLaunchSnapshot({
15115
+ ...readLaunchSource(), localDevLaunchToken: issued.token, expiresAt: issued.expiresAt,
15116
+ }, new URL(`http://${request.headers.host}`))));
15117
+ }
15118
+ catch (error) {
15119
+ const status = error instanceof index.CliError && (error.httpStatus === 401 || error.httpStatus === 403) ? error.httpStatus : 503;
15120
+ logger.debug(`本地开发会话续票失败:${index.readRedactedErrorMessage(error, { verbose: true })}`);
15121
+ response.writeHead(status).end();
15122
+ }
15123
+ return true;
15016
15124
  });
15125
+ const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
15126
+ const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withCompanionArtifactAccessTokenEnv(companionArtifactAccessToken, () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions, launchPlugin)), runtime.env), runtime.env), runtime.env), '已创建 Vite 开发服务器');
15017
15127
  await runDevStartupTask(logger, '正在启动 Vite 开发服务器', () => appServer.listen(appPort), 'Vite 开发服务器已启动');
15018
15128
  const appUrl = await runDevStartupTask(logger, '正在探测小程序入口', () => resolveViteAppUrl(appServer, resolvedOptions, fetchImpl), '已确定小程序入口');
15019
15129
  const buildLanAddresses = () => createLanAddressCandidates({
@@ -15059,12 +15169,27 @@ async function runDevCommand(options, runtime = {}) {
15059
15169
  }
15060
15170
  closers.push(() => browserDevHost.close());
15061
15171
  if (devLaunchContext.source === 'remote' && devLaunchContext.miniProgramId) {
15062
- const tokenSession = startLocalDevLaunchTokenSession({
15172
+ tokenSession = startLocalDevLaunchTokenSession({
15063
15173
  env: runtime.env,
15064
15174
  fetchImpl,
15065
15175
  logger,
15176
+ manifestUrl: new URL(COMPANION_DEV_MANIFEST_PATH, appUrl).toString(),
15177
+ companionArtifactAccessToken,
15178
+ companionApproved: hasCompanionApproval(devLaunchContext.runtimePermissions),
15066
15179
  miniProgramId: devLaunchContext.miniProgramId,
15067
- onToken: (localDevLaunchToken) => {
15180
+ refreshPermissions: async () => {
15181
+ const refreshed = await loadDevLaunchContext({ env: runtime.env, fetchImpl, logger, projectRoot,
15182
+ timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS });
15183
+ if (refreshed.context.miniProgramId !== devLaunchContext.miniProgramId)
15184
+ throw new Error('项目绑定已变更,请重新启动调试');
15185
+ devLaunchContext.runtimePermissions = refreshed.context.runtimePermissions;
15186
+ launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
15187
+ browserDevHost.updateBootstrap({ devContext: { ...devLaunchContext, localDevLaunchToken: currentLocalDevLaunchToken } });
15188
+ return hasCompanionApproval(devLaunchContext.runtimePermissions);
15189
+ },
15190
+ onToken: (localDevLaunchToken, companionManifestSha256, expiresAt) => {
15191
+ currentExpiresAt = expiresAt;
15192
+ currentCompanionManifestSha256 = companionManifestSha256;
15068
15193
  currentLocalDevLaunchToken = localDevLaunchToken;
15069
15194
  const tokenContext = {
15070
15195
  source: 'remote',
@@ -15077,15 +15202,26 @@ async function runDevCommand(options, runtime = {}) {
15077
15202
  lanAddresses: buildLanAddresses(),
15078
15203
  });
15079
15204
  },
15205
+ onReadiness: devReadiness => browserDevHost.updateBootstrap({ devReadiness }),
15080
15206
  projectRoot,
15081
15207
  timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
15082
15208
  });
15083
15209
  closers.push(tokenSession.close);
15084
15210
  }
15085
- logger.success('本地调试已启动');
15211
+ else {
15212
+ const reason = '登录或小程序绑定不可用,请完成 CLI 登录和项目绑定';
15213
+ browserDevHost.updateBootstrap({
15214
+ devReadiness: {
15215
+ browser: { state: 'ready' },
15216
+ mobile: { state: 'blocked', reason },
15217
+ companion: { state: 'blocked', reason },
15218
+ },
15219
+ });
15220
+ }
15221
+ logger.success('Web / Browser Fake 调试已就绪');
15086
15222
  logger.info(`调试页:${browserDevHost.url}`);
15087
15223
  logger.info(`小程序地址:${appUrl}`);
15088
- logger.info('手机调试:请在调试页扫码');
15224
+ logger.info('手机与 PC Companion 的就绪状态请查看调试页');
15089
15225
  if (options.open !== false) {
15090
15226
  logger.debug('正在打开浏览器调试页');
15091
15227
  void openUrl(browserDevHost.url);
@@ -15165,10 +15301,26 @@ async function loadRemoteDevContextWithTimeout(options) {
15165
15301
  }
15166
15302
  }
15167
15303
  }
15168
- function createViteServerOptions(projectRoot, options) {
15304
+ function createMiniProgramDevLaunchVitePlugin(getSnapshot, handleSession) {
15305
+ return {
15306
+ name: 'heybox-mini-program-dev-launch',
15307
+ enforce: 'pre',
15308
+ configureServer(server) {
15309
+ server.middlewares.use(async (request, response, next) => {
15310
+ if (handleSession && await handleSession(request, response))
15311
+ return;
15312
+ if (!tryHandleMiniProgramDevLaunchRequest(request, response, getSnapshot))
15313
+ next();
15314
+ });
15315
+ },
15316
+ };
15317
+ }
15318
+ function createViteServerOptions(projectRoot, options, launchPlugin) {
15169
15319
  const configFile = resolveProjectViteConfig(projectRoot);
15170
15320
  const server = {
15171
- cors: true,
15321
+ // launch.json 由 mini-program-dev-launch plugin 自己处理 PNA/CORS 预检;
15322
+ // Vite 通用 CORS 会抢先返回 `*`,缺少 Access-Control-Allow-Private-Network。
15323
+ cors: false,
15172
15324
  hmr: {
15173
15325
  clientPort: options.port ?? DEFAULT_APP_PORT,
15174
15326
  },
@@ -15180,11 +15332,13 @@ function createViteServerOptions(projectRoot, options) {
15180
15332
  return {
15181
15333
  configFile,
15182
15334
  logLevel: VITE_LOG_LEVEL,
15335
+ plugins: [launchPlugin],
15183
15336
  server,
15184
15337
  };
15185
15338
  }
15186
15339
  return {
15187
15340
  logLevel: VITE_LOG_LEVEL,
15341
+ plugins: [launchPlugin],
15188
15342
  root: projectRoot,
15189
15343
  server,
15190
15344
  };
@@ -15298,6 +15452,19 @@ async function withManagedDevOutputEnv(action, env = process.env) {
15298
15452
  }
15299
15453
  }
15300
15454
  }
15455
+ async function withCompanionArtifactAccessTokenEnv(token, action, env = process.env) {
15456
+ const previous = env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
15457
+ env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = token;
15458
+ try {
15459
+ return await action();
15460
+ }
15461
+ finally {
15462
+ if (previous === undefined)
15463
+ delete env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV];
15464
+ else
15465
+ env[COMPANION_ARTIFACT_ACCESS_TOKEN_ENV] = previous;
15466
+ }
15467
+ }
15301
15468
  function installShutdownHandlers(closers, processLike) {
15302
15469
  const shutdown = async () => {
15303
15470
  await closeAll(closers);
@@ -15318,28 +15485,94 @@ function startLocalDevLaunchTokenSession(options) {
15318
15485
  let expirationTimer;
15319
15486
  let hasReportedTokenFailure = false;
15320
15487
  let refreshTimer;
15488
+ let companionApproved = options.companionApproved;
15489
+ let permissionsRefreshAt = Date.now() + 30_000;
15490
+ let publishedToken;
15491
+ let publishedDigest;
15492
+ let ticket;
15493
+ let pending;
15494
+ const getTicket = async (companionManifestSha256) => {
15495
+ if (closed)
15496
+ return undefined;
15497
+ if (ticket && (companionManifestSha256 === undefined || ticket.companionManifestSha256 === companionManifestSha256) && ticket.refreshAt > Date.now())
15498
+ return ticket;
15499
+ return issueTicket(companionManifestSha256);
15500
+ };
15501
+ const issueTicket = (companionManifestSha256) => {
15502
+ if (pending && pending.digest === companionManifestSha256)
15503
+ return pending.promise;
15504
+ const promise = issueLocalDevLaunchToken({
15505
+ ...options,
15506
+ ...(companionManifestSha256 ? { companionManifestSha256 } : {}),
15507
+ })
15508
+ .then(issued => {
15509
+ ticket = issued;
15510
+ return issued;
15511
+ }).finally(() => { if (pending?.promise === promise)
15512
+ pending = undefined; });
15513
+ pending = { digest: companionManifestSha256, promise };
15514
+ return promise;
15515
+ };
15321
15516
  const refresh = async () => {
15322
15517
  try {
15323
- const issued = await issueLocalDevLaunchToken(options);
15518
+ if (options.refreshPermissions && Date.now() >= permissionsRefreshAt) {
15519
+ companionApproved = await options.refreshPermissions();
15520
+ permissionsRefreshAt = Date.now() + 30_000;
15521
+ }
15522
+ let manifest;
15523
+ let snapshotError;
15524
+ try {
15525
+ manifest = await loadRemoteDevContextWithTimeout({
15526
+ fetchImpl: options.fetchImpl,
15527
+ timeoutMs: options.timeoutMs,
15528
+ load: fetchImpl => readCompanionManifest(options.manifestUrl, options.companionArtifactAccessToken, fetchImpl),
15529
+ });
15530
+ }
15531
+ catch (error) {
15532
+ snapshotError = error;
15533
+ }
15534
+ const digest = manifest?.digest;
15535
+ const issued = await getTicket(digest);
15536
+ if (!issued)
15537
+ return;
15324
15538
  if (closed)
15325
15539
  return;
15326
- options.onToken(issued.token);
15540
+ if (publishedToken !== issued.token || publishedDigest !== digest) {
15541
+ publishedToken = issued.token;
15542
+ publishedDigest = digest;
15543
+ options.onToken(issued.token, digest, issued.expiresAt);
15544
+ }
15545
+ options.onReadiness({
15546
+ browser: { state: 'ready' },
15547
+ mobile: { state: 'ready' },
15548
+ companion: snapshotError
15549
+ ? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
15550
+ : manifest?.artifacts.length
15551
+ ? companionApproved
15552
+ ? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
15553
+ : { state: 'blocked', reason: 'Companion 未获 COA 批准或本地权限声明未开启' }
15554
+ : { state: 'not-configured', reason: '未配置本地 Companion 产物' },
15555
+ });
15556
+ if (snapshotError)
15557
+ options.logger.debug(`PC Companion 快照校验失败:${index.readRedactedErrorMessage(snapshotError, { verbose: true })}`);
15327
15558
  const ttl = Math.max(0, issued.expiresAt - Date.now());
15328
15559
  scheduleExpiration(ttl);
15329
- schedule(Math.max(1_000, Math.floor(ttl * 0.8)));
15560
+ schedule(Math.min(2_000, Math.max(1_000, Math.floor(ttl * 0.8))));
15330
15561
  if (hasReportedTokenFailure) {
15331
15562
  hasReportedTokenFailure = false;
15332
- options.logger.success('手机扫码调试已恢复');
15563
+ options.logger.success('本地调试凭证已恢复');
15333
15564
  }
15334
15565
  }
15335
15566
  catch (error) {
15336
15567
  if (closed)
15337
15568
  return;
15338
15569
  if (!hasReportedTokenFailure) {
15339
- options.logger.warn('手机扫码调试凭证获取失败,将自动重试');
15570
+ options.logger.warn('本地调试凭证获取失败,将自动重试;Web / Browser Fake 仍可使用');
15340
15571
  }
15341
15572
  hasReportedTokenFailure = true;
15342
- options.logger.debug(`手机扫码调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
15573
+ options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证暂不可用' },
15574
+ companion: { state: 'blocked', reason: safeCompanionFailureReason(error) } });
15575
+ options.logger.debug(`本地调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
15343
15576
  schedule(options.timeoutMs);
15344
15577
  }
15345
15578
  };
@@ -15353,13 +15586,20 @@ function startLocalDevLaunchTokenSession(options) {
15353
15586
  if (expirationTimer)
15354
15587
  clearTimeout(expirationTimer);
15355
15588
  expirationTimer = setTimeout(() => {
15356
- if (!closed)
15357
- options.onToken(undefined);
15589
+ if (!closed) {
15590
+ options.onToken(undefined, undefined);
15591
+ publishedToken = undefined;
15592
+ options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'blocked', reason: '调试凭证已过期' },
15593
+ companion: { state: 'blocked', reason: '调试凭证已过期' } });
15594
+ }
15358
15595
  }, Math.max(1, ttl));
15359
15596
  expirationTimer.unref?.();
15360
15597
  };
15361
- void refresh();
15598
+ options.onReadiness({ browser: { state: 'ready' }, mobile: { state: 'pending' }, companion: { state: 'pending' } });
15599
+ const ready = refresh();
15362
15600
  return {
15601
+ getTicket,
15602
+ ready,
15363
15603
  close: () => {
15364
15604
  closed = true;
15365
15605
  if (expirationTimer)
@@ -15373,22 +15613,128 @@ async function issueLocalDevLaunchToken(options) {
15373
15613
  const issued = await loadRemoteDevContextWithTimeout({
15374
15614
  fetchImpl: options.fetchImpl,
15375
15615
  timeoutMs: options.timeoutMs,
15376
- load: (fetchImpl) => context.createLocalDevLaunchToken({
15377
- cwd: options.projectRoot,
15378
- env: options.env,
15379
- fetchImpl,
15380
- miniProgramId: options.miniProgramId,
15381
- }),
15616
+ load: async (fetchImpl) => {
15617
+ const result = await context.createLocalDevLaunchToken({
15618
+ cwd: options.projectRoot,
15619
+ env: options.env,
15620
+ fetchImpl,
15621
+ miniProgramId: options.miniProgramId,
15622
+ ...(options.companionManifestSha256
15623
+ ? { companionManifestSha256: options.companionManifestSha256 }
15624
+ : {}),
15625
+ });
15626
+ return { result };
15627
+ },
15382
15628
  });
15383
- const token = typeof issued.token === 'string' ? issued.token.trim() : '';
15629
+ const token = typeof issued.result.token === 'string' ? issued.result.token.trim() : '';
15384
15630
  if (!token) {
15385
15631
  throw new Error('响应缺少 token');
15386
15632
  }
15387
- const expiresAt = normalizeExpiresAt(issued.expires_at);
15633
+ const expiresAt = normalizeExpiresAt(issued.result.expires_at);
15388
15634
  if (!expiresAt || expiresAt <= Date.now()) {
15389
15635
  throw new Error('响应缺少有效 expires_at');
15390
15636
  }
15391
- return { expiresAt, token };
15637
+ return { ...(options.companionManifestSha256 ? { companionManifestSha256: options.companionManifestSha256 } : {}), expiresAt, token,
15638
+ refreshAt: Date.now() + Math.max(1, (expiresAt - Date.now()) * 0.8) };
15639
+ }
15640
+ function safeCompanionFailureReason(error) {
15641
+ const message = index.readRedactedErrorMessage(error, { verbose: true });
15642
+ if (/artifact-updating/.test(message))
15643
+ return '本地 Companion 产物准备中';
15644
+ if (/artifact-missing/.test(message))
15645
+ return '本地 Companion 产物目录不存在,请先构建产物';
15646
+ if (/snapshot-limit/.test(message))
15647
+ return '本地快照数量或大小已达上限,请结束会话后重启 dev';
15648
+ if (/invalid-manifest/.test(message))
15649
+ return '本地 Manifest 或权限配置无效';
15650
+ if (/invalid-artifact/.test(message))
15651
+ return '本地产物或原生入口校验失败';
15652
+ if (/404|not found/i.test(message))
15653
+ return '调试接口尚未部署或当前登录环境不匹配';
15654
+ if (/403|forbidden|permission|授权|权限/i.test(message))
15655
+ return 'Companion 授权未通过,请检查 COA 批准与开发者身份';
15656
+ if (/401|登录/i.test(message))
15657
+ return 'CLI 登录态已失效,请重新登录';
15658
+ if (/manifest|快照|ZIP|ENOENT/i.test(message))
15659
+ return '本地 Companion ZIP 或 Manifest 校验失败,请检查产物与配置';
15660
+ return 'PC Companion 暂不可用,请检查网络、登录环境和调试接口';
15661
+ }
15662
+ function hasCompanionApproval(value) {
15663
+ if (!value || typeof value !== 'object' || !('entries' in value) || !Array.isArray(value.entries))
15664
+ return false;
15665
+ return value.entries.some(entry => entry?.key === 'companion' && entry?.status === 'enabled');
15666
+ }
15667
+ async function readCompanionManifest(manifestUrl, companionArtifactAccessToken, fetchImpl) {
15668
+ try {
15669
+ const response = await fetchImpl(manifestUrl, {
15670
+ cache: 'no-store',
15671
+ credentials: 'omit',
15672
+ headers: {
15673
+ accept: 'application/json',
15674
+ [COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER]: companionArtifactAccessToken,
15675
+ },
15676
+ method: 'GET',
15677
+ redirect: 'error',
15678
+ referrerPolicy: 'no-referrer',
15679
+ });
15680
+ if (response.status === 404) {
15681
+ return undefined;
15682
+ }
15683
+ if (!response.ok) {
15684
+ const code = response.headers.get('x-hb-sdk-companion-error');
15685
+ const safeCode = ['artifact-updating', 'artifact-missing', 'snapshot-limit', 'invalid-manifest', 'invalid-artifact'].includes(code ?? '') ? code : '';
15686
+ throw new Error(`Companion manifest request failed with status ${response.status} ${safeCode}`);
15687
+ }
15688
+ if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) {
15689
+ throw new Error('Companion manifest response is not JSON');
15690
+ }
15691
+ const contentLength = Number(response.headers.get('content-length'));
15692
+ if (Number.isFinite(contentLength) && contentLength > COMPANION_DEV_MANIFEST_MAX_BYTES) {
15693
+ throw new Error('Companion manifest response is too large');
15694
+ }
15695
+ const bytes = await readBoundedCompanionManifestBytes(response, COMPANION_DEV_MANIFEST_MAX_BYTES);
15696
+ const value = JSON.parse(bytes.toString('utf8'));
15697
+ const artifacts = Object.entries(value.companions ?? {}).flatMap(([alias, definition]) => {
15698
+ if (!/^[a-z][a-z0-9-]{0,31}$/.test(alias) || !definition || typeof definition !== 'object') {
15699
+ throw new Error('Companion invalid-manifest');
15700
+ }
15701
+ return Object.keys(definition.targets ?? {}).map(target => {
15702
+ if (target !== 'windows-x64' && target !== 'macos-arm64')
15703
+ throw new Error('Companion invalid-manifest');
15704
+ return { alias, target };
15705
+ });
15706
+ });
15707
+ return { digest: node_crypto.createHash('sha256').update(bytes).digest('hex'), artifacts };
15708
+ }
15709
+ catch (error) {
15710
+ if (error instanceof Error)
15711
+ throw error;
15712
+ throw new Error('Companion manifest request failed');
15713
+ }
15714
+ }
15715
+ async function readBoundedCompanionManifestBytes(response, maxBytes) {
15716
+ if (!response.body)
15717
+ return Buffer.alloc(0);
15718
+ const reader = response.body.getReader();
15719
+ const chunks = [];
15720
+ let total = 0;
15721
+ try {
15722
+ for (;;) {
15723
+ const { done, value } = await reader.read();
15724
+ if (done)
15725
+ break;
15726
+ total += value.byteLength;
15727
+ if (total > maxBytes) {
15728
+ await reader.cancel('Companion manifest response is too large');
15729
+ throw new Error('Companion manifest response is too large');
15730
+ }
15731
+ chunks.push(value);
15732
+ }
15733
+ }
15734
+ finally {
15735
+ reader.releaseLock();
15736
+ }
15737
+ return Buffer.concat(chunks.map(chunk => Buffer.from(chunk)), total);
15392
15738
  }
15393
15739
  function normalizeExpiresAt(value) {
15394
15740
  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
@@ -15501,8 +15847,10 @@ function isPrivateIPv4(address) {
15501
15847
 
15502
15848
  var dev = /*#__PURE__*/Object.freeze({
15503
15849
  __proto__: null,
15850
+ createMiniProgramDevLaunchVitePlugin: createMiniProgramDevLaunchVitePlugin,
15504
15851
  normalizeExpiresAt: normalizeExpiresAt,
15505
- runDevCommand: runDevCommand
15852
+ runDevCommand: runDevCommand,
15853
+ startLocalDevLaunchTokenSession: startLocalDevLaunchTokenSession
15506
15854
  });
15507
15855
 
15508
15856
  exports.RuntimeError = RuntimeError;