cgraphx 1.2.0 → 1.3.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/dist/.claude-template/hooks/precommit-check/precommit-check.cjs +90 -0
- package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +99 -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 +75 -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 +144 -0
- package/dist/.claude-template/skills/write-api/references/collection-layout.md +81 -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,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* db-query CLI subprocess wrapper — isolated in its own module so tests can
|
|
4
|
+
* `vi.spyOn` the `runDbQueryCli` export and intercept calls from
|
|
5
|
+
* `assemble.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Why a separate module: when `runDbQueryCli` lived in `assemble.ts`,
|
|
8
|
+
* `assembleEndpoint` called it as a bare local function reference, which
|
|
9
|
+
* vitest's `vi.spyOn(module, 'runDbQueryCli')` could not intercept (the spy
|
|
10
|
+
* replaces the export binding, not the local binding). Cross-module imports
|
|
11
|
+
* route through the namespace, which the spy CAN replace.
|
|
12
|
+
*
|
|
13
|
+
* Independent subsystem — does NOT import from `src/dbquery/`. The db-query
|
|
14
|
+
* CLI is invoked as a subprocess (spec §12 isolation).
|
|
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_DB_QUERY_CLI = void 0;
|
|
51
|
+
exports.runDbQueryCli = runDbQueryCli;
|
|
52
|
+
const fs = __importStar(require("node:fs"));
|
|
53
|
+
const path = __importStar(require("node:path"));
|
|
54
|
+
const node_child_process_1 = require("node:child_process");
|
|
55
|
+
/**
|
|
56
|
+
* Invoke `cgraphx db query --sql <sql> --format json` via the dist binary.
|
|
57
|
+
* Returns the parsed rows on success, or `{ ok: false, reason: 'db_query_failed' }`
|
|
58
|
+
* on any non-zero exit (covers config error exit 2, safety exit 3, connection
|
|
59
|
+
* /timeout/disabled exit 4, arg error exit 5 — all collapse to the same skip
|
|
60
|
+
* reason per spec).
|
|
61
|
+
*
|
|
62
|
+
* Exported so tests can `vi.spyOn(dbQueryCliModule, 'runDbQueryCli')` instead
|
|
63
|
+
* of spawning a real subprocess / real DB connection.
|
|
64
|
+
*/
|
|
65
|
+
async function runDbQueryCli(sql, projectRoot) {
|
|
66
|
+
const binaryPath = resolveCodegraphBinary();
|
|
67
|
+
if (!binaryPath) {
|
|
68
|
+
return { ok: false, reason: 'db_query_failed' };
|
|
69
|
+
}
|
|
70
|
+
let stdout;
|
|
71
|
+
try {
|
|
72
|
+
stdout = (0, node_child_process_1.execFileSync)(process.execPath, [binaryPath, 'db', 'query', '--sql', sql, '--format', 'json'], {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
cwd: projectRoot,
|
|
75
|
+
timeout: 30_000,
|
|
76
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Non-zero exit (config error / connection / timeout / disabled / safety).
|
|
82
|
+
return { ok: false, reason: 'db_query_failed' };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(stdout);
|
|
86
|
+
return { ok: true, rows: parsed.rows ?? [] };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Unparseable JSON output — treat as db-query failure.
|
|
90
|
+
return { ok: false, reason: 'db_query_failed' };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the `dist/bin/codegraph.js` binary path. Returns `null` when the
|
|
95
|
+
* binary can't be located (e.g., running from source without a build, or
|
|
96
|
+
* tests that don't care because they mock `runDbQueryCli`).
|
|
97
|
+
*
|
|
98
|
+
* Resolution order:
|
|
99
|
+
* 1. `dist/bin/codegraph.js` relative to this module (production —
|
|
100
|
+
* `dist/api-test/db-query-cli.js` → `dist/bin/codegraph.js`).
|
|
101
|
+
* 2. `dist/bin/codegraph.js` relative to the package root when running
|
|
102
|
+
* from `src/` via tsx (vitest) — only if the file exists.
|
|
103
|
+
*/
|
|
104
|
+
function resolveCodegraphBinary() {
|
|
105
|
+
// Production: __dirname = dist/api-test/, binary = dist/bin/codegraph.js
|
|
106
|
+
const prodPath = path.resolve(__dirname, '..', 'bin', 'codegraph.js');
|
|
107
|
+
if (fs.existsSync(prodPath))
|
|
108
|
+
return prodPath;
|
|
109
|
+
// Dev (tsx): __dirname = src/api-test/, binary = dist/bin/codegraph.js
|
|
110
|
+
const devPath = path.resolve(__dirname, '..', '..', '..', 'dist', 'bin', 'codegraph.js');
|
|
111
|
+
if (fs.existsSync(devPath))
|
|
112
|
+
return devPath;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/** Internal helper re-exported for tests. */
|
|
116
|
+
exports.__INTERNAL_DB_QUERY_CLI = {
|
|
117
|
+
resolveCodegraphBinary,
|
|
118
|
+
};
|
|
119
|
+
//# sourceMappingURL=db-query-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-query-cli.js","sourceRoot":"","sources":["../../src/api-test/db-query-cli.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBH,sCAoCC;AAvDD,4CAA8B;AAC9B,gDAAkC;AAClC,2DAAkD;AAOlD;;;;;;;;;GASG;AACI,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,WAAmB;IAEnB,MAAM,UAAU,GAAG,sBAAsB,EAAE,CAAC;IAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAA,iCAAY,EACnB,OAAO,CAAC,QAAQ,EAChB,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,EAC7D;YACE,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,WAAW;YAChB,OAAO,EAAE,MAAM;YACf,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;YAC3B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CACF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAE/B,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;QACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,sBAAsB;IAC7B,yEAAyE;IACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IACtE,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE7C,uEAAuE;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IACzF,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAE3C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6CAA6C;AAChC,QAAA,uBAAuB,GAAG;IACrC,sBAAsB;CACvB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enhance-prepare phase (L-T-10).
|
|
3
|
+
*
|
|
4
|
+
* Aggregates everything an AI needs to infer `fields` for one endpoint into a
|
|
5
|
+
* single JSON payload (one entry per endpoint). The AI consumes this payload,
|
|
6
|
+
* infers fields, then `enhance-write` (L-T-20) persists them back to the
|
|
7
|
+
* `.md` file and `.cgraphx/api-test/ai-fields/`.
|
|
8
|
+
*
|
|
9
|
+
* Pipeline per endpoint:
|
|
10
|
+
* 1. Read the Layer 1 scan `.md` file (`docs/api-test/<service>/<endpoint>.md`).
|
|
11
|
+
* Parse the frontmatter — scalars via `parseFrontmatter`, plus a small
|
|
12
|
+
* custom parser for the nested `method_attr` block and the `fields` /
|
|
13
|
+
* `dep_tables` / `call_chain` arrays (which `parseFrontmatter` skips).
|
|
14
|
+
* 2. Locate the impl class via `findImplFor(interfaceFqn, allClasses)` over
|
|
15
|
+
* the project-wide `.java` cache. `implPath = cls.filePath` or null.
|
|
16
|
+
* 3. For each `dep_table`, call `runDbQueryCli` to fetch up to 5 sample
|
|
17
|
+
* rows. Failures collapse to an empty array (don't block the endpoint).
|
|
18
|
+
* 4. Merge any previously-persisted AI fields from
|
|
19
|
+
* `.cgraphx/api-test/ai-fields/<endpoint>.json` into the result.
|
|
20
|
+
*
|
|
21
|
+
* Independent subsystem — depends only on `./java-parser`, `./impl-finder`,
|
|
22
|
+
* `./report` (parseFrontmatter), `./db-query-cli`, `./ai-fields`, and the
|
|
23
|
+
* shared types. No imports from `src/extraction/` / `src/resolution/` /
|
|
24
|
+
* `src/db/`.
|
|
25
|
+
*/
|
|
26
|
+
import { type ClassInfo } from './java-parser';
|
|
27
|
+
import type { EnhancePrepareResult, Field } from './types';
|
|
28
|
+
/**
|
|
29
|
+
* Run enhance-prepare for one service (or one endpoint within it).
|
|
30
|
+
*
|
|
31
|
+
* @param projectRoot Project root containing `docs/api-test/`.
|
|
32
|
+
* @param service Service subdirectory name (defaults to `inst-service`).
|
|
33
|
+
* @param endpoint Optional endpoint stem (`<svc_id>_<method>`). When set,
|
|
34
|
+
* only that endpoint is prepared; otherwise every `.md`
|
|
35
|
+
* under the service dir is prepared.
|
|
36
|
+
* @returns One `EnhancePrepareResult` per endpoint (single-element when
|
|
37
|
+
* `endpoint` is set, possibly empty when the .md doesn't exist).
|
|
38
|
+
*/
|
|
39
|
+
export declare function enhancePrepare(projectRoot: string, service?: string, endpoint?: string): Promise<EnhancePrepareResult[]>;
|
|
40
|
+
/**
|
|
41
|
+
* List `.md` files under `serviceDir`. When `endpoint` is set, narrow to the
|
|
42
|
+
* single file `<endpoint>.md` (returns [] when absent). Otherwise every .md
|
|
43
|
+
* in the directory.
|
|
44
|
+
*/
|
|
45
|
+
declare function listEndpointMdFiles(serviceDir: string, endpoint?: string): string[];
|
|
46
|
+
/** Parse every `.java` file under `root` into a flat `ClassInfo[]`. */
|
|
47
|
+
declare function collectAllClasses(root: string): Promise<ClassInfo[]>;
|
|
48
|
+
/** Walk `root` and yield every `.java` file path, skipping IGNORE_DIRS. */
|
|
49
|
+
declare function iterJavaFiles(root: string): IterableIterator<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Prepare a single endpoint .md file. Returns null when the frontmatter is
|
|
52
|
+
* missing required keys (the .md isn't a valid catalog entry).
|
|
53
|
+
*/
|
|
54
|
+
declare function prepareOneEndpoint(projectRoot: string, mdFile: string, allClasses: ClassInfo[]): Promise<EnhancePrepareResult | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch up to 5 sample rows from `table` via the db-query CLI. Returns `[]`
|
|
57
|
+
* on any failure (no profile configured, connection error, etc.) — never
|
|
58
|
+
* throws.
|
|
59
|
+
*/
|
|
60
|
+
declare function sampleTable(projectRoot: string, table: string): Promise<unknown[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Merge .md `fields` with previously-persisted AI fields. When the .md fields
|
|
63
|
+
* are empty (scan phase) but AI fields exist, use the AI fields. Otherwise
|
|
64
|
+
* keep the .md fields (analyze phase already filled them).
|
|
65
|
+
*/
|
|
66
|
+
declare function mergeFields(mdFields: Field[], projectRoot: string, endpoint: string): Field[];
|
|
67
|
+
/**
|
|
68
|
+
* Parse the `method_attr:` block from the frontmatter into a typed object.
|
|
69
|
+
* Defaults match a missing annotation (all false).
|
|
70
|
+
*/
|
|
71
|
+
declare function parseMethodAttr(content: string): {
|
|
72
|
+
resolveParam: boolean;
|
|
73
|
+
handleReturn: boolean;
|
|
74
|
+
open: boolean;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Parse a YAML string-array field of the form:
|
|
78
|
+
* <key>:
|
|
79
|
+
* - item1
|
|
80
|
+
* - item2
|
|
81
|
+
* or the inline form `<key>: []`. Returns `[]` when absent or empty.
|
|
82
|
+
*/
|
|
83
|
+
declare function parseStringArray(content: string, key: string): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Parse the `fields:` block (a YAML list of field maps) into `Field[]`.
|
|
86
|
+
* Returns `[]` when the block is `[]` or absent.
|
|
87
|
+
*/
|
|
88
|
+
declare function parseFieldsBlock(content: string): Field[];
|
|
89
|
+
/** Parse one ` - param_index: N` + nested ` key: value` block. */
|
|
90
|
+
declare function parseFieldEntry(block: string): Field;
|
|
91
|
+
/** Strip surrounding quotes from a YAML scalar value (or return 'null' as-is). */
|
|
92
|
+
declare function stripQuotes(v: string): string;
|
|
93
|
+
/** Internal helpers re-exported for tests. */
|
|
94
|
+
export declare const __INTERNAL_ENHANCE_PREPARE: {
|
|
95
|
+
DEFAULT_SERVICE: string;
|
|
96
|
+
DEFAULT_DOCS_DIR: string;
|
|
97
|
+
IGNORE_DIRS: Set<string>;
|
|
98
|
+
listEndpointMdFiles: typeof listEndpointMdFiles;
|
|
99
|
+
collectAllClasses: typeof collectAllClasses;
|
|
100
|
+
iterJavaFiles: typeof iterJavaFiles;
|
|
101
|
+
prepareOneEndpoint: typeof prepareOneEndpoint;
|
|
102
|
+
sampleTable: typeof sampleTable;
|
|
103
|
+
mergeFields: typeof mergeFields;
|
|
104
|
+
parseMethodAttr: typeof parseMethodAttr;
|
|
105
|
+
parseStringArray: typeof parseStringArray;
|
|
106
|
+
parseFieldsBlock: typeof parseFieldsBlock;
|
|
107
|
+
parseFieldEntry: typeof parseFieldEntry;
|
|
108
|
+
stripQuotes: typeof stripQuotes;
|
|
109
|
+
};
|
|
110
|
+
export {};
|
|
111
|
+
//# sourceMappingURL=enhance-prepare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enhance-prepare.d.ts","sourceRoot":"","sources":["../../src/api-test/enhance-prepare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAK9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAmB3D;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,MAAwB,EACjC,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAqBjC;AAED;;;;GAIG;AACH,iBAAS,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAe5E;AAED,uEAAuE;AACvE,iBAAe,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAanE;AAED,2EAA2E;AAC3E,iBAAU,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAgB9D;AAED;;;GAGG;AACH,iBAAe,kBAAkB,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,SAAS,EAAE,GACtB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAgEtC;AAED;;;;GAIG;AACH,iBAAe,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAcjF;AAED;;;;GAIG;AACH,iBAAS,WAAW,CAClB,QAAQ,EAAE,KAAK,EAAE,EACjB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,KAAK,EAAE,CAIT;AAID;;;GAGG;AACH,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf,CASA;AAoBD;;;;;;GAMG;AACH,iBAAS,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBhE;AAED;;;GAGG;AACH,iBAAS,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,EAAE,CA2BlD;AAED,sEAAsE;AACtE,iBAAS,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CA4B7C;AAED,kFAAkF;AAClF,iBAAS,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQtC;AAED,8CAA8C;AAC9C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;CAetC,CAAC"}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* enhance-prepare phase (L-T-10).
|
|
4
|
+
*
|
|
5
|
+
* Aggregates everything an AI needs to infer `fields` for one endpoint into a
|
|
6
|
+
* single JSON payload (one entry per endpoint). The AI consumes this payload,
|
|
7
|
+
* infers fields, then `enhance-write` (L-T-20) persists them back to the
|
|
8
|
+
* `.md` file and `.cgraphx/api-test/ai-fields/`.
|
|
9
|
+
*
|
|
10
|
+
* Pipeline per endpoint:
|
|
11
|
+
* 1. Read the Layer 1 scan `.md` file (`docs/api-test/<service>/<endpoint>.md`).
|
|
12
|
+
* Parse the frontmatter — scalars via `parseFrontmatter`, plus a small
|
|
13
|
+
* custom parser for the nested `method_attr` block and the `fields` /
|
|
14
|
+
* `dep_tables` / `call_chain` arrays (which `parseFrontmatter` skips).
|
|
15
|
+
* 2. Locate the impl class via `findImplFor(interfaceFqn, allClasses)` over
|
|
16
|
+
* the project-wide `.java` cache. `implPath = cls.filePath` or null.
|
|
17
|
+
* 3. For each `dep_table`, call `runDbQueryCli` to fetch up to 5 sample
|
|
18
|
+
* rows. Failures collapse to an empty array (don't block the endpoint).
|
|
19
|
+
* 4. Merge any previously-persisted AI fields from
|
|
20
|
+
* `.cgraphx/api-test/ai-fields/<endpoint>.json` into the result.
|
|
21
|
+
*
|
|
22
|
+
* Independent subsystem — depends only on `./java-parser`, `./impl-finder`,
|
|
23
|
+
* `./report` (parseFrontmatter), `./db-query-cli`, `./ai-fields`, and the
|
|
24
|
+
* shared types. No imports from `src/extraction/` / `src/resolution/` /
|
|
25
|
+
* `src/db/`.
|
|
26
|
+
*/
|
|
27
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
30
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
31
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperty(o, k2, desc);
|
|
34
|
+
}) : (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
o[k2] = m[k];
|
|
37
|
+
}));
|
|
38
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
39
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
40
|
+
}) : function(o, v) {
|
|
41
|
+
o["default"] = v;
|
|
42
|
+
});
|
|
43
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
44
|
+
var ownKeys = function(o) {
|
|
45
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
46
|
+
var ar = [];
|
|
47
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
48
|
+
return ar;
|
|
49
|
+
};
|
|
50
|
+
return ownKeys(o);
|
|
51
|
+
};
|
|
52
|
+
return function (mod) {
|
|
53
|
+
if (mod && mod.__esModule) return mod;
|
|
54
|
+
var result = {};
|
|
55
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
56
|
+
__setModuleDefault(result, mod);
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
})();
|
|
60
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
61
|
+
exports.__INTERNAL_ENHANCE_PREPARE = void 0;
|
|
62
|
+
exports.enhancePrepare = enhancePrepare;
|
|
63
|
+
const fs = __importStar(require("node:fs"));
|
|
64
|
+
const path = __importStar(require("node:path"));
|
|
65
|
+
const java_parser_1 = require("./java-parser");
|
|
66
|
+
const impl_finder_1 = require("./impl-finder");
|
|
67
|
+
const report_1 = require("./report");
|
|
68
|
+
const db_query_cli_1 = require("./db-query-cli");
|
|
69
|
+
const ai_fields_1 = require("./ai-fields");
|
|
70
|
+
/** Default service dir under `docs/api-test/` when `--service` is omitted. */
|
|
71
|
+
const DEFAULT_SERVICE = 'inst-service';
|
|
72
|
+
/** Default docs root (relative to project root). */
|
|
73
|
+
const DEFAULT_DOCS_DIR = 'docs/api-test';
|
|
74
|
+
/** Directories skipped when walking for .java files. */
|
|
75
|
+
const IGNORE_DIRS = new Set([
|
|
76
|
+
'node_modules',
|
|
77
|
+
'dist',
|
|
78
|
+
'build',
|
|
79
|
+
'target',
|
|
80
|
+
'out',
|
|
81
|
+
'.git',
|
|
82
|
+
'.cgraphx',
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Run enhance-prepare for one service (or one endpoint within it).
|
|
86
|
+
*
|
|
87
|
+
* @param projectRoot Project root containing `docs/api-test/`.
|
|
88
|
+
* @param service Service subdirectory name (defaults to `inst-service`).
|
|
89
|
+
* @param endpoint Optional endpoint stem (`<svc_id>_<method>`). When set,
|
|
90
|
+
* only that endpoint is prepared; otherwise every `.md`
|
|
91
|
+
* under the service dir is prepared.
|
|
92
|
+
* @returns One `EnhancePrepareResult` per endpoint (single-element when
|
|
93
|
+
* `endpoint` is set, possibly empty when the .md doesn't exist).
|
|
94
|
+
*/
|
|
95
|
+
async function enhancePrepare(projectRoot, service = DEFAULT_SERVICE, endpoint) {
|
|
96
|
+
const serviceDir = path.join(projectRoot, DEFAULT_DOCS_DIR, service);
|
|
97
|
+
// 1. Enumerate endpoint .md files.
|
|
98
|
+
const mdFiles = listEndpointMdFiles(serviceDir, endpoint);
|
|
99
|
+
if (mdFiles.length === 0)
|
|
100
|
+
return [];
|
|
101
|
+
// 2. Initialize the Java parser once for the whole run.
|
|
102
|
+
await (0, java_parser_1.initJavaParser)();
|
|
103
|
+
// 3. Build the project-wide allClasses cache (lazy: only when at least one
|
|
104
|
+
// .md exists). Skips node_modules / dist / target / etc.
|
|
105
|
+
const allClasses = await collectAllClasses(projectRoot);
|
|
106
|
+
// 4. Prepare each endpoint.
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const mdFile of mdFiles) {
|
|
109
|
+
const result = await prepareOneEndpoint(projectRoot, mdFile, allClasses);
|
|
110
|
+
if (result)
|
|
111
|
+
results.push(result);
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* List `.md` files under `serviceDir`. When `endpoint` is set, narrow to the
|
|
117
|
+
* single file `<endpoint>.md` (returns [] when absent). Otherwise every .md
|
|
118
|
+
* in the directory.
|
|
119
|
+
*/
|
|
120
|
+
function listEndpointMdFiles(serviceDir, endpoint) {
|
|
121
|
+
if (endpoint) {
|
|
122
|
+
const file = path.join(serviceDir, `${endpoint}.md`);
|
|
123
|
+
return fs.existsSync(file) ? [file] : [];
|
|
124
|
+
}
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = fs.readdirSync(serviceDir, { withFileTypes: true });
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
return entries
|
|
133
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
134
|
+
.map((e) => path.join(serviceDir, e.name))
|
|
135
|
+
.sort();
|
|
136
|
+
}
|
|
137
|
+
/** Parse every `.java` file under `root` into a flat `ClassInfo[]`. */
|
|
138
|
+
async function collectAllClasses(root) {
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const file of iterJavaFiles(root)) {
|
|
141
|
+
try {
|
|
142
|
+
const classes = await (0, java_parser_1.parseJavaFile)(file);
|
|
143
|
+
out.push(...classes);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Best-effort — unparseable files are skipped. The scan phase already
|
|
147
|
+
// logs these elsewhere; enhance-prepare shouldn't abort on a single
|
|
148
|
+
// bad file.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
/** Walk `root` and yield every `.java` file path, skipping IGNORE_DIRS. */
|
|
154
|
+
function* iterJavaFiles(root) {
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (entry.isDirectory()) {
|
|
164
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
165
|
+
continue;
|
|
166
|
+
if (entry.name.startsWith('.'))
|
|
167
|
+
continue;
|
|
168
|
+
yield* iterJavaFiles(path.join(root, entry.name));
|
|
169
|
+
}
|
|
170
|
+
else if (entry.isFile() && entry.name.endsWith('.java')) {
|
|
171
|
+
yield path.join(root, entry.name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Prepare a single endpoint .md file. Returns null when the frontmatter is
|
|
177
|
+
* missing required keys (the .md isn't a valid catalog entry).
|
|
178
|
+
*/
|
|
179
|
+
async function prepareOneEndpoint(projectRoot, mdFile, allClasses) {
|
|
180
|
+
let content;
|
|
181
|
+
try {
|
|
182
|
+
content = fs.readFileSync(mdFile, 'utf8');
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const endpoint = path.basename(mdFile, '.md');
|
|
188
|
+
// Scalars (string | null) via the shared parser.
|
|
189
|
+
const scalars = (0, report_1.parseFrontmatter)(content);
|
|
190
|
+
const interfaceFqn = scalars.interface_fqn ?? '';
|
|
191
|
+
const method = scalars.method ?? '';
|
|
192
|
+
const signature = scalars.signature ?? '';
|
|
193
|
+
const urlPattern = scalars.url_pattern ?? '';
|
|
194
|
+
const service = scalars.service ?? '';
|
|
195
|
+
const javadoc = scalars.javadoc ?? null;
|
|
196
|
+
if (!interfaceFqn || !method) {
|
|
197
|
+
// Not a recognizable catalog .md — skip silently.
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
// Nested / list fields via a small custom parser.
|
|
201
|
+
const methodAttr = parseMethodAttr(content);
|
|
202
|
+
const depTables = parseStringArray(content, 'dep_tables');
|
|
203
|
+
const callChain = parseStringArray(content, 'call_chain');
|
|
204
|
+
const fields = parseFieldsBlock(content);
|
|
205
|
+
// Locate the impl class.
|
|
206
|
+
let implPath = null;
|
|
207
|
+
try {
|
|
208
|
+
const impl = await (0, impl_finder_1.findImplFor)(interfaceFqn, allClasses);
|
|
209
|
+
implPath = impl?.filePath ?? null;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
implPath = null;
|
|
213
|
+
}
|
|
214
|
+
// Sample each dep table.
|
|
215
|
+
const dbSamples = {};
|
|
216
|
+
for (const table of depTables) {
|
|
217
|
+
dbSamples[table] = await sampleTable(projectRoot, table);
|
|
218
|
+
}
|
|
219
|
+
// Merge previously-persisted AI fields (don't overwrite the .md fields —
|
|
220
|
+
// augment: if the .md has empty fields and AI fields exist, use AI's).
|
|
221
|
+
const mergedFields = mergeFields(fields, projectRoot, endpoint);
|
|
222
|
+
return {
|
|
223
|
+
endpoint,
|
|
224
|
+
service,
|
|
225
|
+
interfaceFqn,
|
|
226
|
+
method,
|
|
227
|
+
signature,
|
|
228
|
+
urlPattern,
|
|
229
|
+
methodAttr,
|
|
230
|
+
fields: mergedFields,
|
|
231
|
+
callChain,
|
|
232
|
+
depTables,
|
|
233
|
+
javadoc,
|
|
234
|
+
implPath,
|
|
235
|
+
dbSamples,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Fetch up to 5 sample rows from `table` via the db-query CLI. Returns `[]`
|
|
240
|
+
* on any failure (no profile configured, connection error, etc.) — never
|
|
241
|
+
* throws.
|
|
242
|
+
*/
|
|
243
|
+
async function sampleTable(projectRoot, table) {
|
|
244
|
+
// Defensive: the table name comes from the .md frontmatter (trusted input
|
|
245
|
+
// from the scan phase), but guard against obvious injection anyway.
|
|
246
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table))
|
|
247
|
+
return [];
|
|
248
|
+
try {
|
|
249
|
+
const result = await (0, db_query_cli_1.runDbQueryCli)(`SELECT * FROM ${table} LIMIT 5`, projectRoot);
|
|
250
|
+
if (result.ok)
|
|
251
|
+
return result.rows;
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Merge .md `fields` with previously-persisted AI fields. When the .md fields
|
|
260
|
+
* are empty (scan phase) but AI fields exist, use the AI fields. Otherwise
|
|
261
|
+
* keep the .md fields (analyze phase already filled them).
|
|
262
|
+
*/
|
|
263
|
+
function mergeFields(mdFields, projectRoot, endpoint) {
|
|
264
|
+
if (mdFields.length > 0)
|
|
265
|
+
return mdFields;
|
|
266
|
+
const ai = (0, ai_fields_1.readAiFields)(projectRoot, endpoint);
|
|
267
|
+
return ai?.fields ?? [];
|
|
268
|
+
}
|
|
269
|
+
// ─── Frontmatter nested-field parsers ───────────────────────────────────────
|
|
270
|
+
/**
|
|
271
|
+
* Parse the `method_attr:` block from the frontmatter into a typed object.
|
|
272
|
+
* Defaults match a missing annotation (all false).
|
|
273
|
+
*/
|
|
274
|
+
function parseMethodAttr(content) {
|
|
275
|
+
const resolveParam = matchNestedBool(content, 'method_attr', 'resolve_param');
|
|
276
|
+
const handleReturn = matchNestedBool(content, 'method_attr', 'handle_return');
|
|
277
|
+
const open = matchNestedBool(content, 'method_attr', 'open');
|
|
278
|
+
return {
|
|
279
|
+
resolveParam: resolveParam ?? false,
|
|
280
|
+
handleReturn: handleReturn ?? false,
|
|
281
|
+
open: open ?? false,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/** Match ` <key>: true|false` nested under a `<parent>:` block. */
|
|
285
|
+
function matchNestedBool(content, parent, key) {
|
|
286
|
+
// Find the parent block start.
|
|
287
|
+
const parentRe = new RegExp(`^${parent}:\\s*$`, 'm');
|
|
288
|
+
const parentMatch = parentRe.exec(content);
|
|
289
|
+
if (!parentMatch)
|
|
290
|
+
return null;
|
|
291
|
+
const after = content.slice(parentMatch.index + parentMatch[0].length);
|
|
292
|
+
// Match the first ` <key>: <bool>` at 2-space indent.
|
|
293
|
+
const re = new RegExp(`^\\s{2}${key}:\\s*(true|false)\\s*$`, 'm');
|
|
294
|
+
const m = re.exec(after);
|
|
295
|
+
if (!m)
|
|
296
|
+
return null;
|
|
297
|
+
return m[1] === 'true';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Parse a YAML string-array field of the form:
|
|
301
|
+
* <key>:
|
|
302
|
+
* - item1
|
|
303
|
+
* - item2
|
|
304
|
+
* or the inline form `<key>: []`. Returns `[]` when absent or empty.
|
|
305
|
+
*/
|
|
306
|
+
function parseStringArray(content, key) {
|
|
307
|
+
// Inline empty form.
|
|
308
|
+
const inlineRe = new RegExp(`^${key}:\\s*\\[\\]\\s*$`, 'm');
|
|
309
|
+
if (inlineRe.test(content))
|
|
310
|
+
return [];
|
|
311
|
+
const parentRe = new RegExp(`^${key}:\\s*$`, 'm');
|
|
312
|
+
const parentMatch = parentRe.exec(content);
|
|
313
|
+
if (!parentMatch)
|
|
314
|
+
return [];
|
|
315
|
+
const after = content.slice(parentMatch.index + parentMatch[0].length);
|
|
316
|
+
const items = [];
|
|
317
|
+
const itemRe = /^\s{2}-\s+(.+?)\s*$/gm;
|
|
318
|
+
let m;
|
|
319
|
+
while ((m = itemRe.exec(after)) !== null) {
|
|
320
|
+
// Stop if we hit a non-indented line (next top-level key).
|
|
321
|
+
if (!/^\s/.test(after[m.index] ?? ' '))
|
|
322
|
+
break;
|
|
323
|
+
let v = m[1] ?? '';
|
|
324
|
+
// Strip surrounding quotes.
|
|
325
|
+
if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) {
|
|
326
|
+
v = v.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
327
|
+
}
|
|
328
|
+
else if (v.length >= 2 && v.startsWith("'") && v.endsWith("'")) {
|
|
329
|
+
v = v.slice(1, -1).replace(/''/g, "'");
|
|
330
|
+
}
|
|
331
|
+
items.push(v);
|
|
332
|
+
}
|
|
333
|
+
return items;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Parse the `fields:` block (a YAML list of field maps) into `Field[]`.
|
|
337
|
+
* Returns `[]` when the block is `[]` or absent.
|
|
338
|
+
*/
|
|
339
|
+
function parseFieldsBlock(content) {
|
|
340
|
+
// Inline empty form.
|
|
341
|
+
const inlineRe = /^fields:\s*\[\]\s*$/m;
|
|
342
|
+
if (inlineRe.test(content))
|
|
343
|
+
return [];
|
|
344
|
+
const parentRe = /^fields:\s*$/m;
|
|
345
|
+
const parentMatch = parentRe.exec(content);
|
|
346
|
+
if (!parentMatch)
|
|
347
|
+
return [];
|
|
348
|
+
const after = content.slice(parentMatch.index + parentMatch[0].length);
|
|
349
|
+
const fields = [];
|
|
350
|
+
// Each field entry starts with ` - param_index: N` and is followed by
|
|
351
|
+
// ` <key>: <value>` lines until the next entry or a non-indented line.
|
|
352
|
+
const entryRe = /^\s{2}-\s+param_index:\s*(\d+)\s*$/gm;
|
|
353
|
+
const entryStarts = [];
|
|
354
|
+
let m;
|
|
355
|
+
while ((m = entryRe.exec(after)) !== null) {
|
|
356
|
+
entryStarts.push(m.index);
|
|
357
|
+
}
|
|
358
|
+
for (let i = 0; i < entryStarts.length; i++) {
|
|
359
|
+
const start = entryStarts[i];
|
|
360
|
+
const end = i + 1 < entryStarts.length ? entryStarts[i + 1] : after.length;
|
|
361
|
+
const block = after.slice(start, end);
|
|
362
|
+
fields.push(parseFieldEntry(block));
|
|
363
|
+
}
|
|
364
|
+
return fields;
|
|
365
|
+
}
|
|
366
|
+
/** Parse one ` - param_index: N` + nested ` key: value` block. */
|
|
367
|
+
function parseFieldEntry(block) {
|
|
368
|
+
const get = (key) => {
|
|
369
|
+
const re = new RegExp(`^\\s{4}${key}:\\s*(.*?)\\s*$`, 'm');
|
|
370
|
+
const m = re.exec(block);
|
|
371
|
+
if (!m)
|
|
372
|
+
return null;
|
|
373
|
+
return stripQuotes(m[1] ?? '');
|
|
374
|
+
};
|
|
375
|
+
const getBool = (key) => {
|
|
376
|
+
const v = get(key);
|
|
377
|
+
return v === 'true';
|
|
378
|
+
};
|
|
379
|
+
const getNullStr = (key) => {
|
|
380
|
+
const v = get(key);
|
|
381
|
+
if (v === null || v === 'null')
|
|
382
|
+
return null;
|
|
383
|
+
return v;
|
|
384
|
+
};
|
|
385
|
+
const paramIndex = parseInt(get('param_index') ?? '0', 10);
|
|
386
|
+
return {
|
|
387
|
+
paramIndex: Number.isNaN(paramIndex) ? 0 : paramIndex,
|
|
388
|
+
name: get('name') ?? '',
|
|
389
|
+
javaType: get('java_type') ?? '',
|
|
390
|
+
sourceTable: getNullStr('source_table'),
|
|
391
|
+
sourceColumn: getNullStr('source_column'),
|
|
392
|
+
sourceFilter: getNullStr('source_filter'),
|
|
393
|
+
literal: getNullStr('literal'),
|
|
394
|
+
fixtureOnly: getBool('fixture_only'),
|
|
395
|
+
required: getBool('required'),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/** Strip surrounding quotes from a YAML scalar value (or return 'null' as-is). */
|
|
399
|
+
function stripQuotes(v) {
|
|
400
|
+
if (v.length >= 2 && v.startsWith('"') && v.endsWith('"')) {
|
|
401
|
+
return v.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
402
|
+
}
|
|
403
|
+
if (v.length >= 2 && v.startsWith("'") && v.endsWith("'")) {
|
|
404
|
+
return v.slice(1, -1).replace(/''/g, "'");
|
|
405
|
+
}
|
|
406
|
+
return v;
|
|
407
|
+
}
|
|
408
|
+
/** Internal helpers re-exported for tests. */
|
|
409
|
+
exports.__INTERNAL_ENHANCE_PREPARE = {
|
|
410
|
+
DEFAULT_SERVICE,
|
|
411
|
+
DEFAULT_DOCS_DIR,
|
|
412
|
+
IGNORE_DIRS,
|
|
413
|
+
listEndpointMdFiles,
|
|
414
|
+
collectAllClasses,
|
|
415
|
+
iterJavaFiles,
|
|
416
|
+
prepareOneEndpoint,
|
|
417
|
+
sampleTable,
|
|
418
|
+
mergeFields,
|
|
419
|
+
parseMethodAttr,
|
|
420
|
+
parseStringArray,
|
|
421
|
+
parseFieldsBlock,
|
|
422
|
+
parseFieldEntry,
|
|
423
|
+
stripQuotes,
|
|
424
|
+
};
|
|
425
|
+
//# sourceMappingURL=enhance-prepare.js.map
|