@foldspace_npm/harness 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -16
- package/bin/attach.mjs +779 -107
- package/bin/cli.mjs +129 -44
- package/bin/deploy.mjs +37 -33
- package/bin/inject.mjs +64 -295
- package/package.json +5 -2
- package/src/action-observer.mjs +271 -0
- package/src/attach-helpers.mjs +162 -0
- package/src/attach-preflight.mjs +332 -0
- package/src/bootstrap-script.mjs +120 -0
- package/src/cdp-request-manager.mjs +61 -0
- package/src/cli-help.mjs +147 -0
- package/src/cli-registry.mjs +310 -0
- package/src/diagnostics.mjs +482 -0
- package/src/init.mjs +20 -9
- package/src/project-config.mjs +37 -0
- package/src/protocol.mjs +181 -0
- package/src/session-summary.mjs +133 -0
- package/templates/agent-starter/CLAUDE.md +31 -8
- package/templates/agent-starter/README.md +26 -3
- package/templates/agent-starter/foldspace.dev.json +1 -3
- package/bin/buildExtension.mjs +0 -90
- package/bin/packageExtension.mjs +0 -29
package/bin/inject.mjs
CHANGED
|
@@ -2,28 +2,24 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Local Foldspace injector.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Launches an isolated Chrome profile for local CDP verification. Action
|
|
6
|
+
* loading and SDK bootstrap are owned by attach.mjs; inject does not generate
|
|
7
|
+
* or load an application extension.
|
|
8
8
|
*
|
|
9
9
|
* npm run inject # default target
|
|
10
10
|
* npm run inject -- --target app
|
|
11
11
|
* npm run inject -- --url https://app.other.com --product ABC123 --agent other-agent
|
|
12
|
-
* npm run inject -- --
|
|
13
|
-
* npm run inject -- --print # generate only, don't launch
|
|
12
|
+
* npm run inject -- --print # resolve target only, don't launch
|
|
14
13
|
*/
|
|
15
14
|
import fs from "fs";
|
|
16
15
|
import path from "path";
|
|
17
16
|
import os from "os";
|
|
18
17
|
import { spawn } from "child_process";
|
|
19
|
-
import { fileURLToPath } from "url";
|
|
20
18
|
|
|
21
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
19
|
// Resolve the CONSUMING repo, not this package. FOLDSPACE_PROJECT_DIR lets a
|
|
23
20
|
// hosted builder point the harness at a workspace it controls.
|
|
24
21
|
const root = process.env.FOLDSPACE_PROJECT_DIR || process.cwd();
|
|
25
22
|
const workDir = path.join(root, ".foldspace-dev");
|
|
26
|
-
const extDir = path.join(workDir, "extension");
|
|
27
23
|
const themeDir = path.join(workDir, "theme-extension");
|
|
28
24
|
const profileDir = path.join(workDir, "chrome-profile");
|
|
29
25
|
|
|
@@ -31,16 +27,37 @@ const profileDir = path.join(workDir, "chrome-profile");
|
|
|
31
27
|
|
|
32
28
|
function parseArgs(argv) {
|
|
33
29
|
const out = { flags: new Set() };
|
|
30
|
+
const valueOptions = new Set([
|
|
31
|
+
"--target",
|
|
32
|
+
"--url",
|
|
33
|
+
"--product",
|
|
34
|
+
"--agent",
|
|
35
|
+
"--hosts",
|
|
36
|
+
"--key",
|
|
37
|
+
"--port",
|
|
38
|
+
]);
|
|
34
39
|
for (let i = 0; i < argv.length; i++) {
|
|
35
40
|
const a = argv[i];
|
|
36
|
-
if (a === "--
|
|
37
|
-
else if (a === "--print") out.flags.add("print");
|
|
41
|
+
if (a === "--print") out.flags.add("print");
|
|
38
42
|
else if (a === "--embedded") out.flags.add("embedded");
|
|
39
|
-
else if (
|
|
43
|
+
else if (valueOptions.has(a)) {
|
|
44
|
+
if (!argv[i + 1] || argv[i + 1].startsWith("--")) {
|
|
45
|
+
throw new Error(`${a} requires a value`);
|
|
46
|
+
}
|
|
47
|
+
out[a.slice(2)] = argv[++i];
|
|
48
|
+
} else {
|
|
49
|
+
throw new Error(`unknown option '${a}'`);
|
|
50
|
+
}
|
|
40
51
|
}
|
|
41
52
|
return out;
|
|
42
53
|
}
|
|
43
|
-
|
|
54
|
+
let args;
|
|
55
|
+
try {
|
|
56
|
+
args = parseArgs(process.argv.slice(2));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`inject: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
44
61
|
|
|
45
62
|
// ---------------------------------------------------------------- config
|
|
46
63
|
|
|
@@ -62,13 +79,12 @@ if (!args.url && !base.startUrl) {
|
|
|
62
79
|
|
|
63
80
|
const startUrl = args.url || base.startUrl;
|
|
64
81
|
|
|
65
|
-
//
|
|
82
|
+
// Without explicit hosts, fail closed to the exact launch hostname. Expanding
|
|
83
|
+
// registrable domains correctly requires public-suffix data.
|
|
66
84
|
function hostsFor(urlStr, configured) {
|
|
67
85
|
if (configured && configured.length) return configured;
|
|
68
86
|
const { hostname } = new URL(urlStr);
|
|
69
|
-
|
|
70
|
-
const apex = parts.length > 2 ? parts.slice(-2).join(".") : hostname;
|
|
71
|
-
return [apex, `*.${apex}`];
|
|
87
|
+
return [hostname];
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
// A --url with no --target means we are pointing at an app the config does not
|
|
@@ -88,9 +104,7 @@ const target = {
|
|
|
88
104
|
agentApiName: args.agent || (explicitUrl ? null : base.agentApiName),
|
|
89
105
|
hosts: hostsFor(startUrl, configuredHosts),
|
|
90
106
|
mode: args.flags.has("embedded") ? "EMBEDDED" : base.mode || "OVERLAY",
|
|
91
|
-
loadLocally: args.flags.has("remote") ? false : base.loadLocally !== false,
|
|
92
107
|
overrideKey: args.key || base.overrideKey || null,
|
|
93
|
-
remoteActionsEnv: args.env || base.remoteActionsEnv || "PROD",
|
|
94
108
|
};
|
|
95
109
|
|
|
96
110
|
for (const required of ["productId", "agentApiName"]) {
|
|
@@ -100,21 +114,24 @@ for (const required of ["productId", "agentApiName"]) {
|
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
console.log(`Resolved Foldspace target:`);
|
|
118
|
+
console.log(` target ${target.name}`);
|
|
119
|
+
console.log(` agent ${target.agentApiName} (product ${target.productId})`);
|
|
120
|
+
console.log(` hosts ${target.hosts.join(", ")}`);
|
|
121
|
+
console.log(` url ${startUrl}`);
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
if (args.flags.has("print")) process.exit(0);
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------- launch
|
|
110
126
|
|
|
111
|
-
|
|
112
|
-
fs.
|
|
127
|
+
// Remove the obsolete generated application extension from older harness runs.
|
|
128
|
+
fs.rmSync(path.join(workDir, "extension"), {
|
|
129
|
+
recursive: true,
|
|
130
|
+
force: true,
|
|
131
|
+
});
|
|
113
132
|
|
|
114
133
|
// A theme-only extension, regenerated each run so it survives `rm -rf
|
|
115
|
-
// .foldspace-dev`.
|
|
116
|
-
// the window is never mistaken for the user's own Chrome. Chrome ignores
|
|
117
|
-
// hand-edited theme keys in Preferences, but honours a theme extension.
|
|
134
|
+
// .foldspace-dev`. It is cosmetic and never loads Foldspace or action code.
|
|
118
135
|
fs.rmSync(themeDir, { recursive: true, force: true });
|
|
119
136
|
fs.mkdirSync(themeDir, { recursive: true });
|
|
120
137
|
fs.writeFileSync(
|
|
@@ -142,262 +159,6 @@ fs.writeFileSync(
|
|
|
142
159
|
}, null, 2),
|
|
143
160
|
);
|
|
144
161
|
|
|
145
|
-
const manifest = {
|
|
146
|
-
manifest_version: 3,
|
|
147
|
-
name: `Foldspace Dev — ${target.name}`,
|
|
148
|
-
version: "0.0.1",
|
|
149
|
-
description: `Local Foldspace injection for ${target.agentApiName}`,
|
|
150
|
-
permissions: [
|
|
151
|
-
"scripting",
|
|
152
|
-
"activeTab",
|
|
153
|
-
"tabs",
|
|
154
|
-
"declarativeNetRequest",
|
|
155
|
-
"declarativeNetRequestWithHostAccess",
|
|
156
|
-
],
|
|
157
|
-
host_permissions: [...matches, "http://localhost/*", `https://${scriptHost}/*`],
|
|
158
|
-
declarative_net_request: {
|
|
159
|
-
rule_resources: [{ id: "csp_bypass_rules", enabled: true, path: "csp_rules.json" }],
|
|
160
|
-
},
|
|
161
|
-
...(fs.existsSync(path.join(root, "extension", "logo.png"))
|
|
162
|
-
? { icons: { 16: "logo.png", 32: "logo.png", 48: "logo.png", 128: "logo.png" } }
|
|
163
|
-
: {}),
|
|
164
|
-
background: { service_worker: "background.js", type: "module" },
|
|
165
|
-
web_accessible_resources: [{ resources: ["index.js"], matches }],
|
|
166
|
-
content_scripts: [
|
|
167
|
-
{ matches, js: ["contentScript.js"], run_at: "document_start", all_frames: false },
|
|
168
|
-
],
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// Strip CSP + frame guards on the target, and allow the target to pull from
|
|
172
|
-
// localhost and the SDK host.
|
|
173
|
-
const rules = [
|
|
174
|
-
{
|
|
175
|
-
id: 1,
|
|
176
|
-
priority: 1,
|
|
177
|
-
action: {
|
|
178
|
-
type: "modifyHeaders",
|
|
179
|
-
responseHeaders: [
|
|
180
|
-
{ operation: "remove", header: "content-security-policy" },
|
|
181
|
-
{ operation: "remove", header: "content-security-policy-report-only" },
|
|
182
|
-
{ operation: "remove", header: "x-frame-options" },
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
condition: {
|
|
186
|
-
requestDomains,
|
|
187
|
-
resourceTypes: [
|
|
188
|
-
"main_frame", "sub_frame", "stylesheet", "script", "xmlhttprequest",
|
|
189
|
-
"font", "object", "image", "media", "websocket", "other",
|
|
190
|
-
],
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
id: 2,
|
|
195
|
-
priority: 1,
|
|
196
|
-
action: {
|
|
197
|
-
type: "modifyHeaders",
|
|
198
|
-
responseHeaders: [
|
|
199
|
-
{ operation: "set", header: "access-control-allow-origin", value: new URL(startUrl).origin },
|
|
200
|
-
{ operation: "set", header: "access-control-allow-methods", value: "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH" },
|
|
201
|
-
{ operation: "set", header: "access-control-allow-headers", value: "*" },
|
|
202
|
-
{ operation: "set", header: "access-control-allow-credentials", value: "true" },
|
|
203
|
-
],
|
|
204
|
-
},
|
|
205
|
-
condition: {
|
|
206
|
-
requestDomains: ["localhost", scriptHost],
|
|
207
|
-
initiatorDomains: requestDomains,
|
|
208
|
-
resourceTypes: ["script", "xmlhttprequest", "stylesheet", "font", "image", "media", "websocket", "other"],
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
const indexJs = `// GENERATED by scripts/inject.mjs — do not edit. Target: ${target.name}
|
|
214
|
-
const LOAD_LOCALLY = ${target.loadLocally};
|
|
215
|
-
const SDK_URL = ${JSON.stringify(sdkUrl)};
|
|
216
|
-
const LOCAL_ACTIONS_URL = ${JSON.stringify(localActionsUrl)};
|
|
217
|
-
const NAMESPACE = "foldspace";
|
|
218
|
-
const PRODUCT_ID = ${JSON.stringify(target.productId)};
|
|
219
|
-
const AGENT_API_NAME = ${JSON.stringify(target.agentApiName)};
|
|
220
|
-
const REMOTE_ACTIONS_ENV = ${JSON.stringify(target.remoteActionsEnv)};
|
|
221
|
-
const MODE = ${JSON.stringify(target.mode)};
|
|
222
|
-
const OVERRIDE_KEY = ${JSON.stringify(target.overrideKey)};
|
|
223
|
-
|
|
224
|
-
function attachActions() {
|
|
225
|
-
const actions = window.__FOLDSPACE_REMOTE_ACTIONS__;
|
|
226
|
-
const agent = window.__FOLDSPACE_AGENT__;
|
|
227
|
-
if (!actions || !agent) {
|
|
228
|
-
console.error("[foldspace-dev] actions or agent missing");
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
delete window.__FOLDSPACE_REMOTE_ACTIONS__;
|
|
232
|
-
agent.addActionHandlers(actions);
|
|
233
|
-
console.log("[foldspace-dev] actions attached:", Object.keys(actions).length);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function loadRemoteActionsLocally() {
|
|
237
|
-
const script = document.createElement("script");
|
|
238
|
-
script.type = "text/javascript";
|
|
239
|
-
script.async = true;
|
|
240
|
-
script.src = LOCAL_ACTIONS_URL + "?t=" + Date.now();
|
|
241
|
-
script.onload = attachActions;
|
|
242
|
-
script.onerror = () => {
|
|
243
|
-
// Direct fetch blocked — fall back to the service worker.
|
|
244
|
-
window.addEventListener(
|
|
245
|
-
"message",
|
|
246
|
-
(event) => {
|
|
247
|
-
if (event.data.type === "INJECT_FOLDSPACE_REMOTE_ACTIONS_RESPONSE" && event.data.ok) {
|
|
248
|
-
attachActions();
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
{ once: true }
|
|
252
|
-
);
|
|
253
|
-
window.postMessage({ type: "INJECT_FOLDSPACE_REMOTE_ACTIONS", src: LOCAL_ACTIONS_URL }, "*");
|
|
254
|
-
console.warn("[foldspace-dev] direct load failed, using background fetch. Is 'npm run dev' running?");
|
|
255
|
-
};
|
|
256
|
-
document.head.appendChild(script);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// The real SDK has loaded when it has replaced the stub with its own methods.
|
|
260
|
-
// The stub only ever has q and k.
|
|
261
|
-
function sdkLoaded() {
|
|
262
|
-
return typeof window[NAMESPACE] === "function" && typeof window[NAMESPACE].agent === "function";
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function appendSdkScript() {
|
|
266
|
-
const w = window, d = document, n = NAMESPACE;
|
|
267
|
-
const k = OVERRIDE_KEY || ("EU-" + PRODUCT_ID + "-1-1");
|
|
268
|
-
w[n] = w[n] || function () { (w[n].q = w[n].q || []).push(arguments); };
|
|
269
|
-
w.__FOLD_SPACE__ = n;
|
|
270
|
-
w[n].k = k;
|
|
271
|
-
if (d.querySelector("script[data-foldspace-sdk]")) return;
|
|
272
|
-
// Wait for a real body. The SDK mounts its widget container on load, and
|
|
273
|
-
// appendChild into a null body throws inside eucera.js — the widget then
|
|
274
|
-
// never renders even though the SDK "loaded". Throwing here is deliberate:
|
|
275
|
-
// the retry loop keeps trying until the document is ready for it.
|
|
276
|
-
if (!d.body) throw new Error("document.body not ready");
|
|
277
|
-
const s = d.createElement("script");
|
|
278
|
-
s.async = true;
|
|
279
|
-
s.src = SDK_URL + "?k=" + k;
|
|
280
|
-
s.setAttribute("data-foldspace-sdk", "1");
|
|
281
|
-
const h = d.getElementsByTagName("script")[0];
|
|
282
|
-
if (h && h.parentNode) h.parentNode.insertBefore(s, h);
|
|
283
|
-
else (d.head || d.documentElement).appendChild(s);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function initFoldspaceSDK() {
|
|
287
|
-
// At document-start there may be no head and no documentElement yet, so the
|
|
288
|
-
// first append can throw. Schedule the retry BEFORE trying, or a throw here
|
|
289
|
-
// leaves the stub on window with nothing ever loading the real SDK — which
|
|
290
|
-
// looks identical to a working page until you ask for the agent.
|
|
291
|
-
let tries = 0;
|
|
292
|
-
const retry = setInterval(() => {
|
|
293
|
-
if (sdkLoaded() || ++tries > 60) { clearInterval(retry); return; }
|
|
294
|
-
tryAppendSdkScript();
|
|
295
|
-
}, 250);
|
|
296
|
-
document.addEventListener("DOMContentLoaded", tryAppendSdkScript);
|
|
297
|
-
tryAppendSdkScript();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function tryAppendSdkScript() {
|
|
301
|
-
try { appendSdkScript(); } catch (e) { /* DOM not ready yet; the retry covers it */ }
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function init() {
|
|
305
|
-
const win = window;
|
|
306
|
-
win.foldspace("when", "ready", async () => {
|
|
307
|
-
const configuration = { enableDebugLogs: true };
|
|
308
|
-
|
|
309
|
-
if (MODE === "EMBEDDED") {
|
|
310
|
-
const container = document.createElement("div");
|
|
311
|
-
container.id = "foldspace-container";
|
|
312
|
-
document.body.appendChild(container);
|
|
313
|
-
configuration.embeddedConfiguration = { container };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (!LOAD_LOCALLY) {
|
|
317
|
-
configuration.remoteActionsSettings = { enabled: true, environment: REMOTE_ACTIONS_ENV };
|
|
318
|
-
console.log("[foldspace-dev] remote actions (" + REMOTE_ACTIONS_ENV + ")");
|
|
319
|
-
} else {
|
|
320
|
-
console.log("[foldspace-dev] local actions from " + LOCAL_ACTIONS_URL);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const agent = win.foldspace.agent({ apiName: AGENT_API_NAME, mode: MODE, configuration });
|
|
324
|
-
win.__FOLDSPACE_AGENT__ = agent;
|
|
325
|
-
|
|
326
|
-
agent.on("*", (e) => {
|
|
327
|
-
if (e.eventName === "agent.ready" && LOAD_LOCALLY) {
|
|
328
|
-
// attach.mjs injects the bundle at document-start, so the global is
|
|
329
|
-
// usually already here — no network, no dev server, and it works
|
|
330
|
-
// against a browser that cannot reach this machine at all.
|
|
331
|
-
if (window.__FOLDSPACE_REMOTE_ACTIONS__) attachActions();
|
|
332
|
-
else loadRemoteActionsLocally();
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Ctrl+Shift+R reloads local actions without a page refresh.
|
|
337
|
-
window.addEventListener("keydown", (e) => {
|
|
338
|
-
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "r" && LOAD_LOCALLY) {
|
|
339
|
-
e.preventDefault();
|
|
340
|
-
console.log("[foldspace-dev] reloading actions...");
|
|
341
|
-
loadRemoteActionsLocally();
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (window.top !== window.self) {
|
|
348
|
-
// Third-party iframes (Zendesk, Chameleon, ...) must not run this.
|
|
349
|
-
} else {
|
|
350
|
-
// Guard on the loaded SDK, not on the stub: window.foldspace being truthy
|
|
351
|
-
// only means some copy of this script got as far as defining the queue.
|
|
352
|
-
if (!sdkLoaded()) initFoldspaceSDK();
|
|
353
|
-
init();
|
|
354
|
-
}
|
|
355
|
-
function addDevBadge() {
|
|
356
|
-
if (document.getElementById("foldspace-dev-badge")) return;
|
|
357
|
-
const b = document.createElement("div");
|
|
358
|
-
b.id = "foldspace-dev-badge";
|
|
359
|
-
b.textContent = "FOLDSPACE DEV \u00b7 " + AGENT_API_NAME;
|
|
360
|
-
b.style.cssText = [
|
|
361
|
-
"position:fixed", "top:0", "left:0", "z-index:2147483647",
|
|
362
|
-
"background:#3247F2", "color:#fff",
|
|
363
|
-
"font:700 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace",
|
|
364
|
-
"letter-spacing:.12em", "padding:5px 10px",
|
|
365
|
-
"border-bottom-right-radius:4px", "pointer-events:none",
|
|
366
|
-
"box-shadow:0 1px 6px rgba(0,0,0,.35)",
|
|
367
|
-
].join(";");
|
|
368
|
-
(document.body || document.documentElement).appendChild(b);
|
|
369
|
-
}
|
|
370
|
-
if (window.top === window.self) {
|
|
371
|
-
// The host app is a SPA and will re-render over us, so keep re-asserting.
|
|
372
|
-
const keepBadge = () => { if (document.body) addDevBadge(); };
|
|
373
|
-
keepBadge();
|
|
374
|
-
document.addEventListener("DOMContentLoaded", keepBadge);
|
|
375
|
-
setInterval(keepBadge, 2000);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
console.log("[foldspace-dev] injected", { agent: AGENT_API_NAME, product: PRODUCT_ID, mode: MODE });
|
|
379
|
-
`;
|
|
380
|
-
|
|
381
|
-
fs.writeFileSync(path.join(extDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
382
|
-
fs.writeFileSync(path.join(extDir, "csp_rules.json"), JSON.stringify(rules, null, 2));
|
|
383
|
-
fs.writeFileSync(path.join(extDir, "index.js"), indexJs);
|
|
384
|
-
for (const f of ["contentScript.js", "background.js"]) {
|
|
385
|
-
fs.copyFileSync(path.join(root, "extension", f), path.join(extDir, f));
|
|
386
|
-
}
|
|
387
|
-
const logoSrc = path.join(root, "extension", "logo.png");
|
|
388
|
-
if (fs.existsSync(logoSrc)) fs.copyFileSync(logoSrc, path.join(extDir, "logo.png"));
|
|
389
|
-
|
|
390
|
-
console.log(`Generated ${path.relative(root, extDir)}/`);
|
|
391
|
-
console.log(` target ${target.name}`);
|
|
392
|
-
console.log(` agent ${target.agentApiName} (product ${target.productId})`);
|
|
393
|
-
console.log(` hosts ${target.hosts.join(", ")}`);
|
|
394
|
-
console.log(` actions ${target.loadLocally ? localActionsUrl : "remote " + target.remoteActionsEnv}`);
|
|
395
|
-
console.log(` url ${startUrl}`);
|
|
396
|
-
|
|
397
|
-
if (args.flags.has("print")) process.exit(0);
|
|
398
|
-
|
|
399
|
-
// ---------------------------------------------------------------- launch
|
|
400
|
-
|
|
401
162
|
function findChrome() {
|
|
402
163
|
const candidates =
|
|
403
164
|
os.platform() === "darwin"
|
|
@@ -426,7 +187,18 @@ const debugPort = args.port || "9222";
|
|
|
426
187
|
fs.mkdirSync(workDir, { recursive: true });
|
|
427
188
|
fs.writeFileSync(
|
|
428
189
|
path.join(workDir, "state.json"),
|
|
429
|
-
JSON.stringify(
|
|
190
|
+
JSON.stringify(
|
|
191
|
+
{
|
|
192
|
+
debugPort,
|
|
193
|
+
target: targetName,
|
|
194
|
+
resolvedTarget: {
|
|
195
|
+
...target,
|
|
196
|
+
sdkUrl: cfg.sdkUrl,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
null,
|
|
200
|
+
2,
|
|
201
|
+
),
|
|
430
202
|
);
|
|
431
203
|
|
|
432
204
|
// Name the profile so this window is identifiable among other Chrome windows.
|
|
@@ -461,10 +233,10 @@ nameProfile();
|
|
|
461
233
|
const chromeArgs = [
|
|
462
234
|
`--user-data-dir=${profileDir}`,
|
|
463
235
|
`--remote-debugging-port=${debugPort}`,
|
|
464
|
-
// The
|
|
465
|
-
//
|
|
466
|
-
`--load-extension=${
|
|
467
|
-
`--disable-extensions-except=${
|
|
236
|
+
// The theme only paints the isolated window Foldspace blue. SDK and action
|
|
237
|
+
// code are loaded later by attach over CDP.
|
|
238
|
+
`--load-extension=${themeDir}`,
|
|
239
|
+
`--disable-extensions-except=${themeDir}`,
|
|
468
240
|
// Chrome 136+ dropped --load-extension. Keep the legacy switch for older
|
|
469
241
|
// builds, and enable the CDP Extensions domain so we can load it at runtime.
|
|
470
242
|
"--disable-features=DisableLoadExtensionCommandLineSwitch",
|
|
@@ -483,11 +255,8 @@ child.unref();
|
|
|
483
255
|
|
|
484
256
|
// --- theme -------------------------------------------------------------
|
|
485
257
|
//
|
|
486
|
-
// Chrome 151 ignores --load-extension
|
|
487
|
-
//
|
|
488
|
-
// extension has never actually loaded — its content script, service worker and
|
|
489
|
-
// CSP rules have all been inert. Verified 2026-08-25 by reading
|
|
490
|
-
// Default/Preferences after launch.
|
|
258
|
+
// Chrome 151 ignores --load-extension, so load the optional cosmetic theme
|
|
259
|
+
// through CDP. No application extension is generated or loaded.
|
|
491
260
|
//
|
|
492
261
|
// The CDP Extensions domain still works (that is what
|
|
493
262
|
// --enable-unsafe-extension-debugging is for), so load the theme that way.
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foldspace_npm/harness",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Build
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Build and verify portable Foldspace action artifacts against a live app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"foldspace": "bin/cli.mjs",
|
|
8
8
|
"harness": "bin/cli.mjs"
|
|
9
9
|
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/*.test.mjs"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"bin",
|
|
12
15
|
"src",
|