@mcoda/integrations 0.1.8 → 0.1.9
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/CHANGELOG.md +3 -0
- package/README.md +3 -1
- package/dist/docdex/DocdexClient.d.ts +9 -4
- package/dist/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/docdex/DocdexClient.js +252 -134
- package/dist/docdex/DocdexRuntime.d.ts +42 -0
- package/dist/docdex/DocdexRuntime.d.ts.map +1 -0
- package/dist/docdex/DocdexRuntime.js +145 -0
- package/dist/qa/ChromiumQaAdapter.d.ts.map +1 -1
- package/dist/qa/ChromiumQaAdapter.js +26 -15
- package/dist/vcs/VcsClient.d.ts +2 -1
- package/dist/vcs/VcsClient.d.ts.map +1 -1
- package/dist/vcs/VcsClient.js +26 -6
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ External integrations for mcoda (docdex, telemetry, VCS, QA runners, update chec
|
|
|
7
7
|
- Install: `npm i @mcoda/integrations`
|
|
8
8
|
|
|
9
9
|
## What it provides
|
|
10
|
-
- DocdexClient for
|
|
10
|
+
- DocdexClient for docdex daemon queries and CLI-backed ingestion.
|
|
11
11
|
- TelemetryClient for token usage reporting.
|
|
12
12
|
- VcsClient for Git operations.
|
|
13
13
|
- SystemClient for update checks.
|
|
@@ -26,6 +26,8 @@ const docs = await client.search({ docType: "rfp", query: "payments" });
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## Notes
|
|
29
|
+
- Docdex state lives under `~/.docdex` (managed by the `docdex` CLI); mcoda does not create repo-local `.docdex`.
|
|
30
|
+
- Chromium QA expects Playwright browsers provisioned via `docdex setup`.
|
|
29
31
|
- Some integrations call external services; configure base URLs and tokens as needed.
|
|
30
32
|
- Primarily used by the mcoda CLI; APIs may evolve.
|
|
31
33
|
|
|
@@ -25,17 +25,21 @@ export interface RegisterDocumentInput {
|
|
|
25
25
|
}
|
|
26
26
|
export declare class DocdexClient {
|
|
27
27
|
private options;
|
|
28
|
+
private resolvedBaseUrl?;
|
|
29
|
+
private repoId?;
|
|
30
|
+
private initializing;
|
|
28
31
|
constructor(options?: {
|
|
29
32
|
workspaceRoot?: string;
|
|
30
|
-
storePath?: string;
|
|
31
33
|
baseUrl?: string;
|
|
32
34
|
authToken?: string;
|
|
35
|
+
repoId?: string;
|
|
33
36
|
});
|
|
34
|
-
private getStorePath;
|
|
35
37
|
private normalizePath;
|
|
36
|
-
private
|
|
38
|
+
private resolveBaseUrl;
|
|
39
|
+
private ensureRepoInitialized;
|
|
37
40
|
private fetchRemote;
|
|
38
|
-
private
|
|
41
|
+
private buildLocalDoc;
|
|
42
|
+
private coerceSearchResults;
|
|
39
43
|
fetchDocumentById(id: string): Promise<DocdexDocument>;
|
|
40
44
|
findDocumentByPath(docPath: string, docType?: string): Promise<DocdexDocument | undefined>;
|
|
41
45
|
search(filter: {
|
|
@@ -44,6 +48,7 @@ export declare class DocdexClient {
|
|
|
44
48
|
query?: string;
|
|
45
49
|
profile?: string;
|
|
46
50
|
}): Promise<DocdexDocument[]>;
|
|
51
|
+
reindex(): Promise<void>;
|
|
47
52
|
registerDocument(input: RegisterDocumentInput): Promise<DocdexDocument>;
|
|
48
53
|
ensureRegisteredFromFile(docPath: string, docType: string, metadata?: Record<string, unknown>): Promise<DocdexDocument>;
|
|
49
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocdexClient.d.ts","sourceRoot":"","sources":["../../src/docdex/DocdexClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DocdexClient.d.ts","sourceRoot":"","sources":["../../src/docdex/DocdexClient.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAkDD,qBAAa,YAAY;IAMrB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,YAAY,CAAS;gBAGnB,OAAO,GAAE;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACZ;IAKR,OAAO,CAAC,aAAa;YAYP,cAAc;YAWd,qBAAqB;YA0BrB,WAAW;IA8BzB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,mBAAmB;IAmDrB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA4BtD,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAS1F,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAkBtH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAcxB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;IA0BvE,wBAAwB,CAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,cAAc,CAAC;CAoB3B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { promises as fs } from "node:fs";
|
|
4
|
+
import { resolveDocdexBaseUrl, runDocdex } from "./DocdexRuntime.js";
|
|
4
5
|
const nowIso = () => new Date().toISOString();
|
|
5
6
|
const segmentize = (docId, content) => {
|
|
6
7
|
const lines = content.split(/\r?\n/);
|
|
@@ -33,15 +34,30 @@ const segmentize = (docId, content) => {
|
|
|
33
34
|
flush();
|
|
34
35
|
return segments;
|
|
35
36
|
};
|
|
37
|
+
const inferDocType = (docPath, fallback = "DOC") => {
|
|
38
|
+
if (!docPath)
|
|
39
|
+
return fallback;
|
|
40
|
+
const name = path.basename(docPath).toLowerCase();
|
|
41
|
+
if (name.includes("openapi") || name.includes("swagger"))
|
|
42
|
+
return "OPENAPI";
|
|
43
|
+
if (name.includes("sds"))
|
|
44
|
+
return "SDS";
|
|
45
|
+
if (name.includes("pdr"))
|
|
46
|
+
return "PDR";
|
|
47
|
+
if (name.includes("rfp"))
|
|
48
|
+
return "RFP";
|
|
49
|
+
return fallback;
|
|
50
|
+
};
|
|
51
|
+
const normalizeBaseUrl = (value) => {
|
|
52
|
+
if (!value)
|
|
53
|
+
return value;
|
|
54
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
55
|
+
};
|
|
36
56
|
export class DocdexClient {
|
|
37
57
|
constructor(options = {}) {
|
|
38
58
|
this.options = options;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const base = this.options.storePath
|
|
42
|
-
? path.resolve(this.options.storePath)
|
|
43
|
-
: path.join(this.options.workspaceRoot ?? process.cwd(), ".mcoda", "docdex", "documents.json");
|
|
44
|
-
return base;
|
|
59
|
+
this.initializing = false;
|
|
60
|
+
this.repoId = options.repoId;
|
|
45
61
|
}
|
|
46
62
|
normalizePath(inputPath) {
|
|
47
63
|
if (!inputPath)
|
|
@@ -55,162 +71,264 @@ export class DocdexClient {
|
|
|
55
71
|
}
|
|
56
72
|
return absolute;
|
|
57
73
|
}
|
|
58
|
-
async
|
|
59
|
-
|
|
74
|
+
async resolveBaseUrl() {
|
|
75
|
+
if (this.options.baseUrl !== undefined) {
|
|
76
|
+
const trimmed = this.options.baseUrl.trim();
|
|
77
|
+
return trimmed ? normalizeBaseUrl(trimmed) : undefined;
|
|
78
|
+
}
|
|
79
|
+
if (this.resolvedBaseUrl !== undefined)
|
|
80
|
+
return this.resolvedBaseUrl;
|
|
81
|
+
const resolved = await resolveDocdexBaseUrl({ cwd: this.options.workspaceRoot });
|
|
82
|
+
this.resolvedBaseUrl = resolved ? normalizeBaseUrl(resolved) : undefined;
|
|
83
|
+
return this.resolvedBaseUrl;
|
|
84
|
+
}
|
|
85
|
+
async ensureRepoInitialized(baseUrl, force = false) {
|
|
86
|
+
if ((this.repoId && !force) || this.initializing)
|
|
87
|
+
return;
|
|
88
|
+
if (!this.options.workspaceRoot)
|
|
89
|
+
return;
|
|
90
|
+
this.initializing = true;
|
|
60
91
|
try {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
const rootUri = `file://${path.resolve(this.options.workspaceRoot)}`;
|
|
93
|
+
const response = await fetch(`${baseUrl}/v1/initialize`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ rootUri }),
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok)
|
|
99
|
+
return;
|
|
100
|
+
const payload = (await response.json().catch(() => undefined));
|
|
101
|
+
const repoId = payload?.repoId ??
|
|
102
|
+
payload?.repo_id ??
|
|
103
|
+
payload?.repo ??
|
|
104
|
+
payload?.id;
|
|
105
|
+
if (repoId)
|
|
106
|
+
this.repoId = String(repoId);
|
|
66
107
|
}
|
|
67
108
|
catch {
|
|
68
|
-
|
|
109
|
+
// ignore initialize errors; assume single-repo daemon
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.initializing = false;
|
|
69
113
|
}
|
|
70
114
|
}
|
|
71
115
|
async fetchRemote(pathname, init) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
116
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
117
|
+
if (!baseUrl) {
|
|
118
|
+
throw new Error("Docdex baseUrl not configured. Run docdex setup or set MCODA_DOCDEX_URL.");
|
|
119
|
+
}
|
|
120
|
+
await this.ensureRepoInitialized(baseUrl);
|
|
121
|
+
const url = new URL(pathname, baseUrl);
|
|
122
|
+
const buildHeaders = () => {
|
|
123
|
+
const headers = { "Content-Type": "application/json" };
|
|
124
|
+
if (this.options.authToken)
|
|
125
|
+
headers.authorization = `Bearer ${this.options.authToken}`;
|
|
126
|
+
if (this.repoId)
|
|
127
|
+
headers["x-docdex-repo-id"] = this.repoId;
|
|
128
|
+
return { ...headers, ...init?.headers };
|
|
129
|
+
};
|
|
130
|
+
const response = await fetch(url, { ...init, headers: buildHeaders() });
|
|
79
131
|
if (!response.ok) {
|
|
80
|
-
|
|
132
|
+
const message = await response.text();
|
|
133
|
+
if (message.includes("missing_repo")) {
|
|
134
|
+
await this.ensureRepoInitialized(baseUrl, true);
|
|
135
|
+
if (this.repoId) {
|
|
136
|
+
const retry = await fetch(url, { ...init, headers: buildHeaders() });
|
|
137
|
+
if (retry.ok)
|
|
138
|
+
return retry;
|
|
139
|
+
const retryMessage = await retry.text();
|
|
140
|
+
throw new Error(`Docdex request failed (${retry.status}): ${retryMessage}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`Docdex request failed (${response.status}): ${message}`);
|
|
81
144
|
}
|
|
82
|
-
return
|
|
145
|
+
return response;
|
|
83
146
|
}
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
147
|
+
buildLocalDoc(docType, docPath, content, metadata) {
|
|
148
|
+
const now = nowIso();
|
|
149
|
+
const id = `local-${randomUUID()}`;
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
docType,
|
|
153
|
+
path: docPath,
|
|
154
|
+
title: docPath ? path.basename(docPath) : undefined,
|
|
155
|
+
content,
|
|
156
|
+
metadata,
|
|
157
|
+
createdAt: now,
|
|
158
|
+
updatedAt: now,
|
|
159
|
+
segments: segmentize(id, content),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
coerceSearchResults(raw, fallbackDocType) {
|
|
163
|
+
const items = Array.isArray(raw)
|
|
164
|
+
? raw
|
|
165
|
+
: Array.isArray(raw?.results)
|
|
166
|
+
? raw.results
|
|
167
|
+
: Array.isArray(raw?.hits)
|
|
168
|
+
? raw.hits
|
|
169
|
+
: [];
|
|
170
|
+
const now = nowIso();
|
|
171
|
+
return items
|
|
172
|
+
.map((item, idx) => {
|
|
173
|
+
if (!item || typeof item !== "object")
|
|
174
|
+
return undefined;
|
|
175
|
+
const id = (item.doc_id ?? item.docId ?? item.id ?? `doc-${idx + 1}`);
|
|
176
|
+
const pathValue = (item.path ?? item.file ?? item.rel_path ?? item.file_path);
|
|
177
|
+
const title = (item.title ?? item.name ?? item.file_name);
|
|
178
|
+
const docType = (item.doc_type ?? item.docType ?? item.type ?? inferDocType(pathValue, fallbackDocType ?? "DOC"));
|
|
179
|
+
const snippet = (item.snippet ?? item.summary ?? item.excerpt);
|
|
180
|
+
const content = (item.content ?? snippet);
|
|
181
|
+
const segments = Array.isArray(item.segments)
|
|
182
|
+
? item.segments.map((seg, segIdx) => ({
|
|
183
|
+
id: seg.id ?? `${id}-seg-${segIdx + 1}`,
|
|
184
|
+
docId: id,
|
|
185
|
+
index: segIdx,
|
|
186
|
+
content: seg.content ?? seg.text ?? "",
|
|
187
|
+
heading: seg.heading ?? seg.title ?? undefined,
|
|
188
|
+
}))
|
|
189
|
+
: snippet
|
|
190
|
+
? [
|
|
191
|
+
{
|
|
192
|
+
id: `${id}-seg-1`,
|
|
193
|
+
docId: id,
|
|
194
|
+
index: 0,
|
|
195
|
+
content: snippet,
|
|
196
|
+
heading: undefined,
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
: undefined;
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
docType,
|
|
203
|
+
path: pathValue,
|
|
204
|
+
title,
|
|
205
|
+
content,
|
|
206
|
+
createdAt: item.created_at ?? item.createdAt ?? now,
|
|
207
|
+
updatedAt: item.updated_at ?? item.updatedAt ?? now,
|
|
208
|
+
segments,
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
.filter(Boolean);
|
|
89
212
|
}
|
|
90
213
|
async fetchDocumentById(id) {
|
|
91
|
-
|
|
214
|
+
const response = await this.fetchRemote(`/snippet/${encodeURIComponent(id)}?text_only=true`);
|
|
215
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
216
|
+
const body = await response.text();
|
|
217
|
+
let content = body;
|
|
218
|
+
if (contentType.includes("application/json")) {
|
|
92
219
|
try {
|
|
93
|
-
const
|
|
94
|
-
|
|
220
|
+
const parsed = JSON.parse(body);
|
|
221
|
+
content =
|
|
222
|
+
parsed.text ??
|
|
223
|
+
parsed.content ??
|
|
224
|
+
parsed.snippet ??
|
|
225
|
+
body;
|
|
95
226
|
}
|
|
96
|
-
catch
|
|
97
|
-
|
|
98
|
-
// eslint-disable-next-line no-console
|
|
99
|
-
console.warn(`Docdex remote fetch failed, falling back to local: ${error.message}`);
|
|
227
|
+
catch {
|
|
228
|
+
content = body;
|
|
100
229
|
}
|
|
101
230
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
231
|
+
const now = nowIso();
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
docType: "DOC",
|
|
235
|
+
content,
|
|
236
|
+
createdAt: now,
|
|
237
|
+
updatedAt: now,
|
|
238
|
+
segments: content ? segmentize(id, content) : undefined,
|
|
239
|
+
};
|
|
109
240
|
}
|
|
110
241
|
async findDocumentByPath(docPath, docType) {
|
|
111
242
|
const normalized = this.normalizePath(docPath);
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
243
|
+
const query = normalized ?? docPath;
|
|
244
|
+
const docs = await this.search({ query, docType });
|
|
245
|
+
if (!docs.length)
|
|
115
246
|
return undefined;
|
|
116
|
-
|
|
247
|
+
if (!normalized)
|
|
248
|
+
return docs[0];
|
|
249
|
+
return docs.find((doc) => doc.path === normalized) ?? docs[0];
|
|
117
250
|
}
|
|
118
251
|
async search(filter) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
catch (error) {
|
|
135
|
-
// eslint-disable-next-line no-console
|
|
136
|
-
console.warn(`Docdex remote search failed, falling back to local: ${error.message}`);
|
|
137
|
-
}
|
|
252
|
+
const params = new URLSearchParams();
|
|
253
|
+
const queryParts = [filter.query, filter.docType, filter.projectKey].filter(Boolean);
|
|
254
|
+
const query = queryParts.join(" ").trim();
|
|
255
|
+
if (query)
|
|
256
|
+
params.set("q", query);
|
|
257
|
+
if (filter.profile)
|
|
258
|
+
params.set("profile", filter.profile);
|
|
259
|
+
if (filter.docType)
|
|
260
|
+
params.set("doc_type", filter.docType);
|
|
261
|
+
if (filter.projectKey)
|
|
262
|
+
params.set("project_key", filter.projectKey);
|
|
263
|
+
params.set("limit", "8");
|
|
264
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
265
|
+
if (!baseUrl) {
|
|
266
|
+
return [];
|
|
138
267
|
}
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
268
|
+
const response = await this.fetchRemote(`/search?${params.toString()}`);
|
|
269
|
+
const payload = (await response.json());
|
|
270
|
+
return this.coerceSearchResults(payload, filter.docType);
|
|
271
|
+
}
|
|
272
|
+
async reindex() {
|
|
273
|
+
const repoRoot = this.options.workspaceRoot ?? process.cwd();
|
|
274
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
275
|
+
await runDocdex(["index", "--repo", repoRoot], {
|
|
276
|
+
cwd: repoRoot,
|
|
277
|
+
env: baseUrl
|
|
278
|
+
? {
|
|
279
|
+
DOCDEX_URL: baseUrl,
|
|
280
|
+
MCODA_DOCDEX_URL: baseUrl,
|
|
281
|
+
}
|
|
282
|
+
: undefined,
|
|
283
|
+
});
|
|
149
284
|
}
|
|
150
285
|
async registerDocument(input) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
body: JSON.stringify({
|
|
156
|
-
doc_type: input.docType,
|
|
157
|
-
path: input.path,
|
|
158
|
-
title: input.title,
|
|
159
|
-
content: input.content,
|
|
160
|
-
metadata: input.metadata,
|
|
161
|
-
}),
|
|
162
|
-
});
|
|
163
|
-
return registered;
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
// eslint-disable-next-line no-console
|
|
167
|
-
console.warn(`Docdex remote register failed, falling back to local: ${error.message}`);
|
|
168
|
-
}
|
|
286
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
287
|
+
if (!baseUrl) {
|
|
288
|
+
const normalized = input.path ? this.normalizePath(input.path) ?? input.path : undefined;
|
|
289
|
+
return this.buildLocalDoc(input.docType, normalized, input.content, input.metadata);
|
|
169
290
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
:
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await this.saveStore(store);
|
|
190
|
-
return { ...updated, segments };
|
|
191
|
-
}
|
|
192
|
-
const doc = {
|
|
193
|
-
id: randomUUID(),
|
|
194
|
-
docType: input.docType,
|
|
195
|
-
path: normalizedPath,
|
|
196
|
-
content: input.content,
|
|
197
|
-
metadata: input.metadata,
|
|
198
|
-
title: input.title,
|
|
199
|
-
createdAt: now,
|
|
200
|
-
updatedAt: now,
|
|
201
|
-
};
|
|
202
|
-
const segments = segmentize(doc.id, input.content);
|
|
203
|
-
store.documents.push(doc);
|
|
204
|
-
store.segments.push(...segments);
|
|
205
|
-
await this.saveStore(store);
|
|
206
|
-
return { ...doc, segments };
|
|
291
|
+
if (!input.path) {
|
|
292
|
+
throw new Error("Docdex register requires a file path to ingest.");
|
|
293
|
+
}
|
|
294
|
+
await this.ensureRepoInitialized(baseUrl);
|
|
295
|
+
const resolvedPath = path.isAbsolute(input.path)
|
|
296
|
+
? input.path
|
|
297
|
+
: path.join(this.options.workspaceRoot ?? process.cwd(), input.path);
|
|
298
|
+
const repoRoot = this.options.workspaceRoot ?? process.cwd();
|
|
299
|
+
await runDocdex(["ingest", "--repo", repoRoot, "--file", resolvedPath], {
|
|
300
|
+
cwd: repoRoot,
|
|
301
|
+
env: {
|
|
302
|
+
DOCDEX_URL: baseUrl,
|
|
303
|
+
MCODA_DOCDEX_URL: baseUrl,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const registered = await this.findDocumentByPath(resolvedPath, input.docType).catch(() => undefined);
|
|
307
|
+
if (registered)
|
|
308
|
+
return registered;
|
|
309
|
+
return this.buildLocalDoc(input.docType, resolvedPath, input.content, input.metadata);
|
|
207
310
|
}
|
|
208
311
|
async ensureRegisteredFromFile(docPath, docType, metadata) {
|
|
209
312
|
const normalizedPath = this.normalizePath(docPath) ?? docPath;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
313
|
+
try {
|
|
314
|
+
const existing = await this.findDocumentByPath(normalizedPath, docType);
|
|
315
|
+
if (existing)
|
|
316
|
+
return existing;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// ignore docdex lookup failures; fall back to local
|
|
320
|
+
}
|
|
213
321
|
const content = await fs.readFile(docPath, "utf8");
|
|
214
|
-
|
|
322
|
+
const inferredType = docType || inferDocType(docPath);
|
|
323
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
324
|
+
if (!baseUrl) {
|
|
325
|
+
return this.buildLocalDoc(inferredType, normalizedPath, content, metadata);
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
return await this.registerDocument({ docType: inferredType, path: docPath, content, metadata });
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return this.buildLocalDoc(inferredType, normalizedPath, content, metadata);
|
|
332
|
+
}
|
|
215
333
|
}
|
|
216
334
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface DocdexCheckResult {
|
|
2
|
+
status?: string;
|
|
3
|
+
success?: boolean;
|
|
4
|
+
checks?: Array<{
|
|
5
|
+
name?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
details?: Record<string, unknown>;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface DocdexBrowserInfo {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
message?: string;
|
|
14
|
+
browsersPath?: string;
|
|
15
|
+
browsers?: Array<{
|
|
16
|
+
name?: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare const resolveDocdexBinary: () => string | undefined;
|
|
22
|
+
export declare const resolvePlaywrightCli: () => string | undefined;
|
|
23
|
+
export declare const runDocdex: (args: string[], options?: {
|
|
24
|
+
cwd?: string;
|
|
25
|
+
env?: NodeJS.ProcessEnv;
|
|
26
|
+
}) => Promise<{
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const readDocdexCheck: (options?: {
|
|
31
|
+
cwd?: string;
|
|
32
|
+
env?: NodeJS.ProcessEnv;
|
|
33
|
+
}) => Promise<DocdexCheckResult>;
|
|
34
|
+
export declare const resolveDocdexBaseUrl: (options?: {
|
|
35
|
+
cwd?: string;
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
}) => Promise<string | undefined>;
|
|
38
|
+
export declare const resolveDocdexBrowserInfo: (options?: {
|
|
39
|
+
cwd?: string;
|
|
40
|
+
env?: NodeJS.ProcessEnv;
|
|
41
|
+
}) => Promise<DocdexBrowserInfo>;
|
|
42
|
+
//# sourceMappingURL=DocdexRuntime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DocdexRuntime.d.ts","sourceRoot":"","sources":["../../src/docdex/DocdexRuntime.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACnC,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAuBD,eAAO,MAAM,mBAAmB,QAAO,MAAM,GAAG,SAI/C,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,MAAM,GAAG,SAehD,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EAAE,EACd,UAAS;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,KACtD,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAU5C,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,UAAS;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,KAAG,OAAO,CAAC,iBAAiB,CAsBxH,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,UAAS;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAsB9H,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,UAAS;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,KACtD,OAAO,CAAC,iBAAiB,CAgC3B,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const execFile = promisify(execFileCb);
|
|
7
|
+
const DOCDEX_ENV_URLS = ["MCODA_DOCDEX_URL", "DOCDEX_URL"];
|
|
8
|
+
const DEFAULT_DOCDEX_STATE_DIR = path.join(os.homedir(), ".docdex", "state");
|
|
9
|
+
const buildDocdexEnv = (env) => {
|
|
10
|
+
const merged = { ...process.env, ...(env ?? {}) };
|
|
11
|
+
if (!merged.DOCDEX_STATE_DIR) {
|
|
12
|
+
merged.DOCDEX_STATE_DIR = DEFAULT_DOCDEX_STATE_DIR;
|
|
13
|
+
}
|
|
14
|
+
return merged;
|
|
15
|
+
};
|
|
16
|
+
const resolveDocdexPackageRoot = () => {
|
|
17
|
+
try {
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const pkgPath = require.resolve("docdex/package.json");
|
|
20
|
+
return path.dirname(pkgPath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
export const resolveDocdexBinary = () => {
|
|
27
|
+
const root = resolveDocdexPackageRoot();
|
|
28
|
+
if (!root)
|
|
29
|
+
return undefined;
|
|
30
|
+
return path.join(root, "bin", "docdex.js");
|
|
31
|
+
};
|
|
32
|
+
export const resolvePlaywrightCli = () => {
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
try {
|
|
35
|
+
return require.resolve("playwright/cli.js");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// fall through to docdex-local resolution
|
|
39
|
+
}
|
|
40
|
+
const root = resolveDocdexPackageRoot();
|
|
41
|
+
if (!root)
|
|
42
|
+
return undefined;
|
|
43
|
+
try {
|
|
44
|
+
const requireFromDocdex = createRequire(path.join(root, "package.json"));
|
|
45
|
+
return requireFromDocdex.resolve("playwright/cli.js");
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export const runDocdex = async (args, options = {}) => {
|
|
52
|
+
const binary = resolveDocdexBinary();
|
|
53
|
+
if (!binary) {
|
|
54
|
+
throw new Error("Docdex npm package not found. Install docdex and retry.");
|
|
55
|
+
}
|
|
56
|
+
const { stdout, stderr } = await execFile(process.execPath, [binary, ...args], {
|
|
57
|
+
cwd: options.cwd,
|
|
58
|
+
env: buildDocdexEnv(options.env),
|
|
59
|
+
});
|
|
60
|
+
return { stdout: stdout ?? "", stderr: stderr ?? "" };
|
|
61
|
+
};
|
|
62
|
+
export const readDocdexCheck = async (options = {}) => {
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
try {
|
|
66
|
+
({ stdout, stderr } = await runDocdex(["check"], options));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const execError = error;
|
|
70
|
+
stdout = typeof execError.stdout === "string" ? execError.stdout : execError.stdout?.toString() ?? "";
|
|
71
|
+
stderr = typeof execError.stderr === "string" ? execError.stderr : execError.stderr?.toString() ?? "";
|
|
72
|
+
if (!stdout && !stderr) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const trimmed = stdout.trim() || stderr.trim();
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
throw new Error("Docdex check returned empty output");
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(trimmed);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw new Error(`Docdex check returned invalid JSON: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
export const resolveDocdexBaseUrl = async (options = {}) => {
|
|
88
|
+
for (const key of DOCDEX_ENV_URLS) {
|
|
89
|
+
const envValue = process.env[key];
|
|
90
|
+
if (envValue)
|
|
91
|
+
return envValue;
|
|
92
|
+
}
|
|
93
|
+
if (process.env.MCODA_SKIP_DOCDEX_CHECKS === "1" ||
|
|
94
|
+
process.env.MCODA_SKIP_DOCDEX_RUNTIME_CHECKS === "1" ||
|
|
95
|
+
(process.platform === "win32" && process.env.CI)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const check = await readDocdexCheck(options);
|
|
100
|
+
const bind = check.checks?.find((c) => c.name === "bind")?.details;
|
|
101
|
+
const bindAddr = bind?.bind_addr;
|
|
102
|
+
if (!bindAddr)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (bindAddr.startsWith("http://") || bindAddr.startsWith("https://"))
|
|
105
|
+
return bindAddr;
|
|
106
|
+
return `http://${bindAddr}`;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
export const resolveDocdexBrowserInfo = async (options = {}) => {
|
|
113
|
+
const setupHint = "Run `docdex setup` to install Playwright and at least one browser.";
|
|
114
|
+
try {
|
|
115
|
+
const check = await readDocdexCheck(options);
|
|
116
|
+
const browserCheck = check.checks?.find((c) => c.name === "browser");
|
|
117
|
+
if (!browserCheck || browserCheck.status !== "ok") {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
message: `${browserCheck?.message ?? "Docdex browser check failed."} ${setupHint}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const details = browserCheck.details ?? {};
|
|
124
|
+
const playwright = details.playwright ?? {};
|
|
125
|
+
const browsers = Array.isArray(playwright.browsers) ? playwright.browsers : [];
|
|
126
|
+
if (!browsers.length) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
message: `Docdex has no Playwright browsers configured. ${setupHint}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const browsersPath = typeof playwright.browsers_path === "string" ? playwright.browsers_path : undefined;
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
browsersPath,
|
|
136
|
+
browsers,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
message: `Docdex check failed: ${error.message}. ${setupHint}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChromiumQaAdapter.d.ts","sourceRoot":"","sources":["../../src/qa/ChromiumQaAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ChromiumQaAdapter.d.ts","sourceRoot":"","sources":["../../src/qa/ChromiumQaAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAQtE,qBAAa,iBAAkB,YAAW,SAAS;IACjD,OAAO,CAAC,UAAU;IASZ,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC;YAqBpE,WAAW;IAYnB,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;CA8CvE"}
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { exec as execCb } from 'node:child_process';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
|
+
import { resolveDocdexBrowserInfo, resolvePlaywrightCli } from '../docdex/DocdexRuntime.js';
|
|
5
6
|
const exec = promisify(execCb);
|
|
6
7
|
const shouldSkipInstall = (ctx) => process.env.MCODA_QA_SKIP_INSTALL === '1' || ctx.env?.MCODA_QA_SKIP_INSTALL === '1';
|
|
7
8
|
export class ChromiumQaAdapter {
|
|
@@ -17,20 +18,21 @@ export class ChromiumQaAdapter {
|
|
|
17
18
|
if (shouldSkipInstall(ctx))
|
|
18
19
|
return { ok: true, details: { skipped: true } };
|
|
19
20
|
const cwd = this.resolveCwd(profile, ctx);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
await exec(installCommand, { cwd, env: { ...process.env, ...profile.env, ...ctx.env } });
|
|
28
|
-
return { ok: true, details: { installedVia: installCommand } };
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
return { ok: false, message: error?.message ?? versionError?.message ?? 'Chromium QA install failed' };
|
|
32
|
-
}
|
|
21
|
+
const browserInfo = await resolveDocdexBrowserInfo({ cwd });
|
|
22
|
+
if (!browserInfo.ok) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
message: browserInfo.message ??
|
|
26
|
+
'Playwright browsers not installed. Run `docdex setup` and install Playwright with at least one browser.',
|
|
27
|
+
};
|
|
33
28
|
}
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
details: {
|
|
32
|
+
playwrightBrowsersPath: browserInfo.browsersPath,
|
|
33
|
+
browsers: browserInfo.browsers,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
34
36
|
}
|
|
35
37
|
async persistLogs(ctx, stdout, stderr) {
|
|
36
38
|
const artifacts = [];
|
|
@@ -45,13 +47,22 @@ export class ChromiumQaAdapter {
|
|
|
45
47
|
return artifacts;
|
|
46
48
|
}
|
|
47
49
|
async invoke(profile, ctx) {
|
|
48
|
-
const
|
|
50
|
+
const playwrightCli = resolvePlaywrightCli();
|
|
51
|
+
const defaultCommand = playwrightCli
|
|
52
|
+
? `node ${playwrightCli} test --reporter=list`
|
|
53
|
+
: 'npx playwright test --reporter=list';
|
|
54
|
+
const command = ctx.testCommandOverride ?? profile.test_command ?? defaultCommand;
|
|
49
55
|
const startedAt = new Date().toISOString();
|
|
50
56
|
const cwd = this.resolveCwd(profile, ctx);
|
|
51
57
|
try {
|
|
52
58
|
const { stdout, stderr } = await exec(command, {
|
|
53
59
|
cwd,
|
|
54
|
-
env: {
|
|
60
|
+
env: {
|
|
61
|
+
...process.env,
|
|
62
|
+
...profile.env,
|
|
63
|
+
...ctx.env,
|
|
64
|
+
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD ?? '1',
|
|
65
|
+
},
|
|
55
66
|
});
|
|
56
67
|
const finishedAt = new Date().toISOString();
|
|
57
68
|
const artifacts = await this.persistLogs(ctx, stdout, stderr);
|
package/dist/vcs/VcsClient.d.ts
CHANGED
|
@@ -17,12 +17,13 @@ export declare class VcsClient {
|
|
|
17
17
|
noGpgSign?: boolean;
|
|
18
18
|
}): Promise<void>;
|
|
19
19
|
merge(cwd: string, source: string, target: string, ensureClean?: boolean): Promise<void>;
|
|
20
|
+
abortMerge(cwd: string): Promise<void>;
|
|
20
21
|
push(cwd: string, remote: string, branch: string): Promise<void>;
|
|
21
22
|
pull(cwd: string, remote: string, branch: string, ffOnly?: boolean): Promise<void>;
|
|
22
23
|
status(cwd: string): Promise<string>;
|
|
23
24
|
dirtyPaths(cwd: string): Promise<string[]>;
|
|
24
25
|
conflictPaths(cwd: string): Promise<string[]>;
|
|
25
|
-
ensureClean(cwd: string, ignoreDotMcoda?: boolean): Promise<void>;
|
|
26
|
+
ensureClean(cwd: string, ignoreDotMcoda?: boolean, ignorePaths?: string[]): Promise<void>;
|
|
26
27
|
lastCommitSha(cwd: string): Promise<string>;
|
|
27
28
|
diff(cwd: string, base: string, head: string, paths?: string[]): Promise<string>;
|
|
28
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VcsClient.d.ts","sourceRoot":"","sources":["../../src/vcs/VcsClient.ts"],"names":[],"mappings":"AAQA,qBAAa,SAAS;YACN,MAAM;YAKN,YAAY;IASpB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASxC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASlD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YASnD,UAAU;YASV,gBAAgB;IAaxB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhF,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"VcsClient.d.ts","sourceRoot":"","sources":["../../src/vcs/VcsClient.ts"],"names":[],"mappings":"AAQA,qBAAa,SAAS;YACN,MAAM;YAKN,YAAY;IASpB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASxC,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASlD,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YASnD,UAAU;YASV,gBAAgB;IAaxB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1D,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhF,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBrD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,MAAM,CACV,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO,GACxD,OAAO,CAAC,IAAI,CAAC;IAOV,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKpC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAsB1C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS7C,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,UAAO,EAAE,WAAW,GAAE,MAAM,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1F,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK3C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAQvF"}
|
package/dist/vcs/VcsClient.js
CHANGED
|
@@ -103,14 +103,22 @@ export class VcsClient {
|
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
catch (error) {
|
|
106
|
-
//
|
|
107
|
-
const
|
|
106
|
+
// Retry with 3-way merge for drifted files.
|
|
107
|
+
const apply3wayCmd = `cat <<'__PATCH__' | git apply --3way --whitespace=nowarn\n${patch}\n__PATCH__`;
|
|
108
108
|
try {
|
|
109
|
-
await exec(
|
|
109
|
+
await exec(apply3wayCmd, opts);
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
catch {
|
|
113
|
-
|
|
113
|
+
// If the patch is already applied, a reverse --check succeeds; treat that as a no-op.
|
|
114
|
+
const reverseCheckCmd = `cat <<'__PATCH__' | git apply --reverse --check --whitespace=nowarn\n${patch}\n__PATCH__`;
|
|
115
|
+
try {
|
|
116
|
+
await exec(reverseCheckCmd, opts);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
}
|
|
@@ -132,6 +140,14 @@ export class VcsClient {
|
|
|
132
140
|
}
|
|
133
141
|
await this.runGit(cwd, ["merge", "--no-edit", source]);
|
|
134
142
|
}
|
|
143
|
+
async abortMerge(cwd) {
|
|
144
|
+
try {
|
|
145
|
+
await this.runGit(cwd, ["merge", "--abort"]);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Ignore when no merge is in progress.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
135
151
|
async push(cwd, remote, branch) {
|
|
136
152
|
await this.runGit(cwd, ["push", remote, branch]);
|
|
137
153
|
}
|
|
@@ -179,9 +195,13 @@ export class VcsClient {
|
|
|
179
195
|
return [];
|
|
180
196
|
}
|
|
181
197
|
}
|
|
182
|
-
async ensureClean(cwd, ignoreDotMcoda = true) {
|
|
198
|
+
async ensureClean(cwd, ignoreDotMcoda = true, ignorePaths = []) {
|
|
183
199
|
const dirty = await this.dirtyPaths(cwd);
|
|
184
|
-
const filtered =
|
|
200
|
+
const filtered = dirty.filter((p) => {
|
|
201
|
+
if (ignoreDotMcoda && p.startsWith(".mcoda"))
|
|
202
|
+
return false;
|
|
203
|
+
return !ignorePaths.some((prefix) => p.startsWith(prefix));
|
|
204
|
+
});
|
|
185
205
|
if (filtered.length) {
|
|
186
206
|
throw new Error(`Working tree dirty: ${filtered.join(", ")}`);
|
|
187
207
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcoda/integrations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "External integrations for mcoda (vcs, QA, telemetry).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"
|
|
40
|
+
"docdex": "^0.2.22",
|
|
41
|
+
"@mcoda/shared": "0.1.9"
|
|
41
42
|
},
|
|
42
43
|
"scripts": {
|
|
43
44
|
"build": "tsc -p tsconfig.json",
|