@foldspace_npm/harness 0.1.2 → 0.1.3
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 +61 -12
- package/bin/attach.mjs +779 -107
- package/bin/cli.mjs +122 -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 +143 -0
- package/src/cli-registry.mjs +309 -0
- package/src/diagnostics.mjs +482 -0
- package/src/init.mjs +2 -2
- 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/src/protocol.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Browser-safe request/result contracts shared by local CDP and future
|
|
2
|
+
// extension adapters. This module describes evidence; it does not inspect a
|
|
3
|
+
// page or select a transport.
|
|
4
|
+
export const HARNESS_PROTOCOL_VERSION = 1;
|
|
5
|
+
|
|
6
|
+
export const CAPABILITIES = Object.freeze({
|
|
7
|
+
PAGE_EVALUATE: "page.evaluate",
|
|
8
|
+
PAGE_MUTATE: "page.mutate",
|
|
9
|
+
ARTIFACT_LOAD: "artifact.load",
|
|
10
|
+
CDP: "browser.cdp",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const ERROR_CODES = Object.freeze({
|
|
14
|
+
INVALID_REQUEST: "INVALID_REQUEST",
|
|
15
|
+
CAPABILITY_DISABLED: "CAPABILITY_DISABLED",
|
|
16
|
+
SDK_NOT_FOUND: "SDK_NOT_FOUND",
|
|
17
|
+
AGENT_NOT_FOUND: "AGENT_NOT_FOUND",
|
|
18
|
+
REGISTRATION_MISMATCH: "REGISTRATION_MISMATCH",
|
|
19
|
+
DIAGNOSTIC_FAILED: "DIAGNOSTIC_FAILED",
|
|
20
|
+
TIMEOUT: "TIMEOUT",
|
|
21
|
+
EVIDENCE_LIMIT: "EVIDENCE_LIMIT",
|
|
22
|
+
TRANSPORT_ERROR: "TRANSPORT_ERROR",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const LIFECYCLE_OPERATIONS = Object.freeze([
|
|
26
|
+
"prepare_page",
|
|
27
|
+
"load_artifact",
|
|
28
|
+
"inspect_registration",
|
|
29
|
+
"run_diagnostic",
|
|
30
|
+
"cleanup",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const PROTOCOL_LIMITS = Object.freeze({
|
|
34
|
+
maxRequestBytes: 16 * 1024,
|
|
35
|
+
maxResultBytes: 64 * 1024,
|
|
36
|
+
maxIdentifierLength: 128,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
40
|
+
|
|
41
|
+
function isRecord(value) {
|
|
42
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function jsonByteLength(value, label) {
|
|
46
|
+
let json;
|
|
47
|
+
try {
|
|
48
|
+
json = JSON.stringify(value);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new TypeError(`${label} must be JSON serializable`);
|
|
51
|
+
}
|
|
52
|
+
if (json === undefined) {
|
|
53
|
+
throw new TypeError(`${label} must be JSON serializable`);
|
|
54
|
+
}
|
|
55
|
+
return new TextEncoder().encode(json).length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertIdentifier(value, label) {
|
|
59
|
+
if (
|
|
60
|
+
typeof value !== "string" ||
|
|
61
|
+
value.length === 0 ||
|
|
62
|
+
value.length > PROTOCOL_LIMITS.maxIdentifierLength ||
|
|
63
|
+
!identifierPattern.test(value)
|
|
64
|
+
) {
|
|
65
|
+
throw new TypeError(
|
|
66
|
+
`${label} must be a non-empty identifier of at most ${PROTOCOL_LIMITS.maxIdentifierLength} characters`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createDiagnosticRequest({
|
|
72
|
+
id,
|
|
73
|
+
name,
|
|
74
|
+
args = {},
|
|
75
|
+
timeoutMs = 10_000,
|
|
76
|
+
}) {
|
|
77
|
+
assertIdentifier(id, "Diagnostic request id");
|
|
78
|
+
assertIdentifier(name, "Diagnostic name");
|
|
79
|
+
if (!isRecord(args)) {
|
|
80
|
+
throw new TypeError("Diagnostic args must be an object");
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000) {
|
|
83
|
+
throw new TypeError("Diagnostic timeoutMs must be an integer between 1 and 60000");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const request = {
|
|
87
|
+
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
88
|
+
id,
|
|
89
|
+
name,
|
|
90
|
+
args,
|
|
91
|
+
timeoutMs,
|
|
92
|
+
};
|
|
93
|
+
if (jsonByteLength(request, "Diagnostic request") > PROTOCOL_LIMITS.maxRequestBytes) {
|
|
94
|
+
throw new RangeError("Diagnostic request exceeds the protocol size limit");
|
|
95
|
+
}
|
|
96
|
+
return request;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function diagnosticSuccess(request, result, metadata = {}) {
|
|
100
|
+
if (!isRecord(metadata)) {
|
|
101
|
+
throw new TypeError("Diagnostic metadata must be an object");
|
|
102
|
+
}
|
|
103
|
+
const response = {
|
|
104
|
+
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
105
|
+
id: request.id,
|
|
106
|
+
name: request.name,
|
|
107
|
+
ok: true,
|
|
108
|
+
result,
|
|
109
|
+
metadata,
|
|
110
|
+
};
|
|
111
|
+
if (jsonByteLength(response, "Diagnostic result") > PROTOCOL_LIMITS.maxResultBytes) {
|
|
112
|
+
return diagnosticFailure(request, {
|
|
113
|
+
code: ERROR_CODES.EVIDENCE_LIMIT,
|
|
114
|
+
message: "Diagnostic result exceeded the protocol size limit",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function diagnosticFailure(request, error) {
|
|
121
|
+
const code = Object.values(ERROR_CODES).includes(error?.code)
|
|
122
|
+
? error.code
|
|
123
|
+
: ERROR_CODES.DIAGNOSTIC_FAILED;
|
|
124
|
+
const message =
|
|
125
|
+
typeof error?.message === "string" && error.message.trim()
|
|
126
|
+
? error.message.slice(0, 2_000)
|
|
127
|
+
: "Diagnostic failed";
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
131
|
+
id: request.id,
|
|
132
|
+
name: request.name,
|
|
133
|
+
ok: false,
|
|
134
|
+
error: { code, message },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createLifecycleResult({
|
|
139
|
+
operation,
|
|
140
|
+
ok,
|
|
141
|
+
state,
|
|
142
|
+
error,
|
|
143
|
+
details = {},
|
|
144
|
+
}) {
|
|
145
|
+
if (!LIFECYCLE_OPERATIONS.includes(operation)) {
|
|
146
|
+
throw new TypeError(`Unknown lifecycle operation: ${operation}`);
|
|
147
|
+
}
|
|
148
|
+
if (typeof ok !== "boolean") {
|
|
149
|
+
throw new TypeError("Lifecycle ok must be a boolean");
|
|
150
|
+
}
|
|
151
|
+
assertIdentifier(state, "Lifecycle state");
|
|
152
|
+
if (!isRecord(details)) {
|
|
153
|
+
throw new TypeError("Lifecycle details must be an object");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = {
|
|
157
|
+
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
158
|
+
operation,
|
|
159
|
+
ok,
|
|
160
|
+
state,
|
|
161
|
+
details,
|
|
162
|
+
...(ok
|
|
163
|
+
? {}
|
|
164
|
+
: {
|
|
165
|
+
error: {
|
|
166
|
+
code: Object.values(ERROR_CODES).includes(error?.code)
|
|
167
|
+
? error.code
|
|
168
|
+
: ERROR_CODES.TRANSPORT_ERROR,
|
|
169
|
+
message:
|
|
170
|
+
typeof error?.message === "string" && error.message.trim()
|
|
171
|
+
? error.message.slice(0, 2_000)
|
|
172
|
+
: "Lifecycle operation failed",
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (jsonByteLength(result, "Lifecycle result") > PROTOCOL_LIMITS.maxResultBytes) {
|
|
178
|
+
throw new RangeError("Lifecycle result exceeds the protocol size limit");
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ACTION_LOG_PREFIX } from "./action-observer.mjs";
|
|
2
|
+
|
|
3
|
+
function normalizeConsoleText(value) {
|
|
4
|
+
return String(value ?? "")
|
|
5
|
+
.replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi, "<id>")
|
|
6
|
+
.replace(/\b(?:bearer\s+)?[A-Za-z0-9_-]{40,}\b/gi, "<redacted>")
|
|
7
|
+
.replace(/([?&](?:token|key|secret|code)=)[^&\s]+/gi, "$1<redacted>")
|
|
8
|
+
.replace(/\s+/g, " ")
|
|
9
|
+
.trim()
|
|
10
|
+
.slice(0, 1_000);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseActionObservation(text) {
|
|
14
|
+
const value = String(text ?? "");
|
|
15
|
+
if (!value.startsWith(`${ACTION_LOG_PREFIX} `)) return null;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(value.slice(ACTION_LOG_PREFIX.length + 1));
|
|
18
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatActionObservation(event) {
|
|
25
|
+
const action = event.actionName ? ` ${event.actionName}` : "";
|
|
26
|
+
const duration = Number.isFinite(event.durationMs)
|
|
27
|
+
? ` ${event.durationMs}ms`
|
|
28
|
+
: "";
|
|
29
|
+
const keys =
|
|
30
|
+
Array.isArray(event.parameterKeys) && event.parameterKeys.length
|
|
31
|
+
? ` keys=[${event.parameterKeys.join(",")}]`
|
|
32
|
+
: "";
|
|
33
|
+
if (event.phase === "registration") {
|
|
34
|
+
const count = Array.isArray(event.actionNames)
|
|
35
|
+
? event.actionNames.length
|
|
36
|
+
: 0;
|
|
37
|
+
return `[actions] local artifact ${event.status} (${count} action${count === 1 ? "" : "s"})`;
|
|
38
|
+
}
|
|
39
|
+
return `[actions] ${event.source}:${event.phase}${action} ${event.status}${duration}${keys}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createSessionCollector({
|
|
43
|
+
maxErrorGroups = 50,
|
|
44
|
+
maxActionEvents = 200,
|
|
45
|
+
} = {}) {
|
|
46
|
+
const errorGroups = new Map();
|
|
47
|
+
const actionEvents = [];
|
|
48
|
+
let droppedErrorGroups = 0;
|
|
49
|
+
let droppedActionEvents = 0;
|
|
50
|
+
|
|
51
|
+
function recordConsole({ level = "log", text = "" } = {}) {
|
|
52
|
+
const action = parseActionObservation(text);
|
|
53
|
+
if (action) {
|
|
54
|
+
if (actionEvents.length >= maxActionEvents) {
|
|
55
|
+
actionEvents.shift();
|
|
56
|
+
droppedActionEvents += 1;
|
|
57
|
+
}
|
|
58
|
+
actionEvents.push(action);
|
|
59
|
+
return action;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const normalized = normalizeConsoleText(text);
|
|
63
|
+
const normalizedLevel = String(level).toLowerCase();
|
|
64
|
+
const isFailure =
|
|
65
|
+
normalizedLevel === "error" ||
|
|
66
|
+
normalizedLevel === "warning" ||
|
|
67
|
+
normalizedLevel === "warn" ||
|
|
68
|
+
/\b(?:error|failed|failure|status\s+[45]\d\d)\b/i.test(normalized);
|
|
69
|
+
if (!normalized || !isFailure) return null;
|
|
70
|
+
|
|
71
|
+
const key = `${normalizedLevel}:${normalized}`;
|
|
72
|
+
const existing = errorGroups.get(key);
|
|
73
|
+
if (existing) {
|
|
74
|
+
existing.count += 1;
|
|
75
|
+
} else if (errorGroups.size < maxErrorGroups) {
|
|
76
|
+
errorGroups.set(key, {
|
|
77
|
+
level: normalizedLevel,
|
|
78
|
+
text: normalized,
|
|
79
|
+
count: 1,
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
droppedErrorGroups += 1;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function snapshot() {
|
|
88
|
+
return {
|
|
89
|
+
actionEvents: actionEvents.map((event) => ({ ...event })),
|
|
90
|
+
errorGroups: Array.from(errorGroups.values()).map((group) => ({
|
|
91
|
+
...group,
|
|
92
|
+
})),
|
|
93
|
+
droppedActionEvents,
|
|
94
|
+
droppedErrorGroups,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { recordConsole, snapshot };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function formatSessionSummary(snapshot) {
|
|
102
|
+
const lines = [];
|
|
103
|
+
if (snapshot.actionEvents.length) {
|
|
104
|
+
lines.push(
|
|
105
|
+
`Action observations (${snapshot.actionEvents.length}${
|
|
106
|
+
snapshot.droppedActionEvents
|
|
107
|
+
? `, ${snapshot.droppedActionEvents} older dropped`
|
|
108
|
+
: ""
|
|
109
|
+
}):`,
|
|
110
|
+
);
|
|
111
|
+
for (const event of snapshot.actionEvents.slice(-20)) {
|
|
112
|
+
lines.push(` ${formatActionObservation(event)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (snapshot.errorGroups.length) {
|
|
116
|
+
lines.push(
|
|
117
|
+
`Page failures (${snapshot.errorGroups.length} group${
|
|
118
|
+
snapshot.errorGroups.length === 1 ? "" : "s"
|
|
119
|
+
}):`,
|
|
120
|
+
);
|
|
121
|
+
for (const group of snapshot.errorGroups) {
|
|
122
|
+
lines.push(
|
|
123
|
+
` [${group.level} x${group.count}] ${group.text}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (snapshot.droppedErrorGroups) {
|
|
127
|
+
lines.push(
|
|
128
|
+
` ${snapshot.droppedErrorGroups} additional failure group(s) omitted`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
@@ -8,10 +8,13 @@ changes there.
|
|
|
8
8
|
|
|
9
9
|
## Before building
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
|
|
11
|
+
1. Run `npx foldspace help --json` before an unfamiliar harness operation.
|
|
12
|
+
Treat its risk, prerequisites, effects, and next-step fields as the current
|
|
13
|
+
CLI contract.
|
|
14
|
+
2. Connect Product MCP and verify that `list_agents` works.
|
|
15
|
+
3. Inspect the selected agent with `get_agent_settings`, `list_actions`, and
|
|
13
16
|
`list_task_agents`.
|
|
14
|
-
|
|
17
|
+
4. Use `discover_actions` to identify candidate experiences, then let the user
|
|
15
18
|
choose what to build.
|
|
16
19
|
|
|
17
20
|
## Build workflow
|
|
@@ -51,12 +54,18 @@ npm run attach
|
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
`inject` launches an isolated Chrome profile and records its debug port.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Foldspace SDK.
|
|
57
|
+
It does not generate or load an application extension. `attach` prepares the
|
|
58
|
+
page and loads the local `dist/index.js` bundle through CDP.
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
Use the default swap only when the page already has the configured product and
|
|
61
|
+
agent. Use `--bootstrap` only when the page has no Foldspace SDK, and
|
|
62
|
+
`--replace` when it embeds a different product or agent, or when an SDK is
|
|
63
|
+
present without the configured agent.
|
|
64
|
+
|
|
65
|
+
The attach log must report `inspect_registration:registration_ok` before
|
|
66
|
+
treating the page as registered. Prove the experience through the visible agent
|
|
67
|
+
and require both an SDK action callback and local execute/render evidence; do
|
|
68
|
+
not invoke the handler directly.
|
|
60
69
|
|
|
61
70
|
## Verification gates
|
|
62
71
|
|
|
@@ -76,9 +85,23 @@ Do not report success without all six:
|
|
|
76
85
|
- `agent/constants.ts` — agent, product, and domain identifiers
|
|
77
86
|
- `agent/utils.ts` — Foldspace agent lookup
|
|
78
87
|
- `foldspace.dev.json` — local harness target configuration
|
|
88
|
+
- `docs/` — optional coding-agent learnings; create on demand
|
|
79
89
|
|
|
80
90
|
Do not introduce another bundler or bundle format.
|
|
81
91
|
|
|
92
|
+
## Agent learnings
|
|
93
|
+
|
|
94
|
+
Use `docs/` to record durable, repository-specific lessons so later sessions do
|
|
95
|
+
not repeat the same mistakes.
|
|
96
|
+
|
|
97
|
+
- Before similar work, read any relevant notes already in `docs/`.
|
|
98
|
+
- After non-obvious discoveries, add a short note covering verified gotchas,
|
|
99
|
+
failed approaches, design rationale, or useful verification commands.
|
|
100
|
+
- Keep notes concise and evidence-based. Create `docs/` when the first note is
|
|
101
|
+
useful; do not leave an empty directory.
|
|
102
|
+
- Do not store secrets, cookies, HAR files, browser storage, or other transient
|
|
103
|
+
session data in `docs/`.
|
|
104
|
+
|
|
82
105
|
## Safety
|
|
83
106
|
|
|
84
107
|
- Do not commit secrets, cookies, HAR files, browser storage, or
|
|
@@ -26,22 +26,45 @@ agent/
|
|
|
26
26
|
api/ one file per HTTP helper
|
|
27
27
|
constants.ts product, agent, and domain identifiers
|
|
28
28
|
utils.ts Foldspace agent lookup
|
|
29
|
+
docs/ optional notes for coding-agent learnings (create when needed)
|
|
29
30
|
foldspace.dev.json
|
|
30
31
|
CLAUDE.md
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
## Agent learnings
|
|
35
|
+
|
|
36
|
+
Coding agents may keep durable, repository-specific lessons under `docs/` so
|
|
37
|
+
later sessions do not repeat the same mistakes. Create the folder when the first
|
|
38
|
+
note is useful; do not commit an empty `docs/` directory.
|
|
39
|
+
|
|
40
|
+
Good notes cover verified gotchas, failed approaches, design rationale, and
|
|
41
|
+
useful verification commands. Keep them concise and evidence-based. Before
|
|
42
|
+
similar work, read any relevant notes in `docs/`. Do not store secrets, cookies,
|
|
43
|
+
HAR files, browser storage, or other transient session data there.
|
|
44
|
+
|
|
33
45
|
## Commands
|
|
34
46
|
|
|
35
47
|
```bash
|
|
48
|
+
npx foldspace help # discover commands, risks, and next steps
|
|
36
49
|
npm run build # create dist/index.js
|
|
37
50
|
npm run dev # rebuild dist/index.js on changes
|
|
38
51
|
npm run inject # launch the dedicated Chrome profile
|
|
39
|
-
npm run attach # load
|
|
52
|
+
npm run attach # load local actions and observe the agent over CDP
|
|
40
53
|
```
|
|
41
54
|
|
|
42
55
|
Run `inject` before `attach`. Sign in to the product in the Chrome window that
|
|
43
|
-
`inject` opens.
|
|
44
|
-
`
|
|
56
|
+
`inject` opens. `inject` does not generate an application extension; `attach`
|
|
57
|
+
loads `dist/index.js` directly through CDP.
|
|
58
|
+
|
|
59
|
+
Choose the attach mode from the state of the target page:
|
|
60
|
+
|
|
61
|
+
- Default swap: the page already has the configured product and agent.
|
|
62
|
+
- `npm run attach -- --bootstrap`: the page has no Foldspace SDK.
|
|
63
|
+
- `npm run attach -- --replace`: the page has a different product or agent, or
|
|
64
|
+
an SDK without the configured agent.
|
|
65
|
+
|
|
66
|
+
Run `npx foldspace help attach` for full requirements and safety options. Coding
|
|
67
|
+
agents can read the versioned contract with `npx foldspace help --json`.
|
|
45
68
|
|
|
46
69
|
## Add an action
|
|
47
70
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"$comment": "Generated by foldspace init. Use the bare product ID, not the EU-… SDK key.",
|
|
3
3
|
"defaultTarget": "app",
|
|
4
4
|
"sdkUrl": "https://script.eucerahive.io/web/sdk/foldspace.js",
|
|
5
|
-
"localActionsUrl": "http://localhost:3007/dist/index.js",
|
|
6
5
|
"targets": {
|
|
7
6
|
"app": {
|
|
8
7
|
"name": "App",
|
|
@@ -10,8 +9,7 @@
|
|
|
10
9
|
"productId": {{PRODUCT_ID_JSON}},
|
|
11
10
|
"agentApiName": {{AGENT_API_NAME_JSON}},
|
|
12
11
|
"hosts": {{HOSTS_JSON}},
|
|
13
|
-
"mode": "OVERLAY"
|
|
14
|
-
"loadLocally": true
|
|
12
|
+
"mode": "OVERLAY"
|
|
15
13
|
}
|
|
16
14
|
}
|
|
17
15
|
}
|
package/bin/buildExtension.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "child_process";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const projectDir = process.env.FOLDSPACE_PROJECT_DIR || process.cwd();
|
|
8
|
-
const indexPath = path.join(projectDir, "extension", "index.js");
|
|
9
|
-
|
|
10
|
-
const LOAD_LOCALLY_TRUE = "const LOAD_LOCALLY = true;";
|
|
11
|
-
const LOAD_LOCALLY_FALSE = "const LOAD_LOCALLY = false;";
|
|
12
|
-
const REMOTE_ENV_DEV = 'const REMOTE_ACTIONS_ENV = "DEV";';
|
|
13
|
-
const REMOTE_ENV_PROD = 'const REMOTE_ACTIONS_ENV = "PROD";';
|
|
14
|
-
|
|
15
|
-
function parseArgs(argv) {
|
|
16
|
-
for (let i = 0; i < argv.length; i++) {
|
|
17
|
-
if (argv[i] === "--env" && argv[i + 1]) return argv[++i];
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const env = parseArgs(process.argv.slice(2));
|
|
23
|
-
if (!env || (env !== "dev" && env !== "prod")) {
|
|
24
|
-
console.error('buildExtension: --env <dev|prod> is required');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function replaceInIndex(from, to, label) {
|
|
29
|
-
const source = fs.readFileSync(indexPath, "utf8");
|
|
30
|
-
|
|
31
|
-
if (!source.includes(from)) {
|
|
32
|
-
if (source.includes(to)) {
|
|
33
|
-
console.log(`buildExtension: ${label} already set`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
console.error(
|
|
37
|
-
`buildExtension: expected ${label} pattern not found in index.js`,
|
|
38
|
-
);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fs.writeFileSync(indexPath, source.replace(from, to), "utf8");
|
|
43
|
-
console.log(`buildExtension: ${label} → ${to}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function setLoadLocally(value) {
|
|
47
|
-
if (!fs.existsSync(indexPath)) {
|
|
48
|
-
console.error("buildExtension: extension/index.js not found");
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
const from = value ? LOAD_LOCALLY_FALSE : LOAD_LOCALLY_TRUE;
|
|
52
|
-
const to = value ? LOAD_LOCALLY_TRUE : LOAD_LOCALLY_FALSE;
|
|
53
|
-
replaceInIndex(from, to, "LOAD_LOCALLY");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function setRemoteActionsEnv(targetEnv) {
|
|
57
|
-
const from = targetEnv === "dev" ? REMOTE_ENV_PROD : REMOTE_ENV_DEV;
|
|
58
|
-
const to = targetEnv === "dev" ? REMOTE_ENV_DEV : REMOTE_ENV_PROD;
|
|
59
|
-
replaceInIndex(from, to, "REMOTE_ACTIONS_ENV");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let failed = false;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
execFileSync("tsx", ["scripts/build.ts"], {
|
|
66
|
-
cwd: projectDir,
|
|
67
|
-
stdio: "inherit",
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
setLoadLocally(false);
|
|
71
|
-
setRemoteActionsEnv(env);
|
|
72
|
-
|
|
73
|
-
execFileSync("node", ["scripts/packageExtension.mjs"], {
|
|
74
|
-
cwd: projectDir,
|
|
75
|
-
stdio: "inherit",
|
|
76
|
-
});
|
|
77
|
-
} catch {
|
|
78
|
-
failed = true;
|
|
79
|
-
} finally {
|
|
80
|
-
try {
|
|
81
|
-
setLoadLocally(true);
|
|
82
|
-
setRemoteActionsEnv("prod");
|
|
83
|
-
} catch {
|
|
84
|
-
failed = true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (failed) {
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
package/bin/packageExtension.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "child_process";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const projectDir = process.env.FOLDSPACE_PROJECT_DIR || process.cwd();
|
|
8
|
-
const projectName = path.basename(projectDir);
|
|
9
|
-
const extensionDir = path.join(projectDir, "extension");
|
|
10
|
-
const zipPath = path.join(projectDir, `${projectName}-extension.zip`);
|
|
11
|
-
|
|
12
|
-
if (!fs.existsSync(extensionDir)) {
|
|
13
|
-
console.error("packageExtension: extension/ directory not found");
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
if (fs.existsSync(zipPath)) {
|
|
19
|
-
fs.rmSync(zipPath);
|
|
20
|
-
}
|
|
21
|
-
execFileSync("zip", ["-r", zipPath, "."], {
|
|
22
|
-
cwd: extensionDir,
|
|
23
|
-
stdio: "inherit",
|
|
24
|
-
});
|
|
25
|
-
console.log(`packageExtension: created ${zipPath}`);
|
|
26
|
-
} catch {
|
|
27
|
-
console.error("packageExtension: failed to create extension.zip");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|