@icyouo/evt-cli 0.1.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.
- package/README.md +45 -7
- package/README.zh-CN.md +41 -7
- package/data/scanner.example.json +2 -5
- package/package.json +5 -2
- package/scripts/build-package.js +173 -0
- package/scripts/check-api-coverage.js +41 -0
- package/scripts/local-ci.js +38 -0
- package/scripts/sync-api-coverage.js +227 -0
- package/skills/evt-api-scanner/SKILL.md +60 -0
- package/skills/evt-api-scanner/scripts/discover.js +160 -0
- package/skills/evt-api-scanner/scripts/scan.js +231 -0
- package/src/config/serviceScanner.js +60 -10
- package/src/index.js +38 -1
- package/test/duration.test.js +11 -0
- package/test/flow.test.js +215 -0
- package/test/liveTester.test.js +54 -0
- package/test/requestBuilder.test.js +214 -0
- package/test/serviceScanner.test.js +196 -0
- package/test/template.test.js +18 -0
- package/test/yaml.test.js +20 -0
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ evt-cli is a general-purpose HTTP CLI for running YAML-defined APIs and workflow
|
|
|
9
9
|
- Runtime endpoints come only from `apis/*.yaml`.
|
|
10
10
|
- Flows come only from `flows/*.yaml`.
|
|
11
11
|
- Environments, base URLs, headers, and test fixtures come only from `profiles/*.json`.
|
|
12
|
-
-
|
|
12
|
+
- Endpoint definitions are persisted as YAML. Scanner JSON is only an internal machine-readable handoff.
|
|
13
|
+
- The bundled skill scanner is preferred for discovery and YAML generation. The built-in regex scanner remains as a fallback for users without an agent or skill workflow.
|
|
13
14
|
- The default data directory is bundled with the CLI package. You can also point to your own project data directory with `--config-root` or `EVT_CLI_ROOT`.
|
|
14
15
|
|
|
15
16
|
## Quick Start
|
|
@@ -83,6 +84,9 @@ For compatibility with existing projects, evt also reads `apis/`, `flows/`, `pro
|
|
|
83
84
|
npm run ci:local
|
|
84
85
|
```
|
|
85
86
|
|
|
87
|
+
The npm package includes source code, scripts, tests, data examples, and skills
|
|
88
|
+
so users can run local checks and build the standalone local binary package.
|
|
89
|
+
|
|
86
90
|
This runs:
|
|
87
91
|
|
|
88
92
|
- `npm test`
|
|
@@ -100,6 +104,7 @@ The local package directory contains only:
|
|
|
100
104
|
|
|
101
105
|
- `evt`
|
|
102
106
|
- `data/`
|
|
107
|
+
- `skills/`
|
|
103
108
|
- `README.md`
|
|
104
109
|
- `README.zh-CN.md`
|
|
105
110
|
|
|
@@ -146,7 +151,35 @@ Each endpoint should include:
|
|
|
146
151
|
- `response`
|
|
147
152
|
- `dangerous: true` when the endpoint is destructive or sensitive
|
|
148
153
|
|
|
149
|
-
## Scan And Coverage
|
|
154
|
+
## Skill Scan, Sync, And Coverage
|
|
155
|
+
|
|
156
|
+
evt-cli includes `skills/evt-api-scanner/SKILL.md`. Agents can use this skill to
|
|
157
|
+
discover source directories, write scanner config, generate YAML endpoint
|
|
158
|
+
definitions, and audit coverage.
|
|
159
|
+
|
|
160
|
+
Endpoint definitions are written to YAML under `data/apis/*.yaml`. The scanner
|
|
161
|
+
may use JSON internally, but JSON is not the endpoint definition format.
|
|
162
|
+
|
|
163
|
+
Discover API source paths and write `data/scanner.json`:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
evt api discover --config-root ./cli
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Generate or update YAML endpoint definitions:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
evt api sync --config-root ./cli
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Check YAML coverage:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
evt api coverage --config-root ./cli
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The built-in regex scanner is still available as a fallback. It is less complete
|
|
182
|
+
than the skill-guided workflow, but it works without an agent.
|
|
150
183
|
|
|
151
184
|
Scan source code for candidate endpoints:
|
|
152
185
|
|
|
@@ -166,7 +199,8 @@ Generate missing YAML skeletons:
|
|
|
166
199
|
npm run sync:api -- --config-root /path/to/project/cli
|
|
167
200
|
```
|
|
168
201
|
|
|
169
|
-
The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports
|
|
202
|
+
The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports the
|
|
203
|
+
bundled skill scanner and external command scanners:
|
|
170
204
|
|
|
171
205
|
```json
|
|
172
206
|
{
|
|
@@ -179,15 +213,16 @@ The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports exter
|
|
|
179
213
|
},
|
|
180
214
|
{
|
|
181
215
|
"language": "skill",
|
|
182
|
-
"name": "
|
|
183
|
-
"
|
|
184
|
-
"args": ["tools/scan-endpoints.js"]
|
|
216
|
+
"name": "evt-api-scanner",
|
|
217
|
+
"skill": "evt-api-scanner"
|
|
185
218
|
}
|
|
186
219
|
]
|
|
187
220
|
}
|
|
188
221
|
```
|
|
189
222
|
|
|
190
|
-
An external scanner can output a JSON array or `{ "endpoints": [] }
|
|
223
|
+
An external scanner can output a JSON array or `{ "endpoints": [] }` as an
|
|
224
|
+
internal scan result. `evt api sync` converts that scan result into YAML. Each
|
|
225
|
+
intermediate endpoint should include at least:
|
|
191
226
|
|
|
192
227
|
```json
|
|
193
228
|
{
|
|
@@ -206,6 +241,9 @@ This JSON is used only for scanning, coverage, and sync. Runtime execution still
|
|
|
206
241
|
```bash
|
|
207
242
|
evt profile list
|
|
208
243
|
evt api list
|
|
244
|
+
evt api discover --config-root ./cli
|
|
245
|
+
evt api sync --config-root ./cli
|
|
246
|
+
evt api coverage --config-root ./cli
|
|
209
247
|
evt api call todo.list --dry-run
|
|
210
248
|
evt flow run login --profile local
|
|
211
249
|
evt cache show
|
package/README.zh-CN.md
CHANGED
|
@@ -9,7 +9,8 @@ evt-cli 是一个通用 HTTP CLI,用 YAML 定义接口、用 YAML 定义 flow
|
|
|
9
9
|
- 运行时端点只来自 `apis/*.yaml`。
|
|
10
10
|
- flow 只来自 `flows/*.yaml`。
|
|
11
11
|
- 环境、base URL、headers、测试 fixtures 只来自 `profiles/*.json`。
|
|
12
|
-
-
|
|
12
|
+
- endpoint 定义持久化格式始终是 YAML。scanner JSON 只作为内部机器中间结果。
|
|
13
|
+
- 内置 skill scanner 优先用于发现、生成 YAML 和覆盖率审计。现有正则 scanner 保留给没有 agent 或 skill workflow 的用户兜底。
|
|
13
14
|
- 默认数据目录是当前 CLI 包内目录;也可以通过 `--config-root` 或 `EVT_CLI_ROOT` 指向项目自己的数据目录。
|
|
14
15
|
|
|
15
16
|
## 快速开始
|
|
@@ -83,6 +84,8 @@ evt-cli 自带 `data/**/*.example.*`,用于开箱验证和复制参考。真
|
|
|
83
84
|
npm run ci:local
|
|
84
85
|
```
|
|
85
86
|
|
|
87
|
+
npm 包会包含源码、脚本、测试、示例数据和 skills,用户可以自行运行本地校验并构建独立本地二进制包。
|
|
88
|
+
|
|
86
89
|
执行内容:
|
|
87
90
|
|
|
88
91
|
- `npm test`
|
|
@@ -100,6 +103,7 @@ npm run ci:local
|
|
|
100
103
|
|
|
101
104
|
- `evt`
|
|
102
105
|
- `data/`
|
|
106
|
+
- `skills/`
|
|
103
107
|
- `README.md`
|
|
104
108
|
- `README.zh-CN.md`
|
|
105
109
|
|
|
@@ -146,7 +150,34 @@ endpoints:
|
|
|
146
150
|
- `response`
|
|
147
151
|
- 必要时标记 `dangerous: true`
|
|
148
152
|
|
|
149
|
-
##
|
|
153
|
+
## Skill 扫描、同步和覆盖率
|
|
154
|
+
|
|
155
|
+
evt-cli 内置 `skills/evt-api-scanner/SKILL.md`。agent 可以使用这个 skill
|
|
156
|
+
发现源码目录、写入 scanner 配置、生成 YAML endpoint 定义,并做覆盖率审计。
|
|
157
|
+
|
|
158
|
+
endpoint 定义会写到 `data/apis/*.yaml`。scanner 可以在内部使用 JSON,但 JSON
|
|
159
|
+
不是 endpoint 的最终定义格式。
|
|
160
|
+
|
|
161
|
+
发现 API 源码路径并写入 `data/scanner.json`:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
evt api discover --config-root ./cli
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
生成或更新 YAML endpoint 定义:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
evt api sync --config-root ./cli
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
检查 YAML 覆盖率:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
evt api coverage --config-root ./cli
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
内置正则 scanner 仍然保留为兜底方案。它不如 skill workflow 完整,但不依赖
|
|
180
|
+
agent,也能让没有 agent 的用户勉强使用。
|
|
150
181
|
|
|
151
182
|
扫描源码候选 endpoint:
|
|
152
183
|
|
|
@@ -166,7 +197,7 @@ npm run coverage:api -- --config-root /path/to/project/cli
|
|
|
166
197
|
npm run sync:api -- --config-root /path/to/project/cli
|
|
167
198
|
```
|
|
168
199
|
|
|
169
|
-
扫描器支持 `kotlin`、`swift`、`js`、`dart
|
|
200
|
+
扫描器支持 `kotlin`、`swift`、`js`、`dart`,也支持内置 skill scanner 和外部命令 scanner:
|
|
170
201
|
|
|
171
202
|
```json
|
|
172
203
|
{
|
|
@@ -179,15 +210,15 @@ npm run sync:api -- --config-root /path/to/project/cli
|
|
|
179
210
|
},
|
|
180
211
|
{
|
|
181
212
|
"language": "skill",
|
|
182
|
-
"name": "
|
|
183
|
-
"
|
|
184
|
-
"args": ["tools/scan-endpoints.js"]
|
|
213
|
+
"name": "evt-api-scanner",
|
|
214
|
+
"skill": "evt-api-scanner"
|
|
185
215
|
}
|
|
186
216
|
]
|
|
187
217
|
}
|
|
188
218
|
```
|
|
189
219
|
|
|
190
|
-
外部 scanner
|
|
220
|
+
外部 scanner 可以输出 JSON 数组,或 `{ "endpoints": [] }` 作为内部扫描结果。
|
|
221
|
+
`evt api sync` 会把这个扫描结果转换成 YAML。每个中间 endpoint 至少包含:
|
|
191
222
|
|
|
192
223
|
```json
|
|
193
224
|
{
|
|
@@ -206,6 +237,9 @@ npm run sync:api -- --config-root /path/to/project/cli
|
|
|
206
237
|
```bash
|
|
207
238
|
evt profile list
|
|
208
239
|
evt api list
|
|
240
|
+
evt api discover --config-root ./cli
|
|
241
|
+
evt api sync --config-root ./cli
|
|
242
|
+
evt api coverage --config-root ./cli
|
|
209
243
|
evt api call todo.list --dry-run
|
|
210
244
|
evt flow run login --profile local
|
|
211
245
|
evt cache show
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icyouo/evt-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Run YAML-defined HTTP APIs and interactive workflows from your terminal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,14 +17,17 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"bin/",
|
|
19
19
|
"src/",
|
|
20
|
+
"scripts/",
|
|
21
|
+
"test/",
|
|
20
22
|
"data/",
|
|
23
|
+
"skills/",
|
|
21
24
|
"README.md",
|
|
22
25
|
"README.zh-CN.md"
|
|
23
26
|
],
|
|
24
27
|
"scripts": {
|
|
25
28
|
"ci:local": "node scripts/local-ci.js",
|
|
26
29
|
"test": "node --test",
|
|
27
|
-
"check": "node --check bin/evt.js && node --check src/index.js && node --check src/api/liveTester.js && node --check scripts/sync-api-coverage.js && node --check scripts/check-api-coverage.js && node --check scripts/local-ci.js && node --check scripts/build-package.js",
|
|
30
|
+
"check": "node --check bin/evt.js && node --check src/index.js && node --check src/api/liveTester.js && node --check scripts/sync-api-coverage.js && node --check scripts/check-api-coverage.js && node --check scripts/local-ci.js && node --check scripts/build-package.js && node --check skills/evt-api-scanner/scripts/discover.js && node --check skills/evt-api-scanner/scripts/scan.js",
|
|
28
31
|
"package:local": "node scripts/build-package.js",
|
|
29
32
|
"sync:api": "node scripts/sync-api-coverage.js",
|
|
30
33
|
"coverage:api": "node scripts/check-api-coverage.js"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const cliRoot = path.resolve(__dirname, "..");
|
|
8
|
+
const packageJson = require(path.join(cliRoot, "package.json"));
|
|
9
|
+
const distDir = path.join(cliRoot, "dist");
|
|
10
|
+
const packageName = "evt-cli";
|
|
11
|
+
const packageDir = path.join(distDir, packageName);
|
|
12
|
+
const entryId = "bin/evt.js";
|
|
13
|
+
|
|
14
|
+
function toPosix(file) {
|
|
15
|
+
return file.split(path.sep).join("/");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeModuleId(file) {
|
|
19
|
+
return path.posix.normalize(toPosix(path.relative(cliRoot, file)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveLocalModule(fromId, request) {
|
|
23
|
+
const base = path.posix.dirname(fromId);
|
|
24
|
+
const target = path.posix.normalize(path.posix.join(base, request));
|
|
25
|
+
const candidates = [
|
|
26
|
+
target,
|
|
27
|
+
`${target}.js`,
|
|
28
|
+
path.posix.join(target, "index.js")
|
|
29
|
+
];
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
const absolute = path.join(cliRoot, ...candidate.split("/"));
|
|
32
|
+
if (fs.existsSync(absolute) && fs.statSync(absolute).isFile()) {
|
|
33
|
+
return normalizeModuleId(absolute);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Unable to resolve ${request} from ${fromId}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findLocalRequires(source) {
|
|
40
|
+
const requires = [];
|
|
41
|
+
const pattern = /require\(\s*["']([^"']+)["']\s*\)/g;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
44
|
+
if (match[1].startsWith(".")) {
|
|
45
|
+
requires.push(match[1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return requires;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectModules(entry) {
|
|
52
|
+
const modules = new Map();
|
|
53
|
+
const queue = [entry];
|
|
54
|
+
|
|
55
|
+
while (queue.length > 0) {
|
|
56
|
+
const id = queue.shift();
|
|
57
|
+
if (modules.has(id)) continue;
|
|
58
|
+
|
|
59
|
+
const absolute = path.join(cliRoot, ...id.split("/"));
|
|
60
|
+
let source = fs.readFileSync(absolute, "utf8");
|
|
61
|
+
if (source.startsWith("#!")) {
|
|
62
|
+
source = source.replace(/^#!.*\n/, "");
|
|
63
|
+
}
|
|
64
|
+
modules.set(id, source);
|
|
65
|
+
|
|
66
|
+
for (const request of findLocalRequires(source)) {
|
|
67
|
+
const resolved = resolveLocalModule(id, request);
|
|
68
|
+
if (!modules.has(resolved)) {
|
|
69
|
+
queue.push(resolved);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return modules;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderExecutable(modules) {
|
|
78
|
+
const moduleEntries = Array.from(modules.entries())
|
|
79
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
80
|
+
.map(([id, source]) => `modules[${JSON.stringify(id)}] = function(require, module, exports, __filename, __dirname) {\n${source}\n};`)
|
|
81
|
+
.join("\n\n");
|
|
82
|
+
|
|
83
|
+
return `#!/usr/bin/env node
|
|
84
|
+
const __nodeRequire = require;
|
|
85
|
+
const path = __nodeRequire("node:path");
|
|
86
|
+
const runtimeRoot = path.dirname(process.argv[1] ? path.resolve(process.argv[1]) : __filename);
|
|
87
|
+
const modules = Object.create(null);
|
|
88
|
+
const moduleCache = Object.create(null);
|
|
89
|
+
|
|
90
|
+
${moduleEntries}
|
|
91
|
+
|
|
92
|
+
function resolveModule(fromId, request) {
|
|
93
|
+
if (!request.startsWith(".")) return request;
|
|
94
|
+
const base = path.posix.dirname(fromId);
|
|
95
|
+
const target = path.posix.normalize(path.posix.join(base, request));
|
|
96
|
+
const candidates = [target, target + ".js", path.posix.join(target, "index.js")];
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
if (Object.prototype.hasOwnProperty.call(modules, candidate)) return candidate;
|
|
99
|
+
}
|
|
100
|
+
throw new Error("Unable to resolve " + request + " from " + fromId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadModule(id) {
|
|
104
|
+
if (!Object.prototype.hasOwnProperty.call(modules, id)) {
|
|
105
|
+
return __nodeRequire(id);
|
|
106
|
+
}
|
|
107
|
+
if (moduleCache[id]) return moduleCache[id].exports;
|
|
108
|
+
const filename = path.join(runtimeRoot, ...id.split("/"));
|
|
109
|
+
const dirname = path.dirname(filename);
|
|
110
|
+
const module = { exports: {} };
|
|
111
|
+
moduleCache[id] = module;
|
|
112
|
+
const localRequire = (request) => loadModule(resolveModule(id, request));
|
|
113
|
+
modules[id](localRequire, module, module.exports, filename, dirname);
|
|
114
|
+
return module.exports;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
loadModule(${JSON.stringify(entryId)});
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function copyDir(sourceDir, targetDir) {
|
|
122
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
123
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
124
|
+
if (entry.name === ".DS_Store") continue;
|
|
125
|
+
const source = path.join(sourceDir, entry.name);
|
|
126
|
+
const target = path.join(targetDir, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
copyDir(source, target);
|
|
129
|
+
} else if (entry.isFile()) {
|
|
130
|
+
fs.copyFileSync(source, target);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function run(command, args) {
|
|
136
|
+
const result = spawnSync(command, args, {
|
|
137
|
+
cwd: distDir,
|
|
138
|
+
stdio: "inherit",
|
|
139
|
+
shell: false
|
|
140
|
+
});
|
|
141
|
+
if (result.status !== 0) {
|
|
142
|
+
process.exit(result.status || 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function cleanDist() {
|
|
147
|
+
fs.rmSync(distDir, { recursive: true, force: true });
|
|
148
|
+
fs.mkdirSync(packageDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function main() {
|
|
152
|
+
cleanDist();
|
|
153
|
+
|
|
154
|
+
const executablePath = path.join(packageDir, "evt");
|
|
155
|
+
fs.writeFileSync(executablePath, renderExecutable(collectModules(entryId)));
|
|
156
|
+
fs.chmodSync(executablePath, 0o755);
|
|
157
|
+
|
|
158
|
+
copyDir(path.join(cliRoot, "data"), path.join(packageDir, "data"));
|
|
159
|
+
copyDir(path.join(cliRoot, "skills"), path.join(packageDir, "skills"));
|
|
160
|
+
fs.copyFileSync(path.join(cliRoot, "README.md"), path.join(packageDir, "README.md"));
|
|
161
|
+
fs.copyFileSync(path.join(cliRoot, "README.zh-CN.md"), path.join(packageDir, "README.zh-CN.md"));
|
|
162
|
+
|
|
163
|
+
const artifactName = `${packageName}-${packageJson.version}-${process.platform}-${process.arch}.tar.gz`;
|
|
164
|
+
run("tar", ["-czf", artifactName, packageName]);
|
|
165
|
+
|
|
166
|
+
console.log("\nLocal package artifacts:");
|
|
167
|
+
console.log(`- dist/${packageName}/`);
|
|
168
|
+
console.log(`- dist/${artifactName}`);
|
|
169
|
+
console.log("\nPackage contents are limited to evt, data examples, skills, README.md, and README.zh-CN.md.");
|
|
170
|
+
console.log("No npm publish or remote upload was performed.");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
main();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { loadApiRegistry } = require("../src/config/loaders");
|
|
4
|
+
const { compareScanToRegistry, parseCliScanOptions, scanServices } = require("../src/config/serviceScanner");
|
|
5
|
+
const { setConfigRoot } = require("../src/util/paths");
|
|
6
|
+
|
|
7
|
+
function runApiCoverage(argv = process.argv.slice(2), options = {}) {
|
|
8
|
+
const scanOptions = parseCliScanOptions(argv);
|
|
9
|
+
setConfigRoot(scanOptions.configRoot);
|
|
10
|
+
const registry = loadApiRegistry();
|
|
11
|
+
const scanned = scanServices(scanOptions);
|
|
12
|
+
const comparison = compareScanToRegistry(scanned, registry);
|
|
13
|
+
const missingSchema = Object.values(registry)
|
|
14
|
+
.filter((endpoint) => !endpoint.schema)
|
|
15
|
+
.map((endpoint) => endpoint.id);
|
|
16
|
+
const missingResponse = Object.values(registry)
|
|
17
|
+
.filter((endpoint) => !endpoint.response || Object.keys(endpoint.response).length === 0)
|
|
18
|
+
.map((endpoint) => endpoint.id);
|
|
19
|
+
|
|
20
|
+
const failed = comparison.missing.length > 0 || missingSchema.length > 0 || missingResponse.length > 0;
|
|
21
|
+
const payload = {
|
|
22
|
+
scanned: scanned.length,
|
|
23
|
+
covered: comparison.covered.length,
|
|
24
|
+
missing: comparison.missing.map((endpoint) => endpoint.id),
|
|
25
|
+
endpoints: Object.keys(registry).length,
|
|
26
|
+
missingSchema,
|
|
27
|
+
missingResponse
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
31
|
+
if (failed && options.exitOnFailure) process.exit(1);
|
|
32
|
+
return { failed, payload };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (require.main === module) {
|
|
36
|
+
runApiCoverage(process.argv.slice(2), { exitOnFailure: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
runApiCoverage
|
|
41
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const cliRoot = path.resolve(__dirname, "..");
|
|
8
|
+
const distDir = path.join(cliRoot, "dist");
|
|
9
|
+
|
|
10
|
+
function run(command, args, options = {}) {
|
|
11
|
+
const label = [command, ...args].join(" ");
|
|
12
|
+
console.log(`\n> ${label}`);
|
|
13
|
+
const result = spawnSync(command, args, {
|
|
14
|
+
cwd: cliRoot,
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
shell: false,
|
|
17
|
+
...options
|
|
18
|
+
});
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
process.exit(result.status || 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cleanDist() {
|
|
25
|
+
fs.rmSync(distDir, { recursive: true, force: true });
|
|
26
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function main() {
|
|
30
|
+
run("npm", ["test"]);
|
|
31
|
+
run("npm", ["run", "check"]);
|
|
32
|
+
run("node", ["bin/evt.js", "validate"]);
|
|
33
|
+
run("npm", ["run", "coverage:api"]);
|
|
34
|
+
cleanDist();
|
|
35
|
+
run("node", ["scripts/build-package.js"]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main();
|