@walkeros/cli 2.0.0 → 2.1.0
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/CHANGELOG.md +60 -0
- package/README.md +57 -46
- package/dist/cli.js +17006 -1330
- package/dist/dev.d.ts +20 -28
- package/dist/dev.js +19 -13
- package/dist/dev.js.map +1 -1
- package/dist/examples/README.md +8 -8
- package/dist/examples/flow-complete.json +164 -1
- package/dist/examples/flow-complete.md +2 -2
- package/dist/examples/flow-selfhost-test.json +24 -0
- package/dist/index.d.ts +182 -67
- package/dist/index.js +2321 -1020
- package/dist/index.js.map +1 -1
- package/dist/runtime/main.js +16129 -912
- package/examples/README.md +8 -8
- package/examples/flow-complete.json +164 -1
- package/examples/flow-complete.md +2 -2
- package/examples/flow-selfhost-test.json +24 -0
- package/package.json +7 -8
package/dist/index.js
CHANGED
|
@@ -1,182 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x2, {
|
|
4
|
+
get: (a2, b2) => (typeof require !== "undefined" ? require : a2)[b2]
|
|
5
|
+
}) : x2)(function(x2) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x2 + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
var __esm = (fn2, res) => function __init() {
|
|
10
|
+
return fn2 && (res = (0, fn2[__getOwnPropNames(fn2)[0]])(fn2 = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
5
16
|
|
|
6
|
-
// src/core/logger.ts
|
|
17
|
+
// src/core/cli-logger.ts
|
|
7
18
|
import chalk from "chalk";
|
|
8
|
-
|
|
9
|
-
function
|
|
19
|
+
import { createLogger, Level } from "@walkeros/core";
|
|
20
|
+
function createCLILogger(options = {}) {
|
|
10
21
|
const {
|
|
11
22
|
verbose = false,
|
|
12
23
|
silent = false,
|
|
13
24
|
json = false,
|
|
14
25
|
stderr = false
|
|
15
26
|
} = options;
|
|
16
|
-
const shouldLog = !silent && !json;
|
|
17
|
-
const shouldDebug = verbose && !silent && !json;
|
|
18
27
|
const out = stderr ? console.error : console.log;
|
|
19
|
-
return {
|
|
20
|
-
log: (...args) => {
|
|
21
|
-
if (shouldLog) {
|
|
22
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
23
|
-
out(message);
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
brand: (...args) => {
|
|
27
|
-
if (shouldLog) {
|
|
28
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
29
|
-
out(chalk.hex(BRAND_COLOR)(message));
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
error: (...args) => {
|
|
33
|
-
if (!json) {
|
|
34
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
35
|
-
console.error(chalk.red(message));
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
debug: (...args) => {
|
|
39
|
-
if (shouldDebug) {
|
|
40
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
41
|
-
out(` ${message}`);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
json: (data) => {
|
|
45
|
-
if (!silent) {
|
|
46
|
-
out(JSON.stringify(data, null, 2));
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
// Backward-compatible methods (all use default terminal color per design)
|
|
50
|
-
info: (...args) => {
|
|
51
|
-
if (shouldLog) {
|
|
52
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
53
|
-
out(message);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
success: (...args) => {
|
|
57
|
-
if (shouldLog) {
|
|
58
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
59
|
-
out(message);
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
warning: (...args) => {
|
|
63
|
-
if (shouldLog) {
|
|
64
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
65
|
-
out(message);
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
warn: (...args) => {
|
|
69
|
-
if (shouldLog) {
|
|
70
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
71
|
-
out(message);
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
gray: (...args) => {
|
|
75
|
-
if (shouldLog) {
|
|
76
|
-
const message = args.map((arg) => String(arg)).join(" ");
|
|
77
|
-
out(message);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function createCommandLogger(options) {
|
|
83
28
|
return createLogger({
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
stderr: options.stderr
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// src/core/collector-logger.ts
|
|
92
|
-
function createCollectorLoggerConfig(cliLogger, verbose) {
|
|
93
|
-
return {
|
|
94
|
-
level: verbose ? "DEBUG" : "ERROR",
|
|
95
|
-
handler: (level, message, context, scope) => {
|
|
29
|
+
// Let handler control visibility — pass everything through
|
|
30
|
+
level: Level.DEBUG,
|
|
31
|
+
handler: (level, message, _context, scope) => {
|
|
96
32
|
const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} else if (verbose) {
|
|
102
|
-
cliLogger.debug(`${scopePath}${message}${contextStr}`);
|
|
33
|
+
const fullMessage = `${scopePath}${message}`;
|
|
34
|
+
if (level === Level.ERROR) {
|
|
35
|
+
if (!json) console.error(chalk.red(fullMessage));
|
|
36
|
+
return;
|
|
103
37
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let endTime = 0;
|
|
112
|
-
return {
|
|
113
|
-
start() {
|
|
114
|
-
startTime2 = Date.now();
|
|
115
|
-
endTime = 0;
|
|
116
|
-
},
|
|
117
|
-
end() {
|
|
118
|
-
endTime = Date.now();
|
|
119
|
-
return endTime - startTime2;
|
|
120
|
-
},
|
|
121
|
-
getElapsed() {
|
|
122
|
-
const currentTime = endTime || Date.now();
|
|
123
|
-
return currentTime - startTime2;
|
|
38
|
+
if (silent || json) return;
|
|
39
|
+
if (level === Level.DEBUG) {
|
|
40
|
+
if (!verbose) return;
|
|
41
|
+
out(` ${fullMessage}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
out(fullMessage);
|
|
124
45
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return (elapsed / 1e3).toFixed(2) + "s";
|
|
46
|
+
jsonHandler: (data) => {
|
|
47
|
+
if (!silent) out(JSON.stringify(data, null, 2));
|
|
128
48
|
}
|
|
129
|
-
};
|
|
49
|
+
});
|
|
130
50
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
import path from "path";
|
|
135
|
-
async function writeResult(content, options) {
|
|
136
|
-
if (options.output) {
|
|
137
|
-
const outputPath = path.resolve(options.output);
|
|
138
|
-
await fs.ensureDir(path.dirname(outputPath));
|
|
139
|
-
await fs.writeFile(outputPath, content);
|
|
140
|
-
} else {
|
|
141
|
-
process.stdout.write(content);
|
|
142
|
-
process.stdout.write("\n");
|
|
51
|
+
var init_cli_logger = __esm({
|
|
52
|
+
"src/core/cli-logger.ts"() {
|
|
53
|
+
"use strict";
|
|
143
54
|
}
|
|
144
|
-
}
|
|
145
|
-
function createJsonOutput(success, data, error, duration) {
|
|
146
|
-
return {
|
|
147
|
-
success,
|
|
148
|
-
...data && { data },
|
|
149
|
-
...error && { error },
|
|
150
|
-
...duration && { duration }
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function createSuccessOutput(data, duration) {
|
|
154
|
-
return createJsonOutput(true, data, void 0, duration);
|
|
155
|
-
}
|
|
156
|
-
function createErrorOutput(error, duration) {
|
|
157
|
-
return createJsonOutput(false, void 0, error, duration);
|
|
158
|
-
}
|
|
159
|
-
function formatBytes(bytes) {
|
|
160
|
-
return (bytes / 1024).toFixed(2);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// src/core/tmp.ts
|
|
164
|
-
import path2 from "path";
|
|
165
|
-
var DEFAULT_TMP_ROOT = ".tmp";
|
|
166
|
-
function getTmpPath(tmpDir, ...segments) {
|
|
167
|
-
const root = tmpDir || DEFAULT_TMP_ROOT;
|
|
168
|
-
const absoluteRoot = path2.isAbsolute(root) ? root : path2.resolve(root);
|
|
169
|
-
return path2.join(absoluteRoot, ...segments);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// src/core/asset-resolver.ts
|
|
173
|
-
import { fileURLToPath } from "url";
|
|
174
|
-
import { existsSync as existsSync2 } from "fs";
|
|
175
|
-
import path4 from "path";
|
|
176
|
-
|
|
177
|
-
// src/config/utils.ts
|
|
178
|
-
import fs2 from "fs-extra";
|
|
179
|
-
import path3 from "path";
|
|
55
|
+
});
|
|
180
56
|
|
|
181
57
|
// src/lib/config-file.ts
|
|
182
58
|
import {
|
|
@@ -226,6 +102,9 @@ function resolveToken() {
|
|
|
226
102
|
if (config?.token) return { token: config.token, source: "config" };
|
|
227
103
|
return null;
|
|
228
104
|
}
|
|
105
|
+
function resolveDeployToken() {
|
|
106
|
+
return process.env.WALKEROS_DEPLOY_TOKEN ?? null;
|
|
107
|
+
}
|
|
229
108
|
function resolveAppUrl() {
|
|
230
109
|
const envUrl = process.env.WALKEROS_APP_URL;
|
|
231
110
|
if (envUrl) return envUrl;
|
|
@@ -233,6 +112,11 @@ function resolveAppUrl() {
|
|
|
233
112
|
if (config?.appUrl) return config.appUrl;
|
|
234
113
|
return "https://app.walkeros.io";
|
|
235
114
|
}
|
|
115
|
+
var init_config_file = __esm({
|
|
116
|
+
"src/lib/config-file.ts"() {
|
|
117
|
+
"use strict";
|
|
118
|
+
}
|
|
119
|
+
});
|
|
236
120
|
|
|
237
121
|
// src/core/auth.ts
|
|
238
122
|
function getToken() {
|
|
@@ -252,6 +136,19 @@ async function authenticatedFetch(url, init) {
|
|
|
252
136
|
headers: { ...existingHeaders, ...authHeaders }
|
|
253
137
|
});
|
|
254
138
|
}
|
|
139
|
+
async function deployAuthenticatedFetch(url, init) {
|
|
140
|
+
const deployToken = resolveDeployToken();
|
|
141
|
+
const token = deployToken ?? getToken();
|
|
142
|
+
if (!token)
|
|
143
|
+
throw new Error(
|
|
144
|
+
"No authentication token available. Set WALKEROS_DEPLOY_TOKEN or run walkeros auth login."
|
|
145
|
+
);
|
|
146
|
+
const existingHeaders = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : Array.isArray(init?.headers) ? Object.fromEntries(init.headers) : init?.headers ?? {};
|
|
147
|
+
return fetch(url, {
|
|
148
|
+
...init,
|
|
149
|
+
headers: { ...existingHeaders, Authorization: `Bearer ${token}` }
|
|
150
|
+
});
|
|
151
|
+
}
|
|
255
152
|
function resolveBaseUrl() {
|
|
256
153
|
return resolveAppUrl();
|
|
257
154
|
}
|
|
@@ -260,8 +157,227 @@ function requireProjectId() {
|
|
|
260
157
|
if (!projectId) throw new Error("WALKEROS_PROJECT_ID not set.");
|
|
261
158
|
return projectId;
|
|
262
159
|
}
|
|
160
|
+
var init_auth = __esm({
|
|
161
|
+
"src/core/auth.ts"() {
|
|
162
|
+
"use strict";
|
|
163
|
+
init_config_file();
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// src/version.ts
|
|
168
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
169
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
170
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
171
|
+
function findPackageJson() {
|
|
172
|
+
const paths = [
|
|
173
|
+
join2(versionDirname, "../package.json"),
|
|
174
|
+
// dist/ or src/
|
|
175
|
+
join2(versionDirname, "../../package.json")
|
|
176
|
+
// src/core/ (not used, but safe)
|
|
177
|
+
];
|
|
178
|
+
for (const p2 of paths) {
|
|
179
|
+
try {
|
|
180
|
+
return readFileSync2(p2, "utf-8");
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return JSON.stringify({ version: "0.0.0" });
|
|
185
|
+
}
|
|
186
|
+
var versionFilename, versionDirname, VERSION;
|
|
187
|
+
var init_version = __esm({
|
|
188
|
+
"src/version.ts"() {
|
|
189
|
+
"use strict";
|
|
190
|
+
versionFilename = fileURLToPath2(import.meta.url);
|
|
191
|
+
versionDirname = dirname2(versionFilename);
|
|
192
|
+
VERSION = JSON.parse(findPackageJson()).version;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// src/commands/run/heartbeat.ts
|
|
197
|
+
var heartbeat_exports = {};
|
|
198
|
+
__export(heartbeat_exports, {
|
|
199
|
+
startHeartbeat: () => startHeartbeat
|
|
200
|
+
});
|
|
201
|
+
import { randomUUID } from "crypto";
|
|
202
|
+
async function startHeartbeat(options) {
|
|
203
|
+
const projectId = options.projectId ?? requireProjectId();
|
|
204
|
+
const base = resolveBaseUrl();
|
|
205
|
+
const instanceId2 = randomUUID();
|
|
206
|
+
const healthEndpoint = options.healthEndpoint ?? "/health";
|
|
207
|
+
const intervalSec = options.heartbeatInterval ?? 60;
|
|
208
|
+
const log = createCLILogger();
|
|
209
|
+
const startTime = Date.now();
|
|
210
|
+
const heartbeatUrl = `${base}/api/projects/${projectId}/deployments/${options.deployment}/heartbeat`;
|
|
211
|
+
const initResponse = await deployAuthenticatedFetch(heartbeatUrl, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: { "Content-Type": "application/json" },
|
|
214
|
+
body: JSON.stringify({
|
|
215
|
+
url: options.url,
|
|
216
|
+
healthEndpoint,
|
|
217
|
+
instanceId: instanceId2,
|
|
218
|
+
cliVersion: VERSION
|
|
219
|
+
})
|
|
220
|
+
});
|
|
221
|
+
if (!initResponse.ok) {
|
|
222
|
+
const err = await initResponse.json().catch(() => ({}));
|
|
223
|
+
throw new Error(
|
|
224
|
+
err.error?.message || `Initial heartbeat failed (${initResponse.status})`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const initData = await initResponse.json();
|
|
228
|
+
log.info(
|
|
229
|
+
`Registered as ${instanceId2} on deployment ${options.deployment} (${initData.deploymentId})`
|
|
230
|
+
);
|
|
231
|
+
const heartbeatTimer = setInterval(async () => {
|
|
232
|
+
try {
|
|
233
|
+
const resp = await deployAuthenticatedFetch(heartbeatUrl, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
236
|
+
body: JSON.stringify({
|
|
237
|
+
instanceId: instanceId2,
|
|
238
|
+
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
239
|
+
cliVersion: VERSION,
|
|
240
|
+
metadata: {
|
|
241
|
+
nodeVersion: process.version,
|
|
242
|
+
platform: process.platform
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
if (resp.ok) {
|
|
247
|
+
const data = await resp.json();
|
|
248
|
+
if (data.action === "update" && data.bundleUrl) {
|
|
249
|
+
log.info(
|
|
250
|
+
`Update available: version ${data.versionNumber}, downloading from ${data.bundleUrl}`
|
|
251
|
+
);
|
|
252
|
+
} else if (data.action === "stop") {
|
|
253
|
+
log.info("Received stop signal from server, shutting down...");
|
|
254
|
+
await cleanup();
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
log.error(
|
|
260
|
+
`Heartbeat failed: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}, intervalSec * 1e3);
|
|
264
|
+
const cleanup = async () => {
|
|
265
|
+
clearInterval(heartbeatTimer);
|
|
266
|
+
try {
|
|
267
|
+
await deployAuthenticatedFetch(heartbeatUrl, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "Content-Type": "application/json" },
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
instanceId: instanceId2,
|
|
272
|
+
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
273
|
+
shutting_down: true
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
process.on("SIGTERM", async () => {
|
|
280
|
+
await cleanup();
|
|
281
|
+
process.exit(0);
|
|
282
|
+
});
|
|
283
|
+
process.on("SIGINT", async () => {
|
|
284
|
+
await cleanup();
|
|
285
|
+
process.exit(0);
|
|
286
|
+
});
|
|
287
|
+
return { instanceId: instanceId2, deploymentId: initData.deploymentId, cleanup };
|
|
288
|
+
}
|
|
289
|
+
var init_heartbeat = __esm({
|
|
290
|
+
"src/commands/run/heartbeat.ts"() {
|
|
291
|
+
"use strict";
|
|
292
|
+
init_auth();
|
|
293
|
+
init_cli_logger();
|
|
294
|
+
init_version();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// src/commands/bundle/index.ts
|
|
299
|
+
init_cli_logger();
|
|
300
|
+
import path10 from "path";
|
|
301
|
+
import fs10 from "fs-extra";
|
|
302
|
+
import { getPlatform as getPlatform2 } from "@walkeros/core";
|
|
303
|
+
|
|
304
|
+
// src/core/index.ts
|
|
305
|
+
init_cli_logger();
|
|
306
|
+
|
|
307
|
+
// src/core/timer.ts
|
|
308
|
+
function createTimer() {
|
|
309
|
+
let startTime = 0;
|
|
310
|
+
let endTime = 0;
|
|
311
|
+
return {
|
|
312
|
+
start() {
|
|
313
|
+
startTime = Date.now();
|
|
314
|
+
endTime = 0;
|
|
315
|
+
},
|
|
316
|
+
end() {
|
|
317
|
+
endTime = Date.now();
|
|
318
|
+
return endTime - startTime;
|
|
319
|
+
},
|
|
320
|
+
getElapsed() {
|
|
321
|
+
const currentTime = endTime || Date.now();
|
|
322
|
+
return currentTime - startTime;
|
|
323
|
+
},
|
|
324
|
+
format() {
|
|
325
|
+
const elapsed = this.getElapsed();
|
|
326
|
+
return (elapsed / 1e3).toFixed(2) + "s";
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/core/output.ts
|
|
332
|
+
import fs from "fs-extra";
|
|
333
|
+
import path from "path";
|
|
334
|
+
async function writeResult(content, options) {
|
|
335
|
+
if (options.output) {
|
|
336
|
+
const outputPath = path.resolve(options.output);
|
|
337
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
338
|
+
await fs.writeFile(outputPath, content);
|
|
339
|
+
} else {
|
|
340
|
+
process.stdout.write(content);
|
|
341
|
+
process.stdout.write("\n");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function createJsonOutput(success, data, error, duration) {
|
|
345
|
+
return {
|
|
346
|
+
success,
|
|
347
|
+
...data && { data },
|
|
348
|
+
...error && { error },
|
|
349
|
+
...duration && { duration }
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function createSuccessOutput(data, duration) {
|
|
353
|
+
return createJsonOutput(true, data, void 0, duration);
|
|
354
|
+
}
|
|
355
|
+
function createErrorOutput(error, duration) {
|
|
356
|
+
return createJsonOutput(false, void 0, error, duration);
|
|
357
|
+
}
|
|
358
|
+
function formatBytes(bytes) {
|
|
359
|
+
return (bytes / 1024).toFixed(2);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/core/tmp.ts
|
|
363
|
+
import os from "os";
|
|
364
|
+
import path2 from "path";
|
|
365
|
+
var DEFAULT_TMP_ROOT = os.tmpdir();
|
|
366
|
+
function getTmpPath(tmpDir, ...segments) {
|
|
367
|
+
const root = tmpDir || DEFAULT_TMP_ROOT;
|
|
368
|
+
const absoluteRoot = path2.isAbsolute(root) ? root : path2.resolve(root);
|
|
369
|
+
return path2.join(absoluteRoot, ...segments);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/core/asset-resolver.ts
|
|
373
|
+
import { fileURLToPath } from "url";
|
|
374
|
+
import { existsSync as existsSync2 } from "fs";
|
|
375
|
+
import path4 from "path";
|
|
263
376
|
|
|
264
377
|
// src/config/utils.ts
|
|
378
|
+
import fs2 from "fs-extra";
|
|
379
|
+
import path3 from "path";
|
|
380
|
+
init_auth();
|
|
265
381
|
function isUrl(str) {
|
|
266
382
|
try {
|
|
267
383
|
const url = new URL(str);
|
|
@@ -467,6 +583,7 @@ async function copyLocalPackage(localPkg, targetDir, logger2) {
|
|
|
467
583
|
|
|
468
584
|
// src/core/input-detector.ts
|
|
469
585
|
import fs4 from "fs-extra";
|
|
586
|
+
init_auth();
|
|
470
587
|
async function detectInput(inputPath, platformOverride) {
|
|
471
588
|
const content = await loadContent(inputPath);
|
|
472
589
|
try {
|
|
@@ -508,6 +625,32 @@ async function readStdin() {
|
|
|
508
625
|
return content;
|
|
509
626
|
}
|
|
510
627
|
|
|
628
|
+
// src/core/index.ts
|
|
629
|
+
init_auth();
|
|
630
|
+
|
|
631
|
+
// src/core/sse.ts
|
|
632
|
+
function parseSSEEvents(buffer) {
|
|
633
|
+
const events = [];
|
|
634
|
+
const blocks = buffer.split("\n\n");
|
|
635
|
+
const remainder = blocks.pop() || "";
|
|
636
|
+
for (const block of blocks) {
|
|
637
|
+
if (!block.trim()) continue;
|
|
638
|
+
let eventType = "message";
|
|
639
|
+
const dataLines = [];
|
|
640
|
+
for (const line of block.split("\n")) {
|
|
641
|
+
if (line.startsWith("event:")) {
|
|
642
|
+
eventType = line.slice(6).trim();
|
|
643
|
+
} else if (line.startsWith("data:")) {
|
|
644
|
+
dataLines.push(line.slice(5).trimStart());
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (dataLines.length > 0) {
|
|
648
|
+
events.push({ type: eventType, data: dataLines.join("\n") });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { parsed: events, remainder };
|
|
652
|
+
}
|
|
653
|
+
|
|
511
654
|
// src/config/validators.ts
|
|
512
655
|
import { schemas } from "@walkeros/core/dev";
|
|
513
656
|
var { safeParseSetup } = schemas;
|
|
@@ -518,8 +661,8 @@ function validateFlowSetup(data) {
|
|
|
518
661
|
const result = safeParseSetup(data);
|
|
519
662
|
if (!result.success) {
|
|
520
663
|
const errors = result.error.issues.map((issue) => {
|
|
521
|
-
const
|
|
522
|
-
return ` - ${
|
|
664
|
+
const path14 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
665
|
+
return ` - ${path14}: ${issue.message}`;
|
|
523
666
|
}).join("\n");
|
|
524
667
|
throw new Error(`Invalid configuration:
|
|
525
668
|
${errors}`);
|
|
@@ -569,13 +712,16 @@ function loadBundleConfig(rawConfig, options) {
|
|
|
569
712
|
const setup = validateFlowSetup(rawConfig);
|
|
570
713
|
const availableFlows = getAvailableFlows(setup);
|
|
571
714
|
const flowName = resolveFlow(setup, options.flowName, availableFlows);
|
|
572
|
-
|
|
715
|
+
let flowConfig = getFlowConfig(setup, flowName, { deferred: true });
|
|
573
716
|
const platform = getPlatform(flowConfig);
|
|
574
717
|
if (!platform) {
|
|
575
718
|
throw new Error(
|
|
576
719
|
`Invalid configuration: flow "${flowName}" must have a "web" or "server" key.`
|
|
577
720
|
);
|
|
578
721
|
}
|
|
722
|
+
if (platform === "web") {
|
|
723
|
+
flowConfig = getFlowConfig(setup, flowName);
|
|
724
|
+
}
|
|
579
725
|
const buildDefaults = getBuildDefaults(platform);
|
|
580
726
|
const packages = flowConfig.packages || {};
|
|
581
727
|
const output = options.buildOverrides?.output || getDefaultOutput(platform);
|
|
@@ -651,7 +797,7 @@ async function loadFlowConfig(configPath, options) {
|
|
|
651
797
|
import esbuild from "esbuild";
|
|
652
798
|
import path9 from "path";
|
|
653
799
|
import fs8 from "fs-extra";
|
|
654
|
-
import { packageNameToVariable } from "@walkeros/core";
|
|
800
|
+
import { packageNameToVariable, ENV_MARKER_PREFIX } from "@walkeros/core";
|
|
655
801
|
|
|
656
802
|
// src/commands/bundle/package-manager.ts
|
|
657
803
|
import pacote from "pacote";
|
|
@@ -691,10 +837,15 @@ async function getFlowConfigCacheKey(content, date) {
|
|
|
691
837
|
// src/commands/bundle/package-manager.ts
|
|
692
838
|
var PACKAGE_DOWNLOAD_TIMEOUT_MS = 6e4;
|
|
693
839
|
async function withTimeout(promise, ms, errorMessage) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
840
|
+
let timer;
|
|
841
|
+
const timeout = new Promise((_2, reject) => {
|
|
842
|
+
timer = setTimeout(() => reject(new Error(errorMessage)), ms);
|
|
843
|
+
});
|
|
844
|
+
try {
|
|
845
|
+
return await Promise.race([promise, timeout]);
|
|
846
|
+
} finally {
|
|
847
|
+
clearTimeout(timer);
|
|
848
|
+
}
|
|
698
849
|
}
|
|
699
850
|
function getPackageDirectory(baseDir, packageName, version) {
|
|
700
851
|
return path7.join(baseDir, "node_modules", packageName);
|
|
@@ -726,7 +877,7 @@ function validateNoDuplicatePackages(packages) {
|
|
|
726
877
|
if (conflicts.length > 0) {
|
|
727
878
|
throw new Error(
|
|
728
879
|
`Version conflicts detected:
|
|
729
|
-
${conflicts.map((
|
|
880
|
+
${conflicts.map((c2) => ` - ${c2}`).join("\n")}
|
|
730
881
|
|
|
731
882
|
Each package must use the same version across all declarations. Please update your configuration to use consistent versions.`
|
|
732
883
|
);
|
|
@@ -762,7 +913,7 @@ async function downloadPackages(packages, targetDir, logger2, useCache = true, c
|
|
|
762
913
|
const packagePaths = /* @__PURE__ */ new Map();
|
|
763
914
|
const downloadQueue = [...packages];
|
|
764
915
|
const processed = /* @__PURE__ */ new Set();
|
|
765
|
-
const userSpecifiedPackages = new Set(packages.map((
|
|
916
|
+
const userSpecifiedPackages = new Set(packages.map((p2) => p2.name));
|
|
766
917
|
const localPackageMap = /* @__PURE__ */ new Map();
|
|
767
918
|
for (const pkg of packages) {
|
|
768
919
|
if (pkg.path) {
|
|
@@ -970,7 +1121,7 @@ function validateFlowConfig(flowConfig, logger2) {
|
|
|
970
1121
|
const sources = flowConfig.sources || {};
|
|
971
1122
|
for (const [sourceId, source] of Object.entries(sources)) {
|
|
972
1123
|
if (source && typeof source === "object" && source.code === true) {
|
|
973
|
-
logger2.
|
|
1124
|
+
logger2.warn(
|
|
974
1125
|
`DEPRECATED: Source "${sourceId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a source package instead.`
|
|
975
1126
|
);
|
|
976
1127
|
hasDeprecatedCodeTrue = true;
|
|
@@ -979,7 +1130,7 @@ function validateFlowConfig(flowConfig, logger2) {
|
|
|
979
1130
|
const destinations = flowConfig.destinations || {};
|
|
980
1131
|
for (const [destId, dest] of Object.entries(destinations)) {
|
|
981
1132
|
if (dest && typeof dest === "object" && dest.code === true) {
|
|
982
|
-
logger2.
|
|
1133
|
+
logger2.warn(
|
|
983
1134
|
`DEPRECATED: Destination "${destId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a destination package instead.`
|
|
984
1135
|
);
|
|
985
1136
|
hasDeprecatedCodeTrue = true;
|
|
@@ -988,14 +1139,14 @@ function validateFlowConfig(flowConfig, logger2) {
|
|
|
988
1139
|
const transformers = flowConfig.transformers || {};
|
|
989
1140
|
for (const [transformerId, transformer] of Object.entries(transformers)) {
|
|
990
1141
|
if (transformer && typeof transformer === "object" && transformer.code === true) {
|
|
991
|
-
logger2.
|
|
1142
|
+
logger2.warn(
|
|
992
1143
|
`DEPRECATED: Transformer "${transformerId}" uses code: true which is no longer supported. Use $code: prefix in config values or create a transformer package instead.`
|
|
993
1144
|
);
|
|
994
1145
|
hasDeprecatedCodeTrue = true;
|
|
995
1146
|
}
|
|
996
1147
|
}
|
|
997
1148
|
if (hasDeprecatedCodeTrue) {
|
|
998
|
-
logger2.
|
|
1149
|
+
logger2.warn(
|
|
999
1150
|
`See https://www.elbwalker.com/docs/walkeros/getting-started/flow for migration guide.`
|
|
1000
1151
|
);
|
|
1001
1152
|
}
|
|
@@ -1005,7 +1156,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
|
|
|
1005
1156
|
const bundleStartTime = Date.now();
|
|
1006
1157
|
const hasDeprecatedFeatures = validateFlowConfig(flowConfig, logger2);
|
|
1007
1158
|
if (hasDeprecatedFeatures) {
|
|
1008
|
-
logger2.
|
|
1159
|
+
logger2.warn("Skipping deprecated code: true entries from bundle.");
|
|
1009
1160
|
}
|
|
1010
1161
|
const TEMP_DIR = buildOptions.tempDir || getTmpPath();
|
|
1011
1162
|
if (buildOptions.cache !== false) {
|
|
@@ -1020,7 +1171,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
|
|
|
1020
1171
|
await fs8.writeFile(outputPath, cachedBuild);
|
|
1021
1172
|
const stats = await fs8.stat(outputPath);
|
|
1022
1173
|
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
1023
|
-
logger2.
|
|
1174
|
+
logger2.info(`Output: ${outputPath} (${sizeKB} KB, cached)`);
|
|
1024
1175
|
if (showStats) {
|
|
1025
1176
|
const stats2 = await fs8.stat(outputPath);
|
|
1026
1177
|
const packageStats = Object.entries(buildOptions.packages).map(
|
|
@@ -1030,11 +1181,14 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
|
|
|
1030
1181
|
// Size estimation not available for cached builds
|
|
1031
1182
|
})
|
|
1032
1183
|
);
|
|
1184
|
+
const hasWildcardImports = /import\s+\*\s+as\s+\w+\s+from/.test(
|
|
1185
|
+
buildOptions.code || ""
|
|
1186
|
+
);
|
|
1033
1187
|
return {
|
|
1034
1188
|
totalSize: stats2.size,
|
|
1035
1189
|
packages: packageStats,
|
|
1036
1190
|
buildTime: Date.now() - bundleStartTime,
|
|
1037
|
-
treeshakingEffective:
|
|
1191
|
+
treeshakingEffective: !hasWildcardImports
|
|
1038
1192
|
};
|
|
1039
1193
|
}
|
|
1040
1194
|
return;
|
|
@@ -1123,7 +1277,7 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
|
|
|
1123
1277
|
const outputStats = await fs8.stat(outputPath);
|
|
1124
1278
|
const sizeKB = (outputStats.size / 1024).toFixed(1);
|
|
1125
1279
|
const buildTime = ((Date.now() - bundleStartTime) / 1e3).toFixed(1);
|
|
1126
|
-
logger2.
|
|
1280
|
+
logger2.info(`Output: ${outputPath} (${sizeKB} KB, ${buildTime}s)`);
|
|
1127
1281
|
if (buildOptions.cache !== false) {
|
|
1128
1282
|
const configContent = generateCacheKeyContent(flowConfig, buildOptions);
|
|
1129
1283
|
const buildOutput = await fs8.readFile(outputPath, "utf-8");
|
|
@@ -1153,10 +1307,10 @@ async function bundleCore(flowConfig, buildOptions, logger2, showStats = false)
|
|
|
1153
1307
|
throw error;
|
|
1154
1308
|
}
|
|
1155
1309
|
}
|
|
1156
|
-
async function collectBundleStats(outputPath, packages,
|
|
1310
|
+
async function collectBundleStats(outputPath, packages, startTime, entryContent) {
|
|
1157
1311
|
const stats = await fs8.stat(outputPath);
|
|
1158
1312
|
const totalSize = stats.size;
|
|
1159
|
-
const buildTime = Date.now() -
|
|
1313
|
+
const buildTime = Date.now() - startTime;
|
|
1160
1314
|
const packageStats = Object.entries(packages).map(([name, pkg]) => {
|
|
1161
1315
|
const importPattern = new RegExp(`from\\s+['"]${name}['"]`, "g");
|
|
1162
1316
|
const namedImportPattern = new RegExp(
|
|
@@ -1428,9 +1582,9 @@ async function createEntryPoint(flowConfig, buildOptions, packagePaths) {
|
|
|
1428
1582
|
);
|
|
1429
1583
|
const importsCode = importStatements.join("\n");
|
|
1430
1584
|
const hasFlow = Object.values(flowConfig.sources || {}).some(
|
|
1431
|
-
(
|
|
1585
|
+
(s2) => s2.package || isInlineCode(s2.code)
|
|
1432
1586
|
) || Object.values(flowConfig.destinations || {}).some(
|
|
1433
|
-
(
|
|
1587
|
+
(d2) => d2.package || isInlineCode(d2.code)
|
|
1434
1588
|
);
|
|
1435
1589
|
if (!hasFlow) {
|
|
1436
1590
|
const userCode = buildOptions.code || "";
|
|
@@ -1582,12 +1736,42 @@ function serializeWithCode(value, indent) {
|
|
|
1582
1736
|
if (value.startsWith("$code:")) {
|
|
1583
1737
|
return value.slice(6);
|
|
1584
1738
|
}
|
|
1739
|
+
const esc = ENV_MARKER_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1740
|
+
const markerRe = new RegExp(
|
|
1741
|
+
esc + "([a-zA-Z_][a-zA-Z0-9_]*)(?::((?:(?!" + esc + `)[^\\s"'])*))?`,
|
|
1742
|
+
"g"
|
|
1743
|
+
);
|
|
1744
|
+
if (markerRe.test(value)) {
|
|
1745
|
+
markerRe.lastIndex = 0;
|
|
1746
|
+
const pureRe = new RegExp(
|
|
1747
|
+
"^" + esc + "([a-zA-Z_][a-zA-Z0-9_]*)(?::((?:(?!" + esc + `)[^\\s"'])*))?$`
|
|
1748
|
+
);
|
|
1749
|
+
const pureMatch = value.match(pureRe);
|
|
1750
|
+
if (pureMatch) {
|
|
1751
|
+
const [, name, defaultValue] = pureMatch;
|
|
1752
|
+
return defaultValue !== void 0 ? `process.env[${JSON.stringify(name)}] ?? ${JSON.stringify(defaultValue)}` : `process.env[${JSON.stringify(name)}]`;
|
|
1753
|
+
}
|
|
1754
|
+
const segments = [];
|
|
1755
|
+
let lastIndex = 0;
|
|
1756
|
+
markerRe.lastIndex = 0;
|
|
1757
|
+
let m2;
|
|
1758
|
+
while ((m2 = markerRe.exec(value)) !== null) {
|
|
1759
|
+
const staticPart = value.slice(lastIndex, m2.index).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$(?!{)/g, "\\$");
|
|
1760
|
+
const [, name, defaultValue] = m2;
|
|
1761
|
+
const envExpr = defaultValue !== void 0 ? `\${process.env[${JSON.stringify(name)}] ?? ${JSON.stringify(defaultValue)}}` : `\${process.env[${JSON.stringify(name)}]}`;
|
|
1762
|
+
segments.push(staticPart + envExpr);
|
|
1763
|
+
lastIndex = m2.index + m2[0].length;
|
|
1764
|
+
}
|
|
1765
|
+
const trailing = value.slice(lastIndex).replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$(?!{)/g, "\\$");
|
|
1766
|
+
segments.push(trailing);
|
|
1767
|
+
return "`" + segments.join("") + "`";
|
|
1768
|
+
}
|
|
1585
1769
|
return JSON.stringify(value);
|
|
1586
1770
|
}
|
|
1587
1771
|
if (Array.isArray(value)) {
|
|
1588
1772
|
if (value.length === 0) return "[]";
|
|
1589
1773
|
const items = value.map(
|
|
1590
|
-
(
|
|
1774
|
+
(v2) => nextSpaces + serializeWithCode(v2, indent + 1)
|
|
1591
1775
|
);
|
|
1592
1776
|
return `[
|
|
1593
1777
|
${items.join(",\n")}
|
|
@@ -1597,7 +1781,7 @@ ${spaces}]`;
|
|
|
1597
1781
|
const entries = Object.entries(value);
|
|
1598
1782
|
if (entries.length === 0) return "{}";
|
|
1599
1783
|
const props = entries.map(
|
|
1600
|
-
([
|
|
1784
|
+
([k2, v2]) => `${nextSpaces}${JSON.stringify(k2)}: ${serializeWithCode(v2, indent + 1)}`
|
|
1601
1785
|
);
|
|
1602
1786
|
return `{
|
|
1603
1787
|
${props.join(",\n")}
|
|
@@ -1637,11 +1821,56 @@ function generatePlatformWrapper(configObject, userCode, buildOptions) {
|
|
|
1637
1821
|
config.logger = { ...config.logger, ...context.logger };
|
|
1638
1822
|
}
|
|
1639
1823
|
|
|
1640
|
-
|
|
1824
|
+
// When runner provides external server, strip port from sources
|
|
1825
|
+
// so they don't self-listen (runner owns the port)
|
|
1826
|
+
if (context.externalServer && config.sources) {
|
|
1827
|
+
for (const src of Object.values(config.sources)) {
|
|
1828
|
+
if (src.config && src.config.settings && 'port' in src.config.settings) {
|
|
1829
|
+
delete src.config.settings.port;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const result = await startFlow(config);
|
|
1835
|
+
|
|
1836
|
+
// Discover httpHandler from initialized sources (duck-typing, no source-type coupling)
|
|
1837
|
+
const httpSource = Object.values(result.collector.sources || {})
|
|
1838
|
+
.find(s => 'httpHandler' in s && typeof s.httpHandler === 'function');
|
|
1839
|
+
|
|
1840
|
+
return { ...result, httpHandler: httpSource ? httpSource.httpHandler : undefined };
|
|
1641
1841
|
}`;
|
|
1642
1842
|
}
|
|
1643
1843
|
}
|
|
1644
1844
|
|
|
1845
|
+
// src/commands/bundle/upload.ts
|
|
1846
|
+
import fs9 from "fs-extra";
|
|
1847
|
+
function sanitizeUrl(url) {
|
|
1848
|
+
return url.split("?")[0];
|
|
1849
|
+
}
|
|
1850
|
+
async function uploadBundleToUrl(filePath, url, timeoutMs = 3e4) {
|
|
1851
|
+
const bundleContent = await fs9.readFile(filePath);
|
|
1852
|
+
const doUpload = async (attempt) => {
|
|
1853
|
+
const response = await fetch(url, {
|
|
1854
|
+
method: "PUT",
|
|
1855
|
+
body: bundleContent,
|
|
1856
|
+
headers: {
|
|
1857
|
+
"Content-Type": "application/javascript",
|
|
1858
|
+
"Content-Length": String(bundleContent.length)
|
|
1859
|
+
},
|
|
1860
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1861
|
+
});
|
|
1862
|
+
if (response.status >= 500 && attempt === 1) {
|
|
1863
|
+
return doUpload(2);
|
|
1864
|
+
}
|
|
1865
|
+
if (!response.ok) {
|
|
1866
|
+
throw new Error(
|
|
1867
|
+
`Upload failed: ${response.status} ${response.statusText}`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
await doUpload(1);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1645
1874
|
// src/commands/bundle/stats.ts
|
|
1646
1875
|
function displayStats(stats, logger2) {
|
|
1647
1876
|
logger2.info("\n\u{1F4CA} Bundle Statistics");
|
|
@@ -1666,6 +1895,7 @@ Package Breakdown:`);
|
|
|
1666
1895
|
}
|
|
1667
1896
|
|
|
1668
1897
|
// src/core/api-client.ts
|
|
1898
|
+
init_auth();
|
|
1669
1899
|
import createClient from "openapi-fetch";
|
|
1670
1900
|
function createApiClient() {
|
|
1671
1901
|
const token = getToken();
|
|
@@ -1693,7 +1923,7 @@ async function bundleCommand(options) {
|
|
|
1693
1923
|
const timer = createTimer();
|
|
1694
1924
|
timer.start();
|
|
1695
1925
|
const writingToStdout = !options.output;
|
|
1696
|
-
const logger2 =
|
|
1926
|
+
const logger2 = createCLILogger({
|
|
1697
1927
|
...options,
|
|
1698
1928
|
stderr: writingToStdout
|
|
1699
1929
|
});
|
|
@@ -1739,16 +1969,24 @@ async function bundleCommand(options) {
|
|
|
1739
1969
|
if (options.cache !== void 0) {
|
|
1740
1970
|
buildOptions.cache = options.cache;
|
|
1741
1971
|
}
|
|
1742
|
-
|
|
1972
|
+
const outputIsUrl = options.output ? isUrl(options.output) : false;
|
|
1973
|
+
const uploadUrl = outputIsUrl ? options.output : void 0;
|
|
1974
|
+
if (outputIsUrl) {
|
|
1975
|
+
const ext = buildOptions.platform === "browser" ? ".js" : ".mjs";
|
|
1976
|
+
buildOptions.output = getTmpPath(
|
|
1977
|
+
void 0,
|
|
1978
|
+
`url-bundle-${Date.now()}${ext}`
|
|
1979
|
+
);
|
|
1980
|
+
} else if (options.output) {
|
|
1743
1981
|
buildOptions.output = resolveOutputPath(options.output, buildOptions);
|
|
1744
1982
|
} else {
|
|
1745
1983
|
const ext = buildOptions.platform === "browser" ? ".js" : ".mjs";
|
|
1746
1984
|
buildOptions.output = getTmpPath(void 0, "stdout-bundle" + ext);
|
|
1747
1985
|
}
|
|
1748
1986
|
if (isMultiFlow || options.all) {
|
|
1749
|
-
logger2.
|
|
1987
|
+
logger2.info(`Bundling flow: ${flowName}...`);
|
|
1750
1988
|
} else {
|
|
1751
|
-
logger2.
|
|
1989
|
+
logger2.info("Bundling...");
|
|
1752
1990
|
}
|
|
1753
1991
|
const shouldCollectStats = options.stats || options.json;
|
|
1754
1992
|
const stats = await bundleCore(
|
|
@@ -1758,11 +1996,16 @@ async function bundleCommand(options) {
|
|
|
1758
1996
|
shouldCollectStats
|
|
1759
1997
|
);
|
|
1760
1998
|
results.push({ flowName, success: true, stats });
|
|
1999
|
+
if (uploadUrl) {
|
|
2000
|
+
await uploadBundleToUrl(buildOptions.output, uploadUrl);
|
|
2001
|
+
logger2.info(`Uploaded to: ${sanitizeUrl(uploadUrl)}`);
|
|
2002
|
+
await fs10.remove(buildOptions.output);
|
|
2003
|
+
}
|
|
1761
2004
|
if (!options.json && !options.all && options.stats && stats) {
|
|
1762
2005
|
displayStats(stats, logger2);
|
|
1763
2006
|
}
|
|
1764
2007
|
if (writingToStdout && !options.json) {
|
|
1765
|
-
const bundleContent = await
|
|
2008
|
+
const bundleContent = await fs10.readFile(buildOptions.output);
|
|
1766
2009
|
await writeResult(bundleContent, {});
|
|
1767
2010
|
}
|
|
1768
2011
|
if (options.dockerfile && options.output) {
|
|
@@ -1782,8 +2025,8 @@ async function bundleCommand(options) {
|
|
|
1782
2025
|
}
|
|
1783
2026
|
}
|
|
1784
2027
|
const duration = timer.end() / 1e3;
|
|
1785
|
-
const successCount = results.filter((
|
|
1786
|
-
const failureCount = results.filter((
|
|
2028
|
+
const successCount = results.filter((r2) => r2.success).length;
|
|
2029
|
+
const failureCount = results.filter((r2) => !r2.success).length;
|
|
1787
2030
|
if (options.json) {
|
|
1788
2031
|
const jsonResult = failureCount === 0 ? createSuccessOutput(
|
|
1789
2032
|
{
|
|
@@ -1804,7 +2047,7 @@ async function bundleCommand(options) {
|
|
|
1804
2047
|
});
|
|
1805
2048
|
} else {
|
|
1806
2049
|
if (options.all) {
|
|
1807
|
-
logger2.
|
|
2050
|
+
logger2.info(
|
|
1808
2051
|
`
|
|
1809
2052
|
Build Summary: ${successCount}/${results.length} succeeded`
|
|
1810
2053
|
);
|
|
@@ -1848,7 +2091,7 @@ async function bundle(configOrPath, options = {}) {
|
|
|
1848
2091
|
if (options.cache !== void 0) {
|
|
1849
2092
|
buildOptions.cache = options.cache;
|
|
1850
2093
|
}
|
|
1851
|
-
const logger2 =
|
|
2094
|
+
const logger2 = createCLILogger(options);
|
|
1852
2095
|
return await bundleCore(
|
|
1853
2096
|
flowConfig,
|
|
1854
2097
|
buildOptions,
|
|
@@ -1858,26 +2101,24 @@ async function bundle(configOrPath, options = {}) {
|
|
|
1858
2101
|
}
|
|
1859
2102
|
async function generateDockerfile(outputDir, platform, logger2, customFile) {
|
|
1860
2103
|
const destPath = path10.join(outputDir, "Dockerfile");
|
|
1861
|
-
if (customFile && await
|
|
1862
|
-
await
|
|
1863
|
-
logger2.
|
|
2104
|
+
if (customFile && await fs10.pathExists(customFile)) {
|
|
2105
|
+
await fs10.copy(customFile, destPath);
|
|
2106
|
+
logger2.info(`Dockerfile: ${destPath} (copied from ${customFile})`);
|
|
1864
2107
|
return;
|
|
1865
2108
|
}
|
|
1866
2109
|
const isWeb = platform === "web";
|
|
1867
2110
|
const bundleFile = isWeb ? "walker.js" : "bundle.mjs";
|
|
1868
|
-
const mode = isWeb ? "serve" : "collect";
|
|
1869
2111
|
const dockerfile = `# Generated by walkeros CLI
|
|
1870
2112
|
FROM walkeros/flow:latest
|
|
1871
2113
|
|
|
1872
2114
|
COPY ${bundleFile} /app/flow/${bundleFile}
|
|
1873
2115
|
|
|
1874
|
-
ENV MODE=${mode}
|
|
1875
2116
|
ENV BUNDLE=/app/flow/${bundleFile}
|
|
1876
2117
|
|
|
1877
2118
|
EXPOSE 8080
|
|
1878
2119
|
`;
|
|
1879
|
-
await
|
|
1880
|
-
logger2.
|
|
2120
|
+
await fs10.writeFile(destPath, dockerfile);
|
|
2121
|
+
logger2.info(`Dockerfile: ${destPath}`);
|
|
1881
2122
|
}
|
|
1882
2123
|
async function bundleRemote(options) {
|
|
1883
2124
|
const client = createApiClient();
|
|
@@ -1899,279 +2140,545 @@ async function bundleRemote(options) {
|
|
|
1899
2140
|
}
|
|
1900
2141
|
|
|
1901
2142
|
// src/commands/simulate/simulator.ts
|
|
1902
|
-
import path11 from "path";
|
|
1903
2143
|
import fs11 from "fs-extra";
|
|
1904
|
-
import {
|
|
2144
|
+
import { Level as Level2 } from "@walkeros/core";
|
|
1905
2145
|
|
|
1906
|
-
//
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
current = current[part];
|
|
1952
|
-
source = source && typeof source[part] === "object" && source[part] !== null ? source[part] : void 0;
|
|
1953
|
-
}
|
|
1954
|
-
const finalKey = parts[parts.length - 1];
|
|
1955
|
-
const originalFn = source?.[finalKey];
|
|
1956
|
-
current[finalKey] = this.wrapFunction(
|
|
1957
|
-
`${destKey}:${cleanPath}`,
|
|
1958
|
-
typeof originalFn === "function" ? originalFn : void 0
|
|
1959
|
-
);
|
|
1960
|
-
}
|
|
1961
|
-
return wrapped;
|
|
1962
|
-
}
|
|
1963
|
-
logCall(fullPath, args) {
|
|
1964
|
-
const [destKey, ...pathParts] = fullPath.split(":");
|
|
1965
|
-
const apiPath = pathParts.join(":");
|
|
1966
|
-
if (!this.calls.has(destKey)) {
|
|
1967
|
-
this.calls.set(destKey, []);
|
|
2146
|
+
// ../collector/dist/index.mjs
|
|
2147
|
+
import { assign as o } from "@walkeros/core";
|
|
2148
|
+
import { assign as r, createLogger as i } from "@walkeros/core";
|
|
2149
|
+
import { assign as a, clone as c, debounce as u, getId as f, getGrantedConsent as l, isDefined as d, isFunction as g, isObject as m, processEventMapping as p, tryCatchAsync as h, useHooks as w } from "@walkeros/core";
|
|
2150
|
+
import { isArray as y } from "@walkeros/core";
|
|
2151
|
+
import { tryCatch as b, tryCatchAsync as v } from "@walkeros/core";
|
|
2152
|
+
import { getMappingValue as k, tryCatchAsync as C } from "@walkeros/core";
|
|
2153
|
+
import { isObject as O, tryCatchAsync as q, useHooks as j } from "@walkeros/core";
|
|
2154
|
+
import { assign as M, getId as Q, isFunction as V, isString as K } from "@walkeros/core";
|
|
2155
|
+
import { isObject as X } from "@walkeros/core";
|
|
2156
|
+
import { getGrantedConsent as tn, processEventMapping as on, tryCatchAsync as sn, useHooks as rn } from "@walkeros/core";
|
|
2157
|
+
import { useHooks as cn, tryCatchAsync as un } from "@walkeros/core";
|
|
2158
|
+
var e = { Action: "action", Actions: "actions", Config: "config", Consent: "consent", Context: "context", Custom: "custom", Destination: "destination", Elb: "elb", Globals: "globals", Hook: "hook", Init: "init", Link: "link", On: "on", Prefix: "data-elb", Ready: "ready", Run: "run", Session: "session", Shutdown: "shutdown", User: "user", Walker: "walker" };
|
|
2159
|
+
var t = { Commands: e, Utils: { Storage: { Cookie: "cookie", Local: "local", Session: "session" } } };
|
|
2160
|
+
function s(n, e2) {
|
|
2161
|
+
let t2 = false;
|
|
2162
|
+
const s2 = {};
|
|
2163
|
+
return Object.entries(e2).forEach(([n2, e3]) => {
|
|
2164
|
+
const o2 = !!e3;
|
|
2165
|
+
s2[n2] = o2, t2 = t2 || o2;
|
|
2166
|
+
}), n.consent = o(n.consent, s2), { update: s2, runQueue: t2 };
|
|
2167
|
+
}
|
|
2168
|
+
function D(n) {
|
|
2169
|
+
return null != n && false !== n && "object" == typeof n && true === n.__branch;
|
|
2170
|
+
}
|
|
2171
|
+
function A(n) {
|
|
2172
|
+
const e2 = {};
|
|
2173
|
+
for (const [t2, o2] of Object.entries(n)) o2.config?.next ? e2[t2] = { next: o2.config.next } : e2[t2] = {};
|
|
2174
|
+
return e2;
|
|
2175
|
+
}
|
|
2176
|
+
function E(n, e2) {
|
|
2177
|
+
const t2 = n.config || {}, o2 = n[e2];
|
|
2178
|
+
return void 0 !== o2 ? { config: { ...t2, [e2]: o2 }, chainValue: o2 } : { config: t2, chainValue: void 0 };
|
|
2179
|
+
}
|
|
2180
|
+
function P(n, e2 = {}) {
|
|
2181
|
+
if (!n) return [];
|
|
2182
|
+
if (Array.isArray(n)) return n;
|
|
2183
|
+
const t2 = [], o2 = /* @__PURE__ */ new Set();
|
|
2184
|
+
let s2 = n;
|
|
2185
|
+
for (; s2 && e2[s2] && !o2.has(s2); ) {
|
|
2186
|
+
o2.add(s2), t2.push(s2);
|
|
2187
|
+
const n2 = e2[s2].next;
|
|
2188
|
+
if (Array.isArray(n2)) {
|
|
2189
|
+
t2.push(...n2);
|
|
2190
|
+
break;
|
|
1968
2191
|
}
|
|
1969
|
-
|
|
1970
|
-
type: "call",
|
|
1971
|
-
path: apiPath,
|
|
1972
|
-
args,
|
|
1973
|
-
timestamp: Date.now()
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
getCalls() {
|
|
1977
|
-
return Object.fromEntries(this.calls);
|
|
2192
|
+
s2 = n2;
|
|
1978
2193
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
2194
|
+
return t2;
|
|
2195
|
+
}
|
|
2196
|
+
async function x(n, e2, t2) {
|
|
2197
|
+
if (e2.init && !e2.config.init) {
|
|
2198
|
+
const o2 = e2.type || "unknown", s2 = n.logger.scope(`transformer:${o2}`), r2 = { collector: n, logger: s2, id: t2, config: e2.config, env: R(e2.config.env) };
|
|
2199
|
+
s2.debug("init");
|
|
2200
|
+
const i2 = await j(e2.init, "TransformerInit", n.hooks)(r2);
|
|
2201
|
+
if (false === i2) return false;
|
|
2202
|
+
e2.config = { ...i2 || e2.config, init: true }, s2.debug("init done");
|
|
1981
2203
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
location: { href: "http://localhost" },
|
|
1998
|
-
navigator: { userAgent: "Mozilla/5.0 (walkerOS Simulation)" }
|
|
1999
|
-
};
|
|
2000
|
-
const sandbox = {
|
|
2001
|
-
window: { ...baseBrowserMocks },
|
|
2002
|
-
document: {}
|
|
2003
|
-
};
|
|
2004
|
-
for (const [destKey, destConfig] of Object.entries(destinations)) {
|
|
2005
|
-
const destEnv = envs[destKey];
|
|
2006
|
-
if (!destEnv?.push) continue;
|
|
2007
|
-
const mockEnv = destEnv.push;
|
|
2008
|
-
const trackPaths = destEnv.simulation || [];
|
|
2009
|
-
const trackedEnv = tracker.wrapEnv(
|
|
2010
|
-
mockEnv,
|
|
2011
|
-
trackPaths.map((p) => `${destKey}:${p}`)
|
|
2012
|
-
);
|
|
2013
|
-
if (trackedEnv.window && typeof trackedEnv.window === "object") {
|
|
2014
|
-
Object.assign(sandbox.window, trackedEnv.window);
|
|
2204
|
+
return true;
|
|
2205
|
+
}
|
|
2206
|
+
async function S(n, e2, t2, o2, s2, r2) {
|
|
2207
|
+
const i2 = e2.type || "unknown", a2 = n.logger.scope(`transformer:${i2}`), c2 = { collector: n, logger: a2, id: t2, ingest: s2, config: e2.config, env: { ...R(e2.config.env), ...r2 ? { respond: r2 } : {} } };
|
|
2208
|
+
a2.debug("push", { event: o2.name });
|
|
2209
|
+
const u2 = await j(e2.push, "TransformerPush", n.hooks)(o2, c2);
|
|
2210
|
+
return a2.debug("push done"), u2;
|
|
2211
|
+
}
|
|
2212
|
+
async function $(n, e2, t2, o2, s2, r2) {
|
|
2213
|
+
let i2 = o2;
|
|
2214
|
+
for (const o3 of t2) {
|
|
2215
|
+
const t3 = e2[o3];
|
|
2216
|
+
if (!t3) {
|
|
2217
|
+
n.logger.warn(`Transformer not found: ${o3}`);
|
|
2218
|
+
continue;
|
|
2015
2219
|
}
|
|
2016
|
-
if (
|
|
2017
|
-
|
|
2220
|
+
if (!await q(x)(n, t3, o3)) return n.logger.error(`Transformer init failed: ${o3}`), null;
|
|
2221
|
+
const a2 = await q(S, (e3) => (n.logger.scope(`transformer:${t3.type || "unknown"}`).error("Push failed", { error: e3 }), false))(n, t3, o3, i2, s2, r2);
|
|
2222
|
+
if (false === a2) return null;
|
|
2223
|
+
if (D(a2)) {
|
|
2224
|
+
const t4 = P(a2.next, A(e2));
|
|
2225
|
+
return t4.length > 0 ? $(n, e2, t4, a2.event, s2, r2) : (n.logger.warn(`Branch target not found: ${JSON.stringify(a2.next)}`), null);
|
|
2226
|
+
}
|
|
2227
|
+
void 0 !== a2 && (i2 = a2);
|
|
2228
|
+
}
|
|
2229
|
+
return i2;
|
|
2230
|
+
}
|
|
2231
|
+
function R(n) {
|
|
2232
|
+
return n && O(n) ? n : {};
|
|
2233
|
+
}
|
|
2234
|
+
async function T(n, e2, t2) {
|
|
2235
|
+
const { code: o2, config: s2 = {}, env: r2 = {}, primary: i2, next: a2 } = t2;
|
|
2236
|
+
let c2, u2;
|
|
2237
|
+
const f2 = P(a2, A(n.transformers)), l2 = n.logger.scope("source").scope(e2), d2 = { push: (t3, o3 = {}) => n.push(t3, { ...o3, id: e2, ingest: c2, respond: u2, mapping: s2, preChain: f2 }), command: n.command, sources: n.sources, elb: n.sources.elb.push, logger: l2, ...r2 }, g2 = { collector: n, logger: l2, id: e2, config: s2, env: d2, setIngest: async (e3) => {
|
|
2238
|
+
c2 = s2.ingest ? await k(e3, s2.ingest, { collector: n }) : void 0;
|
|
2239
|
+
}, setRespond: (n2) => {
|
|
2240
|
+
u2 = n2;
|
|
2241
|
+
} }, m2 = await C(o2)(g2);
|
|
2242
|
+
if (!m2) return;
|
|
2243
|
+
const p2 = m2.type || "unknown", h2 = n.logger.scope(p2).scope(e2);
|
|
2244
|
+
return d2.logger = h2, i2 && (m2.config = { ...m2.config, primary: i2 }), m2;
|
|
2245
|
+
}
|
|
2246
|
+
async function I(n, e2 = {}) {
|
|
2247
|
+
const t2 = {};
|
|
2248
|
+
for (const [o2, s2] of Object.entries(e2)) {
|
|
2249
|
+
const { config: e3 = {} } = s2;
|
|
2250
|
+
if (e3.require && e3.require.length > 0) {
|
|
2251
|
+
n.pending.sources[o2] = s2;
|
|
2252
|
+
continue;
|
|
2018
2253
|
}
|
|
2254
|
+
const r2 = await T(n, o2, s2);
|
|
2255
|
+
r2 && (t2[o2] = r2);
|
|
2019
2256
|
}
|
|
2020
|
-
return
|
|
2257
|
+
return t2;
|
|
2021
2258
|
}
|
|
2022
|
-
function
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
resolve3();
|
|
2028
|
-
} else if (Date.now() - start > timeout) {
|
|
2029
|
-
reject(
|
|
2030
|
-
new Error(
|
|
2031
|
-
`Timeout waiting for window.${prop}. IIFE may have failed to execute or assign to window.`
|
|
2032
|
-
)
|
|
2033
|
-
);
|
|
2034
|
-
} else {
|
|
2035
|
-
setImmediate(check);
|
|
2036
|
-
}
|
|
2037
|
-
};
|
|
2038
|
-
check();
|
|
2039
|
-
});
|
|
2259
|
+
async function B(n, e2, t2) {
|
|
2260
|
+
const o2 = n.on, s2 = o2[e2] || [], r2 = y(t2) ? t2 : [t2];
|
|
2261
|
+
r2.forEach((n2) => {
|
|
2262
|
+
s2.push(n2);
|
|
2263
|
+
}), o2[e2] = s2, await H(n, e2, r2);
|
|
2040
2264
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2045
|
-
url: "http://localhost",
|
|
2046
|
-
runScripts: "dangerously",
|
|
2047
|
-
// Allow script execution
|
|
2048
|
-
resources: "usable",
|
|
2049
|
-
virtualConsole
|
|
2050
|
-
});
|
|
2051
|
-
const { window } = dom;
|
|
2052
|
-
const sandbox = buildSandboxFromEnvs(envs, destinations, tracker);
|
|
2053
|
-
Object.assign(window, sandbox.window);
|
|
2054
|
-
Object.assign(window.document, sandbox.document);
|
|
2055
|
-
const bundleCode = await fs10.readFile(bundlePath, "utf8");
|
|
2056
|
-
try {
|
|
2057
|
-
window.eval(bundleCode);
|
|
2058
|
-
} catch (error) {
|
|
2059
|
-
throw new Error(`Bundle execution failed: ${getErrorMessage(error)}`);
|
|
2060
|
-
}
|
|
2061
|
-
try {
|
|
2062
|
-
await waitForWindowProperty(
|
|
2063
|
-
window,
|
|
2064
|
-
"collector",
|
|
2065
|
-
timeout
|
|
2066
|
-
);
|
|
2067
|
-
await waitForWindowProperty(
|
|
2068
|
-
window,
|
|
2069
|
-
"elb",
|
|
2070
|
-
timeout
|
|
2071
|
-
);
|
|
2072
|
-
} catch (error) {
|
|
2073
|
-
throw new Error(
|
|
2074
|
-
`Window property assignment failed: ${getErrorMessage(error)}`
|
|
2075
|
-
);
|
|
2076
|
-
}
|
|
2077
|
-
const { collector, elb } = window;
|
|
2078
|
-
let elbResult;
|
|
2079
|
-
try {
|
|
2080
|
-
elbResult = await elb(event.name, event.data);
|
|
2081
|
-
} catch (error) {
|
|
2082
|
-
throw new Error(`Event execution failed: ${getErrorMessage(error)}`);
|
|
2083
|
-
}
|
|
2084
|
-
return {
|
|
2085
|
-
collector,
|
|
2086
|
-
elb,
|
|
2087
|
-
elbResult,
|
|
2088
|
-
usage: tracker.getCalls(),
|
|
2089
|
-
duration: Date.now() - start
|
|
2090
|
-
};
|
|
2265
|
+
function G(n, e2, t2, o2, s2) {
|
|
2266
|
+
if (!e2.on) return;
|
|
2267
|
+
const r2 = e2.type || "unknown", i2 = n.logger.scope(r2).scope("on").scope(o2), a2 = { collector: n, logger: i2, id: t2, config: e2.config, data: s2, env: L(e2.env, e2.config.env) };
|
|
2268
|
+
b(e2.on)(o2, a2);
|
|
2091
2269
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2270
|
+
async function H(n, e2, o2, s2) {
|
|
2271
|
+
let r2, i2 = o2 || [];
|
|
2272
|
+
switch (o2 || (i2 = n.on[e2] || []), e2) {
|
|
2273
|
+
case t.Commands.Consent:
|
|
2274
|
+
r2 = s2 || n.consent;
|
|
2275
|
+
break;
|
|
2276
|
+
case t.Commands.Session:
|
|
2277
|
+
r2 = n.session;
|
|
2278
|
+
break;
|
|
2279
|
+
case t.Commands.User:
|
|
2280
|
+
r2 = s2 || n.user;
|
|
2281
|
+
break;
|
|
2282
|
+
case t.Commands.Custom:
|
|
2283
|
+
r2 = s2 || n.custom;
|
|
2284
|
+
break;
|
|
2285
|
+
case t.Commands.Globals:
|
|
2286
|
+
r2 = s2 || n.globals;
|
|
2287
|
+
break;
|
|
2288
|
+
case t.Commands.Config:
|
|
2289
|
+
r2 = s2 || n.config;
|
|
2290
|
+
break;
|
|
2291
|
+
case t.Commands.Ready:
|
|
2292
|
+
case t.Commands.Run:
|
|
2293
|
+
default:
|
|
2294
|
+
r2 = void 0;
|
|
2295
|
+
}
|
|
2296
|
+
let a2 = false;
|
|
2297
|
+
for (const t2 of Object.values(n.sources)) if (t2.on) {
|
|
2298
|
+
false === await v(t2.on)(e2, r2) && (a2 = true);
|
|
2299
|
+
}
|
|
2300
|
+
if (Object.entries(n.destinations).forEach(([t2, o3]) => {
|
|
2301
|
+
if (o3.on) {
|
|
2302
|
+
if (!o3.config.init) return o3.queueOn = o3.queueOn || [], void o3.queueOn.push({ type: e2, data: r2 });
|
|
2303
|
+
G(n, o3, t2, e2, r2);
|
|
2304
|
+
}
|
|
2305
|
+
}), (Object.keys(n.pending.sources).length > 0 || Object.keys(n.pending.destinations).length > 0) && await (async function(n2, e3) {
|
|
2306
|
+
for (const [t2, o3] of Object.entries(n2.pending.sources)) {
|
|
2307
|
+
if (!n2.pending.sources[t2] || n2.sources[t2]) continue;
|
|
2308
|
+
const s3 = o3.config?.require;
|
|
2309
|
+
if (!s3) continue;
|
|
2310
|
+
const r3 = s3.indexOf(e3);
|
|
2311
|
+
if (-1 === r3) continue;
|
|
2312
|
+
if (s3.splice(r3, 1), s3.length > 0) continue;
|
|
2313
|
+
delete n2.pending.sources[t2];
|
|
2314
|
+
const i3 = await T(n2, t2, o3);
|
|
2315
|
+
i3 && (n2.sources[t2] = i3);
|
|
2316
|
+
}
|
|
2317
|
+
for (const [t2, o3] of Object.entries(n2.pending.destinations)) {
|
|
2318
|
+
if (!n2.pending.destinations[t2] || n2.destinations[t2]) continue;
|
|
2319
|
+
const s3 = o3.config?.require;
|
|
2320
|
+
if (!s3) continue;
|
|
2321
|
+
const r3 = s3.indexOf(e3);
|
|
2322
|
+
if (-1 === r3) continue;
|
|
2323
|
+
if (s3.splice(r3, 1), s3.length > 0) continue;
|
|
2324
|
+
delete n2.pending.destinations[t2];
|
|
2325
|
+
const i3 = W(o3);
|
|
2326
|
+
false !== i3.config.queue && (i3.queuePush = [...n2.queue]), n2.destinations[t2] = i3;
|
|
2327
|
+
}
|
|
2328
|
+
})(n, e2), !i2.length) return !a2;
|
|
2329
|
+
switch (e2) {
|
|
2330
|
+
case t.Commands.Consent:
|
|
2331
|
+
!(function(n2, e3, t2) {
|
|
2332
|
+
const o3 = t2 || n2.consent;
|
|
2333
|
+
e3.forEach((e4) => {
|
|
2334
|
+
Object.keys(o3).filter((n3) => n3 in e4).forEach((t3) => {
|
|
2335
|
+
b(e4[t3])(n2, o3);
|
|
2336
|
+
});
|
|
2337
|
+
});
|
|
2338
|
+
})(n, i2, s2);
|
|
2339
|
+
break;
|
|
2340
|
+
case t.Commands.Ready:
|
|
2341
|
+
case t.Commands.Run:
|
|
2342
|
+
!(function(n2, e3) {
|
|
2343
|
+
n2.allowed && e3.forEach((e4) => {
|
|
2344
|
+
b(e4)(n2);
|
|
2345
|
+
});
|
|
2346
|
+
})(n, i2);
|
|
2347
|
+
break;
|
|
2348
|
+
case t.Commands.Session:
|
|
2349
|
+
!(function(n2, e3) {
|
|
2350
|
+
if (!n2.session) return;
|
|
2351
|
+
e3.forEach((e4) => {
|
|
2352
|
+
b(e4)(n2, n2.session);
|
|
2353
|
+
});
|
|
2354
|
+
})(n, i2);
|
|
2355
|
+
break;
|
|
2356
|
+
default:
|
|
2357
|
+
i2.forEach((e3) => {
|
|
2358
|
+
"function" == typeof e3 && b(e3)(n, r2);
|
|
2359
|
+
});
|
|
2115
2360
|
}
|
|
2116
|
-
return
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2361
|
+
return !a2;
|
|
2362
|
+
}
|
|
2363
|
+
async function U(n, e2, t2) {
|
|
2364
|
+
const { code: o2, config: s2 = {}, env: r2 = {}, before: i2 } = e2;
|
|
2365
|
+
if (!g(o2.push)) return N({ ok: false, failed: { invalid: { type: "invalid", error: "Destination code must have a push method" } } });
|
|
2366
|
+
const a2 = t2 || s2 || { init: false }, c2 = i2 ? { ...a2, before: i2 } : a2, u2 = { ...o2, config: c2, env: L(o2.env, r2) };
|
|
2367
|
+
let l2 = u2.config.id;
|
|
2368
|
+
if (!l2) do {
|
|
2369
|
+
l2 = f(4);
|
|
2370
|
+
} while (n.destinations[l2]);
|
|
2371
|
+
return n.destinations[l2] = u2, false !== u2.config.queue && (u2.queuePush = [...n.queue]), _(n, void 0, {}, { [l2]: u2 });
|
|
2372
|
+
}
|
|
2373
|
+
async function _(n, e2, t2 = {}, o2) {
|
|
2374
|
+
const { allowed: s2, consent: r2, globals: i2, user: u2 } = n;
|
|
2375
|
+
if (!s2) return N({ ok: false });
|
|
2376
|
+
e2 && (n.queue.push(e2), n.status.in++), o2 || (o2 = n.destinations);
|
|
2377
|
+
const f2 = await Promise.all(Object.entries(o2 || {}).map(async ([o3, s3]) => {
|
|
2378
|
+
let f3 = (s3.queuePush || []).map((n2) => ({ ...n2, consent: r2 }));
|
|
2379
|
+
if (s3.queuePush = [], e2) {
|
|
2380
|
+
const n2 = c(e2);
|
|
2381
|
+
f3.push(n2);
|
|
2382
|
+
}
|
|
2383
|
+
if (!f3.length && !s3.queueOn?.length) return { id: o3, destination: s3, skipped: true };
|
|
2384
|
+
if (!f3.length && s3.queueOn?.length) {
|
|
2385
|
+
const e3 = await h(F)(n, s3, o3);
|
|
2386
|
+
return { id: o3, destination: s3, skipped: !e3 };
|
|
2387
|
+
}
|
|
2388
|
+
const d3 = [], g3 = f3.filter((n2) => {
|
|
2389
|
+
const e3 = l(s3.config.consent, r2, n2.consent);
|
|
2390
|
+
return !e3 || (n2.consent = e3, d3.push(n2), false);
|
|
2391
|
+
});
|
|
2392
|
+
if (s3.queuePush.push(...g3), !d3.length) return { id: o3, destination: s3, queue: f3 };
|
|
2393
|
+
if (!await h(F)(n, s3, o3)) return { id: o3, destination: s3, queue: f3 };
|
|
2394
|
+
let m3, p2;
|
|
2395
|
+
s3.dlq || (s3.dlq = []);
|
|
2396
|
+
const w2 = (function(n2, e3) {
|
|
2397
|
+
const t3 = n2.config.before;
|
|
2398
|
+
return t3 ? P(t3, A(e3)) : [];
|
|
2399
|
+
})(s3, n.transformers);
|
|
2400
|
+
let y2 = 0;
|
|
2401
|
+
return await Promise.all(d3.map(async (e3) => {
|
|
2402
|
+
e3.globals = a(i2, e3.globals), e3.user = a(u2, e3.user);
|
|
2403
|
+
let r3 = e3;
|
|
2404
|
+
if (w2.length > 0 && n.transformers && Object.keys(n.transformers).length > 0) {
|
|
2405
|
+
const o4 = await $(n, n.transformers, w2, e3, t2.ingest, t2.respond);
|
|
2406
|
+
if (null === o4) return e3;
|
|
2407
|
+
r3 = o4;
|
|
2122
2408
|
}
|
|
2409
|
+
const c2 = Date.now(), f4 = await h(J, (e4) => {
|
|
2410
|
+
const t3 = s3.type || "unknown";
|
|
2411
|
+
n.logger.scope(t3).error("Push failed", { error: e4, event: r3.name }), m3 = e4, s3.dlq.push([r3, e4]);
|
|
2412
|
+
})(n, s3, o3, r3, t2.ingest, t2.respond);
|
|
2413
|
+
return y2 += Date.now() - c2, void 0 !== f4 && (p2 = f4), e3;
|
|
2414
|
+
})), { id: o3, destination: s3, error: m3, response: p2, totalDuration: y2 };
|
|
2415
|
+
})), d2 = {}, g2 = {}, m2 = {};
|
|
2416
|
+
for (const e3 of f2) {
|
|
2417
|
+
if (e3.skipped) continue;
|
|
2418
|
+
const t3 = e3.destination, o3 = { type: t3.type || "unknown", data: e3.response };
|
|
2419
|
+
n.status.destinations[e3.id] || (n.status.destinations[e3.id] = { count: 0, failed: 0, duration: 0 });
|
|
2420
|
+
const s3 = n.status.destinations[e3.id], r3 = Date.now();
|
|
2421
|
+
e3.error ? (o3.error = e3.error, m2[e3.id] = o3, s3.failed++, s3.lastAt = r3, s3.duration += e3.totalDuration || 0, n.status.failed++) : e3.queue && e3.queue.length ? (t3.queuePush = (t3.queuePush || []).concat(e3.queue), g2[e3.id] = o3) : (d2[e3.id] = o3, s3.count++, s3.lastAt = r3, s3.duration += e3.totalDuration || 0, n.status.out++);
|
|
2422
|
+
}
|
|
2423
|
+
return N({ event: e2, ...Object.keys(d2).length && { done: d2 }, ...Object.keys(g2).length && { queued: g2 }, ...Object.keys(m2).length && { failed: m2 } });
|
|
2424
|
+
}
|
|
2425
|
+
async function F(n, e2, t2) {
|
|
2426
|
+
if (e2.init && !e2.config.init) {
|
|
2427
|
+
const o2 = e2.type || "unknown", s2 = n.logger.scope(o2), r2 = { collector: n, logger: s2, id: t2, config: e2.config, env: L(e2.env, e2.config.env) };
|
|
2428
|
+
s2.debug("init");
|
|
2429
|
+
const i2 = await w(e2.init, "DestinationInit", n.hooks)(r2);
|
|
2430
|
+
if (false === i2) return i2;
|
|
2431
|
+
if (e2.config = { ...i2 || e2.config, init: true }, e2.queueOn?.length) {
|
|
2432
|
+
const o3 = e2.queueOn;
|
|
2433
|
+
e2.queueOn = [];
|
|
2434
|
+
for (const { type: s3, data: r3 } of o3) G(n, e2, t2, s3, r3);
|
|
2435
|
+
}
|
|
2436
|
+
s2.debug("init done");
|
|
2437
|
+
}
|
|
2438
|
+
return true;
|
|
2439
|
+
}
|
|
2440
|
+
async function J(n, e2, t2, o2, s2, r2) {
|
|
2441
|
+
const { config: i2 } = e2, a2 = await p(o2, i2, n);
|
|
2442
|
+
if (a2.ignore) return false;
|
|
2443
|
+
const c2 = e2.type || "unknown", f2 = n.logger.scope(c2), l2 = { collector: n, logger: f2, id: t2, config: i2, data: a2.data, rule: a2.mapping, ingest: s2, env: { ...L(e2.env, i2.env), ...r2 ? { respond: r2 } : {} } }, g2 = a2.mapping, m2 = a2.mappingKey || "* *";
|
|
2444
|
+
if (!g2?.batch || !e2.pushBatch) {
|
|
2445
|
+
f2.debug("push", { event: a2.event.name });
|
|
2446
|
+
const t3 = await w(e2.push, "DestinationPush", n.hooks)(a2.event, l2);
|
|
2447
|
+
return f2.debug("push done"), t3;
|
|
2448
|
+
}
|
|
2449
|
+
{
|
|
2450
|
+
if (e2.batches = e2.batches || {}, !e2.batches[m2]) {
|
|
2451
|
+
const o4 = { key: m2, events: [], data: [] };
|
|
2452
|
+
e2.batches[m2] = { batched: o4, batchFn: u(() => {
|
|
2453
|
+
const o5 = e2.batches[m2].batched, a3 = { collector: n, logger: f2, id: t2, config: i2, data: void 0, rule: g2, ingest: s2, env: { ...L(e2.env, i2.env), ...r2 ? { respond: r2 } : {} } };
|
|
2454
|
+
f2.debug("push batch", { events: o5.events.length }), w(e2.pushBatch, "DestinationPushBatch", n.hooks)(o5, a3), f2.debug("push batch done"), o5.events = [], o5.data = [];
|
|
2455
|
+
}, g2.batch) };
|
|
2456
|
+
}
|
|
2457
|
+
const o3 = e2.batches[m2];
|
|
2458
|
+
o3.batched.events.push(a2.event), d(a2.data) && o3.batched.data.push(a2.data), o3.batchFn();
|
|
2459
|
+
}
|
|
2460
|
+
return true;
|
|
2461
|
+
}
|
|
2462
|
+
function N(n) {
|
|
2463
|
+
return { ok: !n?.failed, ...n };
|
|
2464
|
+
}
|
|
2465
|
+
function W(n) {
|
|
2466
|
+
const { code: e2, config: t2 = {}, env: o2 = {} } = n, { config: s2 } = E(n, "before"), r2 = { ...e2.config, ...t2, ...s2 }, i2 = L(e2.env, o2);
|
|
2467
|
+
return { ...e2, config: r2, env: i2 };
|
|
2468
|
+
}
|
|
2469
|
+
async function z(n, e2 = {}) {
|
|
2470
|
+
const t2 = {};
|
|
2471
|
+
for (const [o2, s2] of Object.entries(e2)) s2.config?.require?.length ? n.pending.destinations[o2] = s2 : t2[o2] = W(s2);
|
|
2472
|
+
return t2;
|
|
2473
|
+
}
|
|
2474
|
+
function L(n, e2) {
|
|
2475
|
+
return n || e2 ? e2 ? n && m(n) && m(e2) ? { ...n, ...e2 } : e2 : n : {};
|
|
2476
|
+
}
|
|
2477
|
+
async function Y(n, e2, t2) {
|
|
2478
|
+
const o2 = Object.entries(n).map(async ([n2, o3]) => {
|
|
2479
|
+
const s2 = o3.destroy;
|
|
2480
|
+
if (!s2) return;
|
|
2481
|
+
const r2 = o3.type || "unknown", i2 = t2.scope(r2), a2 = { id: n2, config: o3.config, env: o3.env ?? {}, logger: i2 };
|
|
2482
|
+
try {
|
|
2483
|
+
await Promise.race([s2(a2), new Promise((t3, o4) => setTimeout(() => o4(new Error(`${e2} '${n2}' destroy timed out`)), 5e3))]);
|
|
2484
|
+
} catch (t3) {
|
|
2485
|
+
i2.error(`${e2} '${n2}' destroy failed: ${t3}`);
|
|
2123
2486
|
}
|
|
2124
|
-
};
|
|
2487
|
+
});
|
|
2488
|
+
await Promise.allSettled(o2);
|
|
2125
2489
|
}
|
|
2126
|
-
async function
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
}
|
|
2137
|
-
const result = await module.default(context);
|
|
2138
|
-
if (!result || !result.collector || typeof result.collector.push !== "function") {
|
|
2139
|
-
throw new Error(
|
|
2140
|
-
"Factory function did not return valid result with collector"
|
|
2141
|
-
);
|
|
2490
|
+
async function Z(n, e2, o2, r2) {
|
|
2491
|
+
let i2, a2, c2 = false, u2 = false;
|
|
2492
|
+
switch (e2) {
|
|
2493
|
+
case t.Commands.Config:
|
|
2494
|
+
X(o2) && (M(n.config, o2, { shallow: false }), a2 = o2, c2 = true);
|
|
2495
|
+
break;
|
|
2496
|
+
case t.Commands.Consent:
|
|
2497
|
+
if (X(o2)) {
|
|
2498
|
+
const { update: e3, runQueue: t2 } = s(n, o2);
|
|
2499
|
+
a2 = e3, c2 = true, u2 = t2;
|
|
2142
2500
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2501
|
+
break;
|
|
2502
|
+
case t.Commands.Custom:
|
|
2503
|
+
X(o2) && (n.custom = M(n.custom, o2), a2 = o2, c2 = true);
|
|
2504
|
+
break;
|
|
2505
|
+
case t.Commands.Destination:
|
|
2506
|
+
X(o2) && ("code" in o2 && X(o2.code) ? i2 = await U(n, o2, r2) : V(o2.push) && (i2 = await U(n, { code: o2 }, r2)));
|
|
2507
|
+
break;
|
|
2508
|
+
case t.Commands.Globals:
|
|
2509
|
+
X(o2) && (n.globals = M(n.globals, o2), a2 = o2, c2 = true);
|
|
2510
|
+
break;
|
|
2511
|
+
case t.Commands.On:
|
|
2512
|
+
K(o2) && await B(n, o2, r2);
|
|
2513
|
+
break;
|
|
2514
|
+
case t.Commands.Ready:
|
|
2515
|
+
c2 = true;
|
|
2516
|
+
break;
|
|
2517
|
+
case t.Commands.Run:
|
|
2518
|
+
i2 = await en(n, o2), c2 = true;
|
|
2519
|
+
break;
|
|
2520
|
+
case t.Commands.Session:
|
|
2521
|
+
c2 = true;
|
|
2522
|
+
break;
|
|
2523
|
+
case t.Commands.Shutdown:
|
|
2524
|
+
await (async function(n2) {
|
|
2525
|
+
const e3 = n2.logger;
|
|
2526
|
+
await Y(n2.sources, "source", e3), await Y(n2.destinations, "destination", e3), await Y(n2.transformers, "transformer", e3);
|
|
2527
|
+
})(n);
|
|
2528
|
+
break;
|
|
2529
|
+
case t.Commands.User:
|
|
2530
|
+
X(o2) && (M(n.user, o2, { shallow: false }), a2 = o2, c2 = true);
|
|
2531
|
+
}
|
|
2532
|
+
return c2 && await H(n, e2, void 0, a2), u2 && (i2 = await _(n)), i2 || N({ ok: true });
|
|
2533
|
+
}
|
|
2534
|
+
function nn(n, e2) {
|
|
2535
|
+
if (!e2.name) throw new Error("Event name is required");
|
|
2536
|
+
const [t2, o2] = e2.name.split(" ");
|
|
2537
|
+
if (!t2 || !o2) throw new Error("Event name is invalid");
|
|
2538
|
+
++n.count;
|
|
2539
|
+
const { timestamp: s2 = Date.now(), group: r2 = n.group, count: i2 = n.count } = e2, { name: a2 = `${t2} ${o2}`, data: c2 = {}, context: u2 = {}, globals: f2 = n.globals, custom: l2 = {}, user: d2 = n.user, nested: g2 = [], consent: m2 = n.consent, id: p2 = `${s2}-${r2}-${i2}`, trigger: h2 = "", entity: w2 = t2, action: y2 = o2, timing: b2 = 0, version: v2 = { source: n.version, tagging: n.config.tagging || 0 }, source: k2 = { type: "collector", id: "", previous_id: "" } } = e2;
|
|
2540
|
+
return { name: a2, data: c2, context: u2, globals: f2, custom: l2, user: d2, nested: g2, consent: m2, id: p2, trigger: h2, entity: w2, action: y2, timestamp: s2, timing: b2, group: r2, count: i2, version: v2, source: k2 };
|
|
2541
|
+
}
|
|
2542
|
+
async function en(n, e2) {
|
|
2543
|
+
n.allowed = true, n.count = 0, n.group = Q(), n.timing = Date.now(), e2 && (e2.consent && (n.consent = M(n.consent, e2.consent)), e2.user && (n.user = M(n.user, e2.user)), e2.globals && (n.globals = M(n.config.globalsStatic || {}, e2.globals)), e2.custom && (n.custom = M(n.custom, e2.custom))), Object.values(n.destinations).forEach((n2) => {
|
|
2544
|
+
n2.queuePush = [];
|
|
2545
|
+
}), n.queue = [], n.round++;
|
|
2546
|
+
return await _(n);
|
|
2547
|
+
}
|
|
2548
|
+
function an(n, e2) {
|
|
2549
|
+
return rn(async (t2, o2 = {}) => await sn(async () => {
|
|
2550
|
+
const s2 = Date.now(), { id: r2, ingest: i2, respond: a2, mapping: c2, preChain: u2 } = o2;
|
|
2551
|
+
let f2 = t2;
|
|
2552
|
+
const l2 = i2 ? Object.freeze(i2) : void 0;
|
|
2553
|
+
if (c2) {
|
|
2554
|
+
const e3 = await on(f2, c2, n);
|
|
2555
|
+
if (e3.ignore) return N({ ok: true });
|
|
2556
|
+
if (c2.consent) {
|
|
2557
|
+
if (!tn(c2.consent, n.consent, e3.event.consent)) return N({ ok: true });
|
|
2152
2558
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
};
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2559
|
+
f2 = e3.event;
|
|
2560
|
+
}
|
|
2561
|
+
if (u2?.length && n.transformers && Object.keys(n.transformers).length > 0) {
|
|
2562
|
+
const e3 = await $(n, n.transformers, u2, f2, l2, a2);
|
|
2563
|
+
if (null === e3) return N({ ok: true });
|
|
2564
|
+
f2 = e3;
|
|
2565
|
+
}
|
|
2566
|
+
const d2 = e2(f2), g2 = nn(n, d2), m2 = await _(n, g2, { id: r2, ingest: l2, respond: a2 });
|
|
2567
|
+
if (r2) {
|
|
2568
|
+
n.status.sources[r2] || (n.status.sources[r2] = { count: 0, duration: 0 });
|
|
2569
|
+
const e3 = n.status.sources[r2];
|
|
2570
|
+
e3.count++, e3.lastAt = Date.now(), e3.duration += Date.now() - s2;
|
|
2571
|
+
}
|
|
2572
|
+
return m2;
|
|
2573
|
+
}, () => N({ ok: false }))(), "Push", n.hooks);
|
|
2574
|
+
}
|
|
2575
|
+
async function fn(n) {
|
|
2576
|
+
const e2 = r({ globalsStatic: {}, sessionStatic: {}, tagging: 0, run: true }, n, { merge: false, extend: false }), t2 = { level: n.logger?.level, handler: n.logger?.handler }, o2 = i(t2), s2 = { ...e2.globalsStatic, ...n.globals }, a2 = { allowed: false, config: e2, consent: n.consent || {}, count: 0, custom: n.custom || {}, destinations: {}, transformers: {}, globals: s2, group: "", hooks: {}, logger: o2, on: {}, queue: [], round: 0, session: void 0, status: { startedAt: Date.now(), in: 0, out: 0, failed: 0, sources: {}, destinations: {} }, timing: Date.now(), user: n.user || {}, version: "2.0.1", sources: {}, pending: { sources: {}, destinations: {} }, push: void 0, command: void 0 };
|
|
2577
|
+
return a2.push = an(a2, (n2) => ({ timing: Math.round((Date.now() - a2.timing) / 10) / 100, source: { type: "collector", id: "", previous_id: "" }, ...n2 })), a2.command = (function(n2, e3) {
|
|
2578
|
+
return cn(async (t3, o3, s3) => await un(async () => await e3(n2, t3, o3, s3), () => N({ ok: false }))(), "Command", n2.hooks);
|
|
2579
|
+
})(a2, Z), a2.destinations = await z(a2, n.destinations || {}), a2.transformers = await (async function(n2, e3 = {}) {
|
|
2580
|
+
const t3 = {};
|
|
2581
|
+
for (const [o3, s3] of Object.entries(e3)) {
|
|
2582
|
+
const { code: e4, env: r2 = {} } = s3, { config: i2 } = E(s3, "next"), a3 = n2.logger.scope("transformer").scope(o3), c2 = { collector: n2, logger: a3, id: o3, config: i2, env: r2 }, u2 = await e4(c2);
|
|
2583
|
+
t3[o3] = u2;
|
|
2584
|
+
}
|
|
2585
|
+
return t3;
|
|
2586
|
+
})(a2, n.transformers || {}), a2;
|
|
2587
|
+
}
|
|
2588
|
+
async function ln(n) {
|
|
2589
|
+
n = n || {};
|
|
2590
|
+
const e2 = await fn(n), t2 = (o2 = e2, { type: "elb", config: {}, push: async (n2, e3, t3, s3, r3, i3) => {
|
|
2591
|
+
if ("string" == typeof n2 && n2.startsWith("walker ")) {
|
|
2592
|
+
const s4 = n2.replace("walker ", "");
|
|
2593
|
+
return o2.command(s4, e3, t3);
|
|
2594
|
+
}
|
|
2595
|
+
let a3;
|
|
2596
|
+
if ("string" == typeof n2) a3 = { name: n2 }, e3 && "object" == typeof e3 && !Array.isArray(e3) && (a3.data = e3);
|
|
2597
|
+
else {
|
|
2598
|
+
if (!n2 || "object" != typeof n2) return N({ ok: false });
|
|
2599
|
+
a3 = n2, e3 && "object" == typeof e3 && !Array.isArray(e3) && (a3.data = { ...a3.data || {}, ...e3 });
|
|
2600
|
+
}
|
|
2601
|
+
return s3 && "object" == typeof s3 && (a3.context = s3), r3 && Array.isArray(r3) && (a3.nested = r3), i3 && "object" == typeof i3 && (a3.custom = i3), o2.push(a3);
|
|
2602
|
+
} });
|
|
2603
|
+
var o2;
|
|
2604
|
+
e2.sources.elb = t2;
|
|
2605
|
+
const s2 = await I(e2, n.sources || {});
|
|
2606
|
+
Object.assign(e2.sources, s2);
|
|
2607
|
+
const { consent: r2, user: i2, globals: a2, custom: c2 } = n;
|
|
2608
|
+
r2 && await e2.command("consent", r2), i2 && await e2.command("user", i2), a2 && Object.assign(e2.globals, a2), c2 && Object.assign(e2.custom, c2), e2.config.run && await e2.command("run");
|
|
2609
|
+
let u2 = t2.push;
|
|
2610
|
+
const f2 = Object.values(e2.sources).filter((n2) => "elb" !== n2.type), l2 = f2.find((n2) => n2.config.primary);
|
|
2611
|
+
return l2 ? u2 = l2.push : f2.length > 0 && (u2 = f2[0].push), { collector: e2, elb: u2 };
|
|
2612
|
+
}
|
|
2613
|
+
function dn(n) {
|
|
2614
|
+
if (null === n || "object" != typeof n) return n;
|
|
2615
|
+
if (Array.isArray(n)) return n.map(dn);
|
|
2616
|
+
const e2 = {};
|
|
2617
|
+
for (const [t2, o2] of Object.entries(n)) e2[t2] = "function" == typeof o2 ? o2 : dn(o2);
|
|
2618
|
+
return e2;
|
|
2619
|
+
}
|
|
2620
|
+
function gn(n) {
|
|
2621
|
+
const e2 = [], { simulation: t2, ...o2 } = n, s2 = dn(o2);
|
|
2622
|
+
for (const n2 of t2) {
|
|
2623
|
+
const t3 = n2.startsWith("call:") ? n2.slice(5) : n2, o3 = t3.split(".");
|
|
2624
|
+
let r2 = s2;
|
|
2625
|
+
for (let n3 = 0; n3 < o3.length - 1 && null != r2[o3[n3]]; n3++) r2 = r2[o3[n3]];
|
|
2626
|
+
const i2 = o3[o3.length - 1];
|
|
2627
|
+
if (null == r2 || !(i2 in r2)) continue;
|
|
2628
|
+
const a2 = r2[i2];
|
|
2629
|
+
"function" == typeof a2 && (r2[i2] = function(...n3) {
|
|
2630
|
+
return e2.push({ fn: t3, args: n3, ts: Date.now() }), a2.apply(this, n3);
|
|
2166
2631
|
});
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2632
|
+
}
|
|
2633
|
+
return { wrappedEnv: s2, calls: e2 };
|
|
2634
|
+
}
|
|
2635
|
+
async function mn(n) {
|
|
2636
|
+
const e2 = Date.now();
|
|
2637
|
+
try {
|
|
2638
|
+
switch (n.step) {
|
|
2639
|
+
case "transformer":
|
|
2640
|
+
return await (async function(n2, e3) {
|
|
2641
|
+
const { code: t2, config: o2 = {}, event: s2 } = n2, { collector: r2 } = await ln({ transformers: { sim: { code: t2, config: o2 } } }), i2 = r2.transformers?.sim;
|
|
2642
|
+
if (!i2) throw new Error("Transformer failed to initialize");
|
|
2643
|
+
const a2 = await i2.push(s2, { collector: r2, logger: r2.logger.scope("transformer").scope("sim"), id: "sim", config: i2.config, env: i2.config?.env || {} });
|
|
2644
|
+
let c2;
|
|
2645
|
+
c2 = false === a2 ? [] : null == a2 ? [s2] : [a2];
|
|
2646
|
+
return { step: "transformer", name: n2.name, events: c2, calls: [], duration: Date.now() - e3 };
|
|
2647
|
+
})(n, e2);
|
|
2648
|
+
case "source":
|
|
2649
|
+
return await (async function(n2, e3) {
|
|
2650
|
+
const { code: t2, config: o2 = {}, setup: s2, input: r2, env: i2, consent: a2 } = n2, c2 = { functional: true, marketing: true, analytics: true };
|
|
2651
|
+
let u2;
|
|
2652
|
+
if (s2) {
|
|
2653
|
+
const n3 = s2(r2, i2);
|
|
2654
|
+
"function" == typeof n3 && (u2 = n3);
|
|
2655
|
+
}
|
|
2656
|
+
const f2 = [], { collector: l2 } = await ln({ consent: a2 || c2, sources: { sim: { code: t2, config: o2, env: i2, next: "spy" } }, transformers: { spy: { code: () => ({ type: "spy", config: {}, push: (n3) => (f2.push(JSON.parse(JSON.stringify(n3))), n3) }) } } });
|
|
2657
|
+
u2 && u2();
|
|
2658
|
+
return { step: "source", name: n2.name, events: f2, calls: [], duration: Date.now() - e3 };
|
|
2659
|
+
})(n, e2);
|
|
2660
|
+
case "destination":
|
|
2661
|
+
return await (async function(n2, e3) {
|
|
2662
|
+
const { code: t2, config: o2 = {}, event: s2, consent: r2, env: i2, track: a2 } = n2, c2 = { functional: true, marketing: true, analytics: true };
|
|
2663
|
+
let u2 = [], f2 = i2;
|
|
2664
|
+
if (i2 && a2 && a2.length > 0) {
|
|
2665
|
+
const n3 = gn({ ...i2, simulation: a2 });
|
|
2666
|
+
f2 = n3.wrappedEnv, u2 = n3.calls;
|
|
2667
|
+
}
|
|
2668
|
+
const l2 = { ...o2 };
|
|
2669
|
+
f2 && (l2.env = f2);
|
|
2670
|
+
const { collector: d2 } = await ln({ consent: r2 || c2, destinations: { sim: { code: t2, config: l2 } } });
|
|
2671
|
+
return await d2.push(s2), { step: "destination", name: n2.name, events: [], calls: u2, duration: Date.now() - e3 };
|
|
2672
|
+
})(n, e2);
|
|
2673
|
+
}
|
|
2674
|
+
} catch (t2) {
|
|
2675
|
+
return { step: n.step, name: n.name, events: [], calls: [], duration: Date.now() - e2, error: t2 instanceof Error ? t2 : new Error(String(t2)) };
|
|
2172
2676
|
}
|
|
2173
2677
|
}
|
|
2174
2678
|
|
|
2679
|
+
// src/commands/simulate/simulator.ts
|
|
2680
|
+
init_cli_logger();
|
|
2681
|
+
|
|
2175
2682
|
// src/commands/simulate/env-loader.ts
|
|
2176
2683
|
async function loadDestinationEnvs(destinations) {
|
|
2177
2684
|
const envs = {};
|
|
@@ -2201,11 +2708,34 @@ async function loadDestinationEnvs(destinations) {
|
|
|
2201
2708
|
}
|
|
2202
2709
|
|
|
2203
2710
|
// src/commands/simulate/simulator.ts
|
|
2204
|
-
function
|
|
2205
|
-
return
|
|
2711
|
+
function createCollectorLoggerConfigInline(logger2, verbose) {
|
|
2712
|
+
return {
|
|
2713
|
+
level: verbose ? Level2.DEBUG : Level2.ERROR,
|
|
2714
|
+
handler: (level, message, context, scope) => {
|
|
2715
|
+
const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
|
|
2716
|
+
const hasContext = Object.keys(context).length > 0;
|
|
2717
|
+
const contextStr = hasContext ? ` ${JSON.stringify(context)}` : "";
|
|
2718
|
+
if (level === Level2.ERROR) {
|
|
2719
|
+
logger2.error(`${scopePath}${message}${contextStr}`);
|
|
2720
|
+
} else {
|
|
2721
|
+
logger2.debug(`${scopePath}${message}${contextStr}`);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
function callsToUsage(destName, calls) {
|
|
2727
|
+
if (!calls.length) return {};
|
|
2728
|
+
return {
|
|
2729
|
+
[destName]: calls.map((c2) => ({
|
|
2730
|
+
type: "call",
|
|
2731
|
+
path: c2.fn,
|
|
2732
|
+
args: c2.args,
|
|
2733
|
+
timestamp: c2.ts
|
|
2734
|
+
}))
|
|
2735
|
+
};
|
|
2206
2736
|
}
|
|
2207
2737
|
async function simulateCore(inputPath, event, options = {}) {
|
|
2208
|
-
const logger2 =
|
|
2738
|
+
const logger2 = createCLILogger({
|
|
2209
2739
|
verbose: options.verbose || false,
|
|
2210
2740
|
silent: options.silent || false,
|
|
2211
2741
|
json: options.json || false
|
|
@@ -2214,6 +2744,7 @@ async function simulateCore(inputPath, event, options = {}) {
|
|
|
2214
2744
|
logger2.debug(`Simulating event: ${JSON.stringify(event)}`);
|
|
2215
2745
|
const result = await executeSimulation(event, inputPath, options.platform, {
|
|
2216
2746
|
flow: options.flow,
|
|
2747
|
+
step: options.step,
|
|
2217
2748
|
logger: logger2,
|
|
2218
2749
|
verbose: options.verbose
|
|
2219
2750
|
});
|
|
@@ -2233,18 +2764,43 @@ function formatSimulationResult(result, options = {}) {
|
|
|
2233
2764
|
usage: result.usage,
|
|
2234
2765
|
duration: result.duration
|
|
2235
2766
|
};
|
|
2767
|
+
if (result.capturedEvents) {
|
|
2768
|
+
output.capturedEvents = result.capturedEvents;
|
|
2769
|
+
}
|
|
2770
|
+
if (result.exampleMatch) {
|
|
2771
|
+
output.exampleMatch = result.exampleMatch;
|
|
2772
|
+
}
|
|
2236
2773
|
return JSON.stringify(output, null, 2);
|
|
2237
2774
|
}
|
|
2775
|
+
const lines = [];
|
|
2238
2776
|
if (result.success) {
|
|
2239
|
-
|
|
2777
|
+
lines.push("Simulation completed");
|
|
2240
2778
|
} else {
|
|
2241
|
-
|
|
2779
|
+
lines.push(`Simulation failed: ${result.error}`);
|
|
2780
|
+
}
|
|
2781
|
+
if (result.capturedEvents) {
|
|
2782
|
+
lines.push(`Captured ${result.capturedEvents.length} event(s)`);
|
|
2783
|
+
for (const evt of result.capturedEvents) {
|
|
2784
|
+
lines.push(` - ${evt.name || "unknown"}`);
|
|
2785
|
+
}
|
|
2242
2786
|
}
|
|
2787
|
+
if (result.exampleMatch) {
|
|
2788
|
+
const em = result.exampleMatch;
|
|
2789
|
+
if (em.match) {
|
|
2790
|
+
lines.push(`Example "${em.name}" (${em.step}): PASS`);
|
|
2791
|
+
} else {
|
|
2792
|
+
lines.push(`Example "${em.name}" (${em.step}): FAIL`);
|
|
2793
|
+
if (em.diff) {
|
|
2794
|
+
lines.push(em.diff);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return lines.join("\n");
|
|
2243
2799
|
}
|
|
2244
2800
|
async function executeSimulation(event, inputPath, platformOverride, options = {}) {
|
|
2245
|
-
const
|
|
2801
|
+
const startTime = Date.now();
|
|
2246
2802
|
const tempDir = getTmpPath();
|
|
2247
|
-
const collectorLoggerConfig = options.logger ?
|
|
2803
|
+
const collectorLoggerConfig = options.logger ? createCollectorLoggerConfigInline(options.logger, options.verbose) : void 0;
|
|
2248
2804
|
try {
|
|
2249
2805
|
await fs11.ensureDir(tempDir);
|
|
2250
2806
|
const detected = await detectInput(inputPath, platformOverride);
|
|
@@ -2254,176 +2810,436 @@ async function executeSimulation(event, inputPath, platformOverride, options = {
|
|
|
2254
2810
|
);
|
|
2255
2811
|
}
|
|
2256
2812
|
const typedEvent = event;
|
|
2257
|
-
if (detected.type
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
inputPath,
|
|
2261
|
-
typedEvent,
|
|
2262
|
-
tempDir,
|
|
2263
|
-
startTime2,
|
|
2264
|
-
collectorLoggerConfig,
|
|
2265
|
-
options.flow
|
|
2813
|
+
if (detected.type !== "config") {
|
|
2814
|
+
throw new Error(
|
|
2815
|
+
`Input "${inputPath}" is not valid JSON config. simulate only accepts Flow.Setup config files.`
|
|
2266
2816
|
);
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2817
|
+
}
|
|
2818
|
+
return await executeConfigSimulation(
|
|
2819
|
+
detected.content,
|
|
2820
|
+
inputPath,
|
|
2821
|
+
typedEvent,
|
|
2822
|
+
tempDir,
|
|
2823
|
+
startTime,
|
|
2824
|
+
collectorLoggerConfig,
|
|
2825
|
+
options.flow,
|
|
2826
|
+
options.step
|
|
2827
|
+
);
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
const duration = Date.now() - startTime;
|
|
2830
|
+
return {
|
|
2831
|
+
success: false,
|
|
2832
|
+
error: getErrorMessage(error),
|
|
2833
|
+
duration
|
|
2834
|
+
};
|
|
2835
|
+
} finally {
|
|
2836
|
+
if (tempDir) {
|
|
2837
|
+
await fs11.remove(tempDir).catch(() => {
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
function parseStepTarget(stepTarget, flowConfig) {
|
|
2843
|
+
if (stepTarget) {
|
|
2844
|
+
const dotIndex = stepTarget.indexOf(".");
|
|
2845
|
+
if (dotIndex > -1) {
|
|
2846
|
+
const type = stepTarget.substring(0, dotIndex);
|
|
2847
|
+
const name = stepTarget.substring(dotIndex + 1);
|
|
2848
|
+
const section = flowConfig[type + "s"];
|
|
2849
|
+
if (!section?.[name]) {
|
|
2850
|
+
throw new Error(`Step "${stepTarget}" not found in flow config`);
|
|
2851
|
+
}
|
|
2852
|
+
return { type, name, config: section[name] };
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const destinations = flowConfig.destinations;
|
|
2856
|
+
if (destinations) {
|
|
2857
|
+
const [name, config] = Object.entries(destinations)[0];
|
|
2858
|
+
return {
|
|
2859
|
+
type: "destination",
|
|
2860
|
+
name,
|
|
2861
|
+
config
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
throw new Error("No destination found in flow config");
|
|
2865
|
+
}
|
|
2866
|
+
async function executeConfigSimulation(_content, configPath, typedEvent, tempDir, startTime, loggerConfig2, flowName, stepTarget) {
|
|
2867
|
+
const { flowConfig } = await loadFlowConfig(configPath, {
|
|
2868
|
+
flowName
|
|
2869
|
+
});
|
|
2870
|
+
const step = parseStepTarget(
|
|
2871
|
+
stepTarget,
|
|
2872
|
+
flowConfig
|
|
2873
|
+
);
|
|
2874
|
+
if (step.type === "destination") {
|
|
2875
|
+
const packageName = step.config.package;
|
|
2876
|
+
if (!packageName) {
|
|
2877
|
+
throw new Error(`Destination "${step.name}" has no package field`);
|
|
2878
|
+
}
|
|
2879
|
+
const destModule = await import(packageName);
|
|
2880
|
+
const code = destModule.default || Object.values(destModule)[0];
|
|
2881
|
+
const destinations = flowConfig.destinations;
|
|
2882
|
+
const envs = await loadDestinationEnvs(destinations || {});
|
|
2883
|
+
const destEnv = envs[step.name];
|
|
2884
|
+
const result = await mn({
|
|
2885
|
+
step: "destination",
|
|
2886
|
+
name: step.name,
|
|
2887
|
+
code,
|
|
2888
|
+
event: typedEvent,
|
|
2889
|
+
config: step.config.config,
|
|
2890
|
+
env: destEnv?.push,
|
|
2891
|
+
track: destEnv?.simulation
|
|
2892
|
+
});
|
|
2893
|
+
const duration = Date.now() - startTime;
|
|
2894
|
+
return {
|
|
2895
|
+
success: !result.error,
|
|
2896
|
+
error: result.error?.message,
|
|
2897
|
+
usage: callsToUsage(step.name, result.calls),
|
|
2898
|
+
duration,
|
|
2899
|
+
logs: []
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
if (step.type === "transformer") {
|
|
2903
|
+
const packageName = step.config.package;
|
|
2904
|
+
if (!packageName) {
|
|
2905
|
+
throw new Error(`Transformer "${step.name}" has no package field`);
|
|
2906
|
+
}
|
|
2907
|
+
const mod = await import(packageName);
|
|
2908
|
+
const code = mod.default || Object.values(mod)[0];
|
|
2909
|
+
const result = await mn({
|
|
2910
|
+
step: "transformer",
|
|
2911
|
+
name: step.name,
|
|
2912
|
+
code,
|
|
2913
|
+
event: typedEvent,
|
|
2914
|
+
config: step.config.config
|
|
2915
|
+
});
|
|
2916
|
+
const duration = Date.now() - startTime;
|
|
2917
|
+
return {
|
|
2918
|
+
success: !result.error,
|
|
2919
|
+
error: result.error?.message,
|
|
2920
|
+
capturedEvents: result.events,
|
|
2921
|
+
duration,
|
|
2922
|
+
usage: {},
|
|
2923
|
+
logs: []
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
throw new Error(`Unknown step type: ${step.type}`);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// src/commands/simulate/source-simulator.ts
|
|
2930
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
2931
|
+
async function loadSourcePackage(packageName) {
|
|
2932
|
+
const mainModule = await import(packageName);
|
|
2933
|
+
const code = mainModule.default || Object.values(mainModule)[0];
|
|
2934
|
+
if (!code || typeof code !== "function") {
|
|
2935
|
+
throw new Error(`Package ${packageName} missing source init function`);
|
|
2936
|
+
}
|
|
2937
|
+
let setup;
|
|
2938
|
+
try {
|
|
2939
|
+
const devModule = await import(`${packageName}/dev`);
|
|
2940
|
+
const examples = devModule.examples || devModule.default?.examples;
|
|
2941
|
+
if (examples?.setup && typeof examples.setup === "function") {
|
|
2942
|
+
setup = examples.setup;
|
|
2943
|
+
}
|
|
2944
|
+
} catch {
|
|
2945
|
+
}
|
|
2946
|
+
return { code, setup };
|
|
2947
|
+
}
|
|
2948
|
+
async function simulateSourceCLI(flowConfig, setupInput, options) {
|
|
2949
|
+
const startTime = Date.now();
|
|
2950
|
+
try {
|
|
2951
|
+
const sources = flowConfig.sources;
|
|
2952
|
+
if (!sources) {
|
|
2953
|
+
throw new Error("Flow config has no sources");
|
|
2954
|
+
}
|
|
2955
|
+
const sourceConfig = sources[options.sourceStep];
|
|
2956
|
+
if (!sourceConfig) {
|
|
2957
|
+
const available = Object.keys(sources).join(", ");
|
|
2958
|
+
throw new Error(
|
|
2959
|
+
`Source "${options.sourceStep}" not found. Available: ${available}`
|
|
2275
2960
|
);
|
|
2276
2961
|
}
|
|
2962
|
+
if (!sourceConfig.package) {
|
|
2963
|
+
throw new Error(`Source "${options.sourceStep}" has no package field`);
|
|
2964
|
+
}
|
|
2965
|
+
const { code, setup } = await loadSourcePackage(sourceConfig.package);
|
|
2966
|
+
const virtualConsole = new VirtualConsole();
|
|
2967
|
+
const dom = new JSDOM(
|
|
2968
|
+
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
2969
|
+
{
|
|
2970
|
+
url: "http://localhost",
|
|
2971
|
+
runScripts: "dangerously",
|
|
2972
|
+
pretendToBeVisual: true,
|
|
2973
|
+
virtualConsole
|
|
2974
|
+
}
|
|
2975
|
+
);
|
|
2976
|
+
const env = {
|
|
2977
|
+
window: dom.window,
|
|
2978
|
+
document: dom.window.document,
|
|
2979
|
+
localStorage: dom.window.localStorage
|
|
2980
|
+
};
|
|
2981
|
+
const result = await mn({
|
|
2982
|
+
step: "source",
|
|
2983
|
+
name: options.sourceStep,
|
|
2984
|
+
code,
|
|
2985
|
+
config: sourceConfig.config || {},
|
|
2986
|
+
setup,
|
|
2987
|
+
input: setupInput,
|
|
2988
|
+
env
|
|
2989
|
+
});
|
|
2990
|
+
dom.window.close();
|
|
2991
|
+
return {
|
|
2992
|
+
success: !result.error,
|
|
2993
|
+
error: result.error?.message,
|
|
2994
|
+
capturedEvents: result.events,
|
|
2995
|
+
duration: Date.now() - startTime
|
|
2996
|
+
};
|
|
2277
2997
|
} catch (error) {
|
|
2278
|
-
const duration = Date.now() - startTime2;
|
|
2279
2998
|
return {
|
|
2280
2999
|
success: false,
|
|
2281
3000
|
error: getErrorMessage(error),
|
|
2282
|
-
duration
|
|
3001
|
+
duration: Date.now() - startTime
|
|
2283
3002
|
};
|
|
2284
|
-
} finally {
|
|
2285
|
-
if (tempDir) {
|
|
2286
|
-
await fs11.remove(tempDir).catch(() => {
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
3003
|
}
|
|
2290
3004
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
...platform === "web" ? {
|
|
2308
|
-
format: "iife",
|
|
2309
|
-
platform: "browser",
|
|
2310
|
-
windowCollector: "collector",
|
|
2311
|
-
windowElb: "elb"
|
|
2312
|
-
} : {
|
|
2313
|
-
format: "esm",
|
|
2314
|
-
platform: "node"
|
|
2315
|
-
}
|
|
2316
|
-
};
|
|
2317
|
-
await bundleCore(
|
|
2318
|
-
flowConfig,
|
|
2319
|
-
simulationBuildOptions,
|
|
2320
|
-
createLogger({ silent: true }),
|
|
2321
|
-
false
|
|
2322
|
-
);
|
|
2323
|
-
const envs = await loadDestinationEnvs(destinations || {});
|
|
2324
|
-
let result;
|
|
2325
|
-
if (platform === "web") {
|
|
2326
|
-
result = await executeInJSDOM(
|
|
2327
|
-
tempOutput,
|
|
2328
|
-
destinations || {},
|
|
2329
|
-
typedEvent,
|
|
2330
|
-
tracker,
|
|
2331
|
-
envs,
|
|
2332
|
-
1e4
|
|
3005
|
+
|
|
3006
|
+
// src/commands/simulate/index.ts
|
|
3007
|
+
init_cli_logger();
|
|
3008
|
+
|
|
3009
|
+
// src/commands/simulate/example-loader.ts
|
|
3010
|
+
function findExample(config, exampleName, stepTarget) {
|
|
3011
|
+
if (stepTarget) {
|
|
3012
|
+
return findExampleInStep(config, exampleName, stepTarget);
|
|
3013
|
+
}
|
|
3014
|
+
return findExampleAcrossSteps(config, exampleName);
|
|
3015
|
+
}
|
|
3016
|
+
function findExampleInStep(config, exampleName, stepTarget) {
|
|
3017
|
+
const dotIndex = stepTarget.indexOf(".");
|
|
3018
|
+
if (dotIndex === -1) {
|
|
3019
|
+
throw new Error(
|
|
3020
|
+
`Invalid --step format: "${stepTarget}". Expected "type.name" (e.g. "destination.gtag")`
|
|
2333
3021
|
);
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
3022
|
+
}
|
|
3023
|
+
const type = stepTarget.substring(0, dotIndex);
|
|
3024
|
+
const name = stepTarget.substring(dotIndex + 1);
|
|
3025
|
+
const stepMap = getStepMap(config, type);
|
|
3026
|
+
if (!stepMap) {
|
|
3027
|
+
throw new Error(`No ${type}s found in flow config`);
|
|
3028
|
+
}
|
|
3029
|
+
const step = stepMap[name];
|
|
3030
|
+
if (!step) {
|
|
3031
|
+
const available = Object.keys(stepMap).join(", ");
|
|
3032
|
+
throw new Error(`${type} "${name}" not found. Available: ${available}`);
|
|
3033
|
+
}
|
|
3034
|
+
const examples = step.examples;
|
|
3035
|
+
if (!examples || !examples[exampleName]) {
|
|
3036
|
+
const available = examples ? Object.keys(examples).join(", ") : "none";
|
|
3037
|
+
throw new Error(
|
|
3038
|
+
`Example "${exampleName}" not found in ${type} "${name}". Available: ${available}`
|
|
2343
3039
|
);
|
|
2344
3040
|
}
|
|
2345
|
-
const duration = Date.now() - startTime2;
|
|
2346
3041
|
return {
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
logs: []
|
|
3042
|
+
stepType: type,
|
|
3043
|
+
stepName: name,
|
|
3044
|
+
exampleName,
|
|
3045
|
+
example: examples[exampleName]
|
|
2352
3046
|
};
|
|
2353
3047
|
}
|
|
2354
|
-
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
loggerConfig2 ? { logger: loggerConfig2 } : {}
|
|
3048
|
+
function findExampleAcrossSteps(config, exampleName) {
|
|
3049
|
+
const matches = [];
|
|
3050
|
+
const stepTypes = ["source", "transformer", "destination"];
|
|
3051
|
+
for (const type of stepTypes) {
|
|
3052
|
+
const stepMap = getStepMap(config, type);
|
|
3053
|
+
if (!stepMap) continue;
|
|
3054
|
+
for (const [name, step] of Object.entries(stepMap)) {
|
|
3055
|
+
const examples = step.examples;
|
|
3056
|
+
if (examples && examples[exampleName]) {
|
|
3057
|
+
matches.push({
|
|
3058
|
+
stepType: type,
|
|
3059
|
+
stepName: name,
|
|
3060
|
+
exampleName,
|
|
3061
|
+
example: examples[exampleName]
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
if (matches.length === 0) {
|
|
3067
|
+
throw new Error(`Example "${exampleName}" not found in any step`);
|
|
3068
|
+
}
|
|
3069
|
+
if (matches.length > 1) {
|
|
3070
|
+
const locations = matches.map((m2) => `${m2.stepType}.${m2.stepName}`).join(", ");
|
|
3071
|
+
throw new Error(
|
|
3072
|
+
`Example "${exampleName}" found in multiple steps: ${locations}. Use --step to disambiguate.`
|
|
2380
3073
|
);
|
|
2381
3074
|
}
|
|
2382
|
-
|
|
3075
|
+
return matches[0];
|
|
3076
|
+
}
|
|
3077
|
+
function getStepMap(config, type) {
|
|
3078
|
+
switch (type) {
|
|
3079
|
+
case "source":
|
|
3080
|
+
return config.sources;
|
|
3081
|
+
case "transformer":
|
|
3082
|
+
return config.transformers;
|
|
3083
|
+
case "destination":
|
|
3084
|
+
return config.destinations;
|
|
3085
|
+
default:
|
|
3086
|
+
throw new Error(
|
|
3087
|
+
`Invalid step type: "${type}". Must be "source", "transformer", or "destination"`
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/commands/simulate/compare.ts
|
|
3093
|
+
function compareOutput(expected, actual) {
|
|
3094
|
+
const expectedStr = JSON.stringify(expected, null, 2);
|
|
3095
|
+
const actualStr = JSON.stringify(actual, null, 2);
|
|
3096
|
+
if (expectedStr === actualStr) {
|
|
3097
|
+
return { expected, actual, match: true };
|
|
3098
|
+
}
|
|
2383
3099
|
return {
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
3100
|
+
expected,
|
|
3101
|
+
actual,
|
|
3102
|
+
match: false,
|
|
3103
|
+
diff: `Expected:
|
|
3104
|
+
${expectedStr}
|
|
3105
|
+
|
|
3106
|
+
Actual:
|
|
3107
|
+
${actualStr}`
|
|
2389
3108
|
};
|
|
2390
3109
|
}
|
|
2391
3110
|
|
|
2392
3111
|
// src/commands/simulate/index.ts
|
|
2393
3112
|
async function simulateCommand(options) {
|
|
2394
|
-
const logger2 =
|
|
2395
|
-
const
|
|
3113
|
+
const logger2 = createCLILogger({ ...options, stderr: true });
|
|
3114
|
+
const startTime = Date.now();
|
|
2396
3115
|
try {
|
|
2397
3116
|
let config;
|
|
2398
3117
|
if (isStdinPiped() && !options.config) {
|
|
2399
3118
|
const stdinContent = await readStdin();
|
|
2400
3119
|
const fs14 = await import("fs-extra");
|
|
2401
|
-
const
|
|
3120
|
+
const path14 = await import("path");
|
|
2402
3121
|
const tmpPath = getTmpPath(void 0, "stdin-simulate.json");
|
|
2403
|
-
await fs14.default.ensureDir(
|
|
3122
|
+
await fs14.default.ensureDir(path14.default.dirname(tmpPath));
|
|
2404
3123
|
await fs14.default.writeFile(tmpPath, stdinContent, "utf-8");
|
|
2405
3124
|
config = tmpPath;
|
|
2406
3125
|
} else {
|
|
2407
3126
|
config = options.config || "bundle.config.json";
|
|
2408
3127
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
3128
|
+
let event;
|
|
3129
|
+
let exampleContext;
|
|
3130
|
+
if (options.example) {
|
|
3131
|
+
const rawConfig = await loadJsonConfig(config);
|
|
3132
|
+
const setup = validateFlowSetup(rawConfig);
|
|
3133
|
+
const flowNames = Object.keys(setup.flows);
|
|
3134
|
+
let flowName = options.flow;
|
|
3135
|
+
if (!flowName) {
|
|
3136
|
+
if (flowNames.length === 1) {
|
|
3137
|
+
flowName = flowNames[0];
|
|
3138
|
+
} else {
|
|
3139
|
+
throw new Error(
|
|
3140
|
+
`Multiple flows found. Use --flow to specify which flow contains the example.
|
|
3141
|
+
Available flows: ${flowNames.join(", ")}`
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
const flowConfig = setup.flows[flowName];
|
|
3146
|
+
if (!flowConfig) {
|
|
3147
|
+
throw new Error(
|
|
3148
|
+
`Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
const found = findExample(flowConfig, options.example, options.step);
|
|
3152
|
+
if (found.example.in === void 0) {
|
|
3153
|
+
throw new Error(
|
|
3154
|
+
`Example "${options.example}" in ${found.stepType}.${found.stepName} has no "in" value`
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
event = found.example.in;
|
|
3158
|
+
exampleContext = {
|
|
3159
|
+
stepType: found.stepType,
|
|
3160
|
+
stepName: found.stepName,
|
|
3161
|
+
expected: found.example.out
|
|
3162
|
+
};
|
|
3163
|
+
} else {
|
|
3164
|
+
event = await loadJsonFromSource(options.event, {
|
|
3165
|
+
name: "event"
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
const isSourceSimulation = exampleContext?.stepType === "source" || options.step?.startsWith("source.");
|
|
3169
|
+
let result;
|
|
3170
|
+
if (isSourceSimulation) {
|
|
3171
|
+
const rawConfig = await loadJsonConfig(config);
|
|
3172
|
+
const setup = validateFlowSetup(rawConfig);
|
|
3173
|
+
const flowNames = Object.keys(setup.flows);
|
|
3174
|
+
const flowName = options.flow || (flowNames.length === 1 ? flowNames[0] : void 0);
|
|
3175
|
+
if (!flowName) {
|
|
3176
|
+
throw new Error(
|
|
3177
|
+
`Multiple flows found. Use --flow to specify which flow.
|
|
3178
|
+
Available: ${flowNames.join(", ")}`
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
const flowConfig = setup.flows[flowName];
|
|
3182
|
+
if (!flowConfig) {
|
|
3183
|
+
throw new Error(
|
|
3184
|
+
`Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
|
|
3185
|
+
);
|
|
3186
|
+
}
|
|
3187
|
+
const sourceStep = exampleContext?.stepName || options.step.substring("source.".length);
|
|
3188
|
+
result = await simulateSourceCLI(
|
|
3189
|
+
flowConfig,
|
|
3190
|
+
event,
|
|
3191
|
+
{
|
|
3192
|
+
flow: options.flow,
|
|
3193
|
+
sourceStep,
|
|
3194
|
+
json: options.json,
|
|
3195
|
+
verbose: options.verbose,
|
|
3196
|
+
silent: options.silent
|
|
3197
|
+
}
|
|
3198
|
+
);
|
|
3199
|
+
} else {
|
|
3200
|
+
const stepTarget = exampleContext ? `${exampleContext.stepType}.${exampleContext.stepName}` : options.step;
|
|
3201
|
+
result = await simulateCore(config, event, {
|
|
3202
|
+
flow: options.flow,
|
|
3203
|
+
json: options.json,
|
|
3204
|
+
verbose: options.verbose,
|
|
3205
|
+
silent: options.silent,
|
|
3206
|
+
step: stepTarget
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
let exampleMatch;
|
|
3210
|
+
if (exampleContext && result.success) {
|
|
3211
|
+
const stepKey = `${exampleContext.stepType}.${exampleContext.stepName}`;
|
|
3212
|
+
if (exampleContext.expected === false) {
|
|
3213
|
+
const calls = result.usage?.[exampleContext.stepName];
|
|
3214
|
+
const wasFiltered = !calls || calls.length === 0;
|
|
3215
|
+
exampleMatch = {
|
|
3216
|
+
name: options.example,
|
|
3217
|
+
step: stepKey,
|
|
3218
|
+
expected: false,
|
|
3219
|
+
actual: wasFiltered ? false : calls,
|
|
3220
|
+
match: wasFiltered,
|
|
3221
|
+
diff: wasFiltered ? void 0 : `Expected event to be filtered, but ${calls.length} API call(s) were made`
|
|
3222
|
+
};
|
|
3223
|
+
} else if (exampleContext.expected !== void 0) {
|
|
3224
|
+
const actual = result.usage?.[exampleContext.stepName] ?? [];
|
|
3225
|
+
exampleMatch = {
|
|
3226
|
+
name: options.example,
|
|
3227
|
+
step: stepKey,
|
|
3228
|
+
...compareOutput(exampleContext.expected, actual)
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
2418
3232
|
const resultWithDuration = {
|
|
2419
3233
|
...result,
|
|
2420
|
-
duration: (Date.now() -
|
|
3234
|
+
duration: (Date.now() - startTime) / 1e3,
|
|
3235
|
+
...exampleMatch ? { exampleMatch } : {}
|
|
2421
3236
|
};
|
|
2422
3237
|
const formatted = formatSimulationResult(resultWithDuration, {
|
|
2423
3238
|
json: options.json
|
|
2424
3239
|
});
|
|
2425
3240
|
await writeResult(formatted + "\n", { output: options.output });
|
|
2426
|
-
|
|
3241
|
+
const exitCode = !result.success || exampleMatch && !exampleMatch.match ? 1 : 0;
|
|
3242
|
+
process.exit(exitCode);
|
|
2427
3243
|
} catch (error) {
|
|
2428
3244
|
const errorMessage = getErrorMessage(error);
|
|
2429
3245
|
if (options.json) {
|
|
@@ -2431,7 +3247,7 @@ async function simulateCommand(options) {
|
|
|
2431
3247
|
{
|
|
2432
3248
|
success: false,
|
|
2433
3249
|
error: errorMessage,
|
|
2434
|
-
duration: (Date.now() -
|
|
3250
|
+
duration: (Date.now() - startTime) / 1e3
|
|
2435
3251
|
},
|
|
2436
3252
|
null,
|
|
2437
3253
|
2
|
|
@@ -2449,28 +3265,102 @@ async function simulate(configOrPath, event, options = {}) {
|
|
|
2449
3265
|
"simulate() currently only supports config file paths. Config object support will be added in a future version. Please provide a path to a configuration file."
|
|
2450
3266
|
);
|
|
2451
3267
|
}
|
|
2452
|
-
|
|
3268
|
+
let resolvedEvent = event;
|
|
3269
|
+
let exampleContext;
|
|
3270
|
+
if (options.example) {
|
|
3271
|
+
const rawConfig = await loadJsonConfig(configOrPath);
|
|
3272
|
+
const setup = validateFlowSetup(rawConfig);
|
|
3273
|
+
const flowNames = Object.keys(setup.flows);
|
|
3274
|
+
let flowName = options.flow;
|
|
3275
|
+
if (!flowName) {
|
|
3276
|
+
if (flowNames.length === 1) {
|
|
3277
|
+
flowName = flowNames[0];
|
|
3278
|
+
} else {
|
|
3279
|
+
throw new Error(
|
|
3280
|
+
`Multiple flows found. Use --flow to specify which flow contains the example.
|
|
3281
|
+
Available flows: ${flowNames.join(", ")}`
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
const flowConfig = setup.flows[flowName];
|
|
3286
|
+
if (!flowConfig) {
|
|
3287
|
+
throw new Error(
|
|
3288
|
+
`Flow "${flowName}" not found. Available: ${flowNames.join(", ")}`
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
const found = findExample(flowConfig, options.example, options.step);
|
|
3292
|
+
if (found.example.in === void 0) {
|
|
3293
|
+
throw new Error(
|
|
3294
|
+
`Example "${options.example}" in ${found.stepType}.${found.stepName} has no "in" value`
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
resolvedEvent = found.example.in;
|
|
3298
|
+
exampleContext = {
|
|
3299
|
+
stepType: found.stepType,
|
|
3300
|
+
stepName: found.stepName,
|
|
3301
|
+
expected: found.example.out
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
const result = await simulateCore(configOrPath, resolvedEvent, {
|
|
2453
3305
|
json: options.json ?? false,
|
|
2454
3306
|
verbose: options.verbose ?? false,
|
|
2455
3307
|
flow: options.flow,
|
|
2456
3308
|
platform: options.platform
|
|
2457
3309
|
});
|
|
3310
|
+
if (exampleContext && result.success) {
|
|
3311
|
+
const stepKey = `${exampleContext.stepType}.${exampleContext.stepName}`;
|
|
3312
|
+
if (exampleContext.expected === false) {
|
|
3313
|
+
const calls = result.usage?.[exampleContext.stepName];
|
|
3314
|
+
const wasFiltered = !calls || calls.length === 0;
|
|
3315
|
+
result.exampleMatch = {
|
|
3316
|
+
name: options.example,
|
|
3317
|
+
step: stepKey,
|
|
3318
|
+
expected: false,
|
|
3319
|
+
actual: wasFiltered ? false : calls,
|
|
3320
|
+
match: wasFiltered,
|
|
3321
|
+
diff: wasFiltered ? void 0 : `Expected event to be filtered, but ${calls.length} API call(s) were made`
|
|
3322
|
+
};
|
|
3323
|
+
} else if (exampleContext.expected !== void 0) {
|
|
3324
|
+
const actual = result.usage?.[exampleContext.stepName] ?? [];
|
|
3325
|
+
result.exampleMatch = {
|
|
3326
|
+
name: options.example,
|
|
3327
|
+
step: stepKey,
|
|
3328
|
+
...compareOutput(exampleContext.expected, actual)
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
return result;
|
|
2458
3333
|
}
|
|
2459
3334
|
|
|
2460
3335
|
// src/commands/push/index.ts
|
|
2461
|
-
|
|
3336
|
+
init_cli_logger();
|
|
3337
|
+
import path11 from "path";
|
|
2462
3338
|
import { JSDOM as JSDOM2, VirtualConsole as VirtualConsole2 } from "jsdom";
|
|
2463
3339
|
import fs12 from "fs-extra";
|
|
2464
|
-
import {
|
|
2465
|
-
getPlatform as getPlatform4
|
|
2466
|
-
} from "@walkeros/core";
|
|
3340
|
+
import { getPlatform as getPlatform3 } from "@walkeros/core";
|
|
2467
3341
|
import { schemas as schemas2 } from "@walkeros/core/dev";
|
|
3342
|
+
import { Level as Level3 } from "@walkeros/core";
|
|
3343
|
+
function createCollectorLoggerConfig(logger2, verbose) {
|
|
3344
|
+
return {
|
|
3345
|
+
level: verbose ? Level3.DEBUG : Level3.ERROR,
|
|
3346
|
+
handler: (level, message, context, scope) => {
|
|
3347
|
+
const scopePath = scope.length > 0 ? `[${scope.join(":")}] ` : "";
|
|
3348
|
+
const hasContext = Object.keys(context).length > 0;
|
|
3349
|
+
const contextStr = hasContext ? ` ${JSON.stringify(context)}` : "";
|
|
3350
|
+
if (level === Level3.ERROR) {
|
|
3351
|
+
logger2.error(`${scopePath}${message}${contextStr}`);
|
|
3352
|
+
} else {
|
|
3353
|
+
logger2.debug(`${scopePath}${message}${contextStr}`);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
2468
3358
|
async function pushCore(inputPath, event, options = {}) {
|
|
2469
|
-
const logger2 =
|
|
3359
|
+
const logger2 = createCLILogger({
|
|
2470
3360
|
silent: options.silent,
|
|
2471
3361
|
verbose: options.verbose
|
|
2472
3362
|
});
|
|
2473
|
-
const
|
|
3363
|
+
const startTime = Date.now();
|
|
2474
3364
|
let tempDir;
|
|
2475
3365
|
try {
|
|
2476
3366
|
let loadedEvent = event;
|
|
@@ -2491,7 +3381,7 @@ async function pushCore(inputPath, event, options = {}) {
|
|
|
2491
3381
|
data: parsedEvent.data || {}
|
|
2492
3382
|
};
|
|
2493
3383
|
if (!validatedEvent.name.includes(" ")) {
|
|
2494
|
-
logger2.
|
|
3384
|
+
logger2.info(
|
|
2495
3385
|
`Warning: Event name "${validatedEvent.name}" should follow "ENTITY ACTION" format (e.g., "page view")`
|
|
2496
3386
|
);
|
|
2497
3387
|
}
|
|
@@ -2515,10 +3405,6 @@ async function pushCore(inputPath, event, options = {}) {
|
|
|
2515
3405
|
}
|
|
2516
3406
|
);
|
|
2517
3407
|
} else {
|
|
2518
|
-
const collectorLoggerConfig = createCollectorLoggerConfig(
|
|
2519
|
-
logger2,
|
|
2520
|
-
options.verbose
|
|
2521
|
-
);
|
|
2522
3408
|
result = await executeBundlePush(
|
|
2523
3409
|
detected.content,
|
|
2524
3410
|
detected.platform,
|
|
@@ -2527,14 +3413,14 @@ async function pushCore(inputPath, event, options = {}) {
|
|
|
2527
3413
|
(dir) => {
|
|
2528
3414
|
tempDir = dir;
|
|
2529
3415
|
},
|
|
2530
|
-
{ logger:
|
|
3416
|
+
{ logger: createCollectorLoggerConfig(logger2, options.verbose) }
|
|
2531
3417
|
);
|
|
2532
3418
|
}
|
|
2533
3419
|
return result;
|
|
2534
3420
|
} catch (error) {
|
|
2535
3421
|
return {
|
|
2536
3422
|
success: false,
|
|
2537
|
-
duration: Date.now() -
|
|
3423
|
+
duration: Date.now() - startTime,
|
|
2538
3424
|
error: getErrorMessage(error)
|
|
2539
3425
|
};
|
|
2540
3426
|
} finally {
|
|
@@ -2545,14 +3431,14 @@ async function pushCore(inputPath, event, options = {}) {
|
|
|
2545
3431
|
}
|
|
2546
3432
|
}
|
|
2547
3433
|
async function pushCommand(options) {
|
|
2548
|
-
const logger2 =
|
|
2549
|
-
const
|
|
3434
|
+
const logger2 = createCLILogger({ ...options, stderr: true });
|
|
3435
|
+
const startTime = Date.now();
|
|
2550
3436
|
try {
|
|
2551
3437
|
let config;
|
|
2552
3438
|
if (isStdinPiped() && !options.config) {
|
|
2553
3439
|
const stdinContent = await readStdin();
|
|
2554
3440
|
const tmpPath = getTmpPath(void 0, "stdin-push.json");
|
|
2555
|
-
await fs12.ensureDir(
|
|
3441
|
+
await fs12.ensureDir(path11.dirname(tmpPath));
|
|
2556
3442
|
await fs12.writeFile(tmpPath, stdinContent, "utf-8");
|
|
2557
3443
|
config = tmpPath;
|
|
2558
3444
|
} else {
|
|
@@ -2566,7 +3452,7 @@ async function pushCommand(options) {
|
|
|
2566
3452
|
silent: options.silent,
|
|
2567
3453
|
platform: options.platform
|
|
2568
3454
|
});
|
|
2569
|
-
const duration = Date.now() -
|
|
3455
|
+
const duration = Date.now() - startTime;
|
|
2570
3456
|
let output;
|
|
2571
3457
|
if (options.json) {
|
|
2572
3458
|
output = JSON.stringify(
|
|
@@ -2600,7 +3486,7 @@ async function pushCommand(options) {
|
|
|
2600
3486
|
await writeResult(output + "\n", { output: options.output });
|
|
2601
3487
|
process.exit(result.success ? 0 : 1);
|
|
2602
3488
|
} catch (error) {
|
|
2603
|
-
const duration = Date.now() -
|
|
3489
|
+
const duration = Date.now() - startTime;
|
|
2604
3490
|
const errorMessage = getErrorMessage(error);
|
|
2605
3491
|
if (options.json) {
|
|
2606
3492
|
const errorOutput = JSON.stringify(
|
|
@@ -2634,7 +3520,7 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
|
|
|
2634
3520
|
flowName: options.flow,
|
|
2635
3521
|
logger: logger2
|
|
2636
3522
|
});
|
|
2637
|
-
const platform =
|
|
3523
|
+
const platform = getPlatform3(flowConfig);
|
|
2638
3524
|
logger2.debug("Bundling flow configuration");
|
|
2639
3525
|
const configDir = buildOptions.configDir || process.cwd();
|
|
2640
3526
|
const tempDir = getTmpPath(
|
|
@@ -2643,7 +3529,7 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
|
|
|
2643
3529
|
);
|
|
2644
3530
|
setTempDir(tempDir);
|
|
2645
3531
|
await fs12.ensureDir(tempDir);
|
|
2646
|
-
const tempPath =
|
|
3532
|
+
const tempPath = path11.join(
|
|
2647
3533
|
tempDir,
|
|
2648
3534
|
`bundle.${platform === "web" ? "js" : "mjs"}`
|
|
2649
3535
|
);
|
|
@@ -2664,12 +3550,8 @@ async function executeConfigPush(options, validatedEvent, logger2, setTempDir) {
|
|
|
2664
3550
|
return executeWebPush(tempPath, validatedEvent, logger2);
|
|
2665
3551
|
} else if (platform === "server") {
|
|
2666
3552
|
logger2.debug("Executing in server environment (Node.js)");
|
|
2667
|
-
const collectorLoggerConfig = createCollectorLoggerConfig(
|
|
2668
|
-
logger2,
|
|
2669
|
-
options.verbose
|
|
2670
|
-
);
|
|
2671
3553
|
return executeServerPush(tempPath, validatedEvent, logger2, 6e4, {
|
|
2672
|
-
logger:
|
|
3554
|
+
logger: createCollectorLoggerConfig(logger2, options.verbose)
|
|
2673
3555
|
});
|
|
2674
3556
|
} else {
|
|
2675
3557
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
@@ -2682,7 +3564,7 @@ async function executeBundlePush(bundleContent, platform, validatedEvent, logger
|
|
|
2682
3564
|
);
|
|
2683
3565
|
setTempDir(tempDir);
|
|
2684
3566
|
await fs12.ensureDir(tempDir);
|
|
2685
|
-
const tempPath =
|
|
3567
|
+
const tempPath = path11.join(
|
|
2686
3568
|
tempDir,
|
|
2687
3569
|
`bundle.${platform === "server" ? "mjs" : "js"}`
|
|
2688
3570
|
);
|
|
@@ -2697,7 +3579,7 @@ async function executeBundlePush(bundleContent, platform, validatedEvent, logger
|
|
|
2697
3579
|
}
|
|
2698
3580
|
}
|
|
2699
3581
|
async function executeWebPush(bundlePath, event, logger2) {
|
|
2700
|
-
const
|
|
3582
|
+
const startTime = Date.now();
|
|
2701
3583
|
try {
|
|
2702
3584
|
const virtualConsole = new VirtualConsole2();
|
|
2703
3585
|
const dom = new JSDOM2("<!DOCTYPE html><html><body></body></html>", {
|
|
@@ -2711,14 +3593,14 @@ async function executeWebPush(bundlePath, event, logger2) {
|
|
|
2711
3593
|
const bundleCode = await fs12.readFile(bundlePath, "utf8");
|
|
2712
3594
|
window.eval(bundleCode);
|
|
2713
3595
|
logger2.debug("Waiting for collector...");
|
|
2714
|
-
await
|
|
3596
|
+
await waitForWindowProperty(
|
|
2715
3597
|
window,
|
|
2716
3598
|
"collector",
|
|
2717
3599
|
5e3
|
|
2718
3600
|
);
|
|
2719
3601
|
const windowObj = window;
|
|
2720
3602
|
const collector = windowObj.collector;
|
|
2721
|
-
logger2.
|
|
3603
|
+
logger2.info(`Pushing event: ${event.name}`);
|
|
2722
3604
|
const elbResult = await collector.push({
|
|
2723
3605
|
name: event.name,
|
|
2724
3606
|
data: event.data
|
|
@@ -2726,21 +3608,22 @@ async function executeWebPush(bundlePath, event, logger2) {
|
|
|
2726
3608
|
return {
|
|
2727
3609
|
success: true,
|
|
2728
3610
|
elbResult,
|
|
2729
|
-
duration: Date.now() -
|
|
3611
|
+
duration: Date.now() - startTime
|
|
2730
3612
|
};
|
|
2731
3613
|
} catch (error) {
|
|
2732
3614
|
return {
|
|
2733
3615
|
success: false,
|
|
2734
|
-
duration: Date.now() -
|
|
3616
|
+
duration: Date.now() - startTime,
|
|
2735
3617
|
error: getErrorMessage(error)
|
|
2736
3618
|
};
|
|
2737
3619
|
}
|
|
2738
3620
|
}
|
|
2739
3621
|
async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, context = {}) {
|
|
2740
|
-
const
|
|
3622
|
+
const startTime = Date.now();
|
|
3623
|
+
let timer;
|
|
2741
3624
|
try {
|
|
2742
|
-
const timeoutPromise = new Promise((
|
|
2743
|
-
setTimeout(
|
|
3625
|
+
const timeoutPromise = new Promise((_2, reject) => {
|
|
3626
|
+
timer = setTimeout(
|
|
2744
3627
|
() => reject(new Error(`Server push timeout after ${timeout}ms`)),
|
|
2745
3628
|
timeout
|
|
2746
3629
|
);
|
|
@@ -2759,7 +3642,7 @@ async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, cont
|
|
|
2759
3642
|
);
|
|
2760
3643
|
}
|
|
2761
3644
|
const { collector } = result;
|
|
2762
|
-
logger2.
|
|
3645
|
+
logger2.info(`Pushing event: ${event.name}`);
|
|
2763
3646
|
const elbResult = await collector.push({
|
|
2764
3647
|
name: event.name,
|
|
2765
3648
|
data: event.data
|
|
@@ -2767,24 +3650,26 @@ async function executeServerPush(bundlePath, event, logger2, timeout = 6e4, cont
|
|
|
2767
3650
|
return {
|
|
2768
3651
|
success: true,
|
|
2769
3652
|
elbResult,
|
|
2770
|
-
duration: Date.now() -
|
|
3653
|
+
duration: Date.now() - startTime
|
|
2771
3654
|
};
|
|
2772
3655
|
})();
|
|
2773
3656
|
return await Promise.race([executePromise, timeoutPromise]);
|
|
2774
3657
|
} catch (error) {
|
|
2775
3658
|
return {
|
|
2776
3659
|
success: false,
|
|
2777
|
-
duration: Date.now() -
|
|
3660
|
+
duration: Date.now() - startTime,
|
|
2778
3661
|
error: getErrorMessage(error)
|
|
2779
3662
|
};
|
|
3663
|
+
} finally {
|
|
3664
|
+
clearTimeout(timer);
|
|
2780
3665
|
}
|
|
2781
3666
|
}
|
|
2782
|
-
function
|
|
2783
|
-
return new Promise((
|
|
3667
|
+
function waitForWindowProperty(window, prop, timeout = 5e3) {
|
|
3668
|
+
return new Promise((resolve2, reject) => {
|
|
2784
3669
|
const start = Date.now();
|
|
2785
3670
|
const check = () => {
|
|
2786
3671
|
if (window[prop] !== void 0) {
|
|
2787
|
-
|
|
3672
|
+
resolve2();
|
|
2788
3673
|
} else if (Date.now() - start > timeout) {
|
|
2789
3674
|
reject(
|
|
2790
3675
|
new Error(
|
|
@@ -2800,105 +3685,103 @@ function waitForWindowProperty2(window, prop, timeout = 5e3) {
|
|
|
2800
3685
|
}
|
|
2801
3686
|
|
|
2802
3687
|
// src/commands/run/index.ts
|
|
2803
|
-
|
|
3688
|
+
init_cli_logger();
|
|
3689
|
+
import path13 from "path";
|
|
2804
3690
|
|
|
2805
3691
|
// src/commands/run/validators.ts
|
|
2806
3692
|
import { existsSync as existsSync3 } from "fs";
|
|
2807
3693
|
|
|
2808
3694
|
// src/schemas/primitives.ts
|
|
2809
|
-
import { z } from "@walkeros/core/dev";
|
|
2810
|
-
var
|
|
2811
|
-
var
|
|
2812
|
-
var FilePathSchema = z.string().min(1, "File path cannot be empty").describe("Path to configuration file");
|
|
3695
|
+
import { z as z2 } from "@walkeros/core/dev";
|
|
3696
|
+
var PortSchema = z2.number().int("Port must be an integer").min(1, "Port must be at least 1").max(65535, "Port must be at most 65535").describe("HTTP server port number");
|
|
3697
|
+
var FilePathSchema = z2.string().min(1, "File path cannot be empty").describe("Path to configuration file");
|
|
2813
3698
|
|
|
2814
3699
|
// src/schemas/run.ts
|
|
2815
|
-
import { z as
|
|
2816
|
-
var RunOptionsSchema =
|
|
2817
|
-
mode: RunModeSchema,
|
|
3700
|
+
import { z as z3 } from "@walkeros/core/dev";
|
|
3701
|
+
var RunOptionsSchema = z3.object({
|
|
2818
3702
|
flow: FilePathSchema,
|
|
2819
3703
|
port: PortSchema.default(8080),
|
|
2820
|
-
flowName:
|
|
3704
|
+
flowName: z3.string().optional().describe("Specific flow name to run")
|
|
2821
3705
|
});
|
|
2822
3706
|
|
|
2823
3707
|
// src/schemas/validate.ts
|
|
2824
|
-
import { z as
|
|
2825
|
-
var ValidationTypeSchema =
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
)
|
|
2831
|
-
var ValidateOptionsSchema = z3.object({
|
|
2832
|
-
flow: z3.string().optional().describe("Flow name for multi-flow configs")
|
|
3708
|
+
import { z as z4 } from "@walkeros/core/dev";
|
|
3709
|
+
var ValidationTypeSchema = z4.enum(["contract", "event", "flow", "mapping"]).describe('Validation type: "event", "flow", "mapping", or "contract"');
|
|
3710
|
+
var ValidateOptionsSchema = z4.object({
|
|
3711
|
+
flow: z4.string().optional().describe("Flow name for multi-flow configs"),
|
|
3712
|
+
path: z4.string().optional().describe(
|
|
3713
|
+
'Entry path for package schema validation (e.g., "destinations.snowplow", "sources.browser")'
|
|
3714
|
+
)
|
|
2833
3715
|
});
|
|
2834
3716
|
var ValidateInputShape = {
|
|
2835
3717
|
type: ValidationTypeSchema,
|
|
2836
|
-
input:
|
|
2837
|
-
flow:
|
|
3718
|
+
input: z4.string().min(1).describe("JSON string, file path, or URL to validate"),
|
|
3719
|
+
flow: z4.string().optional().describe("Flow name for multi-flow configs"),
|
|
3720
|
+
path: z4.string().optional().describe(
|
|
3721
|
+
'Entry path for package schema validation (e.g., "destinations.snowplow"). When provided, validates the entry against its package JSON Schema instead of using --type.'
|
|
3722
|
+
)
|
|
2838
3723
|
};
|
|
2839
|
-
var ValidateInputSchema =
|
|
3724
|
+
var ValidateInputSchema = z4.object(ValidateInputShape);
|
|
2840
3725
|
|
|
2841
3726
|
// src/schemas/bundle.ts
|
|
2842
|
-
import { z as
|
|
2843
|
-
var BundleOptionsSchema =
|
|
2844
|
-
silent:
|
|
2845
|
-
verbose:
|
|
2846
|
-
stats:
|
|
2847
|
-
cache:
|
|
2848
|
-
flowName:
|
|
3727
|
+
import { z as z5 } from "@walkeros/core/dev";
|
|
3728
|
+
var BundleOptionsSchema = z5.object({
|
|
3729
|
+
silent: z5.boolean().optional().describe("Suppress all output"),
|
|
3730
|
+
verbose: z5.boolean().optional().describe("Enable verbose logging"),
|
|
3731
|
+
stats: z5.boolean().optional().default(true).describe("Return bundle statistics"),
|
|
3732
|
+
cache: z5.boolean().optional().default(true).describe("Enable package caching"),
|
|
3733
|
+
flowName: z5.string().optional().describe("Flow name for multi-flow configs")
|
|
2849
3734
|
});
|
|
2850
3735
|
var BundleInputShape = {
|
|
2851
3736
|
configPath: FilePathSchema.describe(
|
|
2852
3737
|
"Path to flow configuration file (JSON or JavaScript)"
|
|
2853
3738
|
),
|
|
2854
|
-
flow:
|
|
2855
|
-
stats:
|
|
2856
|
-
output:
|
|
3739
|
+
flow: z5.string().optional().describe("Flow name for multi-flow configs"),
|
|
3740
|
+
stats: z5.boolean().optional().default(true).describe("Return bundle statistics"),
|
|
3741
|
+
output: z5.string().optional().describe("Output file path (defaults to config-defined)")
|
|
2857
3742
|
};
|
|
2858
|
-
var BundleInputSchema =
|
|
3743
|
+
var BundleInputSchema = z5.object(BundleInputShape);
|
|
2859
3744
|
|
|
2860
3745
|
// src/schemas/simulate.ts
|
|
2861
|
-
import { z as
|
|
2862
|
-
var PlatformSchema =
|
|
2863
|
-
var SimulateOptionsSchema =
|
|
2864
|
-
silent:
|
|
2865
|
-
verbose:
|
|
2866
|
-
json:
|
|
3746
|
+
import { z as z6 } from "@walkeros/core/dev";
|
|
3747
|
+
var PlatformSchema = z6.enum(["web", "server"]).describe("Platform type for event processing");
|
|
3748
|
+
var SimulateOptionsSchema = z6.object({
|
|
3749
|
+
silent: z6.boolean().optional().describe("Suppress all output"),
|
|
3750
|
+
verbose: z6.boolean().optional().describe("Enable verbose logging"),
|
|
3751
|
+
json: z6.boolean().optional().describe("Format output as JSON")
|
|
2867
3752
|
});
|
|
2868
3753
|
var SimulateInputShape = {
|
|
2869
3754
|
configPath: FilePathSchema.describe("Path to flow configuration file"),
|
|
2870
|
-
event:
|
|
2871
|
-
|
|
2872
|
-
|
|
3755
|
+
event: z6.string().min(1).optional().describe(
|
|
3756
|
+
"Event as JSON string, file path, or URL. Optional when example is provided."
|
|
3757
|
+
),
|
|
3758
|
+
flow: z6.string().optional().describe("Flow name for multi-flow configs"),
|
|
3759
|
+
platform: PlatformSchema.optional().describe("Override platform detection"),
|
|
3760
|
+
example: z6.string().optional().describe(
|
|
3761
|
+
'Name of a step example to use as event input (uses its "in" value)'
|
|
3762
|
+
),
|
|
3763
|
+
step: z6.string().optional().describe(
|
|
3764
|
+
'Step target in type.name format (e.g. "destination.gtag") to narrow example lookup'
|
|
3765
|
+
)
|
|
2873
3766
|
};
|
|
2874
|
-
var SimulateInputSchema =
|
|
3767
|
+
var SimulateInputSchema = z6.object(SimulateInputShape);
|
|
2875
3768
|
|
|
2876
3769
|
// src/schemas/push.ts
|
|
2877
|
-
import { z as
|
|
2878
|
-
var PushOptionsSchema =
|
|
2879
|
-
silent:
|
|
2880
|
-
verbose:
|
|
2881
|
-
json:
|
|
3770
|
+
import { z as z7 } from "@walkeros/core/dev";
|
|
3771
|
+
var PushOptionsSchema = z7.object({
|
|
3772
|
+
silent: z7.boolean().optional().describe("Suppress all output"),
|
|
3773
|
+
verbose: z7.boolean().optional().describe("Enable verbose logging"),
|
|
3774
|
+
json: z7.boolean().optional().describe("Format output as JSON")
|
|
2882
3775
|
});
|
|
2883
3776
|
var PushInputShape = {
|
|
2884
3777
|
configPath: FilePathSchema.describe("Path to flow configuration file"),
|
|
2885
|
-
event:
|
|
2886
|
-
flow:
|
|
3778
|
+
event: z7.string().min(1).describe("Event as JSON string, file path, or URL"),
|
|
3779
|
+
flow: z7.string().optional().describe("Flow name for multi-flow configs"),
|
|
2887
3780
|
platform: PlatformSchema.optional().describe("Override platform detection")
|
|
2888
3781
|
};
|
|
2889
|
-
var PushInputSchema =
|
|
3782
|
+
var PushInputSchema = z7.object(PushInputShape);
|
|
2890
3783
|
|
|
2891
3784
|
// src/commands/run/validators.ts
|
|
2892
|
-
function validateMode(mode) {
|
|
2893
|
-
const result = RunModeSchema.safeParse(mode);
|
|
2894
|
-
if (!result.success) {
|
|
2895
|
-
throw new Error(
|
|
2896
|
-
`Invalid mode: "${mode}"
|
|
2897
|
-
Valid modes: collect, serve
|
|
2898
|
-
Example: walkeros run collect ./flow.json`
|
|
2899
|
-
);
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
3785
|
function validateFlowFile(filePath) {
|
|
2903
3786
|
const absolutePath = resolveAsset(filePath, "bundle");
|
|
2904
3787
|
if (!existsSync3(absolutePath)) {
|
|
@@ -2922,7 +3805,7 @@ function validatePort(port) {
|
|
|
2922
3805
|
}
|
|
2923
3806
|
|
|
2924
3807
|
// src/commands/run/utils.ts
|
|
2925
|
-
import
|
|
3808
|
+
import path12 from "path";
|
|
2926
3809
|
import fs13 from "fs-extra";
|
|
2927
3810
|
async function prepareBundleForRun(configPath, options) {
|
|
2928
3811
|
const tempDir = getTmpPath(
|
|
@@ -2930,11 +3813,12 @@ async function prepareBundleForRun(configPath, options) {
|
|
|
2930
3813
|
`run-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
|
|
2931
3814
|
);
|
|
2932
3815
|
await fs13.ensureDir(tempDir);
|
|
2933
|
-
const tempPath =
|
|
3816
|
+
const tempPath = path12.join(tempDir, "bundle.mjs");
|
|
2934
3817
|
await bundle(configPath, {
|
|
2935
3818
|
cache: true,
|
|
2936
3819
|
verbose: options.verbose,
|
|
2937
3820
|
silent: options.silent,
|
|
3821
|
+
flowName: options.flowName,
|
|
2938
3822
|
buildOverrides: {
|
|
2939
3823
|
output: tempPath,
|
|
2940
3824
|
format: "esm",
|
|
@@ -2948,28 +3832,42 @@ function isPreBuiltConfig(configPath) {
|
|
|
2948
3832
|
}
|
|
2949
3833
|
|
|
2950
3834
|
// src/commands/run/execution.ts
|
|
2951
|
-
import { createLogger as createLogger2, Level } from "@walkeros/core";
|
|
3835
|
+
import { createLogger as createLogger2, Level as Level4 } from "@walkeros/core";
|
|
2952
3836
|
|
|
2953
3837
|
// src/runtime/runner.ts
|
|
2954
|
-
import { pathToFileURL
|
|
3838
|
+
import { pathToFileURL } from "url";
|
|
2955
3839
|
import { resolve, dirname } from "path";
|
|
2956
|
-
async function loadFlow(file, config, logger2, loggerConfig2) {
|
|
3840
|
+
async function loadFlow(file, config, logger2, loggerConfig2, healthServer) {
|
|
2957
3841
|
const absolutePath = resolve(file);
|
|
2958
3842
|
const flowDir = dirname(absolutePath);
|
|
2959
3843
|
process.chdir(flowDir);
|
|
2960
|
-
const fileUrl =
|
|
3844
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
2961
3845
|
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
2962
3846
|
if (!module.default || typeof module.default !== "function") {
|
|
2963
3847
|
throw new Error(
|
|
2964
3848
|
`Invalid flow bundle: ${file} must export a default function`
|
|
2965
3849
|
);
|
|
2966
3850
|
}
|
|
2967
|
-
const flowContext =
|
|
3851
|
+
const flowContext = {
|
|
3852
|
+
...config,
|
|
3853
|
+
...loggerConfig2 ? { logger: loggerConfig2 } : {},
|
|
3854
|
+
...healthServer ? { externalServer: true } : {}
|
|
3855
|
+
};
|
|
2968
3856
|
const result = await module.default(flowContext);
|
|
2969
3857
|
if (!result || !result.collector) {
|
|
2970
3858
|
throw new Error(`Invalid flow bundle: ${file} must return { collector }`);
|
|
2971
3859
|
}
|
|
2972
|
-
|
|
3860
|
+
if (healthServer && typeof result.httpHandler === "function") {
|
|
3861
|
+
healthServer.setFlowHandler(result.httpHandler);
|
|
3862
|
+
}
|
|
3863
|
+
return {
|
|
3864
|
+
collector: {
|
|
3865
|
+
command: result.collector.command,
|
|
3866
|
+
status: result.collector.status
|
|
3867
|
+
},
|
|
3868
|
+
file,
|
|
3869
|
+
httpHandler: result.httpHandler
|
|
3870
|
+
};
|
|
2973
3871
|
}
|
|
2974
3872
|
async function runFlow(file, config, logger2, loggerConfig2) {
|
|
2975
3873
|
logger2.info(`Loading flow from ${file}`);
|
|
@@ -2981,13 +3879,19 @@ async function runFlow(file, config, logger2, loggerConfig2) {
|
|
|
2981
3879
|
}
|
|
2982
3880
|
const shutdown = async (signal) => {
|
|
2983
3881
|
logger2.info(`Received ${signal}, shutting down gracefully...`);
|
|
3882
|
+
const forceTimer = setTimeout(() => {
|
|
3883
|
+
logger2.error("Shutdown timed out, forcing exit");
|
|
3884
|
+
process.exit(1);
|
|
3885
|
+
}, 15e3);
|
|
2984
3886
|
try {
|
|
2985
3887
|
if (handle.collector.command) {
|
|
2986
3888
|
await handle.collector.command("shutdown");
|
|
2987
3889
|
}
|
|
2988
3890
|
logger2.info("Shutdown complete");
|
|
3891
|
+
clearTimeout(forceTimer);
|
|
2989
3892
|
process.exit(0);
|
|
2990
3893
|
} catch (error) {
|
|
3894
|
+
clearTimeout(forceTimer);
|
|
2991
3895
|
const message = error instanceof Error ? error.message : String(error);
|
|
2992
3896
|
logger2.error(`Error during shutdown: ${message}`);
|
|
2993
3897
|
process.exit(1);
|
|
@@ -3007,198 +3911,73 @@ async function runFlow(file, config, logger2, loggerConfig2) {
|
|
|
3007
3911
|
}
|
|
3008
3912
|
}
|
|
3009
3913
|
|
|
3010
|
-
// src/runtime/serve.ts
|
|
3011
|
-
import express from "express";
|
|
3012
|
-
import { resolve as resolve2 } from "path";
|
|
3013
|
-
|
|
3014
|
-
// src/version.ts
|
|
3015
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
3016
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3017
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
3018
|
-
var versionFilename = fileURLToPath2(import.meta.url);
|
|
3019
|
-
var versionDirname = dirname2(versionFilename);
|
|
3020
|
-
function findPackageJson() {
|
|
3021
|
-
const paths = [
|
|
3022
|
-
join2(versionDirname, "../package.json"),
|
|
3023
|
-
// dist/ or src/
|
|
3024
|
-
join2(versionDirname, "../../package.json")
|
|
3025
|
-
// src/core/ (not used, but safe)
|
|
3026
|
-
];
|
|
3027
|
-
for (const p of paths) {
|
|
3028
|
-
try {
|
|
3029
|
-
return readFileSync2(p, "utf-8");
|
|
3030
|
-
} catch {
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
return JSON.stringify({ version: "0.0.0" });
|
|
3034
|
-
}
|
|
3035
|
-
var VERSION = JSON.parse(findPackageJson()).version;
|
|
3036
|
-
|
|
3037
3914
|
// src/runtime/heartbeat.ts
|
|
3915
|
+
init_version();
|
|
3038
3916
|
import { randomBytes } from "crypto";
|
|
3039
3917
|
var instanceId = randomBytes(8).toString("hex");
|
|
3040
|
-
function getInstanceId() {
|
|
3041
|
-
return instanceId;
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// src/runtime/status.ts
|
|
3045
|
-
var state = null;
|
|
3046
|
-
var startTime = Date.now();
|
|
3047
|
-
var currentStatus = "starting";
|
|
3048
|
-
var lastPollTime;
|
|
3049
|
-
var lastHeartbeatTime;
|
|
3050
|
-
function getStatus() {
|
|
3051
|
-
return {
|
|
3052
|
-
status: currentStatus,
|
|
3053
|
-
version: VERSION,
|
|
3054
|
-
mode: state?.mode ?? "collect",
|
|
3055
|
-
instanceId: getInstanceId(),
|
|
3056
|
-
configVersion: state?.configVersion,
|
|
3057
|
-
configSource: state?.configSource ?? "local",
|
|
3058
|
-
apiEnabled: state?.apiEnabled ?? false,
|
|
3059
|
-
lastPoll: lastPollTime,
|
|
3060
|
-
lastHeartbeat: lastHeartbeatTime,
|
|
3061
|
-
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
3062
|
-
port: state?.port ?? 8080
|
|
3063
|
-
};
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
|
-
// src/runtime/serve.ts
|
|
3067
|
-
async function runServeMode(config, logger2) {
|
|
3068
|
-
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : config?.port || 8080;
|
|
3069
|
-
const host = process.env.HOST || config?.host || "0.0.0.0";
|
|
3070
|
-
const file = resolve2(
|
|
3071
|
-
process.env.BUNDLE || config?.file || "./dist/walker.js"
|
|
3072
|
-
);
|
|
3073
|
-
const serveName = process.env.SERVE_NAME || config?.serveName || "walker.js";
|
|
3074
|
-
const servePath = process.env.SERVE_PATH || config?.servePath || "";
|
|
3075
|
-
const urlPath = servePath ? `/${servePath}/${serveName}` : `/${serveName}`;
|
|
3076
|
-
logger2.info("Starting single-file server...");
|
|
3077
|
-
logger2.info(`File: ${file}`);
|
|
3078
|
-
logger2.info(`URL: http://${host}:${port}${urlPath}`);
|
|
3079
|
-
try {
|
|
3080
|
-
const app = express();
|
|
3081
|
-
app.get("/health", (req, res) => {
|
|
3082
|
-
res.json({
|
|
3083
|
-
status: "ok",
|
|
3084
|
-
version: VERSION,
|
|
3085
|
-
timestamp: Date.now(),
|
|
3086
|
-
mode: "serve",
|
|
3087
|
-
file,
|
|
3088
|
-
url: urlPath
|
|
3089
|
-
});
|
|
3090
|
-
});
|
|
3091
|
-
app.get("/status", (req, res) => {
|
|
3092
|
-
res.json(getStatus());
|
|
3093
|
-
});
|
|
3094
|
-
app.get(urlPath, (req, res) => {
|
|
3095
|
-
res.type("application/javascript");
|
|
3096
|
-
res.sendFile(file, { dotfiles: "allow" }, (err) => {
|
|
3097
|
-
if (err && !res.headersSent) {
|
|
3098
|
-
const errCode = err.code;
|
|
3099
|
-
const errStatus = err.status || err.statusCode;
|
|
3100
|
-
if (errCode !== "ECONNABORTED") {
|
|
3101
|
-
logger2.error(
|
|
3102
|
-
`sendFile error for ${file}: ${err.message} (code: ${errCode}, status: ${errStatus})`
|
|
3103
|
-
);
|
|
3104
|
-
}
|
|
3105
|
-
if (errStatus === 404 || errCode === "ENOENT" || errCode === "EISDIR" || errCode === "ENOTDIR") {
|
|
3106
|
-
res.status(404).send("File not found");
|
|
3107
|
-
} else if (errCode !== "ECONNABORTED") {
|
|
3108
|
-
res.status(500).send("Internal server error");
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3111
|
-
});
|
|
3112
|
-
});
|
|
3113
|
-
const server = app.listen(port, host, () => {
|
|
3114
|
-
logger2.info(`Server listening on http://${host}:${port}`);
|
|
3115
|
-
logger2.info(`GET ${urlPath} - Bundle file`);
|
|
3116
|
-
logger2.info(`GET /health - Health check`);
|
|
3117
|
-
});
|
|
3118
|
-
const shutdownHandler = (signal) => {
|
|
3119
|
-
logger2.info(`Received ${signal}, shutting down...`);
|
|
3120
|
-
server.close(() => {
|
|
3121
|
-
logger2.info("Server closed");
|
|
3122
|
-
process.exit(0);
|
|
3123
|
-
});
|
|
3124
|
-
};
|
|
3125
|
-
process.on("SIGTERM", () => shutdownHandler("SIGTERM"));
|
|
3126
|
-
process.on("SIGINT", () => shutdownHandler("SIGINT"));
|
|
3127
|
-
await new Promise(() => {
|
|
3128
|
-
});
|
|
3129
|
-
} catch (error) {
|
|
3130
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3131
|
-
logger2.error(`Server failed: ${message}`);
|
|
3132
|
-
process.exit(1);
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
3918
|
|
|
3136
3919
|
// src/commands/run/execution.ts
|
|
3137
|
-
var logLevel = process.env.VERBOSE === "true" ?
|
|
3920
|
+
var logLevel = process.env.VERBOSE === "true" ? Level4.DEBUG : Level4.INFO;
|
|
3138
3921
|
var loggerConfig = { level: logLevel };
|
|
3139
3922
|
var logger = createLogger2(loggerConfig);
|
|
3140
|
-
async function executeRunLocal(
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
const config = {
|
|
3147
|
-
port: options.port,
|
|
3148
|
-
host: options.host
|
|
3149
|
-
};
|
|
3150
|
-
await runFlow(flowPath, config, logger.scope("runner"), loggerConfig);
|
|
3151
|
-
break;
|
|
3152
|
-
}
|
|
3153
|
-
case "serve": {
|
|
3154
|
-
const config = {
|
|
3155
|
-
port: options.port,
|
|
3156
|
-
host: options.host,
|
|
3157
|
-
serveName: options.serveName,
|
|
3158
|
-
servePath: options.servePath,
|
|
3159
|
-
file: flowPath || void 0
|
|
3160
|
-
};
|
|
3161
|
-
await runServeMode(config, logger.scope("serve"));
|
|
3162
|
-
break;
|
|
3163
|
-
}
|
|
3164
|
-
default:
|
|
3165
|
-
throw new Error(`Unknown mode: ${mode}`);
|
|
3166
|
-
}
|
|
3923
|
+
async function executeRunLocal(flowPath, options) {
|
|
3924
|
+
const config = {
|
|
3925
|
+
port: options.port,
|
|
3926
|
+
host: options.host
|
|
3927
|
+
};
|
|
3928
|
+
await runFlow(flowPath, config, logger.scope("runner"), loggerConfig);
|
|
3167
3929
|
}
|
|
3168
3930
|
|
|
3169
3931
|
// src/commands/run/index.ts
|
|
3170
|
-
async function runCommand(
|
|
3932
|
+
async function runCommand(options) {
|
|
3171
3933
|
const timer = createTimer();
|
|
3172
3934
|
timer.start();
|
|
3173
|
-
const logger2 =
|
|
3935
|
+
const logger2 = createCLILogger(options);
|
|
3174
3936
|
try {
|
|
3175
|
-
validateMode(mode);
|
|
3176
3937
|
const configPath = validateFlowFile(options.config);
|
|
3177
3938
|
if (options.port !== void 0) {
|
|
3178
3939
|
validatePort(options.port);
|
|
3179
3940
|
}
|
|
3180
|
-
const
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
logger2.
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
});
|
|
3192
|
-
logger2.debug("Bundle ready");
|
|
3941
|
+
const runtimeDeps = ["express", "cors"];
|
|
3942
|
+
for (const dep of runtimeDeps) {
|
|
3943
|
+
try {
|
|
3944
|
+
__require.resolve(dep);
|
|
3945
|
+
} catch {
|
|
3946
|
+
logger2.error(
|
|
3947
|
+
`Missing runtime dependency "${dep}"
|
|
3948
|
+
Server flows require express and cors when running outside Docker.
|
|
3949
|
+
Run: npm install express cors`
|
|
3950
|
+
);
|
|
3951
|
+
process.exit(1);
|
|
3193
3952
|
}
|
|
3194
3953
|
}
|
|
3195
|
-
const
|
|
3196
|
-
|
|
3197
|
-
|
|
3954
|
+
const isPreBuilt = isPreBuiltConfig(configPath);
|
|
3955
|
+
let flowPath;
|
|
3956
|
+
if (isPreBuilt) {
|
|
3957
|
+
flowPath = path13.resolve(configPath);
|
|
3958
|
+
logger2.debug(`Using pre-built flow: ${path13.basename(flowPath)}`);
|
|
3959
|
+
} else {
|
|
3960
|
+
logger2.debug("Building flow bundle");
|
|
3961
|
+
flowPath = await prepareBundleForRun(configPath, {
|
|
3962
|
+
verbose: options.verbose,
|
|
3963
|
+
silent: options.json || options.silent
|
|
3964
|
+
});
|
|
3965
|
+
logger2.debug("Bundle ready");
|
|
3966
|
+
}
|
|
3967
|
+
if (options.deployment) {
|
|
3968
|
+
const { startHeartbeat: startHeartbeat2 } = await Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports));
|
|
3969
|
+
await startHeartbeat2({
|
|
3970
|
+
deployment: options.deployment,
|
|
3971
|
+
projectId: options.project,
|
|
3972
|
+
url: options.url || `http://localhost:${options.port || 8080}`,
|
|
3973
|
+
healthEndpoint: options.healthEndpoint,
|
|
3974
|
+
heartbeatInterval: options.heartbeatInterval
|
|
3975
|
+
});
|
|
3976
|
+
}
|
|
3977
|
+
logger2.info("Starting flow...");
|
|
3978
|
+
await executeRunLocal(flowPath, {
|
|
3198
3979
|
port: options.port,
|
|
3199
|
-
host: options.host
|
|
3200
|
-
serveName: options.serveName,
|
|
3201
|
-
servePath: options.servePath
|
|
3980
|
+
host: options.host
|
|
3202
3981
|
});
|
|
3203
3982
|
} catch (error) {
|
|
3204
3983
|
const duration = timer.getElapsed() / 1e3;
|
|
@@ -3206,7 +3985,6 @@ async function runCommand(mode, options) {
|
|
|
3206
3985
|
if (options.json) {
|
|
3207
3986
|
logger2.json({
|
|
3208
3987
|
success: false,
|
|
3209
|
-
mode,
|
|
3210
3988
|
error: errorMessage,
|
|
3211
3989
|
duration
|
|
3212
3990
|
});
|
|
@@ -3216,10 +3994,9 @@ async function runCommand(mode, options) {
|
|
|
3216
3994
|
process.exit(1);
|
|
3217
3995
|
}
|
|
3218
3996
|
}
|
|
3219
|
-
async function run(
|
|
3220
|
-
const
|
|
3997
|
+
async function run(options) {
|
|
3998
|
+
const startTime = Date.now();
|
|
3221
3999
|
try {
|
|
3222
|
-
validateMode(mode);
|
|
3223
4000
|
let flowFile;
|
|
3224
4001
|
if (typeof options.config === "string") {
|
|
3225
4002
|
flowFile = validateFlowFile(options.config);
|
|
@@ -3229,40 +4006,131 @@ async function run(mode, options) {
|
|
|
3229
4006
|
if (options.port !== void 0) {
|
|
3230
4007
|
validatePort(options.port);
|
|
3231
4008
|
}
|
|
4009
|
+
const runtimeDeps = ["express", "cors"];
|
|
4010
|
+
for (const dep of runtimeDeps) {
|
|
4011
|
+
try {
|
|
4012
|
+
__require.resolve(dep);
|
|
4013
|
+
} catch {
|
|
4014
|
+
throw new Error(
|
|
4015
|
+
`Missing runtime dependency "${dep}". Server flows require express and cors when running outside Docker. Run: npm install express cors`
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
3232
4019
|
const isPreBuilt = isPreBuiltConfig(flowFile);
|
|
3233
4020
|
let flowPath;
|
|
3234
4021
|
if (isPreBuilt) {
|
|
3235
|
-
flowPath =
|
|
4022
|
+
flowPath = path13.resolve(flowFile);
|
|
3236
4023
|
} else {
|
|
3237
4024
|
flowPath = await prepareBundleForRun(flowFile, {
|
|
3238
4025
|
verbose: options.verbose,
|
|
3239
4026
|
silent: true
|
|
3240
4027
|
});
|
|
3241
4028
|
}
|
|
3242
|
-
await executeRunLocal(
|
|
4029
|
+
await executeRunLocal(flowPath, {
|
|
3243
4030
|
port: options.port,
|
|
3244
|
-
host: options.host
|
|
3245
|
-
serveName: options.serveName,
|
|
3246
|
-
servePath: options.servePath
|
|
4031
|
+
host: options.host
|
|
3247
4032
|
});
|
|
3248
4033
|
return {
|
|
3249
4034
|
success: true,
|
|
3250
4035
|
exitCode: 0,
|
|
3251
|
-
duration: Date.now() -
|
|
4036
|
+
duration: Date.now() - startTime
|
|
3252
4037
|
};
|
|
3253
4038
|
} catch (error) {
|
|
3254
4039
|
return {
|
|
3255
4040
|
success: false,
|
|
3256
4041
|
exitCode: 1,
|
|
3257
|
-
duration: Date.now() -
|
|
4042
|
+
duration: Date.now() - startTime,
|
|
3258
4043
|
error: getErrorMessage(error)
|
|
3259
4044
|
};
|
|
3260
4045
|
}
|
|
3261
4046
|
}
|
|
3262
4047
|
|
|
3263
4048
|
// src/commands/validate/index.ts
|
|
4049
|
+
init_cli_logger();
|
|
3264
4050
|
import chalk2 from "chalk";
|
|
3265
4051
|
|
|
4052
|
+
// src/commands/validate/validators/contract.ts
|
|
4053
|
+
function validateContract(input) {
|
|
4054
|
+
const errors = [];
|
|
4055
|
+
const warnings = [];
|
|
4056
|
+
const details = {};
|
|
4057
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
4058
|
+
errors.push({
|
|
4059
|
+
path: "root",
|
|
4060
|
+
message: "Contract must be an object",
|
|
4061
|
+
code: "INVALID_CONTRACT"
|
|
4062
|
+
});
|
|
4063
|
+
return { valid: false, type: "contract", errors, warnings, details };
|
|
4064
|
+
}
|
|
4065
|
+
const contract = input;
|
|
4066
|
+
let entityCount = 0;
|
|
4067
|
+
let actionCount = 0;
|
|
4068
|
+
if ("$tagging" in contract) {
|
|
4069
|
+
const tagging = contract.$tagging;
|
|
4070
|
+
if (typeof tagging !== "number" || !Number.isInteger(tagging) || tagging < 0) {
|
|
4071
|
+
errors.push({
|
|
4072
|
+
path: "$tagging",
|
|
4073
|
+
message: "$tagging must be a non-negative integer",
|
|
4074
|
+
value: tagging,
|
|
4075
|
+
code: "INVALID_TAGGING"
|
|
4076
|
+
});
|
|
4077
|
+
} else {
|
|
4078
|
+
details.tagging = tagging;
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
for (const [entityKey, entityValue] of Object.entries(contract)) {
|
|
4082
|
+
if (entityKey.startsWith("$")) continue;
|
|
4083
|
+
if (entityKey.trim() === "") {
|
|
4084
|
+
errors.push({
|
|
4085
|
+
path: entityKey,
|
|
4086
|
+
message: "Entity key cannot be empty",
|
|
4087
|
+
code: "INVALID_ENTITY_KEY"
|
|
4088
|
+
});
|
|
4089
|
+
continue;
|
|
4090
|
+
}
|
|
4091
|
+
if (typeof entityValue !== "object" || entityValue === null) {
|
|
4092
|
+
errors.push({
|
|
4093
|
+
path: entityKey,
|
|
4094
|
+
message: `Entity "${entityKey}" must be an object of action entries`,
|
|
4095
|
+
value: entityValue,
|
|
4096
|
+
code: "INVALID_ENTITY"
|
|
4097
|
+
});
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
entityCount++;
|
|
4101
|
+
const actions = entityValue;
|
|
4102
|
+
for (const [actionKey, actionValue] of Object.entries(actions)) {
|
|
4103
|
+
if (actionKey.trim() === "") {
|
|
4104
|
+
errors.push({
|
|
4105
|
+
path: `${entityKey}.${actionKey}`,
|
|
4106
|
+
message: "Action key cannot be empty",
|
|
4107
|
+
code: "INVALID_ACTION_KEY"
|
|
4108
|
+
});
|
|
4109
|
+
continue;
|
|
4110
|
+
}
|
|
4111
|
+
if (typeof actionValue !== "object" || actionValue === null || Array.isArray(actionValue)) {
|
|
4112
|
+
errors.push({
|
|
4113
|
+
path: `${entityKey}.${actionKey}`,
|
|
4114
|
+
message: `Contract entry must be a JSON Schema object`,
|
|
4115
|
+
value: typeof actionValue,
|
|
4116
|
+
code: "INVALID_SCHEMA_ENTRY"
|
|
4117
|
+
});
|
|
4118
|
+
continue;
|
|
4119
|
+
}
|
|
4120
|
+
actionCount++;
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
details.entityCount = entityCount;
|
|
4124
|
+
details.actionCount = actionCount;
|
|
4125
|
+
return {
|
|
4126
|
+
valid: errors.length === 0,
|
|
4127
|
+
type: "contract",
|
|
4128
|
+
errors,
|
|
4129
|
+
warnings,
|
|
4130
|
+
details
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
|
|
3266
4134
|
// src/commands/validate/validators/event.ts
|
|
3267
4135
|
import { schemas as schemas3 } from "@walkeros/core/dev";
|
|
3268
4136
|
var { PartialEventSchema } = schemas3;
|
|
@@ -3306,10 +4174,10 @@ function validateEvent(input) {
|
|
|
3306
4174
|
const zodResult = PartialEventSchema.safeParse(input);
|
|
3307
4175
|
if (!zodResult.success) {
|
|
3308
4176
|
for (const issue of zodResult.error.issues) {
|
|
3309
|
-
const
|
|
3310
|
-
if (
|
|
4177
|
+
const path14 = issue.path.join(".");
|
|
4178
|
+
if (path14 === "name") continue;
|
|
3311
4179
|
errors.push({
|
|
3312
|
-
path:
|
|
4180
|
+
path: path14 || "root",
|
|
3313
4181
|
message: issue.message,
|
|
3314
4182
|
code: "SCHEMA_VALIDATION"
|
|
3315
4183
|
});
|
|
@@ -3336,23 +4204,37 @@ function validateEvent(input) {
|
|
|
3336
4204
|
|
|
3337
4205
|
// src/commands/validate/validators/flow.ts
|
|
3338
4206
|
import { schemas as schemas4 } from "@walkeros/core/dev";
|
|
3339
|
-
var {
|
|
4207
|
+
var { validateFlowSetup: validateFlowSetup2 } = schemas4;
|
|
3340
4208
|
function validateFlow(input, options = {}) {
|
|
3341
4209
|
const errors = [];
|
|
3342
4210
|
const warnings = [];
|
|
3343
4211
|
const details = {};
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
4212
|
+
let json;
|
|
4213
|
+
try {
|
|
4214
|
+
json = JSON.stringify(input, null, 2);
|
|
4215
|
+
} catch {
|
|
4216
|
+
errors.push({
|
|
4217
|
+
path: "root",
|
|
4218
|
+
message: "Input cannot be serialized to JSON",
|
|
4219
|
+
code: "SERIALIZATION_ERROR"
|
|
4220
|
+
});
|
|
4221
|
+
return { valid: false, type: "flow", errors, warnings, details };
|
|
4222
|
+
}
|
|
4223
|
+
const coreResult = validateFlowSetup2(json);
|
|
4224
|
+
for (const issue of coreResult.errors) {
|
|
4225
|
+
errors.push({
|
|
4226
|
+
path: issue.path || "root",
|
|
4227
|
+
message: issue.message,
|
|
4228
|
+
code: "SCHEMA_VALIDATION"
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
for (const issue of coreResult.warnings) {
|
|
4232
|
+
warnings.push({
|
|
4233
|
+
path: issue.path || "root",
|
|
4234
|
+
message: issue.message
|
|
4235
|
+
});
|
|
3355
4236
|
}
|
|
4237
|
+
const config = typeof input === "object" && input !== null ? input : {};
|
|
3356
4238
|
const flows = config.flows;
|
|
3357
4239
|
if (flows && typeof flows === "object" && Object.keys(flows).length === 0) {
|
|
3358
4240
|
errors.push({
|
|
@@ -3390,6 +4272,34 @@ function validateFlow(input, options = {}) {
|
|
|
3390
4272
|
}
|
|
3391
4273
|
details.packageCount = Object.keys(packages).length;
|
|
3392
4274
|
}
|
|
4275
|
+
if (coreResult.context) {
|
|
4276
|
+
details.context = coreResult.context;
|
|
4277
|
+
}
|
|
4278
|
+
if (flows && typeof flows === "object" && errors.length === 0) {
|
|
4279
|
+
const flowNames = Object.keys(flows);
|
|
4280
|
+
const flowsToCheck = options.flow ? [options.flow] : flowNames;
|
|
4281
|
+
let totalConnections = 0;
|
|
4282
|
+
for (const name of flowsToCheck) {
|
|
4283
|
+
const flowConfig = flows[name];
|
|
4284
|
+
if (!flowConfig) continue;
|
|
4285
|
+
checkExampleCoverage(flowConfig, warnings);
|
|
4286
|
+
const connections = buildConnectionGraph(flowConfig);
|
|
4287
|
+
for (const conn of connections) {
|
|
4288
|
+
checkCompatibility(conn, errors, warnings);
|
|
4289
|
+
}
|
|
4290
|
+
totalConnections += connections.length;
|
|
4291
|
+
const setupContract = config.contract;
|
|
4292
|
+
if (setupContract || flowConfig.contract) {
|
|
4293
|
+
checkContractCompliance(
|
|
4294
|
+
flowConfig,
|
|
4295
|
+
setupContract,
|
|
4296
|
+
flowConfig.contract,
|
|
4297
|
+
warnings
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
details.connectionsChecked = totalConnections;
|
|
4302
|
+
}
|
|
3393
4303
|
return {
|
|
3394
4304
|
valid: errors.length === 0,
|
|
3395
4305
|
type: "flow",
|
|
@@ -3398,6 +4308,147 @@ function validateFlow(input, options = {}) {
|
|
|
3398
4308
|
details
|
|
3399
4309
|
};
|
|
3400
4310
|
}
|
|
4311
|
+
function checkExampleCoverage(config, warnings) {
|
|
4312
|
+
const stepTypes = [
|
|
4313
|
+
{ key: "sources", type: "source" },
|
|
4314
|
+
{ key: "transformers", type: "transformer" },
|
|
4315
|
+
{ key: "destinations", type: "destination" }
|
|
4316
|
+
];
|
|
4317
|
+
for (const { key, type } of stepTypes) {
|
|
4318
|
+
const refs = config[key];
|
|
4319
|
+
if (!refs) continue;
|
|
4320
|
+
for (const [name, ref] of Object.entries(refs)) {
|
|
4321
|
+
if (!ref.examples || Object.keys(ref.examples).length === 0) {
|
|
4322
|
+
warnings.push({
|
|
4323
|
+
path: `${type}.${name}`,
|
|
4324
|
+
message: `Step has no examples`,
|
|
4325
|
+
suggestion: `Add examples to ${type}.${name} for testing and documentation`
|
|
4326
|
+
});
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
function buildConnectionGraph(config) {
|
|
4332
|
+
const connections = [];
|
|
4333
|
+
for (const [name, source] of Object.entries(config.sources || {})) {
|
|
4334
|
+
if (!source.next || !source.examples) continue;
|
|
4335
|
+
const nextNames = Array.isArray(source.next) ? source.next : [source.next];
|
|
4336
|
+
for (const nextName of nextNames) {
|
|
4337
|
+
const transformer = config.transformers?.[nextName];
|
|
4338
|
+
if (transformer?.examples) {
|
|
4339
|
+
connections.push({
|
|
4340
|
+
from: { type: "source", name, examples: source.examples },
|
|
4341
|
+
to: {
|
|
4342
|
+
type: "transformer",
|
|
4343
|
+
name: nextName,
|
|
4344
|
+
examples: transformer.examples
|
|
4345
|
+
}
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
for (const [name, transformer] of Object.entries(config.transformers || {})) {
|
|
4351
|
+
if (!transformer.next || !transformer.examples) continue;
|
|
4352
|
+
const nextNames = Array.isArray(transformer.next) ? transformer.next : [transformer.next];
|
|
4353
|
+
for (const nextName of nextNames) {
|
|
4354
|
+
const nextTransformer = config.transformers?.[nextName];
|
|
4355
|
+
if (nextTransformer?.examples) {
|
|
4356
|
+
connections.push({
|
|
4357
|
+
from: {
|
|
4358
|
+
type: "transformer",
|
|
4359
|
+
name,
|
|
4360
|
+
examples: transformer.examples
|
|
4361
|
+
},
|
|
4362
|
+
to: {
|
|
4363
|
+
type: "transformer",
|
|
4364
|
+
name: nextName,
|
|
4365
|
+
examples: nextTransformer.examples
|
|
4366
|
+
}
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
for (const [name, dest] of Object.entries(config.destinations || {})) {
|
|
4372
|
+
if (!dest.before || !dest.examples) continue;
|
|
4373
|
+
const beforeNames = Array.isArray(dest.before) ? dest.before : [dest.before];
|
|
4374
|
+
for (const beforeName of beforeNames) {
|
|
4375
|
+
const transformer = config.transformers?.[beforeName];
|
|
4376
|
+
if (transformer?.examples) {
|
|
4377
|
+
connections.push({
|
|
4378
|
+
from: {
|
|
4379
|
+
type: "transformer",
|
|
4380
|
+
name: beforeName,
|
|
4381
|
+
examples: transformer.examples
|
|
4382
|
+
},
|
|
4383
|
+
to: { type: "destination", name, examples: dest.examples }
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
return connections;
|
|
4389
|
+
}
|
|
4390
|
+
function checkCompatibility(conn, errors, warnings) {
|
|
4391
|
+
const fromOuts = Object.entries(conn.from.examples).filter(([, ex]) => ex.out !== void 0 && ex.out !== false).map(([name, ex]) => ({ name, value: ex.out }));
|
|
4392
|
+
const toIns = Object.entries(conn.to.examples).filter(([, ex]) => ex.in !== void 0).map(([name, ex]) => ({ name, value: ex.in }));
|
|
4393
|
+
const path14 = `${conn.from.type}.${conn.from.name} \u2192 ${conn.to.type}.${conn.to.name}`;
|
|
4394
|
+
if (fromOuts.length === 0 || toIns.length === 0) {
|
|
4395
|
+
warnings.push({
|
|
4396
|
+
path: path14,
|
|
4397
|
+
message: "Cannot check compatibility: missing out or in examples",
|
|
4398
|
+
suggestion: "Add out examples to the source step or in examples to the target step"
|
|
4399
|
+
});
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
let hasMatch = false;
|
|
4403
|
+
for (const out of fromOuts) {
|
|
4404
|
+
for (const inp of toIns) {
|
|
4405
|
+
if (isStructurallyCompatible(out.value, inp.value)) {
|
|
4406
|
+
hasMatch = true;
|
|
4407
|
+
break;
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
if (hasMatch) break;
|
|
4411
|
+
}
|
|
4412
|
+
if (!hasMatch) {
|
|
4413
|
+
errors.push({
|
|
4414
|
+
path: path14,
|
|
4415
|
+
message: "No compatible out/in pair found between connected steps",
|
|
4416
|
+
code: "INCOMPATIBLE_EXAMPLES"
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
function isStructurallyCompatible(a2, b2) {
|
|
4421
|
+
if (typeof a2 !== typeof b2) return false;
|
|
4422
|
+
if (a2 === null || b2 === null) return a2 === b2;
|
|
4423
|
+
if (Array.isArray(a2) && Array.isArray(b2)) return true;
|
|
4424
|
+
if (typeof a2 === "object" && typeof b2 === "object") {
|
|
4425
|
+
const keysA = Object.keys(a2);
|
|
4426
|
+
const keysB = Object.keys(b2);
|
|
4427
|
+
const shared = keysA.filter((k2) => keysB.includes(k2));
|
|
4428
|
+
return shared.length >= Math.min(keysA.length, keysB.length) * 0.5;
|
|
4429
|
+
}
|
|
4430
|
+
return true;
|
|
4431
|
+
}
|
|
4432
|
+
function checkContractCompliance(config, setupContract, flowContract, warnings) {
|
|
4433
|
+
for (const [name, dest] of Object.entries(config.destinations || {})) {
|
|
4434
|
+
if (!dest.examples) continue;
|
|
4435
|
+
for (const [exName, example] of Object.entries(dest.examples)) {
|
|
4436
|
+
if (!example.in || typeof example.in !== "object") continue;
|
|
4437
|
+
const event = example.in;
|
|
4438
|
+
if (!event.entity || !event.action) continue;
|
|
4439
|
+
const contract = flowContract?.[event.entity] || setupContract?.[event.entity];
|
|
4440
|
+
if (!contract || typeof contract !== "object") continue;
|
|
4441
|
+
const actionSchema = contract[event.action] || contract["*"];
|
|
4442
|
+
if (actionSchema) {
|
|
4443
|
+
warnings.push({
|
|
4444
|
+
path: `destination.${name}.examples.${exName}`,
|
|
4445
|
+
message: `Example has contract for ${event.entity}.${event.action}`,
|
|
4446
|
+
suggestion: "Verify example data matches contract schema"
|
|
4447
|
+
});
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
3401
4452
|
|
|
3402
4453
|
// src/commands/validate/validators/mapping.ts
|
|
3403
4454
|
function validateMapping(input) {
|
|
@@ -3434,7 +4485,7 @@ function validateMapping(input) {
|
|
|
3434
4485
|
});
|
|
3435
4486
|
}
|
|
3436
4487
|
const rule = mapping[pattern];
|
|
3437
|
-
const isValidRule = Array.isArray(rule) ? rule.every((
|
|
4488
|
+
const isValidRule = Array.isArray(rule) ? rule.every((r2) => typeof r2 === "object" && r2 !== null) : typeof rule === "object" && rule !== null;
|
|
3438
4489
|
if (!isValidRule) {
|
|
3439
4490
|
errors.push({
|
|
3440
4491
|
path: pattern,
|
|
@@ -3456,13 +4507,13 @@ function validateMapping(input) {
|
|
|
3456
4507
|
import Ajv from "ajv";
|
|
3457
4508
|
import { fetchPackageSchema } from "@walkeros/core";
|
|
3458
4509
|
var SECTIONS = ["destinations", "sources", "transformers"];
|
|
3459
|
-
function resolveEntry(
|
|
4510
|
+
function resolveEntry(path14, flowConfig) {
|
|
3460
4511
|
const flows = flowConfig.flows;
|
|
3461
4512
|
if (!flows || typeof flows !== "object") return "No flows found in config";
|
|
3462
4513
|
const flowName = Object.keys(flows)[0];
|
|
3463
4514
|
const flow = flows[flowName];
|
|
3464
4515
|
if (!flow) return `Flow "${flowName}" is empty`;
|
|
3465
|
-
const parts =
|
|
4516
|
+
const parts = path14.split(".");
|
|
3466
4517
|
if (parts.length === 2) {
|
|
3467
4518
|
const [section, key] = parts;
|
|
3468
4519
|
if (!SECTIONS.includes(section)) {
|
|
@@ -3494,20 +4545,20 @@ function resolveEntry(path15, flowConfig) {
|
|
|
3494
4545
|
return `Entry "${key}" not found in any section`;
|
|
3495
4546
|
}
|
|
3496
4547
|
if (matches.length > 1) {
|
|
3497
|
-
const sections = matches.map((
|
|
4548
|
+
const sections = matches.map((m2) => m2.section).join(", ");
|
|
3498
4549
|
return `Ambiguous key "${key}" found in multiple sections: ${sections}. Use dot-notation (e.g., destinations.${key})`;
|
|
3499
4550
|
}
|
|
3500
4551
|
return { section: matches[0].section, key, entry: matches[0].entry };
|
|
3501
4552
|
}
|
|
3502
|
-
return `Invalid path "${
|
|
4553
|
+
return `Invalid path "${path14}". Use "section.key" or just "key"`;
|
|
3503
4554
|
}
|
|
3504
|
-
async function validateEntry(
|
|
3505
|
-
const resolved = resolveEntry(
|
|
4555
|
+
async function validateEntry(path14, flowConfig) {
|
|
4556
|
+
const resolved = resolveEntry(path14, flowConfig);
|
|
3506
4557
|
if (typeof resolved === "string") {
|
|
3507
4558
|
return {
|
|
3508
4559
|
valid: false,
|
|
3509
4560
|
type: "entry",
|
|
3510
|
-
errors: [{ path:
|
|
4561
|
+
errors: [{ path: path14, message: resolved, code: "ENTRY_VALIDATION" }],
|
|
3511
4562
|
warnings: [],
|
|
3512
4563
|
details: {}
|
|
3513
4564
|
};
|
|
@@ -3538,7 +4589,7 @@ async function validateEntry(path15, flowConfig) {
|
|
|
3538
4589
|
type: "entry",
|
|
3539
4590
|
errors: [
|
|
3540
4591
|
{
|
|
3541
|
-
path:
|
|
4592
|
+
path: path14,
|
|
3542
4593
|
message: error instanceof Error ? error.message : "Unknown error",
|
|
3543
4594
|
code: "ENTRY_VALIDATION"
|
|
3544
4595
|
}
|
|
@@ -3563,10 +4614,10 @@ async function validateEntry(path15, flowConfig) {
|
|
|
3563
4614
|
const validate2 = ajv.compile(settingsSchema);
|
|
3564
4615
|
const isValid = validate2(settings || {});
|
|
3565
4616
|
if (!isValid) {
|
|
3566
|
-
const errors = (validate2.errors || []).map((
|
|
3567
|
-
path:
|
|
3568
|
-
message:
|
|
3569
|
-
code:
|
|
4617
|
+
const errors = (validate2.errors || []).map((e2) => ({
|
|
4618
|
+
path: e2.instancePath || "/",
|
|
4619
|
+
message: e2.message || "Unknown error",
|
|
4620
|
+
code: e2.keyword
|
|
3570
4621
|
}));
|
|
3571
4622
|
return {
|
|
3572
4623
|
valid: false,
|
|
@@ -3587,10 +4638,12 @@ async function validateEntry(path15, flowConfig) {
|
|
|
3587
4638
|
|
|
3588
4639
|
// src/commands/validate/index.ts
|
|
3589
4640
|
async function validate(type, input, options = {}) {
|
|
3590
|
-
if (
|
|
3591
|
-
return validateEntry(
|
|
4641
|
+
if (options.path) {
|
|
4642
|
+
return validateEntry(options.path, input);
|
|
3592
4643
|
}
|
|
3593
4644
|
switch (type) {
|
|
4645
|
+
case "contract":
|
|
4646
|
+
return validateContract(input);
|
|
3594
4647
|
case "event":
|
|
3595
4648
|
return validateEvent(input);
|
|
3596
4649
|
case "flow":
|
|
@@ -3636,7 +4689,7 @@ function formatResult(result, options) {
|
|
|
3636
4689
|
return lines.join("\n");
|
|
3637
4690
|
}
|
|
3638
4691
|
async function validateCommand(options) {
|
|
3639
|
-
const logger2 =
|
|
4692
|
+
const logger2 = createCLILogger({ ...options, stderr: true });
|
|
3640
4693
|
try {
|
|
3641
4694
|
let input;
|
|
3642
4695
|
if (isStdinPiped() && !options.input) {
|
|
@@ -3653,7 +4706,8 @@ async function validateCommand(options) {
|
|
|
3653
4706
|
});
|
|
3654
4707
|
}
|
|
3655
4708
|
const result = await validate(options.type, input, {
|
|
3656
|
-
flow: options.flow
|
|
4709
|
+
flow: options.flow,
|
|
4710
|
+
path: options.path
|
|
3657
4711
|
});
|
|
3658
4712
|
const formatted = formatResult(result, {
|
|
3659
4713
|
json: options.json,
|
|
@@ -3692,6 +4746,8 @@ async function validateCommand(options) {
|
|
|
3692
4746
|
}
|
|
3693
4747
|
|
|
3694
4748
|
// src/commands/login/index.ts
|
|
4749
|
+
init_cli_logger();
|
|
4750
|
+
init_config_file();
|
|
3695
4751
|
import { hostname } from "os";
|
|
3696
4752
|
var POLL_TIMEOUT_BUFFER_MS = 5e3;
|
|
3697
4753
|
async function openInBrowser(url) {
|
|
@@ -3699,18 +4755,14 @@ async function openInBrowser(url) {
|
|
|
3699
4755
|
await open(url);
|
|
3700
4756
|
}
|
|
3701
4757
|
async function loginCommand(options) {
|
|
3702
|
-
const logger2 =
|
|
3703
|
-
verbose: options.verbose,
|
|
3704
|
-
silent: options.silent,
|
|
3705
|
-
json: options.json
|
|
3706
|
-
});
|
|
4758
|
+
const logger2 = createCLILogger(options);
|
|
3707
4759
|
try {
|
|
3708
4760
|
const result = await login({ url: options.url });
|
|
3709
4761
|
if (options.json) {
|
|
3710
4762
|
logger2.json(result);
|
|
3711
4763
|
} else if (result.success) {
|
|
3712
|
-
logger2.
|
|
3713
|
-
logger2.
|
|
4764
|
+
logger2.info(`Logged in as ${result.email}`);
|
|
4765
|
+
logger2.info(`Token stored in ${result.configPath}`);
|
|
3714
4766
|
}
|
|
3715
4767
|
process.exit(result.success ? 0 : 1);
|
|
3716
4768
|
} catch (error) {
|
|
@@ -3725,8 +4777,8 @@ async function loginCommand(options) {
|
|
|
3725
4777
|
}
|
|
3726
4778
|
async function login(options = {}) {
|
|
3727
4779
|
const appUrl = options.url || resolveAppUrl();
|
|
3728
|
-
const
|
|
3729
|
-
const codeResponse = await
|
|
4780
|
+
const f2 = options.fetch ?? globalThis.fetch;
|
|
4781
|
+
const codeResponse = await f2(`${appUrl}/api/auth/device/code`, {
|
|
3730
4782
|
method: "POST",
|
|
3731
4783
|
headers: { "Content-Type": "application/json" },
|
|
3732
4784
|
body: JSON.stringify({})
|
|
@@ -3745,7 +4797,7 @@ async function login(options = {}) {
|
|
|
3745
4797
|
const prompt = (msg) => process.stderr.write(msg + "\n");
|
|
3746
4798
|
prompt(`
|
|
3747
4799
|
! Your one-time code: ${userCode}`);
|
|
3748
|
-
prompt(` Authorize here: ${verificationUri}
|
|
4800
|
+
prompt(` Authorize here: ${verificationUriComplete || verificationUri}
|
|
3749
4801
|
`);
|
|
3750
4802
|
const opener = options.openUrl ?? openInBrowser;
|
|
3751
4803
|
try {
|
|
@@ -3761,8 +4813,8 @@ async function login(options = {}) {
|
|
|
3761
4813
|
let attempts = 0;
|
|
3762
4814
|
while (Date.now() < deadline && attempts < maxAttempts) {
|
|
3763
4815
|
attempts++;
|
|
3764
|
-
await new Promise((
|
|
3765
|
-
const tokenResponse = await
|
|
4816
|
+
await new Promise((r2) => setTimeout(r2, pollInterval));
|
|
4817
|
+
const tokenResponse = await f2(`${appUrl}/api/auth/device/token`, {
|
|
3766
4818
|
method: "POST",
|
|
3767
4819
|
headers: { "Content-Type": "application/json" },
|
|
3768
4820
|
body: JSON.stringify({ deviceCode, hostname: hostname() })
|
|
@@ -3787,25 +4839,24 @@ async function login(options = {}) {
|
|
|
3787
4839
|
}
|
|
3788
4840
|
|
|
3789
4841
|
// src/commands/logout/index.ts
|
|
4842
|
+
init_cli_logger();
|
|
4843
|
+
init_config_file();
|
|
3790
4844
|
async function logoutCommand(options) {
|
|
3791
|
-
const logger2 =
|
|
3792
|
-
verbose: options.verbose,
|
|
3793
|
-
silent: options.silent,
|
|
3794
|
-
json: options.json
|
|
3795
|
-
});
|
|
4845
|
+
const logger2 = createCLILogger(options);
|
|
3796
4846
|
const deleted = deleteConfig();
|
|
3797
4847
|
const configPath = getConfigPath();
|
|
3798
4848
|
if (options.json) {
|
|
3799
4849
|
logger2.json({ success: true, deleted });
|
|
3800
4850
|
} else if (deleted) {
|
|
3801
|
-
logger2.
|
|
4851
|
+
logger2.info(`Logged out. Token removed from ${configPath}`);
|
|
3802
4852
|
} else {
|
|
3803
|
-
logger2.
|
|
4853
|
+
logger2.info("No stored credentials found.");
|
|
3804
4854
|
}
|
|
3805
4855
|
process.exit(0);
|
|
3806
4856
|
}
|
|
3807
4857
|
|
|
3808
4858
|
// src/commands/auth/index.ts
|
|
4859
|
+
init_cli_logger();
|
|
3809
4860
|
async function whoami() {
|
|
3810
4861
|
const client = createApiClient();
|
|
3811
4862
|
const { data, error } = await client.GET("/api/auth/whoami");
|
|
@@ -3813,16 +4864,16 @@ async function whoami() {
|
|
|
3813
4864
|
return data;
|
|
3814
4865
|
}
|
|
3815
4866
|
async function whoamiCommand(options) {
|
|
3816
|
-
const logger2 =
|
|
4867
|
+
const logger2 = createCLILogger(options);
|
|
3817
4868
|
try {
|
|
3818
4869
|
const result = await whoami();
|
|
3819
4870
|
if (options.json) {
|
|
3820
4871
|
await writeResult(JSON.stringify(result, null, 2), options);
|
|
3821
4872
|
} else {
|
|
3822
4873
|
const data = result;
|
|
3823
|
-
if (data.email) logger2.
|
|
3824
|
-
if (data.userId) logger2.
|
|
3825
|
-
if (data.projectId) logger2.
|
|
4874
|
+
if (data.email) logger2.info(`${data.email}`);
|
|
4875
|
+
if (data.userId) logger2.info(`User: ${data.userId}`);
|
|
4876
|
+
if (data.projectId) logger2.info(`Project: ${data.projectId}`);
|
|
3826
4877
|
}
|
|
3827
4878
|
} catch (error) {
|
|
3828
4879
|
logger2.error(error instanceof Error ? error.message : String(error));
|
|
@@ -3831,6 +4882,8 @@ async function whoamiCommand(options) {
|
|
|
3831
4882
|
}
|
|
3832
4883
|
|
|
3833
4884
|
// src/commands/projects/index.ts
|
|
4885
|
+
init_auth();
|
|
4886
|
+
init_cli_logger();
|
|
3834
4887
|
async function listProjects() {
|
|
3835
4888
|
const client = createApiClient();
|
|
3836
4889
|
const { data, error } = await client.GET("/api/projects");
|
|
@@ -3876,10 +4929,10 @@ async function deleteProject(options = {}) {
|
|
|
3876
4929
|
throw new Error(error.error?.message || "Failed to delete project");
|
|
3877
4930
|
return data ?? { success: true };
|
|
3878
4931
|
}
|
|
3879
|
-
async function handleResult(
|
|
3880
|
-
const logger2 =
|
|
4932
|
+
async function handleResult(fn2, options) {
|
|
4933
|
+
const logger2 = createCLILogger(options);
|
|
3881
4934
|
try {
|
|
3882
|
-
const result = await
|
|
4935
|
+
const result = await fn2();
|
|
3883
4936
|
await writeResult(JSON.stringify(result, null, 2), options);
|
|
3884
4937
|
} catch (error) {
|
|
3885
4938
|
logger2.error(error instanceof Error ? error.message : String(error));
|
|
@@ -3919,6 +4972,8 @@ async function deleteProjectCommand(projectId, options) {
|
|
|
3919
4972
|
}
|
|
3920
4973
|
|
|
3921
4974
|
// src/commands/flows/index.ts
|
|
4975
|
+
init_auth();
|
|
4976
|
+
init_cli_logger();
|
|
3922
4977
|
async function listFlows(options = {}) {
|
|
3923
4978
|
const id = options.projectId ?? requireProjectId();
|
|
3924
4979
|
const client = createApiClient();
|
|
@@ -4001,10 +5056,10 @@ async function duplicateFlow(options) {
|
|
|
4001
5056
|
throw new Error(error.error?.message || "Failed to duplicate flow");
|
|
4002
5057
|
return data;
|
|
4003
5058
|
}
|
|
4004
|
-
async function handleResult2(
|
|
4005
|
-
const logger2 =
|
|
5059
|
+
async function handleResult2(fn2, options) {
|
|
5060
|
+
const logger2 = createCLILogger(options);
|
|
4006
5061
|
try {
|
|
4007
|
-
const result = await
|
|
5062
|
+
const result = await fn2();
|
|
4008
5063
|
await writeResult(JSON.stringify(result, null, 2), options);
|
|
4009
5064
|
} catch (error) {
|
|
4010
5065
|
logger2.error(error instanceof Error ? error.message : String(error));
|
|
@@ -4067,27 +5122,73 @@ async function readFlowStdin() {
|
|
|
4067
5122
|
}
|
|
4068
5123
|
|
|
4069
5124
|
// src/commands/deploy/index.ts
|
|
5125
|
+
init_auth();
|
|
5126
|
+
init_cli_logger();
|
|
4070
5127
|
async function resolveConfigId(options) {
|
|
4071
5128
|
const flow = await getFlow({
|
|
4072
5129
|
flowId: options.flowId,
|
|
4073
5130
|
projectId: options.projectId
|
|
4074
5131
|
});
|
|
4075
|
-
const
|
|
4076
|
-
|
|
4077
|
-
|
|
5132
|
+
const configs = flow.configs;
|
|
5133
|
+
if (!configs?.length) {
|
|
5134
|
+
throw new Error("Flow has no configs.");
|
|
5135
|
+
}
|
|
5136
|
+
const match = configs.find((c2) => c2.name === options.flowName);
|
|
5137
|
+
if (!match) {
|
|
4078
5138
|
throw new Error(
|
|
4079
|
-
`Flow "${options.flowName}" not found. Available: ${
|
|
5139
|
+
`Flow "${options.flowName}" not found. Available: ${configs.map((c2) => c2.name).join(", ")}`
|
|
4080
5140
|
);
|
|
4081
5141
|
}
|
|
4082
|
-
return
|
|
5142
|
+
return match.id;
|
|
4083
5143
|
}
|
|
4084
5144
|
async function getAvailableFlowNames(options) {
|
|
4085
5145
|
const flow = await getFlow({
|
|
4086
5146
|
flowId: options.flowId,
|
|
4087
5147
|
projectId: options.projectId
|
|
4088
5148
|
});
|
|
4089
|
-
const
|
|
4090
|
-
return
|
|
5149
|
+
const configs = flow.configs;
|
|
5150
|
+
return configs?.map((c2) => c2.name) ?? [];
|
|
5151
|
+
}
|
|
5152
|
+
async function streamDeploymentStatus(projectId, deploymentId, options) {
|
|
5153
|
+
const base = resolveBaseUrl();
|
|
5154
|
+
const timeoutMs = options.timeout ?? 12e4;
|
|
5155
|
+
const response = await authenticatedFetch(
|
|
5156
|
+
`${base}/api/projects/${projectId}/deployments/${deploymentId}/stream`,
|
|
5157
|
+
{
|
|
5158
|
+
headers: { Accept: "text/event-stream" },
|
|
5159
|
+
signal: options.signal ?? AbortSignal.timeout(timeoutMs)
|
|
5160
|
+
}
|
|
5161
|
+
);
|
|
5162
|
+
if (!response.ok) throw new Error(`Stream failed: ${response.status}`);
|
|
5163
|
+
if (!response.body) throw new Error("No response body");
|
|
5164
|
+
const reader = response.body.getReader();
|
|
5165
|
+
const decoder = new TextDecoder();
|
|
5166
|
+
let result = null;
|
|
5167
|
+
let buffer = "";
|
|
5168
|
+
try {
|
|
5169
|
+
while (true) {
|
|
5170
|
+
const { done, value } = await reader.read();
|
|
5171
|
+
if (done) break;
|
|
5172
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5173
|
+
const { parsed, remainder } = parseSSEEvents(buffer);
|
|
5174
|
+
buffer = remainder;
|
|
5175
|
+
for (const event of parsed) {
|
|
5176
|
+
if (event.type === "status") {
|
|
5177
|
+
const data = JSON.parse(event.data);
|
|
5178
|
+
result = data;
|
|
5179
|
+
options.onStatus?.(data.status, data.substatus ?? null);
|
|
5180
|
+
}
|
|
5181
|
+
if (event.type === "done") {
|
|
5182
|
+
return result;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
} finally {
|
|
5187
|
+
reader.cancel().catch(() => {
|
|
5188
|
+
});
|
|
5189
|
+
}
|
|
5190
|
+
if (!result) throw new Error("Stream ended without terminal status");
|
|
5191
|
+
return result;
|
|
4091
5192
|
}
|
|
4092
5193
|
async function deploy(options) {
|
|
4093
5194
|
const projectId = options.projectId ?? requireProjectId();
|
|
@@ -4098,7 +5199,14 @@ async function deploy(options) {
|
|
|
4098
5199
|
projectId,
|
|
4099
5200
|
flowName: options.flowName
|
|
4100
5201
|
});
|
|
4101
|
-
return deployConfig({
|
|
5202
|
+
return deployConfig({
|
|
5203
|
+
...options,
|
|
5204
|
+
projectId,
|
|
5205
|
+
configId,
|
|
5206
|
+
timeout: options.timeout,
|
|
5207
|
+
signal: options.signal,
|
|
5208
|
+
onStatus: options.onStatus
|
|
5209
|
+
});
|
|
4102
5210
|
}
|
|
4103
5211
|
const { data, error } = await client.POST(
|
|
4104
5212
|
"/api/projects/{projectId}/flows/{flowId}/deploy",
|
|
@@ -4120,33 +5228,12 @@ Available: ${names.join(", ")}`
|
|
|
4120
5228
|
throw new Error(msg);
|
|
4121
5229
|
}
|
|
4122
5230
|
if (!options.wait) return data;
|
|
4123
|
-
const
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
"/api/projects/{projectId}/flows/{flowId}/deploy/{deploymentId}/advance",
|
|
4130
|
-
{
|
|
4131
|
-
params: {
|
|
4132
|
-
path: {
|
|
4133
|
-
projectId,
|
|
4134
|
-
flowId: options.flowId,
|
|
4135
|
-
deploymentId: data.deploymentId
|
|
4136
|
-
}
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
);
|
|
4140
|
-
if (advanceError)
|
|
4141
|
-
throw new Error(
|
|
4142
|
-
advanceError.error?.message || "Failed to advance deployment"
|
|
4143
|
-
);
|
|
4144
|
-
if (advanced) {
|
|
4145
|
-
status = advanced.status;
|
|
4146
|
-
result = { ...advanced };
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
return result;
|
|
5231
|
+
const result = await streamDeploymentStatus(projectId, data.deploymentId, {
|
|
5232
|
+
timeout: options.timeout,
|
|
5233
|
+
signal: options.signal,
|
|
5234
|
+
onStatus: options.onStatus
|
|
5235
|
+
});
|
|
5236
|
+
return { ...data, ...result };
|
|
4150
5237
|
}
|
|
4151
5238
|
async function deployConfig(options) {
|
|
4152
5239
|
const { flowId, projectId, configId } = options;
|
|
@@ -4163,24 +5250,12 @@ async function deployConfig(options) {
|
|
|
4163
5250
|
}
|
|
4164
5251
|
const data = await response.json();
|
|
4165
5252
|
if (!options.wait) return data;
|
|
4166
|
-
const
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
`${base}/api/projects/${projectId}/flows/${flowId}/configs/${configId}/deployments/${data.deploymentId}/advance`,
|
|
4173
|
-
{ method: "POST" }
|
|
4174
|
-
);
|
|
4175
|
-
if (!advResponse.ok) {
|
|
4176
|
-
const body = await advResponse.json().catch(() => ({}));
|
|
4177
|
-
throw new Error(body.error?.message || "Failed to advance deployment");
|
|
4178
|
-
}
|
|
4179
|
-
const advanced = await advResponse.json();
|
|
4180
|
-
status = advanced.status;
|
|
4181
|
-
result = { ...advanced };
|
|
4182
|
-
}
|
|
4183
|
-
return result;
|
|
5253
|
+
const result = await streamDeploymentStatus(projectId, data.deploymentId, {
|
|
5254
|
+
timeout: options.timeout,
|
|
5255
|
+
signal: options.signal,
|
|
5256
|
+
onStatus: options.onStatus
|
|
5257
|
+
});
|
|
5258
|
+
return { ...data, ...result };
|
|
4184
5259
|
}
|
|
4185
5260
|
async function getDeployment(options) {
|
|
4186
5261
|
const projectId = options.projectId ?? requireProjectId();
|
|
@@ -4209,32 +5284,50 @@ async function getDeployment(options) {
|
|
|
4209
5284
|
throw new Error(error.error?.message || "Failed to get deployment");
|
|
4210
5285
|
return data;
|
|
4211
5286
|
}
|
|
5287
|
+
var statusLabels = {
|
|
5288
|
+
bundling: "Building bundle...",
|
|
5289
|
+
"bundling:building": "Building bundle...",
|
|
5290
|
+
"bundling:publishing": "Publishing to web...",
|
|
5291
|
+
deploying: "Deploying container...",
|
|
5292
|
+
"deploying:provisioning": "Provisioning container...",
|
|
5293
|
+
"deploying:starting": "Starting container...",
|
|
5294
|
+
active: "Container is live",
|
|
5295
|
+
published: "Published",
|
|
5296
|
+
failed: "Deployment failed"
|
|
5297
|
+
};
|
|
4212
5298
|
async function deployCommand(flowId, options) {
|
|
4213
|
-
const log =
|
|
5299
|
+
const log = createCLILogger(options);
|
|
5300
|
+
const timeoutMs = options.timeout ? parseInt(options.timeout, 10) * 1e3 : void 0;
|
|
4214
5301
|
try {
|
|
4215
5302
|
const result = await deploy({
|
|
4216
5303
|
flowId,
|
|
4217
5304
|
projectId: options.project,
|
|
4218
5305
|
flowName: options.flow,
|
|
4219
|
-
wait: options.wait !== false
|
|
5306
|
+
wait: options.wait !== false,
|
|
5307
|
+
timeout: timeoutMs,
|
|
5308
|
+
onStatus: options.json ? void 0 : (status, substatus) => {
|
|
5309
|
+
const key = substatus ? `${status}:${substatus}` : status;
|
|
5310
|
+
log.info(
|
|
5311
|
+
statusLabels[key] || statusLabels[status] || `Status: ${status}`
|
|
5312
|
+
);
|
|
5313
|
+
}
|
|
4220
5314
|
});
|
|
4221
5315
|
if (options.json) {
|
|
4222
5316
|
await writeResult(JSON.stringify(result, null, 2), options);
|
|
4223
5317
|
return;
|
|
4224
5318
|
}
|
|
4225
|
-
const
|
|
4226
|
-
if (
|
|
4227
|
-
log.
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
log.error(`Failed: ${r.errorMessage || "Unknown error"}`);
|
|
5319
|
+
const r2 = result;
|
|
5320
|
+
if (r2.status === "published") {
|
|
5321
|
+
log.info(`Published: ${r2.publicUrl}`);
|
|
5322
|
+
} else if (r2.status === "active") {
|
|
5323
|
+
log.info(`Active: ${r2.containerUrl}`);
|
|
5324
|
+
} else if (r2.status === "failed") {
|
|
5325
|
+
log.error(`Failed: ${r2.errorMessage || "Unknown error"}`);
|
|
4233
5326
|
process.exit(1);
|
|
4234
|
-
} else if (
|
|
4235
|
-
log.info(`Deployment started: ${
|
|
5327
|
+
} else if (r2.status === "bundling") {
|
|
5328
|
+
log.info(`Deployment started: ${r2.deploymentId} (${r2.type})`);
|
|
4236
5329
|
} else {
|
|
4237
|
-
log.info(`Status: ${
|
|
5330
|
+
log.info(`Status: ${r2.status}`);
|
|
4238
5331
|
}
|
|
4239
5332
|
} catch (err) {
|
|
4240
5333
|
log.error(err instanceof Error ? err.message : "Deploy failed");
|
|
@@ -4242,7 +5335,7 @@ async function deployCommand(flowId, options) {
|
|
|
4242
5335
|
}
|
|
4243
5336
|
}
|
|
4244
5337
|
async function getDeploymentCommand(flowId, options) {
|
|
4245
|
-
const log =
|
|
5338
|
+
const log = createCLILogger(options);
|
|
4246
5339
|
try {
|
|
4247
5340
|
const result = await getDeployment({
|
|
4248
5341
|
flowId,
|
|
@@ -4257,50 +5350,258 @@ async function getDeploymentCommand(flowId, options) {
|
|
|
4257
5350
|
log.info("No deployment found");
|
|
4258
5351
|
return;
|
|
4259
5352
|
}
|
|
4260
|
-
const
|
|
4261
|
-
log.info(`Deployment: ${
|
|
4262
|
-
log.info(`Type: ${
|
|
4263
|
-
log.info(`Status: ${
|
|
4264
|
-
if (
|
|
4265
|
-
if (
|
|
4266
|
-
if (
|
|
4267
|
-
if (r.errorMessage) log.error(`Error: ${r.errorMessage}`);
|
|
5353
|
+
const r2 = result;
|
|
5354
|
+
log.info(`Deployment: ${r2.id}`);
|
|
5355
|
+
log.info(`Type: ${r2.type}`);
|
|
5356
|
+
log.info(`Status: ${r2.status}`);
|
|
5357
|
+
if (r2.containerUrl) log.info(`Endpoint: ${r2.containerUrl}`);
|
|
5358
|
+
if (r2.publicUrl) log.info(`URL: ${r2.publicUrl}`);
|
|
5359
|
+
if (r2.errorMessage) log.error(`Error: ${r2.errorMessage}`);
|
|
4268
5360
|
} catch (err) {
|
|
4269
5361
|
log.error(err instanceof Error ? err.message : "Failed to get deployment");
|
|
4270
5362
|
process.exit(1);
|
|
4271
5363
|
}
|
|
4272
5364
|
}
|
|
5365
|
+
|
|
5366
|
+
// src/commands/deployments/index.ts
|
|
5367
|
+
init_auth();
|
|
5368
|
+
init_cli_logger();
|
|
5369
|
+
import { getPlatform as getPlatform4 } from "@walkeros/core";
|
|
5370
|
+
async function listDeployments(options = {}) {
|
|
5371
|
+
const id = options.projectId ?? requireProjectId();
|
|
5372
|
+
const base = resolveBaseUrl();
|
|
5373
|
+
const params = new URLSearchParams();
|
|
5374
|
+
if (options.type) params.set("type", options.type);
|
|
5375
|
+
if (options.status) params.set("status", options.status);
|
|
5376
|
+
const qs = params.toString();
|
|
5377
|
+
const response = await authenticatedFetch(
|
|
5378
|
+
`${base}/api/projects/${id}/deployments${qs ? `?${qs}` : ""}`
|
|
5379
|
+
);
|
|
5380
|
+
if (!response.ok) {
|
|
5381
|
+
const body = await response.json().catch(() => ({}));
|
|
5382
|
+
throw new Error(
|
|
5383
|
+
body.error?.message || "Failed to list deployments"
|
|
5384
|
+
);
|
|
5385
|
+
}
|
|
5386
|
+
return response.json();
|
|
5387
|
+
}
|
|
5388
|
+
async function getDeploymentBySlug(options) {
|
|
5389
|
+
const id = options.projectId ?? requireProjectId();
|
|
5390
|
+
const base = resolveBaseUrl();
|
|
5391
|
+
const response = await authenticatedFetch(
|
|
5392
|
+
`${base}/api/projects/${id}/deployments/${options.slug}`
|
|
5393
|
+
);
|
|
5394
|
+
if (!response.ok) {
|
|
5395
|
+
const body = await response.json().catch(() => ({}));
|
|
5396
|
+
throw new Error(
|
|
5397
|
+
body.error?.message || "Failed to get deployment"
|
|
5398
|
+
);
|
|
5399
|
+
}
|
|
5400
|
+
return response.json();
|
|
5401
|
+
}
|
|
5402
|
+
async function createDeployment(options) {
|
|
5403
|
+
const id = options.projectId ?? requireProjectId();
|
|
5404
|
+
const base = resolveBaseUrl();
|
|
5405
|
+
const response = await authenticatedFetch(
|
|
5406
|
+
`${base}/api/projects/${id}/deployments`,
|
|
5407
|
+
{
|
|
5408
|
+
method: "POST",
|
|
5409
|
+
headers: { "Content-Type": "application/json" },
|
|
5410
|
+
body: JSON.stringify({ type: options.type, label: options.label })
|
|
5411
|
+
}
|
|
5412
|
+
);
|
|
5413
|
+
if (!response.ok) {
|
|
5414
|
+
const body = await response.json().catch(() => ({}));
|
|
5415
|
+
throw new Error(
|
|
5416
|
+
body.error?.message || "Failed to create deployment"
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5419
|
+
return response.json();
|
|
5420
|
+
}
|
|
5421
|
+
async function deleteDeployment(options) {
|
|
5422
|
+
const id = options.projectId ?? requireProjectId();
|
|
5423
|
+
const base = resolveBaseUrl();
|
|
5424
|
+
const response = await authenticatedFetch(
|
|
5425
|
+
`${base}/api/projects/${id}/deployments/${options.slug}`,
|
|
5426
|
+
{ method: "DELETE" }
|
|
5427
|
+
);
|
|
5428
|
+
if (!response.ok) {
|
|
5429
|
+
const body = await response.json().catch(() => ({}));
|
|
5430
|
+
throw new Error(
|
|
5431
|
+
body.error?.message || "Failed to delete deployment"
|
|
5432
|
+
);
|
|
5433
|
+
}
|
|
5434
|
+
const data = await response.json().catch(() => null);
|
|
5435
|
+
return data ?? { success: true };
|
|
5436
|
+
}
|
|
5437
|
+
async function handleResult3(fn2, options) {
|
|
5438
|
+
const logger2 = createCLILogger(options);
|
|
5439
|
+
try {
|
|
5440
|
+
const result = await fn2();
|
|
5441
|
+
await writeResult(JSON.stringify(result, null, 2), options);
|
|
5442
|
+
} catch (error) {
|
|
5443
|
+
logger2.error(error instanceof Error ? error.message : String(error));
|
|
5444
|
+
process.exit(1);
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
async function listDeploymentsCommand(options) {
|
|
5448
|
+
await handleResult3(
|
|
5449
|
+
() => listDeployments({
|
|
5450
|
+
projectId: options.project,
|
|
5451
|
+
type: options.type,
|
|
5452
|
+
status: options.status
|
|
5453
|
+
}),
|
|
5454
|
+
options
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5457
|
+
async function getDeploymentBySlugCommand(slug, options) {
|
|
5458
|
+
await handleResult3(
|
|
5459
|
+
() => getDeploymentBySlug({ slug, projectId: options.project }),
|
|
5460
|
+
options
|
|
5461
|
+
);
|
|
5462
|
+
}
|
|
5463
|
+
async function createDeploymentCommand(options) {
|
|
5464
|
+
await handleResult3(
|
|
5465
|
+
() => createDeployment({
|
|
5466
|
+
type: options.type,
|
|
5467
|
+
label: options.label,
|
|
5468
|
+
projectId: options.project
|
|
5469
|
+
}),
|
|
5470
|
+
options
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
async function deleteDeploymentCommand(slug, options) {
|
|
5474
|
+
await handleResult3(
|
|
5475
|
+
() => deleteDeployment({ slug, projectId: options.project }),
|
|
5476
|
+
options
|
|
5477
|
+
);
|
|
5478
|
+
}
|
|
5479
|
+
async function createDeployCommand(config, options) {
|
|
5480
|
+
const log = createCLILogger(options);
|
|
5481
|
+
try {
|
|
5482
|
+
let type;
|
|
5483
|
+
if (!config) {
|
|
5484
|
+
log.error(
|
|
5485
|
+
"Config required. Provide a flow config file or remote flow ID (cfg_xxx)."
|
|
5486
|
+
);
|
|
5487
|
+
process.exit(1);
|
|
5488
|
+
}
|
|
5489
|
+
const isRemoteFlow = config.startsWith("cfg_");
|
|
5490
|
+
if (isRemoteFlow) {
|
|
5491
|
+
const id = options.project ?? requireProjectId();
|
|
5492
|
+
const base = resolveBaseUrl();
|
|
5493
|
+
const resp = await authenticatedFetch(
|
|
5494
|
+
`${base}/api/projects/${id}/flows/${config}`
|
|
5495
|
+
);
|
|
5496
|
+
if (!resp.ok) {
|
|
5497
|
+
const body = await resp.json().catch(() => ({}));
|
|
5498
|
+
throw new Error(
|
|
5499
|
+
body.error?.message || `Failed to fetch flow ${config}`
|
|
5500
|
+
);
|
|
5501
|
+
}
|
|
5502
|
+
const flow = await resp.json();
|
|
5503
|
+
if (!flow.content) throw new Error("Flow has no content");
|
|
5504
|
+
const content = flow.content;
|
|
5505
|
+
const flows = content.flows;
|
|
5506
|
+
if (!flows) throw new Error("Invalid flow content: missing flows");
|
|
5507
|
+
const flowName = options.flow ?? Object.keys(flows)[0];
|
|
5508
|
+
if (!flowName) throw new Error("No flows found in config");
|
|
5509
|
+
const flowConfig = flows[flowName];
|
|
5510
|
+
if (!flowConfig || typeof flowConfig !== "object")
|
|
5511
|
+
throw new Error("Invalid flow config");
|
|
5512
|
+
if ("web" in flowConfig) type = "web";
|
|
5513
|
+
else if ("server" in flowConfig) type = "server";
|
|
5514
|
+
else throw new Error('Flow must have "web" or "server" key');
|
|
5515
|
+
} else {
|
|
5516
|
+
const result2 = await loadFlowConfig(config, {
|
|
5517
|
+
flowName: options.flow
|
|
5518
|
+
});
|
|
5519
|
+
type = getPlatform4(result2.flowConfig);
|
|
5520
|
+
}
|
|
5521
|
+
const deployment = await createDeployment({
|
|
5522
|
+
type,
|
|
5523
|
+
label: options.label,
|
|
5524
|
+
projectId: options.project
|
|
5525
|
+
});
|
|
5526
|
+
const result = deployment;
|
|
5527
|
+
if (options.json) {
|
|
5528
|
+
await writeResult(JSON.stringify(result, null, 2), options);
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
log.info(`Deployment created: ${result.id}`);
|
|
5532
|
+
log.info(` Slug: ${result.slug}`);
|
|
5533
|
+
log.info(` Type: ${result.type}`);
|
|
5534
|
+
if (result.deployToken) {
|
|
5535
|
+
log.info(` Token: ${result.deployToken}`);
|
|
5536
|
+
log.warn(" Save this token \u2014 it will not be shown again.");
|
|
5537
|
+
}
|
|
5538
|
+
log.info("");
|
|
5539
|
+
log.info("Run locally:");
|
|
5540
|
+
log.info(
|
|
5541
|
+
` walkeros run ${isRemoteFlow ? "flow.json" : config} --deploy ${result.id}`
|
|
5542
|
+
);
|
|
5543
|
+
log.info("");
|
|
5544
|
+
log.info("Docker:");
|
|
5545
|
+
log.info(
|
|
5546
|
+
` docker run -e WALKEROS_DEPLOY_TOKEN=${result.deployToken ?? "<token>"} \\`
|
|
5547
|
+
);
|
|
5548
|
+
log.info(" -e WALKEROS_APP_URL=https://app.walkeros.io \\");
|
|
5549
|
+
log.info(" walkeros/flow:latest");
|
|
5550
|
+
} catch (err) {
|
|
5551
|
+
log.error(
|
|
5552
|
+
err instanceof Error ? err.message : "Failed to create deployment"
|
|
5553
|
+
);
|
|
5554
|
+
process.exit(1);
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
// src/index.ts
|
|
5559
|
+
init_auth();
|
|
4273
5560
|
export {
|
|
4274
5561
|
bundle,
|
|
4275
5562
|
bundleCommand,
|
|
4276
5563
|
bundleRemote,
|
|
5564
|
+
compareOutput,
|
|
4277
5565
|
createApiClient,
|
|
5566
|
+
createDeployCommand,
|
|
5567
|
+
createDeployment,
|
|
5568
|
+
createDeploymentCommand,
|
|
4278
5569
|
createFlow,
|
|
4279
5570
|
createFlowCommand,
|
|
4280
5571
|
createProject,
|
|
4281
5572
|
createProjectCommand,
|
|
5573
|
+
deleteDeployment,
|
|
5574
|
+
deleteDeploymentCommand,
|
|
4282
5575
|
deleteFlow,
|
|
4283
5576
|
deleteFlowCommand,
|
|
4284
5577
|
deleteProject,
|
|
4285
5578
|
deleteProjectCommand,
|
|
4286
5579
|
deploy,
|
|
5580
|
+
deployAuthenticatedFetch,
|
|
4287
5581
|
deployCommand,
|
|
4288
5582
|
duplicateFlow,
|
|
4289
5583
|
duplicateFlowCommand,
|
|
5584
|
+
findExample,
|
|
4290
5585
|
getAuthHeaders,
|
|
4291
5586
|
getDeployment,
|
|
5587
|
+
getDeploymentBySlug,
|
|
5588
|
+
getDeploymentBySlugCommand,
|
|
4292
5589
|
getDeploymentCommand,
|
|
4293
5590
|
getFlow,
|
|
4294
5591
|
getFlowCommand,
|
|
4295
5592
|
getProject,
|
|
4296
5593
|
getProjectCommand,
|
|
4297
5594
|
getToken,
|
|
5595
|
+
listDeployments,
|
|
5596
|
+
listDeploymentsCommand,
|
|
4298
5597
|
listFlows,
|
|
4299
5598
|
listFlowsCommand,
|
|
4300
5599
|
listProjects,
|
|
4301
5600
|
listProjectsCommand,
|
|
5601
|
+
loadJsonConfig,
|
|
4302
5602
|
loginCommand,
|
|
4303
5603
|
logoutCommand,
|
|
5604
|
+
parseSSEEvents,
|
|
4304
5605
|
push,
|
|
4305
5606
|
pushCommand,
|
|
4306
5607
|
requireProjectId,
|