@adep/vite-plugin 0.1.4 → 0.1.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.
@@ -30,6 +30,22 @@
30
30
  import type { IncomingMessage, ServerResponse } from 'node:http';
31
31
  /** 浏览器端 fetch 拦截器脚本(IIFE,纯 JS——必须能直接内联进 script 标签,无 TS / ESM)。 */
32
32
  export declare const FN_FETCH_INTERCEPTOR_SCRIPT = "(function () {\n if (window.__adepFnProxyInstalled) return;\n window.__adepFnProxyInstalled = true;\n var ORIGINAL_FETCH = window.fetch.bind(window);\n var pending = {};\n var nextId = 1;\n var TIMEOUT_MS = 15000;\n\n window.addEventListener(\"message\", function (event) {\n var data = event.data;\n if (!data || data.__adepFnResponse !== true) return;\n var entry = pending[data.id];\n if (!entry) return;\n delete pending[data.id];\n clearTimeout(entry.timer);\n var headers;\n try { headers = new Headers(data.headers || {}); }\n catch (e) { headers = new Headers({ \"content-type\": \"text/plain\" }); }\n var body = data.body == null ? null : data.body;\n try {\n entry.resolve(new Response(body, { status: data.status || 200, statusText: \"\", headers: headers }));\n } catch (e2) {\n entry.resolve(new Response(String(body), { status: data.status || 200, headers: { \"content-type\": \"text/plain\" } }));\n }\n });\n\n function readBody(input, init) {\n if (typeof Request !== \"undefined\" && input instanceof Request) {\n return input.text().catch(function () { return null; });\n }\n var body = init && init.body !== undefined ? init.body : input;\n if (body == null) return Promise.resolve(null);\n if (typeof body === \"string\") return Promise.resolve(body);\n if (typeof ArrayBuffer !== \"undefined\" && body instanceof ArrayBuffer) {\n return Promise.resolve(new TextDecoder().decode(body));\n }\n if (typeof Blob !== \"undefined\" && body instanceof Blob) return body.text();\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) {\n return Promise.resolve(body.toString());\n }\n try { return Promise.resolve(String(body)); } catch (e) { return Promise.resolve(null); }\n }\n\n function collectHeaders(input, init) {\n var out = {};\n try { input.headers.forEach(function (v, k) { out[k] = v; }); } catch (e) {}\n if (init && init.headers) {\n try { new Headers(init.headers).forEach(function (v, k) { out[k] = v; }); } catch (e2) {}\n }\n return out;\n }\n\n window.fetch = function (input, init) {\n var urlStr;\n var method;\n var headers = {};\n var isRequest = typeof Request !== \"undefined\" && input instanceof Request;\n if (isRequest) {\n urlStr = String(input.url);\n method = (init && init.method) || input.method || \"GET\";\n headers = collectHeaders(input, init);\n } else if (typeof input === \"string\" || (typeof URL !== \"undefined\" && input instanceof URL)) {\n urlStr = String(input);\n method = (init && init.method) || \"GET\";\n try { new Headers(init && init.headers || {}).forEach(function (v, k) { headers[k] = v; }); } catch (e) {}\n } else {\n return ORIGINAL_FETCH(input, init);\n }\n var u;\n try { u = new URL(urlStr, document.baseURI); }\n catch (e0) {\n try { u = new URL(urlStr, window.location.origin); }\n catch (e1) { return ORIGINAL_FETCH(input, init); }\n }\n if (u.pathname.indexOf(\"/api/\") !== 0 && u.pathname !== \"/api\") {\n return ORIGINAL_FETCH(input, init);\n }\n var bridgeWindow = window.parent !== window ? window.parent : window.opener;\n if (!bridgeWindow) return ORIGINAL_FETCH(input, init);\n method = (method || \"GET\").toUpperCase();\n var target = u.pathname + u.search;\n return readBody(input, init).then(function (textBody) {\n return new Promise(function (resolve) {\n var id = nextId++;\n var timer = setTimeout(function () {\n delete pending[id];\n resolve(new Response('{\"error\":{\"code\":\"FN_PROXY_TIMEOUT\",\"message\":\"\\u4e91\\u51fd\\u6570\\u6267\\u884c\\u8d85\\u65f6\"}}', { status: 504, headers: { \"content-type\": \"application/json\" } }));\n }, TIMEOUT_MS);\n pending[id] = { resolve: resolve, timer: timer };\n bridgeWindow.postMessage({ __adepFnRequest: true, id: id, method: method, url: target, headers: headers, body: textBody }, \"*\");\n });\n });\n };\n\n /* \u2014\u2014 blob URL SPA \u8DEF\u7531\u652F\u6301 \u2014\u2014\n blob: \u662F\u4E0D\u53EF\u5206\u5C42 scheme\uFF0Chistory.pushState('/about') \u5185\u90E8\u89E3\u6790\u76F8\u5BF9\u8DEF\u5F84\u4F1A\u629B\n TypeError \u2192 SPA history \u6A21\u5F0F\u8DEF\u7531\u5D29\u6E83\u3002patch pushState/replaceState\uFF0C\u5BF9\u76F8\u5BF9\n \u8DEF\u5F84\u76F4\u63A5\u541E\u6389\uFF08\u4E0D\u5BFC\u822A\u3001\u4E0D\u629B\u9519\uFF09\uFF0C\u8DEF\u7531\u5E93\u5185\u90E8\u72B6\u6001\u81EA\u884C\u66F4\u65B0\uFF1Blocation.pathname\n \u8986\u76D6\u4E3A\u5F53\u524D\u8DEF\u7531\u8DEF\u5F84\uFF0C\u4F9B\u8DEF\u7531\u5E93\u521D\u59CB\u5316\u65F6\u8BFB\u53D6\u3002sessionStorage \u6301\u4E45\u5316\uFF0C\u5237\u65B0\u540E\u6062\u590D\u3002 */\n if (window.location.protocol === \"blob:\") {\n var SPA_KEY = \"__adep_spa_path\";\n var spaPath = \"/\";\n try {\n var _saved = window.sessionStorage.getItem(SPA_KEY);\n if (_saved) spaPath = _saved;\n } catch (e) {}\n try {\n Object.defineProperty(window.location, \"pathname\", {\n get: function () { return spaPath; },\n configurable: true\n });\n } catch (e) {}\n var _origPush = window.history.pushState.bind(window.history);\n var _origReplace = window.history.replaceState.bind(window.history);\n function _isRelative(url) {\n if (!url || typeof url !== \"string\") return false;\n if (url.charAt(0) === \"#\") return false;\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) return false;\n return true;\n }\n function _resolvePath(url) {\n var qi = url.indexOf(\"?\");\n var hi = url.indexOf(\"#\");\n var end = url.length;\n if (qi !== -1) end = qi;\n if (hi !== -1 && hi < end) end = hi;\n var p = url.slice(0, end);\n if (p.charAt(0) !== \"/\") {\n var base = spaPath.slice(0, spaPath.lastIndexOf(\"/\") + 1);\n p = base + p;\n }\n var parts = p.split(\"/\");\n var out = [];\n for (var i = 0; i < parts.length; i++) {\n var seg = parts[i];\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") { out.pop(); continue; }\n out.push(seg);\n }\n return \"/\" + out.join(\"/\");\n }\n window.history.pushState = function (state, title, url) {\n if (_isRelative(url)) {\n spaPath = _resolvePath(url);\n try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}\n return;\n }\n return _origPush(state, title, url);\n };\n window.history.replaceState = function (state, title, url) {\n if (_isRelative(url)) {\n spaPath = _resolvePath(url);\n try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}\n return;\n }\n return _origReplace(state, title, url);\n };\n }\n})();";
33
+ /**
34
+ * 浏览器端控制台中继脚本(IIFE,纯 JS——必须能直接内联进 script 标签,无 TS / ESM)。
35
+ *
36
+ * **同步纪律**:本字符串与 `packages/web-container/src/console-relay.ts` 的
37
+ * `CONSOLE_RELAY_SCRIPT`(浏览器内置 vite-dev 注入用)**逐字一致**——理由同上面的拦截器:
38
+ * 预览 iframe / 浏览器内 vite-dev 是两个独立加载上下文,不共享 import,只共享文本契约。
39
+ * 机检见 `app/composables/__tests__/preview-inject-scripts.test.ts`(两份副本逐字比对)。
40
+ *
41
+ * 两条注入路径:
42
+ * - Nodebox:`app/composables/ide-nodebox-preview.ts` 把它写进沙箱内的注入插件(transformIndexHtml);
43
+ * - 浏览器内置 dev server:`vite-dev.ts` 在 buildDocument 里内联进产物文档 `<head>` 首位。
44
+ *
45
+ * 信封 `{ source: 'adep-ide-preview', kind: 'console', level, text }` 由 IDE 的
46
+ * `FrontendPreview.onMessage` 消费;`window.parent === window`(直接打开 / 分享链接)不发。
47
+ */
48
+ export declare const CONSOLE_RELAY_SCRIPT = "(function () {\n if (window.__adepErrorRelayInstalled) return;\n window.__adepErrorRelayInstalled = true;\n var LEVELS = { log: \"log\", info: \"info\", warn: \"warn\", error: \"error\", debug: \"log\" };\n function fmt(value) {\n try {\n if (typeof value === \"string\") return value;\n if (value === undefined) return \"undefined\";\n var seen = [];\n var json = JSON.stringify(value, function (key, val) {\n if (val instanceof Error) return val.stack || (val.name + \": \" + val.message);\n if (typeof val === \"function\") return \"[Function \" + (val.name || \"anonymous\") + \"]\";\n if (typeof val === \"bigint\") return String(val) + \"n\";\n if (val && typeof val === \"object\") {\n if (seen.indexOf(val) !== -1) return \"[Circular]\";\n seen.push(val);\n }\n return val;\n });\n return json === undefined ? String(value) : json;\n } catch {\n return String(value);\n }\n }\n function send(level, args) {\n try {\n if (window.parent === window) return;\n var text = Array.prototype.map.call(args, fmt).join(\" \");\n window.parent.postMessage({ source: \"adep-ide-preview\", kind: \"console\", level: level, text: text }, \"*\");\n } catch {\n }\n }\n Object.keys(LEVELS).forEach(function (name) {\n var native = console[name];\n console[name] = function () {\n send(LEVELS[name], arguments);\n native.apply(console, arguments);\n };\n });\n window.addEventListener('error', function (event) {\n var text = event.message || \"Script error\";\n if (event.filename) {\n text += \" (\" + event.filename + \":\" + event.lineno + \":\" + event.colno + \")\";\n }\n send(\"error\", [text]);\n });\n window.addEventListener('unhandledrejection', function (event) {\n var reason = event.reason;\n var text = reason && reason.stack ? String(reason.stack) : \"Unhandled rejection: \" + fmt(reason);\n send(\"error\", [text]);\n });\n})();\n";
33
49
  /**
34
50
  * 构造中间件:serve `/@adep/fn-proxy.js`(返回上面的拦截器脚本)。
35
51
  * vite dev / preview server 均挂;路径未命中则 next() 放行。
@@ -40,3 +56,16 @@ export declare function createAdepFnProxyScriptMiddleware(): (req: IncomingMessa
40
56
  * Web IDE 预览路径用(CLI 模式下脚本自检 window.parent === window 不激活,无害)。
41
57
  */
42
58
  export declare function injectFnProxyScriptTag(html: string): string;
59
+ /**
60
+ * 构造中间件:serve `/@adep/automation.js`(返回自动化中继脚本)。
61
+ * 与 `createAdepFnProxyScriptMiddleware` 同构——Nodebox 路径凭 3 个静态 URL 注入,
62
+ * 不往沙箱里塞业务代码(注入插件只写 `<script src>`,见 `ide-nodebox-preview.ts`)。
63
+ */
64
+ export declare function createAdepAutomationScriptMiddleware(): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
65
+ /**
66
+ * 把自动化中继 script 标签注入 index.html 的 </head> 前(幂等)。
67
+ *
68
+ * 注入位置排在三段脚本的最后:中继脚本会再包一层 `console.*`(记环形缓冲),
69
+ * 排在控制台中继之后才能既进它的缓冲、又经它转发到 IDE「控制台」页签。
70
+ */
71
+ export declare function injectAutomationScriptTag(html: string): string;
package/dist/index.d.ts CHANGED
@@ -17,4 +17,5 @@ export { adepPlugin, default } from './dev-server';
17
17
  export type { AdepConfigLoader, AdepDevServerFactory, AdepDevServerHandle, AdepVitePlugin, AdepVitePluginOptions, AdepViteServer, } from './dev-server';
18
18
  export { normalizeFunctionPrefix } from './prefix';
19
19
  export { createAdepProxyMiddleware, isAddrInUse } from './proxy';
20
- export { createAdepFnProxyScriptMiddleware, FN_FETCH_INTERCEPTOR_SCRIPT, injectFnProxyScriptTag, } from './ide-proxy';
20
+ export { createAdepAutomationScriptMiddleware, createAdepFnProxyScriptMiddleware, FN_FETCH_INTERCEPTOR_SCRIPT, injectAutomationScriptTag, injectFnProxyScriptTag, } from './ide-proxy';
21
+ export { PREVIEW_AUTOMATION_SCRIPT } from './preview-automation';
package/dist/index.js CHANGED
@@ -48,6 +48,477 @@ function createAdepProxyMiddleware(prefix, targetBase) {
48
48
  };
49
49
  }
50
50
 
51
+ // packages/vite-plugin/src/preview-automation.ts
52
+ var PREVIEW_AUTOMATION_SCRIPT = `(function () {
53
+ if (window.__adepAutomationInstalled) return;
54
+ window.__adepAutomationInstalled = true;
55
+
56
+ var CONSOLE_LIMIT = 200;
57
+ var DEFAULT_TIMEOUT_MS = 8000;
58
+ var MAX_TEXT_CHARS = 12000;
59
+ var MAX_HTML_CHARS = 20000;
60
+ var DEFAULT_INSPECT_SELECTOR = "a,button,input,select,textarea,img,h1,h2,h3,h4,label,[role],[data-testid]";
61
+ var consoleLog = [];
62
+ var consoleSeq = 0;
63
+
64
+ function fail(code, message) {
65
+ var error = new Error(message);
66
+ error.code = code;
67
+ return error;
68
+ }
69
+
70
+ function asNumber(value, fallback) {
71
+ return typeof value === "number" && isFinite(value) ? value : fallback;
72
+ }
73
+
74
+ function asString(value, fallback) {
75
+ return typeof value === "string" ? value : fallback;
76
+ }
77
+
78
+ function truncate(value, max) {
79
+ var text = value == null ? "" : String(value);
80
+ var limit = typeof max === "number" && isFinite(max) && max > 0 ? Math.floor(max) : 0;
81
+ if (limit === 0 || text.length <= limit) return text;
82
+ return text.slice(0, limit) + "...[truncated, total " + text.length + " chars]";
83
+ }
84
+
85
+ /* ---- console / error ring buffer ---- */
86
+ function record(level, text) {
87
+ consoleSeq += 1;
88
+ consoleLog.push({ seq: consoleSeq, level: level, text: truncate(text, 2000), at: Date.now() });
89
+ if (consoleLog.length > CONSOLE_LIMIT) consoleLog.splice(0, consoleLog.length - CONSOLE_LIMIT);
90
+ }
91
+
92
+ function fmt(value) {
93
+ try {
94
+ if (typeof value === "string") return value;
95
+ if (value === undefined) return "undefined";
96
+ if (value instanceof Error) return value.stack || (value.name + ": " + value.message);
97
+ var seen = [];
98
+ var json = JSON.stringify(value, function (key, val) {
99
+ if (val instanceof Error) return val.stack || (val.name + ": " + val.message);
100
+ if (typeof val === "function") return "[Function " + (val.name || "anonymous") + "]";
101
+ if (typeof val === "bigint") return String(val) + "n";
102
+ if (val && typeof val === "object") {
103
+ if (seen.indexOf(val) !== -1) return "[Circular]";
104
+ seen.push(val);
105
+ }
106
+ return val;
107
+ });
108
+ return json === undefined ? String(value) : json;
109
+ } catch (e) {
110
+ return String(value);
111
+ }
112
+ }
113
+
114
+ var LEVELS = { log: "log", info: "info", warn: "warn", error: "error", debug: "log" };
115
+ Object.keys(LEVELS).forEach(function (name) {
116
+ var native = console[name];
117
+ console[name] = function () {
118
+ try { record(LEVELS[name], Array.prototype.map.call(arguments, fmt).join(" ")); } catch (e) {}
119
+ native.apply(console, arguments);
120
+ };
121
+ });
122
+ window.addEventListener('error', function (event) {
123
+ var text = event.message || "Script error";
124
+ if (event.filename) text += " (" + event.filename + ":" + event.lineno + ":" + event.colno + ")";
125
+ record("error", text);
126
+ });
127
+ window.addEventListener('unhandledrejection', function (event) {
128
+ var reason = event.reason;
129
+ record("error", reason && reason.stack ? String(reason.stack) : "Unhandled rejection: " + fmt(reason));
130
+ });
131
+
132
+ /* ---- DOM read ---- */
133
+ function query(selector, nth) {
134
+ var list = document.querySelectorAll(selector);
135
+ if (list.length === 0) throw fail("ELEMENT_NOT_FOUND", "selector matched nothing: " + selector);
136
+ var index = asNumber(nth, 0);
137
+ var el = list[index];
138
+ if (!el) throw fail("ELEMENT_NOT_FOUND", "selector matched " + list.length + " elements; index " + index + " is out of range");
139
+ return el;
140
+ }
141
+
142
+ function isVisible(el) {
143
+ if (!el || !el.getBoundingClientRect) return false;
144
+ var rect = el.getBoundingClientRect();
145
+ if (rect.width <= 0 && rect.height <= 0) return false;
146
+ var style = window.getComputedStyle(el);
147
+ if (style.visibility === "hidden" || style.display === "none") return false;
148
+ if (Number(style.opacity) === 0) return false;
149
+ return true;
150
+ }
151
+
152
+ function rectOf(el) {
153
+ var rect = el.getBoundingClientRect();
154
+ return {
155
+ x: Math.round(rect.left + window.scrollX),
156
+ y: Math.round(rect.top + window.scrollY),
157
+ w: Math.round(rect.width),
158
+ h: Math.round(rect.height),
159
+ };
160
+ }
161
+
162
+ function describe(el, maxText) {
163
+ var tag = String(el.tagName || "").toLowerCase();
164
+ var text = "";
165
+ if (tag === "input" || tag === "textarea" || tag === "select") text = String(el.value || "");
166
+ else text = el.textContent || "";
167
+ var out = {
168
+ tag: tag,
169
+ testid: el.getAttribute ? el.getAttribute("data-testid") : null,
170
+ id: el.id || null,
171
+ cls: el.className && typeof el.className === "string" ? el.className : null,
172
+ role: el.getAttribute ? el.getAttribute("role") : null,
173
+ text: truncate(String(text).replace(/[\\s\\u00a0]+/g, " ").trim(), asNumber(maxText, 120)),
174
+ rect: rectOf(el),
175
+ visible: isVisible(el),
176
+ };
177
+ if (tag === "input" || tag === "textarea" || tag === "select") {
178
+ out.field = el.getAttribute("name") || el.getAttribute("placeholder") || null;
179
+ if (tag === "input") out.type = el.getAttribute("type") || "text";
180
+ }
181
+ if (tag === "a") out.href = el.getAttribute("href") || null;
182
+ if (el.disabled === true) out.disabled = true;
183
+ if (el.checked !== undefined && (tag === "input")) out.checked = el.checked === true;
184
+ return out;
185
+ }
186
+
187
+ /* ---- DOM write ---- */
188
+ function fireMouse(el, x, y, kind) {
189
+ var rect = el.getBoundingClientRect();
190
+ var clientX = typeof x === "number" ? x : rect.left + rect.width / 2;
191
+ var clientY = typeof y === "number" ? y : rect.top + rect.height / 2;
192
+ el.dispatchEvent(new MouseEvent(kind, {
193
+ bubbles: true, cancelable: true, composed: true, view: window,
194
+ clientX: clientX, clientY: clientY, button: 0,
195
+ }));
196
+ }
197
+
198
+ function clickElement(el, x, y) {
199
+ try { el.scrollIntoView({ block: "center", inline: "center" }); } catch (e) {}
200
+ fireMouse(el, x, y, "mousedown");
201
+ fireMouse(el, x, y, "mouseup");
202
+ fireMouse(el, x, y, "click");
203
+ return describe(el, 120);
204
+ }
205
+
206
+ function fireKey(el, key) {
207
+ var target = el || document.activeElement || document.body;
208
+ var init = { key: key, bubbles: true, cancelable: true, composed: true };
209
+ if (key === "Enter") { init.keyCode = 13; init.which = 13; }
210
+ if (key === "Escape") { init.keyCode = 27; init.which = 27; }
211
+ if (key === "Tab") { init.keyCode = 9; init.which = 9; }
212
+ var allowed = target.dispatchEvent(new KeyboardEvent("keydown", init));
213
+ if (allowed) target.dispatchEvent(new KeyboardEvent("keypress", init));
214
+ target.dispatchEvent(new KeyboardEvent("keyup", init));
215
+ return { key: key, target: describe(target, 80) };
216
+ }
217
+
218
+ function setNativeValue(el, value) {
219
+ var proto = null;
220
+ if (window.HTMLTextAreaElement && el instanceof window.HTMLTextAreaElement) proto = window.HTMLTextAreaElement.prototype;
221
+ else if (window.HTMLSelectElement && el instanceof window.HTMLSelectElement) proto = window.HTMLSelectElement.prototype;
222
+ else if (window.HTMLInputElement && el instanceof window.HTMLInputElement) proto = window.HTMLInputElement.prototype;
223
+ var descriptor = proto ? Object.getOwnPropertyDescriptor(proto, "value") : null;
224
+ if (descriptor && descriptor.set) descriptor.set.call(el, value);
225
+ else el.value = value;
226
+ }
227
+
228
+ function fireInput(el) {
229
+ el.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
230
+ el.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
231
+ }
232
+
233
+ function typeInto(el, value, clear, submit) {
234
+ try { el.focus(); } catch (e) {}
235
+ if (clear) {
236
+ setNativeValue(el, "");
237
+ fireInput(el);
238
+ }
239
+ setNativeValue(el, clear ? String(value) : String(el.value || "") + String(value));
240
+ fireInput(el);
241
+ if (submit) fireKey(el, "Enter");
242
+ return describe(el, 200);
243
+ }
244
+
245
+ function waitFor(selector, state, timeoutMs) {
246
+ var want = state === "hidden" || state === "detached" ? state : "visible";
247
+ var deadline = Date.now() + timeoutMs;
248
+ return new Promise(function (resolve, reject) {
249
+ function check() {
250
+ var el = document.querySelector(selector);
251
+ if (want === "detached" && el === null) return resolve({ state: want, selector: selector });
252
+ if (want === "hidden" && (el === null || !isVisible(el))) return resolve({ state: want, selector: selector });
253
+ if (want === "visible" && el !== null && isVisible(el)) return resolve(describe(el, 200));
254
+ if (Date.now() >= deadline) {
255
+ return reject(fail("WAIT_TIMEOUT", "waitFor " + want + " timed out after " + timeoutMs + "ms: " + selector));
256
+ }
257
+ setTimeout(check, 60);
258
+ }
259
+ check();
260
+ });
261
+ }
262
+
263
+ /* ---- screenshot (SVG foreignObject serialization, best effort) ---- */
264
+ function collectCss() {
265
+ var out = [];
266
+ var sheets = document.styleSheets;
267
+ for (var i = 0; i < sheets.length; i++) {
268
+ var rules = null;
269
+ try { rules = sheets[i].cssRules; } catch (e) { rules = null; }
270
+ if (!rules) continue;
271
+ for (var j = 0; j < rules.length; j++) out.push(rules[j].cssText);
272
+ }
273
+ return out.join("\\n");
274
+ }
275
+
276
+ function inlineImages(clone) {
277
+ var images = clone.querySelectorAll("img");
278
+ var jobs = [];
279
+ for (var i = 0; i < images.length; i++) {
280
+ (function (img) {
281
+ var src = img.getAttribute("src") || "";
282
+ if (src === "" || src.indexOf("data:") === 0) return;
283
+ jobs.push(window.fetch(src, { credentials: "omit" }).then(function (res) {
284
+ if (!res.ok) return null;
285
+ return res.blob().then(function (blob) {
286
+ return new Promise(function (resolve) {
287
+ var reader = new FileReader();
288
+ reader.onload = function () { img.setAttribute("src", String(reader.result)); resolve(null); };
289
+ reader.onerror = function () { resolve(null); };
290
+ reader.readAsDataURL(blob);
291
+ });
292
+ });
293
+ }).catch(function () { return null; }));
294
+ })(images[i]);
295
+ }
296
+ return Promise.all(jobs);
297
+ }
298
+
299
+ var QUOTE = String.fromCharCode(34);
300
+ function tag(name, pairs) {
301
+ var out = "<" + name;
302
+ for (var i = 0; i < pairs.length; i += 2) out += " " + pairs[i] + "=" + QUOTE + pairs[i + 1] + QUOTE;
303
+ return out + ">";
304
+ }
305
+
306
+ function screenshot(args) {
307
+ var scale = Math.min(asNumber(args.scale, 1), 2);
308
+ var maxHeight = asNumber(args.maxHeight, 2400);
309
+ var width = Math.max(1, document.documentElement.clientWidth || window.innerWidth || 1024);
310
+ var full = document.documentElement.scrollHeight || window.innerHeight || 768;
311
+ var height = Math.max(1, Math.min(full, maxHeight));
312
+ var clone = document.documentElement.cloneNode(true);
313
+ var scripts = clone.querySelectorAll("script");
314
+ for (var i = 0; i < scripts.length; i++) {
315
+ if (scripts[i].parentNode) scripts[i].parentNode.removeChild(scripts[i]);
316
+ }
317
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
318
+ return inlineImages(clone).then(function () {
319
+ var style = document.createElement("style");
320
+ style.textContent = collectCss();
321
+ var head = clone.querySelector("head");
322
+ if (head) head.insertBefore(style, head.firstChild);
323
+ else clone.insertBefore(style, clone.firstChild);
324
+ var markup = new XMLSerializer().serializeToString(clone);
325
+ var svg = tag("svg", ["xmlns", "http://www.w3.org/2000/svg", "width", width, "height", height])
326
+ + tag("foreignObject", ["x", 0, "y", 0, "width", width, "height", height])
327
+ + markup + "</foreignObject></svg>";
328
+ var image = new Image();
329
+ return new Promise(function (resolve, reject) {
330
+ image.onload = function () {
331
+ var canvas = document.createElement("canvas");
332
+ canvas.width = Math.round(width * scale);
333
+ canvas.height = Math.round(height * scale);
334
+ var ctx = canvas.getContext("2d");
335
+ ctx.scale(scale, scale);
336
+ try { ctx.drawImage(image, 0, 0); } catch (e) {}
337
+ var dataUrl = null;
338
+ try { dataUrl = canvas.toDataURL("image/png"); }
339
+ catch (e2) {
340
+ reject(fail("SCREENSHOT_TAINTED", "canvas is tainted by a cross-origin resource that could not be inlined; fall back to snapshot / inspect"));
341
+ return;
342
+ }
343
+ resolve({ dataUrl: dataUrl, width: canvas.width, height: canvas.height, bytes: dataUrl.length, clipped: full > height });
344
+ };
345
+ image.onerror = function () {
346
+ reject(fail("SCREENSHOT_FAILED", "the browser refused to rasterize the serialized DOM"));
347
+ };
348
+ image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
349
+ });
350
+ });
351
+ }
352
+
353
+ /* ---- console read ---- */
354
+ function readConsole(args) {
355
+ var since = asNumber(args.since, 0);
356
+ var limit = asNumber(args.limit, 60);
357
+ var level = asString(args.level, "");
358
+ var out = [];
359
+ for (var i = 0; i < consoleLog.length; i++) {
360
+ var entry = consoleLog[i];
361
+ if (entry.seq <= since) continue;
362
+ if (level !== "" && entry.level !== level) continue;
363
+ out.push(entry);
364
+ }
365
+ var dropped = 0;
366
+ if (out.length > limit) {
367
+ dropped = out.length - limit;
368
+ out = out.slice(out.length - limit);
369
+ }
370
+ return { entries: out, cursor: consoleSeq, total: consoleLog.length, dropped: dropped };
371
+ }
372
+
373
+ function snapshot(args) {
374
+ var body = document.body;
375
+ var raw = body === null ? "" : body.innerText || body.textContent || "";
376
+ return {
377
+ ping: {
378
+ href: String(window.location.href),
379
+ title: String(document.title || ""),
380
+ readyState: String(document.readyState),
381
+ width: window.innerWidth,
382
+ height: window.innerHeight,
383
+ scrollHeight: document.documentElement.scrollHeight,
384
+ scrollY: Math.round(window.scrollY),
385
+ },
386
+ text: truncate(raw, asNumber(args.maxChars, MAX_TEXT_CHARS)),
387
+ textLength: raw.length,
388
+ console: readConsole({ since: asNumber(args.consoleSince, 0), limit: asNumber(args.consoleLimit, 40) }),
389
+ };
390
+ }
391
+
392
+ function scrollPage(args) {
393
+ if (typeof args.selector === "string" && args.selector !== "") {
394
+ var el = query(args.selector, args.nth);
395
+ try { el.scrollIntoView({ block: asString(args.block, "center"), inline: "nearest" }); } catch (e) { el.scrollIntoView(); }
396
+ } else {
397
+ window.scrollTo(asNumber(args.x, window.scrollX), asNumber(args.y, window.scrollY));
398
+ }
399
+ return { scrollX: Math.round(window.scrollX), scrollY: Math.round(window.scrollY), scrollHeight: document.documentElement.scrollHeight };
400
+ }
401
+
402
+ function run(op, args) {
403
+ if (op === "ping") {
404
+ return {
405
+ href: String(window.location.href),
406
+ title: String(document.title || ""),
407
+ readyState: String(document.readyState),
408
+ width: window.innerWidth,
409
+ height: window.innerHeight,
410
+ consoleCursor: consoleSeq,
411
+ };
412
+ }
413
+ if (op === "snapshot") return snapshot(args);
414
+ if (op === "console") return readConsole(args);
415
+ if (op === "text") {
416
+ var body = document.body;
417
+ var raw = body === null ? "" : body.innerText || body.textContent || "";
418
+ return { title: String(document.title || ""), text: truncate(raw, asNumber(args.maxChars, MAX_TEXT_CHARS)), length: raw.length };
419
+ }
420
+ if (op === "inspect") {
421
+ var selector = asString(args.selector, DEFAULT_INSPECT_SELECTOR);
422
+ var limit = asNumber(args.limit, 60);
423
+ var nodes = document.querySelectorAll(selector);
424
+ var items = [];
425
+ var hidden = 0;
426
+ for (var i = 0; i < nodes.length && items.length < limit; i++) {
427
+ if (args.includeHidden !== true && !isVisible(nodes[i])) { hidden += 1; continue; }
428
+ items.push(describe(nodes[i], args.maxText));
429
+ }
430
+ return { selector: selector, matched: nodes.length, returned: items.length, hiddenSkipped: hidden, items: items };
431
+ }
432
+ if (op === "html") {
433
+ var hasSelector = typeof args.selector === "string" && args.selector !== "";
434
+ var target = hasSelector ? query(args.selector, args.nth) : document.body;
435
+ if (!target) throw fail("ELEMENT_NOT_FOUND", "cannot read html: document.body is unavailable");
436
+ var markup = target.outerHTML || "";
437
+ return { selector: hasSelector ? args.selector : "body", html: truncate(markup, asNumber(args.maxChars, MAX_HTML_CHARS)), length: markup.length };
438
+ }
439
+ if (op === "click") {
440
+ var hasTarget = typeof args.selector === "string" && args.selector !== "";
441
+ if (hasTarget) {
442
+ var el = query(args.selector, args.nth);
443
+ if (!isVisible(el)) throw fail("ELEMENT_NOT_INTERACTABLE", "element is not visible, scroll it into view first: " + args.selector);
444
+ return clickElement(el);
445
+ }
446
+ var x = asNumber(args.x, -1);
447
+ var y = asNumber(args.y, -1);
448
+ if (x < 0 || y < 0) throw fail("INVALID_ARGS", "click requires either selector or x/y");
449
+ var hit = document.elementFromPoint(x, y);
450
+ if (hit === null) throw fail("ELEMENT_NOT_FOUND", "no element at point (" + x + ", " + y + ")");
451
+ return clickElement(hit, x, y);
452
+ }
453
+ if (op === "type") {
454
+ if (typeof args.selector !== "string" || args.selector === "") {
455
+ throw fail("INVALID_ARGS", "type requires a selector");
456
+ }
457
+ var field = query(args.selector, args.nth);
458
+ var tagName = String(field.tagName || "").toLowerCase();
459
+ var editable = tagName === "input" || tagName === "textarea" || tagName === "select" || field.isContentEditable === true;
460
+ if (!editable) throw fail("ELEMENT_NOT_INTERACTABLE", "element is not a text field: " + tagName);
461
+ return typeInto(field, args.value, args.clear !== false, args.submit === true);
462
+ }
463
+ if (op === "press") {
464
+ var keyTarget = null;
465
+ if (typeof args.selector === "string" && args.selector !== "") keyTarget = query(args.selector, args.nth);
466
+ return fireKey(keyTarget, asString(args.key, "Enter"));
467
+ }
468
+ if (op === "waitFor") {
469
+ if (typeof args.selector !== "string" || args.selector === "") {
470
+ throw fail("INVALID_ARGS", "waitFor requires a selector");
471
+ }
472
+ return waitFor(args.selector, asString(args.state, "visible"), asNumber(args.timeoutMs, DEFAULT_TIMEOUT_MS));
473
+ }
474
+ if (op === "scroll") return scrollPage(args);
475
+ if (op === "screenshot") return screenshot(args);
476
+ throw fail("UNKNOWN_OP", "unknown automation op: " + op);
477
+ }
478
+
479
+ function bridgeWindow() {
480
+ if (window.parent !== window) return window.parent;
481
+ return window.opener || null;
482
+ }
483
+
484
+ window.addEventListener("message", function (event) {
485
+ var data = event.data;
486
+ if (!data || data.__adepAutomationRequest !== true) return;
487
+ var replyTo = bridgeWindow();
488
+ if (!replyTo) return;
489
+ var id = data.id;
490
+ function send(ok, payload) {
491
+ try {
492
+ replyTo.postMessage({
493
+ __adepAutomationResponse: true,
494
+ id: id,
495
+ ok: ok,
496
+ result: ok ? payload.result : null,
497
+ error: ok ? null : payload.error,
498
+ }, "*");
499
+ } catch (e) {}
500
+ }
501
+ Promise.resolve().then(function () {
502
+ return run(String(data.op || ""), data.args || {});
503
+ }).then(function (result) {
504
+ send(true, { result: result === undefined ? null : result });
505
+ }).catch(function (error) {
506
+ send(false, {
507
+ error: {
508
+ code: (error && error.code) || "AUTOMATION_FAILED",
509
+ message: error && error.message ? String(error.message) : String(error),
510
+ },
511
+ });
512
+ });
513
+ });
514
+
515
+ window.__adepAutomation = {
516
+ version: 1,
517
+ ops: ["ping", "snapshot", "text", "inspect", "html", "click", "type", "press", "waitFor", "scroll", "screenshot", "console"],
518
+ };
519
+ })();
520
+ `;
521
+
51
522
  // packages/vite-plugin/src/ide-proxy.ts
52
523
  var FN_FETCH_INTERCEPTOR_SCRIPT = `(function () {
53
524
  if (window.__adepFnProxyInstalled) return;
@@ -220,6 +691,18 @@ function injectFnProxyScriptTag(html) {
220
691
  if (html.includes("/@adep/fn-proxy.js")) return html;
221
692
  return html.replace("</head>", '<script src="/@adep/fn-proxy.js"></script></head>');
222
693
  }
694
+ function createAdepAutomationScriptMiddleware() {
695
+ return (req, res, next) => {
696
+ const pathname = (req.url ?? "").split("?")[0];
697
+ if (pathname !== "/@adep/automation.js") return next();
698
+ res.setHeader("content-type", "application/javascript; charset=utf-8");
699
+ res.end(PREVIEW_AUTOMATION_SCRIPT);
700
+ };
701
+ }
702
+ function injectAutomationScriptTag(html) {
703
+ if (html.includes("/@adep/automation.js")) return html;
704
+ return html.replace("</head>", '<script src="/@adep/automation.js"></script></head>');
705
+ }
223
706
 
224
707
  // packages/vite-plugin/src/dev-server.ts
225
708
  function adepPlugin(options = {}) {
@@ -278,6 +761,7 @@ function adepPlugin(options = {}) {
278
761
  server.middlewares.use(createAdepProxyMiddleware(prefixPath, dev.baseUrl));
279
762
  }
280
763
  server.middlewares.use(createAdepFnProxyScriptMiddleware());
764
+ server.middlewares.use(createAdepAutomationScriptMiddleware());
281
765
  };
282
766
  return {
283
767
  name: "adep-cloud-functions",
@@ -295,7 +779,7 @@ function adepPlugin(options = {}) {
295
779
  attachMiddlewares(server);
296
780
  },
297
781
  transformIndexHtml(html) {
298
- return injectFnProxyScriptTag(html);
782
+ return injectAutomationScriptTag(injectFnProxyScriptTag(html));
299
783
  },
300
784
  async closeBundle() {
301
785
  await disposeAdepDev();
@@ -305,10 +789,13 @@ function adepPlugin(options = {}) {
305
789
  var dev_server_default = adepPlugin;
306
790
  export {
307
791
  FN_FETCH_INTERCEPTOR_SCRIPT,
792
+ PREVIEW_AUTOMATION_SCRIPT,
308
793
  adepPlugin,
794
+ createAdepAutomationScriptMiddleware,
309
795
  createAdepFnProxyScriptMiddleware,
310
796
  createAdepProxyMiddleware,
311
797
  dev_server_default as default,
798
+ injectAutomationScriptTag,
312
799
  injectFnProxyScriptTag,
313
800
  isAddrInUse,
314
801
  normalizeFunctionPrefix
@@ -0,0 +1,20 @@
1
+ /**
2
+ * IDE 预览「自动化中继」脚本(`@adep/vite-plugin` 侧副本,**单一真相源**)。
3
+ *
4
+ * **同步纪律**:本字符串与 `packages/web-container/src/preview-automation.ts` 的
5
+ * `PREVIEW_AUTOMATION_SCRIPT`(浏览器内置 vite-dev 注入用)**逐字一致**——理由同
6
+ * `FN_FETCH_INTERCEPTOR_SCRIPT`:预览 iframe / 浏览器内 vite-dev 是两个独立加载上下文,
7
+ * 不共享 import,只共享文本契约。机检见
8
+ * `app/composables/__tests__/preview-inject-scripts.test.ts`(三份副本逐字比对 + 经典脚本语法 + 纯 ASCII)。
9
+ *
10
+ * 两条注入路径:
11
+ * - Nodebox:`app/composables/ide-nodebox-preview.ts` 经沙箱内注入插件(transformIndexHtml)
12
+ * 以 `<script src="/@adep/automation.js">` 注入;
13
+ * - 浏览器内置 dev server:`vite-dev.ts` 在 buildDocument 里内联进产物文档 `<head>` 第三位
14
+ * (控制台中继 → 云函数拦截器 → 本脚本);`dev-server.ts` / `@adep/cli/vite` 路径同款。
15
+ *
16
+ * ⚠️ 本文件是用生成器从 web-container 侧数组字面量转出来的(模板字面量里反斜杠必须双写,
17
+ * 手抄必然漂移)。改脚本请改 web-container 侧那份,再按同样的规则同步这里,最后跑逐字比对用例。
18
+ * 脚本正文只允许 ASCII(内联进 HTML 与 JSON.stringify 两条路都不做编码转换),故文案一律英文。
19
+ */
20
+ export declare const PREVIEW_AUTOMATION_SCRIPT = "(function () {\n if (window.__adepAutomationInstalled) return;\n window.__adepAutomationInstalled = true;\n\n var CONSOLE_LIMIT = 200;\n var DEFAULT_TIMEOUT_MS = 8000;\n var MAX_TEXT_CHARS = 12000;\n var MAX_HTML_CHARS = 20000;\n var DEFAULT_INSPECT_SELECTOR = \"a,button,input,select,textarea,img,h1,h2,h3,h4,label,[role],[data-testid]\";\n var consoleLog = [];\n var consoleSeq = 0;\n\n function fail(code, message) {\n var error = new Error(message);\n error.code = code;\n return error;\n }\n\n function asNumber(value, fallback) {\n return typeof value === \"number\" && isFinite(value) ? value : fallback;\n }\n\n function asString(value, fallback) {\n return typeof value === \"string\" ? value : fallback;\n }\n\n function truncate(value, max) {\n var text = value == null ? \"\" : String(value);\n var limit = typeof max === \"number\" && isFinite(max) && max > 0 ? Math.floor(max) : 0;\n if (limit === 0 || text.length <= limit) return text;\n return text.slice(0, limit) + \"...[truncated, total \" + text.length + \" chars]\";\n }\n\n /* ---- console / error ring buffer ---- */\n function record(level, text) {\n consoleSeq += 1;\n consoleLog.push({ seq: consoleSeq, level: level, text: truncate(text, 2000), at: Date.now() });\n if (consoleLog.length > CONSOLE_LIMIT) consoleLog.splice(0, consoleLog.length - CONSOLE_LIMIT);\n }\n\n function fmt(value) {\n try {\n if (typeof value === \"string\") return value;\n if (value === undefined) return \"undefined\";\n if (value instanceof Error) return value.stack || (value.name + \": \" + value.message);\n var seen = [];\n var json = JSON.stringify(value, function (key, val) {\n if (val instanceof Error) return val.stack || (val.name + \": \" + val.message);\n if (typeof val === \"function\") return \"[Function \" + (val.name || \"anonymous\") + \"]\";\n if (typeof val === \"bigint\") return String(val) + \"n\";\n if (val && typeof val === \"object\") {\n if (seen.indexOf(val) !== -1) return \"[Circular]\";\n seen.push(val);\n }\n return val;\n });\n return json === undefined ? String(value) : json;\n } catch (e) {\n return String(value);\n }\n }\n\n var LEVELS = { log: \"log\", info: \"info\", warn: \"warn\", error: \"error\", debug: \"log\" };\n Object.keys(LEVELS).forEach(function (name) {\n var native = console[name];\n console[name] = function () {\n try { record(LEVELS[name], Array.prototype.map.call(arguments, fmt).join(\" \")); } catch (e) {}\n native.apply(console, arguments);\n };\n });\n window.addEventListener('error', function (event) {\n var text = event.message || \"Script error\";\n if (event.filename) text += \" (\" + event.filename + \":\" + event.lineno + \":\" + event.colno + \")\";\n record(\"error\", text);\n });\n window.addEventListener('unhandledrejection', function (event) {\n var reason = event.reason;\n record(\"error\", reason && reason.stack ? String(reason.stack) : \"Unhandled rejection: \" + fmt(reason));\n });\n\n /* ---- DOM read ---- */\n function query(selector, nth) {\n var list = document.querySelectorAll(selector);\n if (list.length === 0) throw fail(\"ELEMENT_NOT_FOUND\", \"selector matched nothing: \" + selector);\n var index = asNumber(nth, 0);\n var el = list[index];\n if (!el) throw fail(\"ELEMENT_NOT_FOUND\", \"selector matched \" + list.length + \" elements; index \" + index + \" is out of range\");\n return el;\n }\n\n function isVisible(el) {\n if (!el || !el.getBoundingClientRect) return false;\n var rect = el.getBoundingClientRect();\n if (rect.width <= 0 && rect.height <= 0) return false;\n var style = window.getComputedStyle(el);\n if (style.visibility === \"hidden\" || style.display === \"none\") return false;\n if (Number(style.opacity) === 0) return false;\n return true;\n }\n\n function rectOf(el) {\n var rect = el.getBoundingClientRect();\n return {\n x: Math.round(rect.left + window.scrollX),\n y: Math.round(rect.top + window.scrollY),\n w: Math.round(rect.width),\n h: Math.round(rect.height),\n };\n }\n\n function describe(el, maxText) {\n var tag = String(el.tagName || \"\").toLowerCase();\n var text = \"\";\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") text = String(el.value || \"\");\n else text = el.textContent || \"\";\n var out = {\n tag: tag,\n testid: el.getAttribute ? el.getAttribute(\"data-testid\") : null,\n id: el.id || null,\n cls: el.className && typeof el.className === \"string\" ? el.className : null,\n role: el.getAttribute ? el.getAttribute(\"role\") : null,\n text: truncate(String(text).replace(/[\\s\\u00a0]+/g, \" \").trim(), asNumber(maxText, 120)),\n rect: rectOf(el),\n visible: isVisible(el),\n };\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") {\n out.field = el.getAttribute(\"name\") || el.getAttribute(\"placeholder\") || null;\n if (tag === \"input\") out.type = el.getAttribute(\"type\") || \"text\";\n }\n if (tag === \"a\") out.href = el.getAttribute(\"href\") || null;\n if (el.disabled === true) out.disabled = true;\n if (el.checked !== undefined && (tag === \"input\")) out.checked = el.checked === true;\n return out;\n }\n\n /* ---- DOM write ---- */\n function fireMouse(el, x, y, kind) {\n var rect = el.getBoundingClientRect();\n var clientX = typeof x === \"number\" ? x : rect.left + rect.width / 2;\n var clientY = typeof y === \"number\" ? y : rect.top + rect.height / 2;\n el.dispatchEvent(new MouseEvent(kind, {\n bubbles: true, cancelable: true, composed: true, view: window,\n clientX: clientX, clientY: clientY, button: 0,\n }));\n }\n\n function clickElement(el, x, y) {\n try { el.scrollIntoView({ block: \"center\", inline: \"center\" }); } catch (e) {}\n fireMouse(el, x, y, \"mousedown\");\n fireMouse(el, x, y, \"mouseup\");\n fireMouse(el, x, y, \"click\");\n return describe(el, 120);\n }\n\n function fireKey(el, key) {\n var target = el || document.activeElement || document.body;\n var init = { key: key, bubbles: true, cancelable: true, composed: true };\n if (key === \"Enter\") { init.keyCode = 13; init.which = 13; }\n if (key === \"Escape\") { init.keyCode = 27; init.which = 27; }\n if (key === \"Tab\") { init.keyCode = 9; init.which = 9; }\n var allowed = target.dispatchEvent(new KeyboardEvent(\"keydown\", init));\n if (allowed) target.dispatchEvent(new KeyboardEvent(\"keypress\", init));\n target.dispatchEvent(new KeyboardEvent(\"keyup\", init));\n return { key: key, target: describe(target, 80) };\n }\n\n function setNativeValue(el, value) {\n var proto = null;\n if (window.HTMLTextAreaElement && el instanceof window.HTMLTextAreaElement) proto = window.HTMLTextAreaElement.prototype;\n else if (window.HTMLSelectElement && el instanceof window.HTMLSelectElement) proto = window.HTMLSelectElement.prototype;\n else if (window.HTMLInputElement && el instanceof window.HTMLInputElement) proto = window.HTMLInputElement.prototype;\n var descriptor = proto ? Object.getOwnPropertyDescriptor(proto, \"value\") : null;\n if (descriptor && descriptor.set) descriptor.set.call(el, value);\n else el.value = value;\n }\n\n function fireInput(el) {\n el.dispatchEvent(new Event(\"input\", { bubbles: true, composed: true }));\n el.dispatchEvent(new Event(\"change\", { bubbles: true, composed: true }));\n }\n\n function typeInto(el, value, clear, submit) {\n try { el.focus(); } catch (e) {}\n if (clear) {\n setNativeValue(el, \"\");\n fireInput(el);\n }\n setNativeValue(el, clear ? String(value) : String(el.value || \"\") + String(value));\n fireInput(el);\n if (submit) fireKey(el, \"Enter\");\n return describe(el, 200);\n }\n\n function waitFor(selector, state, timeoutMs) {\n var want = state === \"hidden\" || state === \"detached\" ? state : \"visible\";\n var deadline = Date.now() + timeoutMs;\n return new Promise(function (resolve, reject) {\n function check() {\n var el = document.querySelector(selector);\n if (want === \"detached\" && el === null) return resolve({ state: want, selector: selector });\n if (want === \"hidden\" && (el === null || !isVisible(el))) return resolve({ state: want, selector: selector });\n if (want === \"visible\" && el !== null && isVisible(el)) return resolve(describe(el, 200));\n if (Date.now() >= deadline) {\n return reject(fail(\"WAIT_TIMEOUT\", \"waitFor \" + want + \" timed out after \" + timeoutMs + \"ms: \" + selector));\n }\n setTimeout(check, 60);\n }\n check();\n });\n }\n\n /* ---- screenshot (SVG foreignObject serialization, best effort) ---- */\n function collectCss() {\n var out = [];\n var sheets = document.styleSheets;\n for (var i = 0; i < sheets.length; i++) {\n var rules = null;\n try { rules = sheets[i].cssRules; } catch (e) { rules = null; }\n if (!rules) continue;\n for (var j = 0; j < rules.length; j++) out.push(rules[j].cssText);\n }\n return out.join(\"\\n\");\n }\n\n function inlineImages(clone) {\n var images = clone.querySelectorAll(\"img\");\n var jobs = [];\n for (var i = 0; i < images.length; i++) {\n (function (img) {\n var src = img.getAttribute(\"src\") || \"\";\n if (src === \"\" || src.indexOf(\"data:\") === 0) return;\n jobs.push(window.fetch(src, { credentials: \"omit\" }).then(function (res) {\n if (!res.ok) return null;\n return res.blob().then(function (blob) {\n return new Promise(function (resolve) {\n var reader = new FileReader();\n reader.onload = function () { img.setAttribute(\"src\", String(reader.result)); resolve(null); };\n reader.onerror = function () { resolve(null); };\n reader.readAsDataURL(blob);\n });\n });\n }).catch(function () { return null; }));\n })(images[i]);\n }\n return Promise.all(jobs);\n }\n\n var QUOTE = String.fromCharCode(34);\n function tag(name, pairs) {\n var out = \"<\" + name;\n for (var i = 0; i < pairs.length; i += 2) out += \" \" + pairs[i] + \"=\" + QUOTE + pairs[i + 1] + QUOTE;\n return out + \">\";\n }\n\n function screenshot(args) {\n var scale = Math.min(asNumber(args.scale, 1), 2);\n var maxHeight = asNumber(args.maxHeight, 2400);\n var width = Math.max(1, document.documentElement.clientWidth || window.innerWidth || 1024);\n var full = document.documentElement.scrollHeight || window.innerHeight || 768;\n var height = Math.max(1, Math.min(full, maxHeight));\n var clone = document.documentElement.cloneNode(true);\n var scripts = clone.querySelectorAll(\"script\");\n for (var i = 0; i < scripts.length; i++) {\n if (scripts[i].parentNode) scripts[i].parentNode.removeChild(scripts[i]);\n }\n clone.setAttribute(\"xmlns\", \"http://www.w3.org/1999/xhtml\");\n return inlineImages(clone).then(function () {\n var style = document.createElement(\"style\");\n style.textContent = collectCss();\n var head = clone.querySelector(\"head\");\n if (head) head.insertBefore(style, head.firstChild);\n else clone.insertBefore(style, clone.firstChild);\n var markup = new XMLSerializer().serializeToString(clone);\n var svg = tag(\"svg\", [\"xmlns\", \"http://www.w3.org/2000/svg\", \"width\", width, \"height\", height])\n + tag(\"foreignObject\", [\"x\", 0, \"y\", 0, \"width\", width, \"height\", height])\n + markup + \"</foreignObject></svg>\";\n var image = new Image();\n return new Promise(function (resolve, reject) {\n image.onload = function () {\n var canvas = document.createElement(\"canvas\");\n canvas.width = Math.round(width * scale);\n canvas.height = Math.round(height * scale);\n var ctx = canvas.getContext(\"2d\");\n ctx.scale(scale, scale);\n try { ctx.drawImage(image, 0, 0); } catch (e) {}\n var dataUrl = null;\n try { dataUrl = canvas.toDataURL(\"image/png\"); }\n catch (e2) {\n reject(fail(\"SCREENSHOT_TAINTED\", \"canvas is tainted by a cross-origin resource that could not be inlined; fall back to snapshot / inspect\"));\n return;\n }\n resolve({ dataUrl: dataUrl, width: canvas.width, height: canvas.height, bytes: dataUrl.length, clipped: full > height });\n };\n image.onerror = function () {\n reject(fail(\"SCREENSHOT_FAILED\", \"the browser refused to rasterize the serialized DOM\"));\n };\n image.src = \"data:image/svg+xml;charset=utf-8,\" + encodeURIComponent(svg);\n });\n });\n }\n\n /* ---- console read ---- */\n function readConsole(args) {\n var since = asNumber(args.since, 0);\n var limit = asNumber(args.limit, 60);\n var level = asString(args.level, \"\");\n var out = [];\n for (var i = 0; i < consoleLog.length; i++) {\n var entry = consoleLog[i];\n if (entry.seq <= since) continue;\n if (level !== \"\" && entry.level !== level) continue;\n out.push(entry);\n }\n var dropped = 0;\n if (out.length > limit) {\n dropped = out.length - limit;\n out = out.slice(out.length - limit);\n }\n return { entries: out, cursor: consoleSeq, total: consoleLog.length, dropped: dropped };\n }\n\n function snapshot(args) {\n var body = document.body;\n var raw = body === null ? \"\" : body.innerText || body.textContent || \"\";\n return {\n ping: {\n href: String(window.location.href),\n title: String(document.title || \"\"),\n readyState: String(document.readyState),\n width: window.innerWidth,\n height: window.innerHeight,\n scrollHeight: document.documentElement.scrollHeight,\n scrollY: Math.round(window.scrollY),\n },\n text: truncate(raw, asNumber(args.maxChars, MAX_TEXT_CHARS)),\n textLength: raw.length,\n console: readConsole({ since: asNumber(args.consoleSince, 0), limit: asNumber(args.consoleLimit, 40) }),\n };\n }\n\n function scrollPage(args) {\n if (typeof args.selector === \"string\" && args.selector !== \"\") {\n var el = query(args.selector, args.nth);\n try { el.scrollIntoView({ block: asString(args.block, \"center\"), inline: \"nearest\" }); } catch (e) { el.scrollIntoView(); }\n } else {\n window.scrollTo(asNumber(args.x, window.scrollX), asNumber(args.y, window.scrollY));\n }\n return { scrollX: Math.round(window.scrollX), scrollY: Math.round(window.scrollY), scrollHeight: document.documentElement.scrollHeight };\n }\n\n function run(op, args) {\n if (op === \"ping\") {\n return {\n href: String(window.location.href),\n title: String(document.title || \"\"),\n readyState: String(document.readyState),\n width: window.innerWidth,\n height: window.innerHeight,\n consoleCursor: consoleSeq,\n };\n }\n if (op === \"snapshot\") return snapshot(args);\n if (op === \"console\") return readConsole(args);\n if (op === \"text\") {\n var body = document.body;\n var raw = body === null ? \"\" : body.innerText || body.textContent || \"\";\n return { title: String(document.title || \"\"), text: truncate(raw, asNumber(args.maxChars, MAX_TEXT_CHARS)), length: raw.length };\n }\n if (op === \"inspect\") {\n var selector = asString(args.selector, DEFAULT_INSPECT_SELECTOR);\n var limit = asNumber(args.limit, 60);\n var nodes = document.querySelectorAll(selector);\n var items = [];\n var hidden = 0;\n for (var i = 0; i < nodes.length && items.length < limit; i++) {\n if (args.includeHidden !== true && !isVisible(nodes[i])) { hidden += 1; continue; }\n items.push(describe(nodes[i], args.maxText));\n }\n return { selector: selector, matched: nodes.length, returned: items.length, hiddenSkipped: hidden, items: items };\n }\n if (op === \"html\") {\n var hasSelector = typeof args.selector === \"string\" && args.selector !== \"\";\n var target = hasSelector ? query(args.selector, args.nth) : document.body;\n if (!target) throw fail(\"ELEMENT_NOT_FOUND\", \"cannot read html: document.body is unavailable\");\n var markup = target.outerHTML || \"\";\n return { selector: hasSelector ? args.selector : \"body\", html: truncate(markup, asNumber(args.maxChars, MAX_HTML_CHARS)), length: markup.length };\n }\n if (op === \"click\") {\n var hasTarget = typeof args.selector === \"string\" && args.selector !== \"\";\n if (hasTarget) {\n var el = query(args.selector, args.nth);\n if (!isVisible(el)) throw fail(\"ELEMENT_NOT_INTERACTABLE\", \"element is not visible, scroll it into view first: \" + args.selector);\n return clickElement(el);\n }\n var x = asNumber(args.x, -1);\n var y = asNumber(args.y, -1);\n if (x < 0 || y < 0) throw fail(\"INVALID_ARGS\", \"click requires either selector or x/y\");\n var hit = document.elementFromPoint(x, y);\n if (hit === null) throw fail(\"ELEMENT_NOT_FOUND\", \"no element at point (\" + x + \", \" + y + \")\");\n return clickElement(hit, x, y);\n }\n if (op === \"type\") {\n if (typeof args.selector !== \"string\" || args.selector === \"\") {\n throw fail(\"INVALID_ARGS\", \"type requires a selector\");\n }\n var field = query(args.selector, args.nth);\n var tagName = String(field.tagName || \"\").toLowerCase();\n var editable = tagName === \"input\" || tagName === \"textarea\" || tagName === \"select\" || field.isContentEditable === true;\n if (!editable) throw fail(\"ELEMENT_NOT_INTERACTABLE\", \"element is not a text field: \" + tagName);\n return typeInto(field, args.value, args.clear !== false, args.submit === true);\n }\n if (op === \"press\") {\n var keyTarget = null;\n if (typeof args.selector === \"string\" && args.selector !== \"\") keyTarget = query(args.selector, args.nth);\n return fireKey(keyTarget, asString(args.key, \"Enter\"));\n }\n if (op === \"waitFor\") {\n if (typeof args.selector !== \"string\" || args.selector === \"\") {\n throw fail(\"INVALID_ARGS\", \"waitFor requires a selector\");\n }\n return waitFor(args.selector, asString(args.state, \"visible\"), asNumber(args.timeoutMs, DEFAULT_TIMEOUT_MS));\n }\n if (op === \"scroll\") return scrollPage(args);\n if (op === \"screenshot\") return screenshot(args);\n throw fail(\"UNKNOWN_OP\", \"unknown automation op: \" + op);\n }\n\n function bridgeWindow() {\n if (window.parent !== window) return window.parent;\n return window.opener || null;\n }\n\n window.addEventListener(\"message\", function (event) {\n var data = event.data;\n if (!data || data.__adepAutomationRequest !== true) return;\n var replyTo = bridgeWindow();\n if (!replyTo) return;\n var id = data.id;\n function send(ok, payload) {\n try {\n replyTo.postMessage({\n __adepAutomationResponse: true,\n id: id,\n ok: ok,\n result: ok ? payload.result : null,\n error: ok ? null : payload.error,\n }, \"*\");\n } catch (e) {}\n }\n Promise.resolve().then(function () {\n return run(String(data.op || \"\"), data.args || {});\n }).then(function (result) {\n send(true, { result: result === undefined ? null : result });\n }).catch(function (error) {\n send(false, {\n error: {\n code: (error && error.code) || \"AUTOMATION_FAILED\",\n message: error && error.message ? String(error.message) : String(error),\n },\n });\n });\n });\n\n window.__adepAutomation = {\n version: 1,\n ops: [\"ping\", \"snapshot\", \"text\", \"inspect\", \"html\", \"click\", \"type\", \"press\", \"waitFor\", \"scroll\", \"screenshot\", \"console\"],\n };\n})();\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adep/vite-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "云函数 Vite 插件(IDE-030,自 CLI-014 @adep/cli/vite 抽出):vite dev 进程内启动本地 adep dev server 并代理 /{prefix}/* 到云函数;同时 serve /@adep/fn-proxy.js(Web IDE 预览 fetch 拦截器)。本地运行时与配置加载经 createDevServer / loadConfig 注入端口接入,供 CLI / Web IDE / 非 CLI 用户独立使用。",