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,296 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { dirname, join } from "node:path";
4
+ export function apiCollectionsPath(rootDir) {
5
+ return join(rootDir, "catalogs", "api-collections.json");
6
+ }
7
+ export async function loadApiCollections(rootDir) {
8
+ try {
9
+ const parsed = JSON.parse(await readFile(apiCollectionsPath(rootDir), "utf8"));
10
+ return normalizeCollectionFile(parsed);
11
+ }
12
+ catch (error) {
13
+ const err = error;
14
+ if (err.code === "ENOENT")
15
+ return { version: 1, collections: [] };
16
+ throw error;
17
+ }
18
+ }
19
+ export async function writeApiCollections(rootDir, file) {
20
+ const outputPath = apiCollectionsPath(rootDir);
21
+ await mkdir(dirname(outputPath), { recursive: true });
22
+ await writeFile(outputPath, `${JSON.stringify(normalizeCollectionFile(file), null, 2)}\n`, "utf8");
23
+ }
24
+ export async function importPostmanCollection(rootDir, payload) {
25
+ const collection = parsePostmanCollection(payload);
26
+ const file = await loadApiCollections(rootDir);
27
+ const remaining = file.collections.filter((item) => item.id !== collection.id);
28
+ await writeApiCollections(rootDir, { version: 1, collections: [...remaining, collection] });
29
+ return collection;
30
+ }
31
+ export function parsePostmanCollection(payload) {
32
+ const source = asObject(payload);
33
+ const name = String(source.info?.name || "Imported API Collection").trim();
34
+ const collectionId = sanitizeId(source.info?._postman_id || `${name}_${shortHash(JSON.stringify(payload))}`);
35
+ const variables = postmanVariables(source.variable);
36
+ const requests = [];
37
+ const rootAuth = source.auth;
38
+ function visit(items, folderPath, inheritedAuth) {
39
+ for (const item of items ?? []) {
40
+ if (Array.isArray(item.item)) {
41
+ visit(item.item, [...folderPath, String(item.name || "Folder")], inheritedAuth);
42
+ continue;
43
+ }
44
+ const request = typeof item.request === "string" ? undefined : item.request;
45
+ if (!request)
46
+ continue;
47
+ const requestId = uniqueRequestId(collectionId, folderPath, item.name || request.method || "request", requests.length);
48
+ const spec = postmanRequestSpec(request, inheritedAuth);
49
+ const variableNames = [...new Set([
50
+ ...findTemplateVariables(spec),
51
+ ...Object.keys(variables)
52
+ ])].sort((a, b) => a.localeCompare(b));
53
+ requests.push({
54
+ id: requestId,
55
+ name: String(item.name || requestId),
56
+ folderPath,
57
+ operationKey: `pm_${collectionId}_${requestId}`,
58
+ description: descriptionText(request.description),
59
+ request: spec,
60
+ variables,
61
+ variableNames
62
+ });
63
+ }
64
+ }
65
+ visit(source.item, [], rootAuth);
66
+ return {
67
+ id: collectionId,
68
+ name,
69
+ importedAt: new Date().toISOString(),
70
+ variables,
71
+ requestCount: requests.length,
72
+ requests
73
+ };
74
+ }
75
+ export function previewPostmanCollection(payload) {
76
+ const collection = parsePostmanCollection(payload);
77
+ return {
78
+ name: collection.name,
79
+ id: collection.id,
80
+ requestCount: collection.requestCount,
81
+ variables: collection.variables ?? {},
82
+ folders: [...new Set(collection.requests.map((request) => request.folderPath.join(" / ")).filter(Boolean))].sort(),
83
+ requests: collection.requests.map((request) => ({
84
+ id: request.id,
85
+ name: request.name,
86
+ folderPath: request.folderPath,
87
+ operationKey: request.operationKey,
88
+ variableNames: request.variableNames,
89
+ ...request.request
90
+ }))
91
+ };
92
+ }
93
+ export function importedOperationsFromCollections(file) {
94
+ const operations = {};
95
+ for (const collection of file.collections) {
96
+ for (const request of collection.requests) {
97
+ operations[request.operationKey] = {
98
+ type: "rest",
99
+ description: request.description || `${collection.name}${request.folderPath.length ? ` / ${request.folderPath.join(" / ")}` : ""} / ${request.name}`,
100
+ ...request.request,
101
+ params: paramsFromVariables(request.variableNames),
102
+ source: {
103
+ collectionId: collection.id,
104
+ collectionName: collection.name,
105
+ requestId: request.id,
106
+ folderPath: request.folderPath
107
+ }
108
+ };
109
+ }
110
+ }
111
+ return operations;
112
+ }
113
+ export function operationToRequestSpec(operation) {
114
+ if (!operation)
115
+ return {};
116
+ return {
117
+ method: operation.method,
118
+ path: operation.path,
119
+ headers: operation.headers,
120
+ query: operation.query,
121
+ body: operation.body,
122
+ rawBody: operation.rawBody,
123
+ bodyMode: operation.bodyMode,
124
+ auth: operation.auth,
125
+ acceptStatuses: operation.acceptStatuses
126
+ };
127
+ }
128
+ export function mergeApiRequest(base, override) {
129
+ const source = operationToRequestSpec(base);
130
+ return cleanRequest({
131
+ ...source,
132
+ ...(override ?? {}),
133
+ headers: mergeRecord(source.headers, override?.headers),
134
+ query: mergeRecord(source.query, override?.query),
135
+ acceptStatuses: override?.acceptStatuses ?? source.acceptStatuses
136
+ });
137
+ }
138
+ function postmanRequestSpec(request, inheritedAuth) {
139
+ const url = parsePostmanUrl(request.url);
140
+ const body = parsePostmanBody(request.body);
141
+ return cleanRequest({
142
+ method: normalizeMethod(request.method),
143
+ path: url.path,
144
+ query: url.query,
145
+ headers: mergeRecord(generatedPostmanHeaders(body), keyValueRecord(request.header)),
146
+ auth: request.auth ?? inheritedAuth,
147
+ ...body
148
+ });
149
+ }
150
+ function parsePostmanUrl(value) {
151
+ if (typeof value === "string")
152
+ return pathAndQueryFromRaw(value);
153
+ const url = asObject(value);
154
+ const raw = optionalString(url.raw);
155
+ const query = keyValueRecord(url.query);
156
+ if (raw) {
157
+ const parsed = pathAndQueryFromRaw(raw);
158
+ return { path: parsed.path, query: mergeRecord(parsed.query, query) };
159
+ }
160
+ const pathParts = Array.isArray(url.path) ? url.path.map(String) : [];
161
+ return {
162
+ path: pathParts.length ? `/${pathParts.join("/")}` : undefined,
163
+ query
164
+ };
165
+ }
166
+ function pathAndQueryFromRaw(raw) {
167
+ const trimmed = raw.trim();
168
+ const withoutBase = trimmed.replace(/^\{\{[^}]+\}\}/, "");
169
+ try {
170
+ const url = new URL(trimmed.replace(/^\{\{[^}]+\}\}/, "http://postman.local"));
171
+ return { path: url.pathname, query: Object.fromEntries(url.searchParams.entries()) };
172
+ }
173
+ catch {
174
+ const [path, qs] = withoutBase.split("?");
175
+ return {
176
+ path: path.startsWith("/") ? path : `/${path}`,
177
+ query: qs ? Object.fromEntries(new URLSearchParams(qs).entries()) : undefined
178
+ };
179
+ }
180
+ }
181
+ function parsePostmanBody(body) {
182
+ if (!body?.mode)
183
+ return { bodyMode: "none" };
184
+ if (body.mode === "raw") {
185
+ const rawBody = body.raw ?? "";
186
+ const trimmed = rawBody.trim();
187
+ if (trimmed && looksLikeJson(trimmed)) {
188
+ return { bodyMode: "json", rawBody };
189
+ }
190
+ return { bodyMode: "raw", rawBody };
191
+ }
192
+ if (body.mode === "urlencoded") {
193
+ return { bodyMode: "urlencoded", body: keyValueRecord(body.urlencoded) ?? {} };
194
+ }
195
+ if (body.mode === "formdata") {
196
+ return { bodyMode: "formdata", body: keyValueRecord(body.formdata) ?? {} };
197
+ }
198
+ return { bodyMode: "raw", rawBody: body.raw ?? "" };
199
+ }
200
+ function normalizeMethod(value) {
201
+ const method = String(value || "GET").toUpperCase();
202
+ if (["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(method)) {
203
+ return method;
204
+ }
205
+ return "GET";
206
+ }
207
+ function keyValueRecord(items) {
208
+ const entries = (items ?? [])
209
+ .filter((item) => !item.disabled && item.key)
210
+ .map((item) => [String(item.key), String(item.value ?? "")]);
211
+ return entries.length ? Object.fromEntries(entries) : undefined;
212
+ }
213
+ function generatedPostmanHeaders(body) {
214
+ const headers = {
215
+ Accept: "*/*",
216
+ "Cache-Control": "no-cache"
217
+ };
218
+ if (body.bodyMode === "json")
219
+ headers["Content-Type"] = "application/json";
220
+ if (body.bodyMode === "urlencoded")
221
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
222
+ return Object.keys(headers).length ? headers : undefined;
223
+ }
224
+ function postmanVariables(items) {
225
+ return Object.fromEntries((items ?? [])
226
+ .filter((item) => !item.disabled && item.key)
227
+ .map((item) => [String(item.key), item.value ?? ""]));
228
+ }
229
+ function findTemplateVariables(value) {
230
+ const names = [];
231
+ const visit = (current) => {
232
+ if (typeof current === "string") {
233
+ for (const match of current.matchAll(/\{\{([A-Za-z0-9_.-]+)\}\}/g))
234
+ names.push(match[1]);
235
+ for (const match of current.matchAll(/\$\{([A-Za-z0-9_.-]+)\}/g))
236
+ names.push(match[1]);
237
+ return;
238
+ }
239
+ if (Array.isArray(current))
240
+ current.forEach(visit);
241
+ if (current && typeof current === "object")
242
+ Object.values(current).forEach(visit);
243
+ };
244
+ visit(value);
245
+ return names;
246
+ }
247
+ function paramsFromVariables(variableNames) {
248
+ if (!variableNames.length)
249
+ return undefined;
250
+ return Object.fromEntries(variableNames.map((name) => [name, { required: false, type: "string" }]));
251
+ }
252
+ function normalizeCollectionFile(file) {
253
+ return {
254
+ version: 1,
255
+ collections: Array.isArray(file.collections) ? file.collections.map((collection) => ({
256
+ ...collection,
257
+ requestCount: collection.requests?.length ?? collection.requestCount ?? 0,
258
+ requests: collection.requests ?? []
259
+ })) : []
260
+ };
261
+ }
262
+ function cleanRequest(request) {
263
+ return Object.fromEntries(Object.entries(request).filter(([, value]) => value !== undefined && value !== null && !(typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0)));
264
+ }
265
+ function mergeRecord(base, override) {
266
+ const merged = { ...(base ?? {}), ...(override ?? {}) };
267
+ return Object.keys(merged).length ? merged : undefined;
268
+ }
269
+ function asObject(value) {
270
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
271
+ }
272
+ function optionalString(value) {
273
+ return value === undefined || value === null ? undefined : String(value);
274
+ }
275
+ function looksLikeJson(value) {
276
+ return value.startsWith("{") || value.startsWith("[");
277
+ }
278
+ function descriptionText(value) {
279
+ if (typeof value === "string")
280
+ return value;
281
+ const description = asObject(value);
282
+ return optionalString(description.content);
283
+ }
284
+ function uniqueRequestId(collectionId, folderPath, name, index) {
285
+ return sanitizeId(`${folderPath.join("_")}_${name}_${index || ""}`) || `${collectionId}_request_${index + 1}`;
286
+ }
287
+ function sanitizeId(value) {
288
+ return String(value ?? "")
289
+ .toLowerCase()
290
+ .replace(/[^a-z0-9_-]+/g, "_")
291
+ .replace(/^_+|_+$/g, "")
292
+ .slice(0, 80) || "collection";
293
+ }
294
+ function shortHash(value) {
295
+ return createHash("sha1").update(value).digest("hex").slice(0, 8);
296
+ }
@@ -0,0 +1,9 @@
1
+ export function normalizeJsonRawBody(rawBody) {
2
+ return stripStandaloneBackslashLines(rawBody);
3
+ }
4
+ function stripStandaloneBackslashLines(value) {
5
+ return value
6
+ .split(/\r?\n/)
7
+ .filter((line) => !/^\s*\\\s*$/.test(line))
8
+ .join("\n");
9
+ }