adfinem 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.env.example +13 -0
  2. package/CHANGELOG.md +17 -0
  3. package/CODE_OF_CONDUCT.md +21 -0
  4. package/CONTRIBUTING.md +29 -0
  5. package/LICENSE +21 -0
  6. package/README.md +97 -3
  7. package/SECURITY.md +13 -0
  8. package/catalogs/.gitkeep +0 -0
  9. package/catalogs/api-operations.yaml +21 -0
  10. package/catalogs/batches.yaml +74 -0
  11. package/catalogs/queries.yaml +75 -0
  12. package/config/environments.yaml +13 -0
  13. package/dist/actions/assert-db.js +3 -0
  14. package/dist/actions/run-eod.js +3 -0
  15. package/dist/adapters/api/api-collections.js +296 -0
  16. package/dist/adapters/api/body-utils.js +9 -0
  17. package/dist/adapters/api/rest-client.js +557 -0
  18. package/dist/adapters/api/soap-client.js +5 -0
  19. package/dist/adapters/db/assertions.js +87 -0
  20. package/dist/adapters/db/oracle-client.js +115 -0
  21. package/dist/adapters/db/query-catalog.js +75 -0
  22. package/dist/adapters/unix/batch-catalog.js +71 -0
  23. package/dist/adapters/unix/batch-input-files.js +36 -0
  24. package/dist/adapters/unix/batch-runner.js +382 -0
  25. package/dist/adapters/unix/ssh-client.js +228 -0
  26. package/dist/app/server.js +827 -0
  27. package/dist/cli.js +516 -0
  28. package/dist/config/environments.js +138 -0
  29. package/dist/config/registry.js +18 -0
  30. package/dist/config/secrets.js +123 -0
  31. package/dist/dsl/parser.js +20 -0
  32. package/dist/dsl/schema.js +182 -0
  33. package/dist/dsl/types.js +1 -0
  34. package/dist/dsl/validator.js +264 -0
  35. package/dist/engine/captures.js +68 -0
  36. package/dist/engine/context.js +69 -0
  37. package/dist/engine/evidence.js +33 -0
  38. package/dist/engine/known-errors.js +129 -0
  39. package/dist/engine/retry.js +13 -0
  40. package/dist/engine/runner.js +710 -0
  41. package/dist/engine/step-result.js +58 -0
  42. package/dist/flows/catalog-normalizer.js +72 -0
  43. package/dist/flows/compiler.js +237 -0
  44. package/dist/flows/concat.js +130 -0
  45. package/dist/flows/parser.js +21 -0
  46. package/dist/flows/schema.js +142 -0
  47. package/dist/flows/types.js +1 -0
  48. package/dist/flows/validator.js +470 -0
  49. package/dist/reports/html-report.js +112 -0
  50. package/dist/reports/junit-report.js +48 -0
  51. package/docs/.gitkeep +0 -0
  52. package/docs/DB_UNIX_OPERATIONS.md +118 -0
  53. package/docs/FLOW_BUILDER.md +87 -0
  54. package/flows/account_processing_cycle.flow.yaml +88 -0
  55. package/flows/new_flow.flow.yaml +22 -0
  56. package/package.json +98 -11
  57. package/scenarios/smoke/account-processing-smoke.yaml +44 -0
  58. package/scenarios/smoke/api-db-batch-check.yaml +40 -0
  59. package/src/actions/assert-db.ts +6 -0
  60. package/src/actions/run-eod.ts +6 -0
  61. package/src/adapters/api/api-collections.ts +375 -0
  62. package/src/adapters/api/body-utils.ts +10 -0
  63. package/src/adapters/api/rest-client.ts +587 -0
  64. package/src/adapters/api/soap-client.ts +7 -0
  65. package/src/adapters/db/assertions.ts +83 -0
  66. package/src/adapters/db/oracle-client.ts +133 -0
  67. package/src/adapters/db/query-catalog.ts +80 -0
  68. package/src/adapters/unix/batch-catalog.ts +81 -0
  69. package/src/adapters/unix/batch-input-files.ts +39 -0
  70. package/src/adapters/unix/batch-runner.ts +456 -0
  71. package/src/adapters/unix/ssh-client.ts +248 -0
  72. package/src/app/server.ts +914 -0
  73. package/src/cli.ts +517 -0
  74. package/src/config/environments.ts +193 -0
  75. package/src/config/registry.ts +23 -0
  76. package/src/config/secrets.ts +128 -0
  77. package/src/dsl/parser.ts +24 -0
  78. package/src/dsl/schema.ts +189 -0
  79. package/src/dsl/types.ts +371 -0
  80. package/src/dsl/validator.ts +282 -0
  81. package/src/engine/captures.ts +66 -0
  82. package/src/engine/context.ts +76 -0
  83. package/src/engine/evidence.ts +35 -0
  84. package/src/engine/known-errors.ts +145 -0
  85. package/src/engine/retry.ts +11 -0
  86. package/src/engine/runner.ts +746 -0
  87. package/src/engine/step-result.ts +64 -0
  88. package/src/flows/catalog-normalizer.ts +86 -0
  89. package/src/flows/compiler.ts +247 -0
  90. package/src/flows/concat.ts +149 -0
  91. package/src/flows/parser.ts +27 -0
  92. package/src/flows/schema.ts +154 -0
  93. package/src/flows/types.ts +130 -0
  94. package/src/flows/validator.ts +468 -0
  95. package/src/llm/system-prompt.md +9 -0
  96. package/src/reports/html-report.ts +113 -0
  97. package/src/reports/junit-report.ts +55 -0
  98. package/src/types/oracledb.d.ts +1 -0
  99. package/templates/.gitkeep +0 -0
  100. package/templates/api/create-test-case.json +5 -0
  101. package/templates/api/record-test-activity.json +6 -0
  102. package/tsconfig.json +15 -0
  103. package/vite.config.ts +17 -0
  104. package/web/index.html +12 -0
  105. package/web/src/App.tsx +6588 -0
  106. package/web/src/main.tsx +10 -0
  107. package/web/src/styles.css +3147 -0
  108. package/web-dist/assets/elk.bundled-ChwRCIWJ.js +24 -0
  109. package/web-dist/assets/index-CArbX4zm.css +1 -0
  110. package/web-dist/assets/index-vDCbj8xB.js +28 -0
  111. package/web-dist/index.html +13 -0
  112. package/index.js +0 -1
@@ -0,0 +1,228 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { posix as posixPath } from "node:path";
3
+ import { Client } from "ssh2";
4
+ import { cancellationError } from "../../engine/step-result.js";
5
+ const MAX_CAPTURE_BYTES = 5 * 1024 * 1024;
6
+ export class SshClient {
7
+ env;
8
+ constructor(env) {
9
+ this.env = env;
10
+ }
11
+ async execute(hostRef, command, timeoutMs, signal) {
12
+ const host = this.hostConfig(hostRef);
13
+ const privateKey = await this.privateKey(host);
14
+ if (signal?.aborted)
15
+ throw cancellationError();
16
+ const execCommand = buildSshExecCommand(host, command);
17
+ return await new Promise((resolve, reject) => {
18
+ const client = new Client();
19
+ let timer;
20
+ let settled = false;
21
+ const abort = () => finish(() => reject(cancellationError()));
22
+ const finish = (fn) => {
23
+ if (settled)
24
+ return;
25
+ settled = true;
26
+ if (timer)
27
+ clearTimeout(timer);
28
+ signal?.removeEventListener("abort", abort);
29
+ client.end();
30
+ fn();
31
+ };
32
+ signal?.addEventListener("abort", abort, { once: true });
33
+ client.on("ready", () => {
34
+ timer = setTimeout(() => {
35
+ finish(() => reject(new Error(`SSH command timed out after ${timeoutMs}ms.`)));
36
+ }, timeoutMs);
37
+ client.exec(execCommand, (err, stream) => {
38
+ if (err) {
39
+ finish(() => reject(err));
40
+ return;
41
+ }
42
+ let stdout = "";
43
+ let stderr = "";
44
+ let stdoutTruncated = false;
45
+ let stderrTruncated = false;
46
+ let exitCode = 0;
47
+ stream.on("close", (code) => {
48
+ exitCode = code ?? 0;
49
+ finish(() => resolve({ stdout, stderr, exitCode, stdoutTruncated, stderrTruncated }));
50
+ });
51
+ stream.on("data", (data) => {
52
+ const result = appendLimited(stdout, data);
53
+ stdout = result.value;
54
+ stdoutTruncated = stdoutTruncated || result.truncated;
55
+ });
56
+ stream.stderr.on("data", (data) => {
57
+ const result = appendLimited(stderr, data);
58
+ stderr = result.value;
59
+ stderrTruncated = stderrTruncated || result.truncated;
60
+ });
61
+ });
62
+ });
63
+ client.on("error", (error) => finish(() => reject(error)));
64
+ client.connect({
65
+ host: host.host,
66
+ username: host.username,
67
+ password: host.password,
68
+ privateKey,
69
+ readyTimeout: Math.min(timeoutMs, 30_000)
70
+ });
71
+ });
72
+ }
73
+ async uploadFile(hostRef, remotePath, content, timeoutMs, signal) {
74
+ const host = this.hostConfig(hostRef);
75
+ const privateKey = await this.privateKey(host);
76
+ if (signal?.aborted)
77
+ throw cancellationError();
78
+ await new Promise((resolve, reject) => {
79
+ const client = new Client();
80
+ let timer;
81
+ let settled = false;
82
+ const abort = () => finish(() => reject(cancellationError()));
83
+ const finish = (fn) => {
84
+ if (settled)
85
+ return;
86
+ settled = true;
87
+ if (timer)
88
+ clearTimeout(timer);
89
+ signal?.removeEventListener("abort", abort);
90
+ client.end();
91
+ fn();
92
+ };
93
+ signal?.addEventListener("abort", abort, { once: true });
94
+ client.on("ready", () => {
95
+ timer = setTimeout(() => {
96
+ finish(() => reject(new Error(`SFTP upload timed out after ${timeoutMs}ms.`)));
97
+ }, timeoutMs);
98
+ client.sftp((err, sftp) => {
99
+ if (err) {
100
+ finish(() => reject(err));
101
+ return;
102
+ }
103
+ void mkdirpSftp(sftp, posixPath.dirname(remotePath))
104
+ .then(() => new Promise((writeResolve, writeReject) => {
105
+ sftp.writeFile(remotePath, content, (writeError) => {
106
+ if (writeError)
107
+ writeReject(writeError);
108
+ else
109
+ writeResolve();
110
+ });
111
+ }))
112
+ .then(() => finish(resolve))
113
+ .catch((uploadError) => finish(() => reject(uploadError)));
114
+ });
115
+ });
116
+ client.on("error", (error) => finish(() => reject(error)));
117
+ client.connect({
118
+ host: host.host,
119
+ username: host.username,
120
+ password: host.password,
121
+ privateKey,
122
+ readyTimeout: Math.min(timeoutMs, 30_000)
123
+ });
124
+ });
125
+ }
126
+ async downloadFile(hostRef, remotePath, timeoutMs, signal) {
127
+ const host = this.hostConfig(hostRef);
128
+ const privateKey = await this.privateKey(host);
129
+ if (signal?.aborted)
130
+ throw cancellationError();
131
+ return await new Promise((resolve, reject) => {
132
+ const client = new Client();
133
+ let timer;
134
+ let settled = false;
135
+ const abort = () => finish(() => reject(cancellationError()));
136
+ const finish = (fn) => {
137
+ if (settled)
138
+ return;
139
+ settled = true;
140
+ if (timer)
141
+ clearTimeout(timer);
142
+ signal?.removeEventListener("abort", abort);
143
+ client.end();
144
+ fn();
145
+ };
146
+ signal?.addEventListener("abort", abort, { once: true });
147
+ client.on("ready", () => {
148
+ timer = setTimeout(() => {
149
+ finish(() => reject(new Error(`SFTP download timed out after ${timeoutMs}ms.`)));
150
+ }, timeoutMs);
151
+ client.sftp((err, sftp) => {
152
+ if (err) {
153
+ finish(() => reject(err));
154
+ return;
155
+ }
156
+ sftp.readFile(remotePath, (readError, content) => {
157
+ if (readError)
158
+ finish(() => reject(readError));
159
+ else
160
+ finish(() => resolve(content));
161
+ });
162
+ });
163
+ });
164
+ client.on("error", (error) => finish(() => reject(error)));
165
+ client.connect({
166
+ host: host.host,
167
+ username: host.username,
168
+ password: host.password,
169
+ privateKey,
170
+ readyTimeout: Math.min(timeoutMs, 30_000)
171
+ });
172
+ });
173
+ }
174
+ hostConfig(hostRef) {
175
+ const host = this.env.sshHosts[hostRef];
176
+ if (!host)
177
+ throw new Error(`Unknown SSH hostRef '${hostRef}'.`);
178
+ if (!host.host || !host.username)
179
+ throw new Error(`SSH host '${hostRef}' requires host and username.`);
180
+ return host;
181
+ }
182
+ async privateKey(host) {
183
+ return host.privateKeyPath ? await readFile(host.privateKeyPath, "utf8") : undefined;
184
+ }
185
+ }
186
+ export function buildSshExecCommand(host, command) {
187
+ if (!host.loginShell)
188
+ return command;
189
+ const shell = host.shell?.trim() || "bash";
190
+ return `${shellQuote(shell)} -lc ${shellQuote(command)}`;
191
+ }
192
+ function appendLimited(current, chunk) {
193
+ const next = current + chunk.toString("utf8");
194
+ const nextBytes = Buffer.byteLength(next, "utf8");
195
+ if (nextBytes <= MAX_CAPTURE_BYTES)
196
+ return { value: next, truncated: false };
197
+ const buffer = Buffer.from(next, "utf8");
198
+ const tail = buffer.subarray(buffer.length - MAX_CAPTURE_BYTES);
199
+ return { value: tail.toString("utf8"), truncated: true };
200
+ }
201
+ async function mkdirpSftp(sftp, directory) {
202
+ if (!sftp || !directory || directory === "." || directory === "/")
203
+ return;
204
+ const absolute = directory.startsWith("/");
205
+ const parts = directory.split("/").filter(Boolean);
206
+ let current = absolute ? "/" : "";
207
+ for (const part of parts) {
208
+ current = current === "/" ? `/${part}` : current ? `${current}/${part}` : part;
209
+ if (await sftpPathExists(sftp, current))
210
+ continue;
211
+ await new Promise((resolve, reject) => {
212
+ sftp.mkdir(current, (error) => {
213
+ if (error && error.code !== "EEXIST")
214
+ reject(error);
215
+ else
216
+ resolve();
217
+ });
218
+ });
219
+ }
220
+ }
221
+ async function sftpPathExists(sftp, path) {
222
+ return await new Promise((resolve) => {
223
+ sftp.stat(path, (error) => resolve(!error));
224
+ });
225
+ }
226
+ function shellQuote(value) {
227
+ return `'${value.replace(/'/g, "'\\''")}'`;
228
+ }