@saptools/service-flow 0.1.37 → 0.1.39
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/{chunk-WE3A6TOJ.js → chunk-SAZ5OK7R.js} +50 -9
- package/dist/chunk-SAZ5OK7R.js.map +1 -0
- package/dist/cli.js +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/cli.ts +865 -0
- package/src/config/defaults.ts +14 -0
- package/src/config/workspace-config.ts +61 -0
- package/src/db/connection.ts +131 -0
- package/src/db/migrations.ts +81 -0
- package/src/db/repositories.ts +353 -0
- package/src/db/schema.ts +24 -0
- package/src/discovery/classify-repository.ts +50 -0
- package/src/discovery/discover-repositories.ts +58 -0
- package/src/index.ts +13 -0
- package/src/indexer/incremental-index.ts +25 -0
- package/src/indexer/repository-indexer.ts +137 -0
- package/src/indexer/workspace-indexer.ts +29 -0
- package/src/linker/cross-repo-linker.ts +406 -0
- package/src/linker/dynamic-edge-resolver.ts +45 -0
- package/src/linker/external-http-target.ts +38 -0
- package/src/linker/helper-package-linker.ts +57 -0
- package/src/linker/odata-path-normalizer.ts +236 -0
- package/src/linker/operation-decorator-normalizer.ts +47 -0
- package/src/linker/remote-query-target.ts +39 -0
- package/src/linker/service-resolver.ts +223 -0
- package/src/output/json-output.ts +7 -0
- package/src/output/mermaid-output.ts +16 -0
- package/src/output/table-output.ts +21 -0
- package/src/parsers/cds-parser.ts +147 -0
- package/src/parsers/decorator-parser.ts +76 -0
- package/src/parsers/generated-constants-parser.ts +23 -0
- package/src/parsers/handler-registration-parser.ts +177 -0
- package/src/parsers/outbound-call-parser.ts +441 -0
- package/src/parsers/package-json-parser.ts +63 -0
- package/src/parsers/service-binding-parser.ts +826 -0
- package/src/parsers/symbol-parser.ts +328 -0
- package/src/parsers/ts-project.ts +13 -0
- package/src/trace/selectors.ts +20 -0
- package/src/trace/trace-engine.ts +647 -0
- package/src/trace/traversal.ts +4 -0
- package/src/types.ts +186 -0
- package/src/utils/diagnostics.ts +10 -0
- package/src/utils/hashing.ts +10 -0
- package/src/utils/path-utils.ts +13 -0
- package/src/utils/redaction.ts +20 -0
- package/src/version.ts +4 -0
- package/dist/chunk-WE3A6TOJ.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { PackageFacts, RepoKind } from '../types.js';
|
|
4
|
+
export async function classifyRepository(
|
|
5
|
+
repoPath: string,
|
|
6
|
+
facts: PackageFacts
|
|
7
|
+
): Promise<RepoKind> {
|
|
8
|
+
const hasCdsDep = Boolean(
|
|
9
|
+
facts.dependencies['@sap/cds'] ??
|
|
10
|
+
facts.dependencies.cds ??
|
|
11
|
+
facts.dependencies['cds-routing-handlers']
|
|
12
|
+
);
|
|
13
|
+
const cdsFiles = await findFiles(repoPath, '.cds');
|
|
14
|
+
const serverFiles = await Promise.all(
|
|
15
|
+
['srv/server.ts', 'srv/server.js', 'src/server.ts', 'src/server.js'].map(
|
|
16
|
+
async (f) =>
|
|
17
|
+
fs
|
|
18
|
+
.access(path.join(repoPath, f))
|
|
19
|
+
.then(() => true)
|
|
20
|
+
.catch(() => false)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
const helper =
|
|
24
|
+
Object.keys(facts.dependencies).includes('cds-routing-handlers') ||
|
|
25
|
+
facts.packageName?.includes('helper') === true;
|
|
26
|
+
if (helper && cdsFiles.length > 0) return 'mixed';
|
|
27
|
+
if (helper) return 'helper-package';
|
|
28
|
+
if (hasCdsDep && (cdsFiles.length > 0 || serverFiles.some(Boolean)))
|
|
29
|
+
return 'cap-service';
|
|
30
|
+
if (cdsFiles.length > 0)
|
|
31
|
+
return serverFiles.some(Boolean) ? 'cap-service' : 'cap-db-model';
|
|
32
|
+
return 'unknown';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function findFiles(root: string, suffix: string): Promise<string[]> {
|
|
36
|
+
const out: string[] = [];
|
|
37
|
+
async function walk(dir: string): Promise<void> {
|
|
38
|
+
const entries = await fs
|
|
39
|
+
.readdir(dir, { withFileTypes: true })
|
|
40
|
+
.catch(() => []);
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
if (e.isDirectory()) {
|
|
43
|
+
if (!['node_modules', 'dist', 'gen', '.git'].includes(e.name))
|
|
44
|
+
await walk(path.join(dir, e.name));
|
|
45
|
+
} else if (e.name.endsWith(suffix)) out.push(path.join(dir, e.name));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
await walk(root);
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { DiscoveredRepository } from '../types.js';
|
|
4
|
+
import { relativePath } from '../utils/path-utils.js';
|
|
5
|
+
export async function discoverRepositories(
|
|
6
|
+
rootPath: string,
|
|
7
|
+
ignore: readonly string[]
|
|
8
|
+
): Promise<DiscoveredRepository[]> {
|
|
9
|
+
const root = path.resolve(rootPath);
|
|
10
|
+
const ignored = new Set(ignore);
|
|
11
|
+
const found: DiscoveredRepository[] = [];
|
|
12
|
+
async function isRealGitMarker(dir: string): Promise<boolean> {
|
|
13
|
+
const gitPath = path.join(dir, '.git');
|
|
14
|
+
try {
|
|
15
|
+
const st = await fs.stat(gitPath);
|
|
16
|
+
if (st.isDirectory()) {
|
|
17
|
+
const children = await fs.readdir(gitPath);
|
|
18
|
+
return children.includes('HEAD') || children.includes('config');
|
|
19
|
+
}
|
|
20
|
+
if (st.isFile()) {
|
|
21
|
+
const text = await fs.readFile(gitPath, 'utf8');
|
|
22
|
+
return text.trimStart().startsWith('gitdir:');
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
/* not a normal git marker */
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const fixture = await fs.stat(path.join(dir, '.git-fixture'));
|
|
29
|
+
return fixture.isFile() || fixture.isDirectory();
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function walk(dir: string): Promise<void> {
|
|
35
|
+
const rel = relativePath(root, dir);
|
|
36
|
+
if (rel !== '.' && rel.split('/').some((part) => ignored.has(part))) return;
|
|
37
|
+
let entries: Array<{ name: string; isDirectory: () => boolean }>;
|
|
38
|
+
try {
|
|
39
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const hasMarker = entries.some((e) => e.name === '.git' || e.name === '.git-fixture');
|
|
44
|
+
if (hasMarker && await isRealGitMarker(dir)) {
|
|
45
|
+
found.push({
|
|
46
|
+
name: path.basename(dir),
|
|
47
|
+
absolutePath: dir,
|
|
48
|
+
relativePath: relativePath(root, dir),
|
|
49
|
+
isGitRepo: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
for (const entry of entries)
|
|
53
|
+
if (entry.isDirectory() && !ignore.includes(entry.name))
|
|
54
|
+
await walk(path.join(dir, entry.name));
|
|
55
|
+
}
|
|
56
|
+
await walk(root);
|
|
57
|
+
return found.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { discoverRepositories } from './discovery/discover-repositories.js';
|
|
2
|
+
export { parsePackageJson } from './parsers/package-json-parser.js';
|
|
3
|
+
export { parseCdsFile } from './parsers/cds-parser.js';
|
|
4
|
+
export { parseDecorators } from './parsers/decorator-parser.js';
|
|
5
|
+
export { parseHandlerRegistrations } from './parsers/handler-registration-parser.js';
|
|
6
|
+
export { parseServiceBindings } from './parsers/service-binding-parser.js';
|
|
7
|
+
export { parseOutboundCalls } from './parsers/outbound-call-parser.js';
|
|
8
|
+
export { parseGeneratedConstants } from './parsers/generated-constants-parser.js';
|
|
9
|
+
export { linkWorkspace } from './linker/cross-repo-linker.js';
|
|
10
|
+
export { applyVariables, extractPlaceholders, substituteVariables } from './linker/dynamic-edge-resolver.js';
|
|
11
|
+
export type { RuntimeSubstitution } from './linker/dynamic-edge-resolver.js';
|
|
12
|
+
export { trace } from './trace/trace-engine.js';
|
|
13
|
+
export { redactValue, redactText } from './utils/redaction.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { sha256File } from '../utils/hashing.js';
|
|
4
|
+
import { normalizePath } from '../utils/path-utils.js';
|
|
5
|
+
import type { Db } from '../db/connection.js';
|
|
6
|
+
export async function recordFile(
|
|
7
|
+
db: Db,
|
|
8
|
+
repoId: number,
|
|
9
|
+
repoPath: string,
|
|
10
|
+
relativeFile: string
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const abs = path.join(repoPath, relativeFile);
|
|
13
|
+
const stat = await fs.stat(abs);
|
|
14
|
+
const hash = await sha256File(abs);
|
|
15
|
+
db.prepare(
|
|
16
|
+
'INSERT INTO files(repo_id,relative_path,extension,sha256,size_bytes,last_indexed_at) VALUES(?,?,?,?,?,?) ON CONFLICT(repo_id,relative_path) DO UPDATE SET sha256=excluded.sha256,size_bytes=excluded.size_bytes,last_indexed_at=excluded.last_indexed_at'
|
|
17
|
+
).run(
|
|
18
|
+
repoId,
|
|
19
|
+
normalizePath(relativeFile),
|
|
20
|
+
path.extname(relativeFile),
|
|
21
|
+
hash,
|
|
22
|
+
stat.size,
|
|
23
|
+
new Date().toISOString()
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { Db } from '../db/connection.js';
|
|
4
|
+
import {
|
|
5
|
+
clearRepoFacts,
|
|
6
|
+
insertBindings,
|
|
7
|
+
insertCalls,
|
|
8
|
+
insertExecutableSymbols,
|
|
9
|
+
insertHandler,
|
|
10
|
+
insertRegistrations,
|
|
11
|
+
insertSymbolCalls,
|
|
12
|
+
insertRequires,
|
|
13
|
+
insertService,
|
|
14
|
+
type RepoRow,
|
|
15
|
+
} from '../db/repositories.js';
|
|
16
|
+
import { classifyRepository } from '../discovery/classify-repository.js';
|
|
17
|
+
import { parseCdsFile } from '../parsers/cds-parser.js';
|
|
18
|
+
import { parseDecorators } from '../parsers/decorator-parser.js';
|
|
19
|
+
import { parseHandlerRegistrations } from '../parsers/handler-registration-parser.js';
|
|
20
|
+
import { parseOutboundCalls } from '../parsers/outbound-call-parser.js';
|
|
21
|
+
import { parseExecutableSymbols } from '../parsers/symbol-parser.js';
|
|
22
|
+
import { parsePackageJson } from '../parsers/package-json-parser.js';
|
|
23
|
+
import { parseServiceBindings } from '../parsers/service-binding-parser.js';
|
|
24
|
+
import { sha256File } from '../utils/hashing.js';
|
|
25
|
+
import { normalizePath } from '../utils/path-utils.js';
|
|
26
|
+
import { errorMessage } from '../utils/diagnostics.js';
|
|
27
|
+
import { sha256Text } from '../utils/hashing.js';
|
|
28
|
+
import { ANALYZER_VERSION } from '../version.js';
|
|
29
|
+
import type { CdsServiceFact, HandlerClassFact, HandlerRegistrationFact, OutboundCallFact, ServiceBindingFact, ExecutableSymbolFact, SymbolCallFact } from '../types.js';
|
|
30
|
+
export interface IndexRepoResult {
|
|
31
|
+
fileCount: number;
|
|
32
|
+
diagnosticCount: number;
|
|
33
|
+
skipped: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface ParsedFacts {
|
|
36
|
+
services: CdsServiceFact[];
|
|
37
|
+
handlers: HandlerClassFact[];
|
|
38
|
+
registrations: HandlerRegistrationFact[];
|
|
39
|
+
bindings: ServiceBindingFact[];
|
|
40
|
+
calls: OutboundCallFact[];
|
|
41
|
+
symbols: ExecutableSymbolFact[];
|
|
42
|
+
symbolCalls: SymbolCallFact[];
|
|
43
|
+
fileRecords: Array<{ relativePath: string; extension: string; sha256: string; sizeBytes: number }>;
|
|
44
|
+
}
|
|
45
|
+
export async function indexRepository(
|
|
46
|
+
db: Db,
|
|
47
|
+
repo: RepoRow,
|
|
48
|
+
force: boolean,
|
|
49
|
+
): Promise<IndexRepoResult> {
|
|
50
|
+
try {
|
|
51
|
+
const sourceFiles = await findSourceFiles(repo.absolute_path);
|
|
52
|
+
const packageFacts = await parsePackageJson(repo.absolute_path);
|
|
53
|
+
const fingerprint = await repositoryFingerprint(repo.absolute_path, sourceFiles, packageFacts);
|
|
54
|
+
if (!force && repo.fingerprint === fingerprint) return { fileCount: 0, diagnosticCount: 0, skipped: true };
|
|
55
|
+
const kind = await classifyRepository(repo.absolute_path, packageFacts);
|
|
56
|
+
const parsed = await parseAllSourceFacts(repo.absolute_path, sourceFiles);
|
|
57
|
+
db.transaction(() => {
|
|
58
|
+
db.prepare('UPDATE repositories SET package_name=?, package_version=?, dependencies_json=?, kind=?, index_status=? WHERE id=?').run(packageFacts.packageName, packageFacts.packageVersion, JSON.stringify(packageFacts.dependencies), kind, 'indexing', repo.id);
|
|
59
|
+
clearRepoFacts(db, repo.id);
|
|
60
|
+
insertRequires(db, repo.id, packageFacts.cdsRequires);
|
|
61
|
+
const fileStmt = db.prepare('INSERT INTO files(repo_id,relative_path,extension,sha256,size_bytes,last_indexed_at) VALUES(?,?,?,?,?,?) ON CONFLICT(repo_id,relative_path) DO UPDATE SET sha256=excluded.sha256,size_bytes=excluded.size_bytes,last_indexed_at=excluded.last_indexed_at');
|
|
62
|
+
for (const file of parsed.fileRecords) fileStmt.run(repo.id, file.relativePath, file.extension, file.sha256, file.sizeBytes, new Date().toISOString());
|
|
63
|
+
for (const s of parsed.services) insertService(db, repo.id, s);
|
|
64
|
+
for (const h of parsed.handlers) insertHandler(db, repo.id, h);
|
|
65
|
+
insertExecutableSymbols(db, repo.id, parsed.symbols);
|
|
66
|
+
insertSymbolCalls(db, repo.id, parsed.symbolCalls);
|
|
67
|
+
insertRegistrations(db, repo.id, parsed.registrations);
|
|
68
|
+
insertBindings(db, repo.id, parsed.bindings);
|
|
69
|
+
insertCalls(db, repo.id, parsed.calls);
|
|
70
|
+
db.prepare("UPDATE repositories SET last_indexed_at=?, index_status='indexed', error_count=0, fingerprint=?, fact_generation=COALESCE(fact_generation,0)+1, graph_stale_reason='facts_changed', graph_stale_at=?, fact_analyzer_version=? WHERE id=?").run(new Date().toISOString(), fingerprint, new Date().toISOString(), ANALYZER_VERSION, repo.id);
|
|
71
|
+
});
|
|
72
|
+
return { fileCount: sourceFiles.length, diagnosticCount: 0, skipped: false };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const message = errorMessage(error);
|
|
75
|
+
db.prepare("UPDATE repositories SET index_status='failed', error_count=1 WHERE id=?").run(repo.id);
|
|
76
|
+
db.prepare("DELETE FROM diagnostics WHERE repo_id=? AND code IN ('index_failed_snapshot_preserved','source_read_failed')").run(repo.id);
|
|
77
|
+
db.prepare('INSERT INTO diagnostics(repo_id,severity,code,message) VALUES(?,?,?,?)').run(repo.id, 'error', 'source_read_failed', `Index failed before publication; previous facts and fingerprint were preserved. ${message}`);
|
|
78
|
+
return { fileCount: 0, diagnosticCount: 1, skipped: false };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function parseAllSourceFacts(root: string, files: string[]): Promise<ParsedFacts> {
|
|
82
|
+
const facts: ParsedFacts = { services: [], handlers: [], registrations: [], bindings: [], calls: [], symbols: [], symbolCalls: [], fileRecords: [] };
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
const abs = path.join(root, file);
|
|
85
|
+
const stat = await fs.stat(abs);
|
|
86
|
+
facts.fileRecords.push({ relativePath: normalizePath(file), extension: path.extname(file), sha256: await sha256File(abs), sizeBytes: stat.size });
|
|
87
|
+
if (file.endsWith('.cds')) facts.services.push(...(await parseCdsFile(root, file)));
|
|
88
|
+
if (/\.[jt]s$/.test(file)) {
|
|
89
|
+
facts.handlers.push(...(await parseDecorators(root, file)));
|
|
90
|
+
facts.registrations.push(...(await parseHandlerRegistrations(root, file)));
|
|
91
|
+
facts.bindings.push(...(await parseServiceBindings(root, file)));
|
|
92
|
+
const symbolFacts = await parseExecutableSymbols(root, file);
|
|
93
|
+
facts.symbols.push(...symbolFacts.symbols);
|
|
94
|
+
facts.symbolCalls.push(...symbolFacts.calls);
|
|
95
|
+
facts.calls.push(...(await parseOutboundCalls(root, file)));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return facts;
|
|
99
|
+
}
|
|
100
|
+
async function findSourceFiles(root: string): Promise<string[]> {
|
|
101
|
+
const out: string[] = [];
|
|
102
|
+
async function walk(dir: string, prefix = ''): Promise<void> {
|
|
103
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
104
|
+
for (const e of entries) {
|
|
105
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
106
|
+
if (e.isDirectory()) {
|
|
107
|
+
if (!['node_modules', 'dist', 'gen', 'coverage', '.git'].includes(e.name)) await walk(path.join(dir, e.name), rel);
|
|
108
|
+
} else if (/\.(cds|ts|js)$/.test(e.name) && !isDefaultTestFile(rel)) out.push(rel);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await walk(root);
|
|
112
|
+
return out.sort();
|
|
113
|
+
}
|
|
114
|
+
function isDefaultTestFile(relativeFile: string): boolean {
|
|
115
|
+
const parts = relativeFile.split('/');
|
|
116
|
+
if (parts.some((part) => ['test', 'tests', '__tests__'].includes(part))) return true;
|
|
117
|
+
return /\.(test|spec)\.[jt]s$/.test(parts.at(-1) ?? '');
|
|
118
|
+
}
|
|
119
|
+
async function repositoryFingerprint(root: string, files: string[], facts: Awaited<ReturnType<typeof parsePackageJson>>): Promise<string> {
|
|
120
|
+
const packageJson = await fs.readFile(path.join(root, 'package.json'), 'utf8').catch(() => '');
|
|
121
|
+
const normalizedFacts = {
|
|
122
|
+
analyzerVersion: ANALYZER_VERSION,
|
|
123
|
+
packageName: facts.packageName,
|
|
124
|
+
packageVersion: facts.packageVersion,
|
|
125
|
+
dependencies: Object.fromEntries(Object.entries(facts.dependencies).sort()),
|
|
126
|
+
cdsRequires: [...facts.cdsRequires].sort((a, b) => a.alias.localeCompare(b.alias)),
|
|
127
|
+
scripts: Object.fromEntries(Object.entries(facts.scripts).sort()),
|
|
128
|
+
includeTests: false,
|
|
129
|
+
packageJsonHash: sha256Text(packageJson),
|
|
130
|
+
};
|
|
131
|
+
const entries: string[] = [`facts:${JSON.stringify(normalizedFacts)}`];
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
const content = await fs.readFile(path.join(root, file), 'utf8');
|
|
134
|
+
entries.push(`${file}:${sha256Text(content)}`);
|
|
135
|
+
}
|
|
136
|
+
return sha256Text(entries.join('\n'));
|
|
137
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import { listRepositories, repoByName } from '../db/repositories.js';
|
|
3
|
+
import { errorMessage } from '../utils/diagnostics.js';
|
|
4
|
+
import { indexRepository } from './repository-indexer.js';
|
|
5
|
+
export async function indexWorkspace(
|
|
6
|
+
db: Db,
|
|
7
|
+
workspaceId: number,
|
|
8
|
+
options: { repo?: string; force: boolean },
|
|
9
|
+
): Promise<{ repoCount: number; indexedCount: number; skippedCount: number; fileCount: number; diagnosticCount: number }> {
|
|
10
|
+
const started = new Date().toISOString();
|
|
11
|
+
const repos = options.repo ? [repoByName(db, options.repo)].filter((r) => r !== undefined) : listRepositories(db);
|
|
12
|
+
const runId = Number(db.prepare('INSERT INTO index_runs(workspace_id,started_at,status,repo_count,file_count,diagnostic_count) VALUES(?,?,?,?,?,?) RETURNING id').get(workspaceId, started, 'running', repos.length, 0, 0)?.id);
|
|
13
|
+
let fileCount = 0;
|
|
14
|
+
let diagnosticCount = 0;
|
|
15
|
+
let skippedCount = 0;
|
|
16
|
+
try {
|
|
17
|
+
for (const repo of repos) {
|
|
18
|
+
const result = await indexRepository(db, repo, options.force);
|
|
19
|
+
fileCount += result.fileCount;
|
|
20
|
+
diagnosticCount += result.diagnosticCount;
|
|
21
|
+
skippedCount += result.skipped ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
db.prepare('UPDATE index_runs SET finished_at=?, status=?, file_count=?, diagnostic_count=? WHERE id=?').run(new Date().toISOString(), diagnosticCount ? 'failed' : 'success', fileCount, diagnosticCount, runId);
|
|
24
|
+
return { repoCount: repos.length, indexedCount: repos.length - skippedCount, skippedCount, fileCount, diagnosticCount };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
db.prepare("UPDATE index_runs SET finished_at=?, status='failed', file_count=?, diagnostic_count=?, error_message=? WHERE id=?").run(new Date().toISOString(), fileCount, diagnosticCount + 1, errorMessage(error), runId);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|