cgraphx 1.2.0 → 1.3.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.
- package/dist/.claude-template/hooks/precommit-check/precommit-check.cjs +90 -0
- package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +100 -78
- package/dist/.claude-template/skills/precommit-review/SKILL.md +50 -0
- package/dist/.claude-template/skills/run-api-test/SKILL.md +187 -0
- package/dist/.claude-template/skills/run-api-test/assets/template-test-report.md +103 -0
- package/dist/.claude-template/skills/run-api-test/assets/template-test-verify.jsonl +5 -0
- package/dist/.claude-template/skills/run-api-test/references/bru-run.md +60 -0
- package/dist/.claude-template/skills/run-api-test/references/db-verification.md +81 -0
- package/dist/.claude-template/skills/run-api-test/references/report-format.md +104 -0
- package/dist/.claude-template/skills/run-api-test/references/service-readiness.md +61 -0
- package/dist/.claude-template/skills/run-api-test/references/test-scope.md +64 -0
- package/dist/.claude-template/skills/write-api/SKILL.md +150 -0
- package/dist/.claude-template/skills/write-api/assets/template-api-spec.md +112 -0
- package/dist/.claude-template/skills/write-api/assets/template-request.bru +72 -0
- package/dist/.claude-template/skills/write-api/references/ai-prompts.md +133 -0
- package/dist/.claude-template/skills/write-api/references/api-spec-format.md +108 -0
- package/dist/.claude-template/skills/write-api/references/bru-format.md +139 -0
- package/dist/.claude-template/skills/write-api/references/collection-layout.md +90 -0
- package/dist/.claude-template/skills/write-api/references/environment-setup.md +105 -0
- package/dist/.claude-template/skills/write-api/references/interface-scope.md +74 -0
- package/dist/api-test/ai-fields.d.ts +37 -0
- package/dist/api-test/ai-fields.d.ts.map +1 -0
- package/dist/api-test/ai-fields.js +114 -0
- package/dist/api-test/ai-fields.js.map +1 -0
- package/dist/api-test/assemble.d.ts +76 -0
- package/dist/api-test/assemble.d.ts.map +1 -0
- package/dist/api-test/assemble.js +185 -0
- package/dist/api-test/assemble.js.map +1 -0
- package/dist/api-test/bru-cli-invoker.d.ts +72 -0
- package/dist/api-test/bru-cli-invoker.d.ts.map +1 -0
- package/dist/api-test/bru-cli-invoker.js +169 -0
- package/dist/api-test/bru-cli-invoker.js.map +1 -0
- package/dist/api-test/bru-report-parser.d.ts +24 -0
- package/dist/api-test/bru-report-parser.d.ts.map +1 -0
- package/dist/api-test/bru-report-parser.js +110 -0
- package/dist/api-test/bru-report-parser.js.map +1 -0
- package/dist/api-test/bru-runner.d.ts +101 -0
- package/dist/api-test/bru-runner.d.ts.map +1 -0
- package/dist/api-test/bru-runner.js +316 -0
- package/dist/api-test/bru-runner.js.map +1 -0
- package/dist/api-test/bru-writer.d.ts +52 -0
- package/dist/api-test/bru-writer.d.ts.map +1 -0
- package/dist/api-test/bru-writer.js +159 -0
- package/dist/api-test/bru-writer.js.map +1 -0
- package/dist/api-test/call-chain-extractor.d.ts +80 -0
- package/dist/api-test/call-chain-extractor.d.ts.map +1 -0
- package/dist/api-test/call-chain-extractor.js +179 -0
- package/dist/api-test/call-chain-extractor.js.map +1 -0
- package/dist/api-test/cli.d.ts +133 -0
- package/dist/api-test/cli.d.ts.map +1 -0
- package/dist/api-test/cli.js +1009 -0
- package/dist/api-test/cli.js.map +1 -0
- package/dist/api-test/config.d.ts +75 -0
- package/dist/api-test/config.d.ts.map +1 -0
- package/dist/api-test/config.js +406 -0
- package/dist/api-test/config.js.map +1 -0
- package/dist/api-test/db-query-cli.d.ts +51 -0
- package/dist/api-test/db-query-cli.d.ts.map +1 -0
- package/dist/api-test/db-query-cli.js +119 -0
- package/dist/api-test/db-query-cli.js.map +1 -0
- package/dist/api-test/enhance-prepare.d.ts +111 -0
- package/dist/api-test/enhance-prepare.d.ts.map +1 -0
- package/dist/api-test/enhance-prepare.js +425 -0
- package/dist/api-test/enhance-prepare.js.map +1 -0
- package/dist/api-test/enhance-write.d.ts +28 -0
- package/dist/api-test/enhance-write.d.ts.map +1 -0
- package/dist/api-test/enhance-write.js +145 -0
- package/dist/api-test/enhance-write.js.map +1 -0
- package/dist/api-test/errors.d.ts +48 -0
- package/dist/api-test/errors.d.ts.map +1 -0
- package/dist/api-test/errors.js +76 -0
- package/dist/api-test/errors.js.map +1 -0
- package/dist/api-test/field-extractor.d.ts +98 -0
- package/dist/api-test/field-extractor.d.ts.map +1 -0
- package/dist/api-test/field-extractor.js +327 -0
- package/dist/api-test/field-extractor.js.map +1 -0
- package/dist/api-test/impl-finder.d.ts +37 -0
- package/dist/api-test/impl-finder.d.ts.map +1 -0
- package/dist/api-test/impl-finder.js +54 -0
- package/dist/api-test/impl-finder.js.map +1 -0
- package/dist/api-test/index.d.ts +41 -0
- package/dist/api-test/index.d.ts.map +1 -0
- package/dist/api-test/index.js +124 -0
- package/dist/api-test/index.js.map +1 -0
- package/dist/api-test/java-parser.d.ts +89 -0
- package/dist/api-test/java-parser.d.ts.map +1 -0
- package/dist/api-test/java-parser.js +508 -0
- package/dist/api-test/java-parser.js.map +1 -0
- package/dist/api-test/md-writer.d.ts +49 -0
- package/dist/api-test/md-writer.d.ts.map +1 -0
- package/dist/api-test/md-writer.js +202 -0
- package/dist/api-test/md-writer.js.map +1 -0
- package/dist/api-test/parser-httpservice.d.ts +91 -0
- package/dist/api-test/parser-httpservice.d.ts.map +1 -0
- package/dist/api-test/parser-httpservice.js +271 -0
- package/dist/api-test/parser-httpservice.js.map +1 -0
- package/dist/api-test/report.d.ts +188 -0
- package/dist/api-test/report.d.ts.map +1 -0
- package/dist/api-test/report.js +522 -0
- package/dist/api-test/report.js.map +1 -0
- package/dist/api-test/snapshot.d.ts +26 -0
- package/dist/api-test/snapshot.d.ts.map +1 -0
- package/dist/api-test/snapshot.js +150 -0
- package/dist/api-test/snapshot.js.map +1 -0
- package/dist/api-test/test-history.d.ts +48 -0
- package/dist/api-test/test-history.d.ts.map +1 -0
- package/dist/api-test/test-history.js +122 -0
- package/dist/api-test/test-history.js.map +1 -0
- package/dist/api-test/types.d.ts +174 -0
- package/dist/api-test/types.d.ts.map +1 -0
- package/dist/api-test/types.js +13 -0
- package/dist/api-test/types.js.map +1 -0
- package/dist/api-test/verify-prepare.d.ts +30 -0
- package/dist/api-test/verify-prepare.d.ts.map +1 -0
- package/dist/api-test/verify-prepare.js +150 -0
- package/dist/api-test/verify-prepare.js.map +1 -0
- package/dist/api-test/verify-write.d.ts +31 -0
- package/dist/api-test/verify-write.d.ts.map +1 -0
- package/dist/api-test/verify-write.js +159 -0
- package/dist/api-test/verify-write.js.map +1 -0
- package/dist/dbquery/dump-schema.d.ts +46 -0
- package/dist/dbquery/dump-schema.d.ts.map +1 -0
- package/dist/dbquery/dump-schema.js +379 -0
- package/dist/dbquery/dump-schema.js.map +1 -0
- package/dist/installer/targets/claude.d.ts +15 -0
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +53 -0
- package/dist/installer/targets/claude.js.map +1 -1
- package/package.json +1 -1
- package/scripts/agent-eval/block-cgraphx-and-gitnexus-cli-hook.sh +0 -43
- package/scripts/agent-eval/block-cgraphx-cli-hook.sh +0 -32
- package/scripts/agent-eval/block-cgraphx-cli-settings.json +0 -16
- package/scripts/agent-eval/cli-vs-mcp-3arm.sh +0 -121
- package/scripts/agent-eval/multi-tool-eval.sh +0 -171
- package/scripts/agent-eval/parse-cli-vs-mcp.mjs +0 -232
- package/scripts/agent-eval/parse-multi-tool.mjs +0 -242
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# S1 接口范围识别规范
|
|
2
|
+
|
|
3
|
+
## 三级优先级
|
|
4
|
+
|
|
5
|
+
按以下顺序识别需求要测的接口:
|
|
6
|
+
|
|
7
|
+
1. **用户显式给** — 文件列表 / 路由前缀 / handler 函数名 / 接口路径
|
|
8
|
+
2. **spec 的接口章节** — 从 `docs/features/<feature-id>/<文件前缀>-spec.md` 提取
|
|
9
|
+
3. **git diff 自动提取** — 本次 feature 改动新增的路由注册点
|
|
10
|
+
|
|
11
|
+
优先级高的命中就用高的,不向下降级混合使用(避免范围漂移)。用户显式给就只测用户给的;用户没给才查 spec;spec 也没有才扫 git diff。
|
|
12
|
+
|
|
13
|
+
git diff 失效(改动已合并 / 事后补测)不是问题,靠优先级 1/2 自然处理。
|
|
14
|
+
|
|
15
|
+
## 用户确认(必须)
|
|
16
|
+
|
|
17
|
+
无论哪种来源拿到接口清单,**必须显示给用户确认**:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
本次识别到的接口范围(来自:用户显式给 / spec 接口章节 / git diff):
|
|
21
|
+
| 方法 | 路径 | handler | 是否写接口 |
|
|
22
|
+
| ... | ... | ... | ... |
|
|
23
|
+
|
|
24
|
+
确认这是你要测的范围?回车继续,或 Ctrl+C 取消。
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**为什么必须确认**:AI 可能理解偏差,测错接口会浪费 S2/S3 的生成时间。用户一眼确认最稳。
|
|
28
|
+
|
|
29
|
+
## 结果格式
|
|
30
|
+
|
|
31
|
+
接口清单每条含:
|
|
32
|
+
|
|
33
|
+
| 字段 | 说明 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| 方法 | GET / POST / PUT / DELETE / PATCH |
|
|
36
|
+
| 路径 | 如 `/api/v1/users/:id` |
|
|
37
|
+
| handler | 函数全名,如 `UserController.getUser` |
|
|
38
|
+
| 是否写接口 | POST/PUT/DELETE/PATCH = 是;GET = 否 |
|
|
39
|
+
|
|
40
|
+
"是否写接口"决定 .bru 是否需要写 DB 副作用预期注释(见 `bru-format.md`),以及下游 `/run-api-test` 是否对它做 DB 核实。
|
|
41
|
+
|
|
42
|
+
## 边界情况
|
|
43
|
+
|
|
44
|
+
| 情况 | 处理 |
|
|
45
|
+
|---|---|
|
|
46
|
+
| 三种来源都拿不到接口 | 报"未识别到接口范围,请显式给接口清单或确认 spec 接口章节完整"并退出 |
|
|
47
|
+
| spec 无接口章节且用户未显式给 | 报"spec 无接口章节,请显式给接口范围"并退出 |
|
|
48
|
+
| 用户给的接口列表里有修改的老接口 | 跳过修改的老接口,只保留新增的;在测试规格 §4 标注哪些被跳过 |
|
|
49
|
+
| git diff 扫到的路由注册点在 spec 里没有声明 | 仍纳入接口清单,但在测试规格 §4 标注"代码命中但 spec 未声明,需用户确认" |
|
|
50
|
+
| 同一接口在多个来源里都出现 | 按优先级 1 取用户显式给为准,去重 |
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## 结果格式
|
|
55
|
+
|
|
56
|
+
接口清单每条含:
|
|
57
|
+
|
|
58
|
+
| 字段 | 说明 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| 方法 | GET / POST / PUT / DELETE / PATCH |
|
|
61
|
+
| 路径 | 如 `/api/v1/users/:id` |
|
|
62
|
+
| handler | 函数全名,如 `UserController.getUser` |
|
|
63
|
+
| 是否写接口 | POST/PUT/DELETE/PATCH = 是;GET = 否 |
|
|
64
|
+
|
|
65
|
+
"是否写接口"决定 .bru 是否需要写 DB 副作用预期注释(见 `bru-format.md`),以及下游 `/run-api-test` 是否对它做 DB 核实。
|
|
66
|
+
|
|
67
|
+
## 边界情况
|
|
68
|
+
|
|
69
|
+
| 情况 | 处理 |
|
|
70
|
+
|---|---|
|
|
71
|
+
| 本次 feature 无新增接口 | 报"未检测到新增接口"并退出,不生成空产物 |
|
|
72
|
+
| 用户给的接口列表里有修改的老接口 | 跳过修改的老接口,只保留新增的;在测试规格 §4 标注哪些被跳过 |
|
|
73
|
+
| git diff 扫到的路由注册点在 spec 里没有声明 | 仍纳入接口清单,但在测试规格 §4 标注"代码命中但 spec 未声明,需用户确认是否本次新增" |
|
|
74
|
+
| 同一接口在 git diff 和用户显式给里都出现 | 按优先级 1 取用户显式给为准,去重 |
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-inferred fields persistence (L-T-10).
|
|
3
|
+
*
|
|
4
|
+
* `enhance-prepare` reads any previously-written AI fields so a re-run keeps
|
|
5
|
+
* the AI's prior inference unless the human regenerates it. `enhance-write`
|
|
6
|
+
* (L-T-20) writes this file after the AI infers fields.
|
|
7
|
+
*
|
|
8
|
+
* Storage: `.cgraphx/api-test/ai-fields/<endpoint>.json` where `<endpoint>`
|
|
9
|
+
* is the .md filename stem (`<svc_id>_<method>`). One file per endpoint so
|
|
10
|
+
* partial reruns only touch their own file.
|
|
11
|
+
*
|
|
12
|
+
* Independent subsystem — only depends on `node:fs`/`node:path` and the
|
|
13
|
+
* `AiFields` / `Field` types.
|
|
14
|
+
*/
|
|
15
|
+
import type { AiFields, Field } from './types';
|
|
16
|
+
/** Subdir under `.cgraphx/api-test/` holding the per-endpoint JSON files. */
|
|
17
|
+
export declare const AI_FIELDS_SUBDIR = "ai-fields";
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the JSON path for an endpoint: `<projectRoot>/.cgraphx/api-test/
|
|
20
|
+
* ai-fields/<endpoint>.json`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function aiFieldsFilePath(projectRoot: string, endpoint: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Read previously-persisted AI fields for `endpoint`. Returns `null` when the
|
|
25
|
+
* file doesn't exist or can't be parsed (treat as "no prior inference").
|
|
26
|
+
*/
|
|
27
|
+
export declare function readAiFields(projectRoot: string, endpoint: string): AiFields | null;
|
|
28
|
+
/**
|
|
29
|
+
* Persist AI-inferred fields for `endpoint`. Creates the parent directory if
|
|
30
|
+
* needed. `updatedAt` is stamped with the current ISO timestamp.
|
|
31
|
+
*/
|
|
32
|
+
export declare function writeAiFields(projectRoot: string, endpoint: string, fields: Field[]): void;
|
|
33
|
+
/** Internal helpers re-exported for tests. */
|
|
34
|
+
export declare const __INTERNAL_AI_FIELDS: {
|
|
35
|
+
AI_FIELDS_SUBDIR: string;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=ai-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-fields.d.ts","sourceRoot":"","sources":["../../src/api-test/ai-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE/C,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CA0BnF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAS1F;AAED,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB;;CAEhC,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI-inferred fields persistence (L-T-10).
|
|
4
|
+
*
|
|
5
|
+
* `enhance-prepare` reads any previously-written AI fields so a re-run keeps
|
|
6
|
+
* the AI's prior inference unless the human regenerates it. `enhance-write`
|
|
7
|
+
* (L-T-20) writes this file after the AI infers fields.
|
|
8
|
+
*
|
|
9
|
+
* Storage: `.cgraphx/api-test/ai-fields/<endpoint>.json` where `<endpoint>`
|
|
10
|
+
* is the .md filename stem (`<svc_id>_<method>`). One file per endpoint so
|
|
11
|
+
* partial reruns only touch their own file.
|
|
12
|
+
*
|
|
13
|
+
* Independent subsystem — only depends on `node:fs`/`node:path` and the
|
|
14
|
+
* `AiFields` / `Field` types.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.__INTERNAL_AI_FIELDS = exports.AI_FIELDS_SUBDIR = void 0;
|
|
51
|
+
exports.aiFieldsFilePath = aiFieldsFilePath;
|
|
52
|
+
exports.readAiFields = readAiFields;
|
|
53
|
+
exports.writeAiFields = writeAiFields;
|
|
54
|
+
const fs = __importStar(require("node:fs"));
|
|
55
|
+
const path = __importStar(require("node:path"));
|
|
56
|
+
/** Subdir under `.cgraphx/api-test/` holding the per-endpoint JSON files. */
|
|
57
|
+
exports.AI_FIELDS_SUBDIR = 'ai-fields';
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the JSON path for an endpoint: `<projectRoot>/.cgraphx/api-test/
|
|
60
|
+
* ai-fields/<endpoint>.json`.
|
|
61
|
+
*/
|
|
62
|
+
function aiFieldsFilePath(projectRoot, endpoint) {
|
|
63
|
+
return path.join(projectRoot, '.cgraphx', 'api-test', exports.AI_FIELDS_SUBDIR, `${endpoint}.json`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Read previously-persisted AI fields for `endpoint`. Returns `null` when the
|
|
67
|
+
* file doesn't exist or can't be parsed (treat as "no prior inference").
|
|
68
|
+
*/
|
|
69
|
+
function readAiFields(projectRoot, endpoint) {
|
|
70
|
+
const file = aiFieldsFilePath(projectRoot, endpoint);
|
|
71
|
+
let content;
|
|
72
|
+
try {
|
|
73
|
+
content = fs.readFileSync(file, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const parsed = JSON.parse(content);
|
|
80
|
+
if (typeof parsed !== 'object' ||
|
|
81
|
+
parsed === null ||
|
|
82
|
+
typeof parsed.endpoint !== 'string' ||
|
|
83
|
+
!Array.isArray(parsed.fields)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
endpoint: parsed.endpoint,
|
|
88
|
+
fields: parsed.fields,
|
|
89
|
+
updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : '',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Persist AI-inferred fields for `endpoint`. Creates the parent directory if
|
|
98
|
+
* needed. `updatedAt` is stamped with the current ISO timestamp.
|
|
99
|
+
*/
|
|
100
|
+
function writeAiFields(projectRoot, endpoint, fields) {
|
|
101
|
+
const file = aiFieldsFilePath(projectRoot, endpoint);
|
|
102
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
103
|
+
const payload = {
|
|
104
|
+
endpoint,
|
|
105
|
+
fields,
|
|
106
|
+
updatedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
fs.writeFileSync(file, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
109
|
+
}
|
|
110
|
+
/** Internal helpers re-exported for tests. */
|
|
111
|
+
exports.__INTERNAL_AI_FIELDS = {
|
|
112
|
+
AI_FIELDS_SUBDIR: exports.AI_FIELDS_SUBDIR,
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=ai-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-fields.js","sourceRoot":"","sources":["../../src/api-test/ai-fields.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcH,4CAEC;AAMD,oCA0BC;AAMD,sCASC;AA7DD,4CAA8B;AAC9B,gDAAkC;AAIlC,6EAA6E;AAChE,QAAA,gBAAgB,GAAG,WAAW,CAAC;AAE5C;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,WAAmB,EAAE,QAAgB;IACpE,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,wBAAgB,EAAE,GAAG,QAAQ,OAAO,CAAC,CAAC;AAC9F,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAAC,WAAmB,EAAE,QAAgB;IAChE,MAAM,IAAI,GAAG,gBAAgB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAsB,CAAC;QACxD,IACE,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;YACnC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAC7B,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAiB;YAChC,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;SACxE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,WAAmB,EAAE,QAAgB,EAAE,MAAe;IAClF,MAAM,IAAI,GAAG,gBAAgB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACrD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,OAAO,GAAa;QACxB,QAAQ;QACR,MAAM;QACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED,8CAA8C;AACjC,QAAA,oBAAoB,GAAG;IAClC,gBAAgB,EAAhB,wBAAgB;CACjB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assemble phase — source real parameter values for each endpoint and write
|
|
3
|
+
* them into the `.bru` `body:json` block.
|
|
4
|
+
*
|
|
5
|
+
* Spec contract (T-40): three-tier fallback per endpoint:
|
|
6
|
+
* 1. fixture hit → use `fixtures[<svc_id>_<methodName>]`.params verbatim.
|
|
7
|
+
* 2. db-query → for each Field with `sourceTable`, run
|
|
8
|
+
* `cgraphx db query --sql "SELECT ... LIMIT 1" --format json`
|
|
9
|
+
* via the dist binary (subprocess). `literal` fields are
|
|
10
|
+
* filled directly; `fixtureOnly` fields require a fixture
|
|
11
|
+
* (else the endpoint is skip).
|
|
12
|
+
* 3. skip → db-query CLI failure → `db_query_failed`;
|
|
13
|
+
* any field missing source/literal/fixture → `assemble_failed`.
|
|
14
|
+
*
|
|
15
|
+
* Independent subsystem — does NOT import from `src/dbquery/`. The db-query
|
|
16
|
+
* CLI is invoked as a subprocess (subsystem isolation per spec §12). The
|
|
17
|
+
* subprocess wrapper lives in `./db-query-cli.ts` so tests can `vi.spyOn`
|
|
18
|
+
* the export and intercept calls from this module.
|
|
19
|
+
*
|
|
20
|
+
* Public surface:
|
|
21
|
+
* - `assembleEndpoint(endpoint, config, fixtures, projectRoot)`
|
|
22
|
+
* → `{ body, skipped, reason }`
|
|
23
|
+
* - `writeBruBody(bruPath, body)` — patches a `.bru` file's `body:json` block.
|
|
24
|
+
* - `replaceBodyJsonBlock(bruContent, newBody)` — pure-string transform.
|
|
25
|
+
* - re-exports `runDbQueryCli` / `DbQueryResult` from `./db-query-cli` so
|
|
26
|
+
* callers can import everything from one place.
|
|
27
|
+
*/
|
|
28
|
+
import type { ApiTestConfig, Endpoint, Field, Fixtures } from './types';
|
|
29
|
+
import { runDbQueryCli, type DbQueryResult } from './db-query-cli';
|
|
30
|
+
export { runDbQueryCli, type DbQueryResult };
|
|
31
|
+
/** Outcome of assembling one endpoint. */
|
|
32
|
+
export interface AssembleResult {
|
|
33
|
+
/** JSON string ready to drop into a `.bru` `body:json` block. Empty when `skipped`. */
|
|
34
|
+
body: string;
|
|
35
|
+
skipped: boolean;
|
|
36
|
+
reason: string | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Assemble the request body for one endpoint following the three-tier
|
|
40
|
+
* fallback: fixture > db-query > skip.
|
|
41
|
+
*
|
|
42
|
+
* Fixture key: `<svc_id>_<methodName>` (matches the `.bru` filename stem).
|
|
43
|
+
* Fixture `params` is used verbatim as the body array (positional per
|
|
44
|
+
* method-declaration order).
|
|
45
|
+
*
|
|
46
|
+
* For db-query sourcing, fields are grouped by `paramIndex` and one object
|
|
47
|
+
* per param is emitted (in paramIndex order). The body is always a JSON
|
|
48
|
+
* array — `[{...param0}, {...param1}, ...]`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assembleEndpoint(endpoint: Endpoint, _config: ApiTestConfig, fixtures: Fixtures, projectRoot: string): Promise<AssembleResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Build the `SELECT ... FROM ... WHERE ... LIMIT 1` SQL for a db-query-sourced
|
|
53
|
+
* field. `sourceFilter` is optional; `sourceColumn` and `sourceTable` are
|
|
54
|
+
* required (caller guards).
|
|
55
|
+
*/
|
|
56
|
+
declare function buildSourceSql(field: Field): string;
|
|
57
|
+
/**
|
|
58
|
+
* Patch a `.bru` file's `body:json` block content in place. Reads the file,
|
|
59
|
+
* replaces whatever is between `body:json {` and the matching closing `}`,
|
|
60
|
+
* and writes it back. Preserves the rest of the file verbatim.
|
|
61
|
+
*
|
|
62
|
+
* Returns the new content (also written to disk). Caller is responsible for
|
|
63
|
+
* the file existing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function writeBruBody(bruPath: string, body: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Pure-string transform: replace the inner content of the `body:json { ... }`
|
|
68
|
+
* block in a `.bru` file. If the block can't be located, returns the input
|
|
69
|
+
* unchanged (defensive — scan phase always emits one).
|
|
70
|
+
*/
|
|
71
|
+
export declare function replaceBodyJsonBlock(bruContent: string, newBody: string): string;
|
|
72
|
+
/** Internal helpers re-exported for tests. */
|
|
73
|
+
export declare const __INTERNAL_ASSEMBLE: {
|
|
74
|
+
buildSourceSql: typeof buildSourceSql;
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=assemble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../src/api-test/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EACV,aAAa,EACb,QAAQ,EACR,KAAK,EACL,QAAQ,EACT,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,CAAC;AAE7C,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC,CAyEzB;AAED;;;;GAIG;AACH,iBAAS,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAG5C;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKjF;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhF;AAED,8CAA8C;AAC9C,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Assemble phase — source real parameter values for each endpoint and write
|
|
4
|
+
* them into the `.bru` `body:json` block.
|
|
5
|
+
*
|
|
6
|
+
* Spec contract (T-40): three-tier fallback per endpoint:
|
|
7
|
+
* 1. fixture hit → use `fixtures[<svc_id>_<methodName>]`.params verbatim.
|
|
8
|
+
* 2. db-query → for each Field with `sourceTable`, run
|
|
9
|
+
* `cgraphx db query --sql "SELECT ... LIMIT 1" --format json`
|
|
10
|
+
* via the dist binary (subprocess). `literal` fields are
|
|
11
|
+
* filled directly; `fixtureOnly` fields require a fixture
|
|
12
|
+
* (else the endpoint is skip).
|
|
13
|
+
* 3. skip → db-query CLI failure → `db_query_failed`;
|
|
14
|
+
* any field missing source/literal/fixture → `assemble_failed`.
|
|
15
|
+
*
|
|
16
|
+
* Independent subsystem — does NOT import from `src/dbquery/`. The db-query
|
|
17
|
+
* CLI is invoked as a subprocess (subsystem isolation per spec §12). The
|
|
18
|
+
* subprocess wrapper lives in `./db-query-cli.ts` so tests can `vi.spyOn`
|
|
19
|
+
* the export and intercept calls from this module.
|
|
20
|
+
*
|
|
21
|
+
* Public surface:
|
|
22
|
+
* - `assembleEndpoint(endpoint, config, fixtures, projectRoot)`
|
|
23
|
+
* → `{ body, skipped, reason }`
|
|
24
|
+
* - `writeBruBody(bruPath, body)` — patches a `.bru` file's `body:json` block.
|
|
25
|
+
* - `replaceBodyJsonBlock(bruContent, newBody)` — pure-string transform.
|
|
26
|
+
* - re-exports `runDbQueryCli` / `DbQueryResult` from `./db-query-cli` so
|
|
27
|
+
* callers can import everything from one place.
|
|
28
|
+
*/
|
|
29
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
32
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
33
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
34
|
+
}
|
|
35
|
+
Object.defineProperty(o, k2, desc);
|
|
36
|
+
}) : (function(o, m, k, k2) {
|
|
37
|
+
if (k2 === undefined) k2 = k;
|
|
38
|
+
o[k2] = m[k];
|
|
39
|
+
}));
|
|
40
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
41
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
42
|
+
}) : function(o, v) {
|
|
43
|
+
o["default"] = v;
|
|
44
|
+
});
|
|
45
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
46
|
+
var ownKeys = function(o) {
|
|
47
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
48
|
+
var ar = [];
|
|
49
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
50
|
+
return ar;
|
|
51
|
+
};
|
|
52
|
+
return ownKeys(o);
|
|
53
|
+
};
|
|
54
|
+
return function (mod) {
|
|
55
|
+
if (mod && mod.__esModule) return mod;
|
|
56
|
+
var result = {};
|
|
57
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
58
|
+
__setModuleDefault(result, mod);
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
})();
|
|
62
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
|
+
exports.__INTERNAL_ASSEMBLE = exports.runDbQueryCli = void 0;
|
|
64
|
+
exports.assembleEndpoint = assembleEndpoint;
|
|
65
|
+
exports.writeBruBody = writeBruBody;
|
|
66
|
+
exports.replaceBodyJsonBlock = replaceBodyJsonBlock;
|
|
67
|
+
const fs = __importStar(require("node:fs"));
|
|
68
|
+
const db_query_cli_1 = require("./db-query-cli");
|
|
69
|
+
Object.defineProperty(exports, "runDbQueryCli", { enumerable: true, get: function () { return db_query_cli_1.runDbQueryCli; } });
|
|
70
|
+
/**
|
|
71
|
+
* Assemble the request body for one endpoint following the three-tier
|
|
72
|
+
* fallback: fixture > db-query > skip.
|
|
73
|
+
*
|
|
74
|
+
* Fixture key: `<svc_id>_<methodName>` (matches the `.bru` filename stem).
|
|
75
|
+
* Fixture `params` is used verbatim as the body array (positional per
|
|
76
|
+
* method-declaration order).
|
|
77
|
+
*
|
|
78
|
+
* For db-query sourcing, fields are grouped by `paramIndex` and one object
|
|
79
|
+
* per param is emitted (in paramIndex order). The body is always a JSON
|
|
80
|
+
* array — `[{...param0}, {...param1}, ...]`.
|
|
81
|
+
*/
|
|
82
|
+
async function assembleEndpoint(endpoint, _config, fixtures, projectRoot) {
|
|
83
|
+
const fixtureKey = `${endpoint.svcId}_${endpoint.method}`;
|
|
84
|
+
const fixture = fixtures[fixtureKey];
|
|
85
|
+
// ─── Priority 1: fixture hit ────────────────────────────────────────────
|
|
86
|
+
if (fixture) {
|
|
87
|
+
const body = JSON.stringify(fixture.params);
|
|
88
|
+
return { body, skipped: false, reason: null };
|
|
89
|
+
}
|
|
90
|
+
// ─── Priority 2: db-query ───────────────────────────────────────────────
|
|
91
|
+
// No fields → can't assemble (the analyze phase either skipped or produced
|
|
92
|
+
// nothing). Mark `assemble_failed` so the .md reflects it.
|
|
93
|
+
if (endpoint.fields.length === 0) {
|
|
94
|
+
return { body: '', skipped: true, reason: 'assemble_failed' };
|
|
95
|
+
}
|
|
96
|
+
// fixtureOnly fields require a fixture (which we don't have here).
|
|
97
|
+
for (const f of endpoint.fields) {
|
|
98
|
+
if (f.fixtureOnly) {
|
|
99
|
+
return { body: '', skipped: true, reason: 'assemble_failed' };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Group fields by paramIndex (one body object per method parameter).
|
|
103
|
+
const paramGroups = new Map();
|
|
104
|
+
for (const f of endpoint.fields) {
|
|
105
|
+
const arr = paramGroups.get(f.paramIndex) ?? [];
|
|
106
|
+
arr.push(f);
|
|
107
|
+
paramGroups.set(f.paramIndex, arr);
|
|
108
|
+
}
|
|
109
|
+
const paramIndices = [...paramGroups.keys()].sort((a, b) => a - b);
|
|
110
|
+
const bodyArray = [];
|
|
111
|
+
for (const idx of paramIndices) {
|
|
112
|
+
const fields = paramGroups.get(idx);
|
|
113
|
+
const paramObj = {};
|
|
114
|
+
for (const f of fields) {
|
|
115
|
+
// literal — fill directly.
|
|
116
|
+
if (f.literal !== null) {
|
|
117
|
+
paramObj[f.name] = f.literal;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// db-query sourced field.
|
|
121
|
+
if (f.sourceTable && f.sourceColumn) {
|
|
122
|
+
const sql = buildSourceSql(f);
|
|
123
|
+
const result = await (0, db_query_cli_1.runDbQueryCli)(sql, projectRoot);
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
return { body: '', skipped: true, reason: 'db_query_failed' };
|
|
126
|
+
}
|
|
127
|
+
const row = result.rows[0];
|
|
128
|
+
if (!row) {
|
|
129
|
+
// Query succeeded but returned no rows — field value can't be sourced.
|
|
130
|
+
return { body: '', skipped: true, reason: 'assemble_failed' };
|
|
131
|
+
}
|
|
132
|
+
const value = row[f.sourceColumn];
|
|
133
|
+
if (value === undefined || value === null) {
|
|
134
|
+
return { body: '', skipped: true, reason: 'assemble_failed' };
|
|
135
|
+
}
|
|
136
|
+
paramObj[f.name] = value;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// No source, no literal, no fixture → can't source this field.
|
|
140
|
+
return { body: '', skipped: true, reason: 'assemble_failed' };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
bodyArray.push(paramObj);
|
|
144
|
+
}
|
|
145
|
+
return { body: JSON.stringify(bodyArray), skipped: false, reason: null };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build the `SELECT ... FROM ... WHERE ... LIMIT 1` SQL for a db-query-sourced
|
|
149
|
+
* field. `sourceFilter` is optional; `sourceColumn` and `sourceTable` are
|
|
150
|
+
* required (caller guards).
|
|
151
|
+
*/
|
|
152
|
+
function buildSourceSql(field) {
|
|
153
|
+
const filter = field.sourceFilter ? ` WHERE ${field.sourceFilter}` : '';
|
|
154
|
+
return `SELECT ${field.sourceColumn} FROM ${field.sourceTable}${filter} LIMIT 1`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Patch a `.bru` file's `body:json` block content in place. Reads the file,
|
|
158
|
+
* replaces whatever is between `body:json {` and the matching closing `}`,
|
|
159
|
+
* and writes it back. Preserves the rest of the file verbatim.
|
|
160
|
+
*
|
|
161
|
+
* Returns the new content (also written to disk). Caller is responsible for
|
|
162
|
+
* the file existing.
|
|
163
|
+
*/
|
|
164
|
+
async function writeBruBody(bruPath, body) {
|
|
165
|
+
const content = await fs.promises.readFile(bruPath, 'utf8');
|
|
166
|
+
const updated = replaceBodyJsonBlock(content, body);
|
|
167
|
+
await fs.promises.writeFile(bruPath, updated, 'utf8');
|
|
168
|
+
return updated;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Pure-string transform: replace the inner content of the `body:json { ... }`
|
|
172
|
+
* block in a `.bru` file. If the block can't be located, returns the input
|
|
173
|
+
* unchanged (defensive — scan phase always emits one).
|
|
174
|
+
*/
|
|
175
|
+
function replaceBodyJsonBlock(bruContent, newBody) {
|
|
176
|
+
const re = /(body:json\s*\{\n)([\s\S]*?)(\n\})/;
|
|
177
|
+
if (!re.test(bruContent))
|
|
178
|
+
return bruContent;
|
|
179
|
+
return bruContent.replace(re, `$1 ${newBody}$3`);
|
|
180
|
+
}
|
|
181
|
+
/** Internal helpers re-exported for tests. */
|
|
182
|
+
exports.__INTERNAL_ASSEMBLE = {
|
|
183
|
+
buildSourceSql,
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=assemble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../src/api-test/assemble.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bruno CLI subprocess invoker — isolated in its own module so tests can
|
|
3
|
+
* `vi.spyOn` the `invokeBrunoCli` export and intercept calls from
|
|
4
|
+
* `bru-runner.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Why a separate module: same reason as `db-query-cli.ts` — when the
|
|
7
|
+
* subprocess call lives in the same module as its caller, vitest's
|
|
8
|
+
* `vi.spyOn(module, 'fn')` cannot intercept the internal bare-local-binding
|
|
9
|
+
* call. Cross-module imports route through the namespace, which the spy CAN
|
|
10
|
+
* replace.
|
|
11
|
+
*
|
|
12
|
+
* Independent subsystem — does NOT import from `src/extraction/` /
|
|
13
|
+
* `src/resolution/` / `src/db/`. The Bruno CLI is invoked as a subprocess
|
|
14
|
+
* (spec §12 isolation).
|
|
15
|
+
*/
|
|
16
|
+
/** Result of one Bruno CLI invocation. */
|
|
17
|
+
export interface BrunoCliInvocation {
|
|
18
|
+
/** Combined stdout from the subprocess. */
|
|
19
|
+
stdout: string;
|
|
20
|
+
/** Combined stderr from the subprocess. */
|
|
21
|
+
stderr: string;
|
|
22
|
+
/** Process exit code (null if the process was killed by signal). */
|
|
23
|
+
exitCode: number | null;
|
|
24
|
+
/** True when the subprocess was killed by the timeout. */
|
|
25
|
+
timedOut: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface InvokeBrunoCliOptions {
|
|
28
|
+
/** Working directory for the subprocess. */
|
|
29
|
+
cwd: string;
|
|
30
|
+
/** Hard kill timeout in milliseconds. */
|
|
31
|
+
timeoutMs: number;
|
|
32
|
+
/** Max stdout/stderr buffer size in bytes (default 10 MB). */
|
|
33
|
+
maxBuffer?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Invoke `npx @usebruno/cli` with the given args. Returns the captured
|
|
37
|
+
* stdout/stderr/exitCode. The caller is responsible for arg construction
|
|
38
|
+
* (see `bru-runner.ts`).
|
|
39
|
+
*
|
|
40
|
+
* Exported so tests can `vi.spyOn(bruCliInvoker, 'invokeBrunoCli')` instead
|
|
41
|
+
* of spawning a real subprocess / real Bruno CLI.
|
|
42
|
+
*/
|
|
43
|
+
export declare function invokeBrunoCli(args: string[], options: InvokeBrunoCliOptions): BrunoCliInvocation;
|
|
44
|
+
/**
|
|
45
|
+
* Probe whether the Bruno CLI is available. Tries, in order:
|
|
46
|
+
* 1. `npx @usebruno/cli --version` (preferred — always works if npm cache
|
|
47
|
+
* has the package, even without global install).
|
|
48
|
+
* 2. Global `bru` command (`which bru`).
|
|
49
|
+
* 3. Global `bruno` command (`which bruno`).
|
|
50
|
+
*
|
|
51
|
+
* Returns the resolved command + base args for subsequent `invokeBrunoCli`
|
|
52
|
+
* calls, or `null` when no Bruno CLI is reachable.
|
|
53
|
+
*
|
|
54
|
+
* Exported so tests can spy on it to simulate "Bruno CLI unavailable".
|
|
55
|
+
*/
|
|
56
|
+
export declare function detectBrunoCli(): {
|
|
57
|
+
command: string;
|
|
58
|
+
baseArgs: string[];
|
|
59
|
+
} | null;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the collection directory. If `filter.service` is set, narrow to
|
|
62
|
+
* `<collectionDir>/<service>/` when it exists; otherwise fall back to the
|
|
63
|
+
* whole collection dir (Bruno CLI will just find no matching requests).
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveCollectionDir(collectionDir: string, filter?: {
|
|
66
|
+
service?: string;
|
|
67
|
+
}): string;
|
|
68
|
+
/** Internal helper re-exported for tests. */
|
|
69
|
+
export declare const __INTERNAL_BRU_CLI_INVOKER: {
|
|
70
|
+
resolveCollectionDir: typeof resolveCollectionDir;
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=bru-cli-invoker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bru-cli-invoker.d.ts","sourceRoot":"","sources":["../../src/api-test/bru-cli-invoker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,qBAAqB,GAC7B,kBAAkB,CA2BpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CA8C/E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,MAAM,CAQR;AAED,6CAA6C;AAC7C,eAAO,MAAM,0BAA0B;;CAEtC,CAAC"}
|