@aprimediet/codewalker 1.1.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/README.md +28 -3
- package/package.json +1 -1
- package/prompts/codewalker.md +3 -1
- package/skills/codewalker/SKILL.md +118 -28
- package/src/cards.test.ts +123 -1
- package/src/cards.ts +53 -0
- package/src/db.test.ts +405 -3
- package/src/db.ts +402 -29
- package/src/enrich.test.ts +102 -0
- package/src/enrich.ts +107 -0
- package/src/format.test.ts +103 -0
- package/src/format.ts +11 -0
- package/src/index.contract.test.ts +77 -1
- package/src/index.ts +273 -19
- package/src/indexer.heal.test.ts +90 -0
- package/src/indexer.ts +9 -1
- package/src/libs/cards.test.ts +86 -0
- package/src/libs/cards.ts +53 -0
- package/src/libs/dts.test.ts +269 -0
- package/src/libs/dts.ts +213 -0
- package/src/libs/indexer.test.ts +236 -0
- package/src/libs/indexer.ts +291 -0
- package/src/libs/resolve.test.ts +218 -0
- package/src/libs/resolve.ts +120 -0
- package/src/notes-cards.test.ts +99 -0
- package/src/notes-cards.ts +92 -0
- package/src/notes.test.ts +172 -0
- package/src/notes.ts +145 -0
- package/src/project.test.ts +12 -1
- package/src/project.ts +7 -1
- package/src/query.test.ts +148 -1
- package/src/query.ts +28 -8
- package/src/types.ts +44 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for libs/indexer.ts — library indexer.
|
|
3
|
+
*
|
|
4
|
+
* Integration tests using a fixture project with a fake node_modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import { indexLibraries, rebuildLibDbFromCards } from "./indexer.ts";
|
|
12
|
+
import { openDb, searchLibSymbols, getMeta } from "../db.ts";
|
|
13
|
+
|
|
14
|
+
describe("indexLibraries", () => {
|
|
15
|
+
let tmpDir: string;
|
|
16
|
+
let projectRoot: string;
|
|
17
|
+
let libsDir: string;
|
|
18
|
+
let dbPath: string;
|
|
19
|
+
let nodeModulesDir: string;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cw-idx-"));
|
|
23
|
+
projectRoot = path.join(tmpDir, "project");
|
|
24
|
+
libsDir = path.join(tmpDir, "codewalker", "entries", "libs");
|
|
25
|
+
dbPath = path.join(tmpDir, "codewalker", "index.db");
|
|
26
|
+
nodeModulesDir = path.join(projectRoot, "node_modules");
|
|
27
|
+
|
|
28
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
29
|
+
fs.mkdirSync(libsDir, { recursive: true });
|
|
30
|
+
|
|
31
|
+
// Write a project package.json
|
|
32
|
+
fs.writeFileSync(
|
|
33
|
+
path.join(projectRoot, "package.json"),
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
name: "test-project",
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
dependencies: {
|
|
38
|
+
"typed-pkg": "^1.0.0",
|
|
39
|
+
"no-dts-pkg": "^2.0.0",
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function installTypedPkg(): void {
|
|
50
|
+
const pkgDir = path.join(nodeModulesDir, "typed-pkg");
|
|
51
|
+
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
|
|
52
|
+
fs.writeFileSync(
|
|
53
|
+
path.join(pkgDir, "package.json"),
|
|
54
|
+
JSON.stringify({ name: "typed-pkg", version: "1.2.0", types: "dist/index.d.ts" }),
|
|
55
|
+
);
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
path.join(pkgDir, "dist", "index.d.ts"),
|
|
58
|
+
[
|
|
59
|
+
"/** A typed greeting function. */",
|
|
60
|
+
"export declare function greet(name: string): string;",
|
|
61
|
+
"/** Configuration options. */",
|
|
62
|
+
"export interface Config { port: number; }",
|
|
63
|
+
"export const VERSION: string;",
|
|
64
|
+
].join("\n"),
|
|
65
|
+
);
|
|
66
|
+
fs.writeFileSync(pkgDir + "/README.md", "# typed-pkg\nA typed package for testing.\n");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function installNoDtsPkg(): void {
|
|
70
|
+
const pkgDir = path.join(nodeModulesDir, "no-dts-pkg");
|
|
71
|
+
fs.mkdirSync(pkgDir);
|
|
72
|
+
fs.writeFileSync(
|
|
73
|
+
path.join(pkgDir, "package.json"),
|
|
74
|
+
JSON.stringify({ name: "no-dts-pkg", version: "2.1.0", main: "index.js" }),
|
|
75
|
+
);
|
|
76
|
+
fs.writeFileSync(path.join(pkgDir, "index.js"), "module.exports = {};\n");
|
|
77
|
+
fs.writeFileSync(path.join(pkgDir, "README.md"), "# no-dts-pkg\nA JS-only package.\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
it("indexes typed dependency: writes cards and populates DB", async () => {
|
|
81
|
+
installTypedPkg();
|
|
82
|
+
|
|
83
|
+
const result = await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
84
|
+
expect(result.indexed).toBe(1);
|
|
85
|
+
expect(result.symbols).toBe(3);
|
|
86
|
+
expect(result.errors).toBe(0);
|
|
87
|
+
|
|
88
|
+
// Check cards exist
|
|
89
|
+
const pkgCardDir = path.join(libsDir, "typed-pkg@1.2.0");
|
|
90
|
+
expect(fs.existsSync(pkgCardDir)).toBe(true);
|
|
91
|
+
const cardFiles = fs.readdirSync(pkgCardDir);
|
|
92
|
+
// greet, Config, VERSION
|
|
93
|
+
expect(cardFiles).toHaveLength(3);
|
|
94
|
+
|
|
95
|
+
// Check DB
|
|
96
|
+
const db = openDb(dbPath);
|
|
97
|
+
const symbols = searchLibSymbols(db, "", undefined, 10);
|
|
98
|
+
expect(symbols).toHaveLength(3);
|
|
99
|
+
expect(symbols.map(s => s.name)).toContain("greet");
|
|
100
|
+
expect(symbols.map(s => s.name)).toContain("Config");
|
|
101
|
+
expect(symbols.map(s => s.name)).toContain("VERSION");
|
|
102
|
+
expect(symbols[0]!.lib).toBe("typed-pkg");
|
|
103
|
+
expect(symbols[0]!.version).toBe("1.2.0");
|
|
104
|
+
db.close();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("indexes a README-only dependency (no .d.ts)", async () => {
|
|
108
|
+
installNoDtsPkg();
|
|
109
|
+
|
|
110
|
+
const result = await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
111
|
+
// Only no-dts-pkg is installed; typed-pkg missing from node_modules so skipped
|
|
112
|
+
expect(result.indexed).toBe(1);
|
|
113
|
+
expect(result.symbols).toBeGreaterThanOrEqual(1); // at least the module card
|
|
114
|
+
expect(result.errors).toBe(0);
|
|
115
|
+
|
|
116
|
+
// Check README-only package got a module card
|
|
117
|
+
const pkgCardDir = path.join(libsDir, "no-dts-pkg@2.1.0");
|
|
118
|
+
expect(fs.existsSync(pkgCardDir)).toBe(true);
|
|
119
|
+
const cardFiles = fs.readdirSync(pkgCardDir);
|
|
120
|
+
// Should have a module card (README summary)
|
|
121
|
+
expect(cardFiles.length).toBeGreaterThanOrEqual(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("is idempotent: re-running produces no duplicates", async () => {
|
|
125
|
+
installTypedPkg();
|
|
126
|
+
|
|
127
|
+
await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
128
|
+
await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
129
|
+
|
|
130
|
+
const db = openDb(dbPath);
|
|
131
|
+
const symbols = searchLibSymbols(db, "", undefined, 10);
|
|
132
|
+
expect(symbols).toHaveLength(3);
|
|
133
|
+
db.close();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("handles missing node_modules gracefully", async () => {
|
|
137
|
+
// Remove node_modules
|
|
138
|
+
fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
139
|
+
|
|
140
|
+
const result = await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
141
|
+
expect(result.indexed).toBe(0);
|
|
142
|
+
expect(result.symbols).toBe(0);
|
|
143
|
+
expect(result.errors).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("handles a dep that is missing from node_modules with a logged note", async () => {
|
|
147
|
+
// Only install one of the two deps
|
|
148
|
+
installTypedPkg();
|
|
149
|
+
// no-dts-pkg is missing from node_modules
|
|
150
|
+
|
|
151
|
+
const result = await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
152
|
+
// Should index typed-pkg, skip no-dts-pkg
|
|
153
|
+
expect(result.indexed).toBe(1);
|
|
154
|
+
expect(result.errors).toBe(0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("includes devDependencies when includeDev=true", async () => {
|
|
158
|
+
// Add a dev dependency
|
|
159
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
160
|
+
pkgJson.devDependencies = { "dev-pkg": "^3.0.0" };
|
|
161
|
+
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(pkgJson));
|
|
162
|
+
|
|
163
|
+
// Install dev-pkg (typed)
|
|
164
|
+
const devPkgDir = path.join(nodeModulesDir, "dev-pkg");
|
|
165
|
+
fs.mkdirSync(devPkgDir);
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
path.join(devPkgDir, "package.json"),
|
|
168
|
+
JSON.stringify({ name: "dev-pkg", version: "3.0.0" }),
|
|
169
|
+
);
|
|
170
|
+
fs.writeFileSync(path.join(devPkgDir, "index.d.ts"), "export const devVar: number;\n");
|
|
171
|
+
|
|
172
|
+
// Install other deps too
|
|
173
|
+
installTypedPkg();
|
|
174
|
+
|
|
175
|
+
const result = await indexLibraries({ projectRoot, libsDir, dbPath, includeDev: true });
|
|
176
|
+
// typed-pkg + dev-pkg (no-dts-pkg missing from node_modules so skipped silently)
|
|
177
|
+
expect(result.indexed).toBe(2);
|
|
178
|
+
expect(result.symbols).toBeGreaterThanOrEqual(4); // 3 from typed-pkg + 1 from dev-pkg
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("rebuildLibDbFromCards repopulates lib_symbols from cards", async () => {
|
|
182
|
+
installTypedPkg();
|
|
183
|
+
await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
184
|
+
|
|
185
|
+
// Delete the DB and recreate from cards
|
|
186
|
+
const oldDb = openDb(dbPath);
|
|
187
|
+
oldDb.exec("DELETE FROM lib_symbols; DELETE FROM lib_symbols_fts; DELETE FROM libraries;");
|
|
188
|
+
oldDb.close();
|
|
189
|
+
|
|
190
|
+
rebuildLibDbFromCards(dbPath, libsDir);
|
|
191
|
+
|
|
192
|
+
const db = openDb(dbPath);
|
|
193
|
+
const symbols = searchLibSymbols(db, "", undefined, 10);
|
|
194
|
+
expect(symbols).toHaveLength(3);
|
|
195
|
+
db.close();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("version bump prunes old cards+rows and adds new", async () => {
|
|
199
|
+
// Install v1
|
|
200
|
+
const pkgDir = path.join(nodeModulesDir, "typed-pkg");
|
|
201
|
+
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
|
|
202
|
+
fs.writeFileSync(
|
|
203
|
+
path.join(pkgDir, "package.json"),
|
|
204
|
+
JSON.stringify({ name: "typed-pkg", version: "1.0.0", types: "dist/index.d.ts" }),
|
|
205
|
+
);
|
|
206
|
+
fs.writeFileSync(path.join(pkgDir, "dist", "index.d.ts"), "export const OLD: number;\n");
|
|
207
|
+
|
|
208
|
+
await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
209
|
+
|
|
210
|
+
// Check v1 card exists
|
|
211
|
+
expect(fs.existsSync(path.join(libsDir, "typed-pkg@1.0.0"))).toBe(true);
|
|
212
|
+
|
|
213
|
+
// Now "upgrade" to v2
|
|
214
|
+
fs.writeFileSync(
|
|
215
|
+
path.join(pkgDir, "package.json"),
|
|
216
|
+
JSON.stringify({ name: "typed-pkg", version: "2.0.0", types: "dist/index.d.ts" }),
|
|
217
|
+
);
|
|
218
|
+
fs.writeFileSync(path.join(pkgDir, "dist", "index.d.ts"), "export const NEW: number;\n");
|
|
219
|
+
|
|
220
|
+
await indexLibraries({ projectRoot, libsDir, dbPath });
|
|
221
|
+
|
|
222
|
+
// Old card dir should be gone
|
|
223
|
+
expect(fs.existsSync(path.join(libsDir, "typed-pkg@1.0.0"))).toBe(false);
|
|
224
|
+
|
|
225
|
+
// New card dir exists
|
|
226
|
+
expect(fs.existsSync(path.join(libsDir, "typed-pkg@2.0.0"))).toBe(true);
|
|
227
|
+
|
|
228
|
+
// DB has new symbols only
|
|
229
|
+
const db = openDb(dbPath);
|
|
230
|
+
const symbols = searchLibSymbols(db, "", undefined, 10);
|
|
231
|
+
expect(symbols).toHaveLength(1);
|
|
232
|
+
expect(symbols[0]!.name).toBe("NEW");
|
|
233
|
+
expect(symbols[0]!.version).toBe("2.0.0");
|
|
234
|
+
db.close();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library indexer for codewalker v1.2.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates discovery → extraction → card writing → DB population
|
|
5
|
+
* for third-party library dependencies installed in node_modules.
|
|
6
|
+
*
|
|
7
|
+
* - `indexLibraries()`: full pipeline — idempotent
|
|
8
|
+
* - `rebuildLibDbFromCards()`: disposable-index rebuild
|
|
9
|
+
*
|
|
10
|
+
* Uses the same atomic write pattern (tmp + rename, 0o600) as the v1.1 indexer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { openDb, upsertLibrary, upsertLibSymbol, deleteLibrary, setMeta } from "../db.ts";
|
|
16
|
+
import { locateLibrary } from "./resolve.ts";
|
|
17
|
+
import { extractDtsSymbols } from "./dts.ts";
|
|
18
|
+
import { renderLibCard } from "./cards.ts";
|
|
19
|
+
import { parseCard } from "../cards.ts";
|
|
20
|
+
import type { LibSymbol } from "../types.ts";
|
|
21
|
+
|
|
22
|
+
export interface IndexLibrariesOptions {
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
libsDir: string;
|
|
25
|
+
dbPath: string;
|
|
26
|
+
includeDev?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IndexResult {
|
|
30
|
+
indexed: number; // library count indexed
|
|
31
|
+
symbols: number; // total symbols extracted
|
|
32
|
+
errors: number; // libraries that failed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Full library index pipeline:
|
|
37
|
+
* 1. Read project package.json → dependency names
|
|
38
|
+
* 2. For each dep, locate installed package (version, .d.ts, README)
|
|
39
|
+
* 3. Extract symbols from .d.ts
|
|
40
|
+
* 4. Write cards under entries/libs/<pkg>@<version>/
|
|
41
|
+
* 5. Populate libraries + lib_symbols tables
|
|
42
|
+
*
|
|
43
|
+
* Idempotent: version changes prune old data; re-running with no changes is stable.
|
|
44
|
+
*/
|
|
45
|
+
export async function indexLibraries(options: IndexLibrariesOptions): Promise<IndexResult> {
|
|
46
|
+
const { projectRoot, libsDir, dbPath, includeDev } = options;
|
|
47
|
+
|
|
48
|
+
// Read project package.json
|
|
49
|
+
const pkgJsonPath = path.join(projectRoot, "package.json");
|
|
50
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
51
|
+
return { indexed: 0, symbols: 0, errors: 0 };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let pkgJson: Record<string, any>;
|
|
55
|
+
try {
|
|
56
|
+
pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return { indexed: 0, symbols: 0, errors: 0 };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const depNames = parseDependenciesFromPkg(pkgJson, includeDev ?? false);
|
|
62
|
+
if (depNames.length === 0) {
|
|
63
|
+
return { indexed: 0, symbols: 0, errors: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const db = openDb(dbPath);
|
|
67
|
+
let indexedCount = 0;
|
|
68
|
+
let symbolCount = 0;
|
|
69
|
+
let errorCount = 0;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
for (const name of depNames) {
|
|
73
|
+
const lib = locateLibrary(projectRoot, name);
|
|
74
|
+
if (!lib) {
|
|
75
|
+
// Dep missing from node_modules — skip silently
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const version = lib.version;
|
|
80
|
+
const libDirName = `${name}@${version}`;
|
|
81
|
+
const libCardDir = path.join(libsDir, libDirName);
|
|
82
|
+
|
|
83
|
+
// Check if this exact version is already indexed (idempotency check)
|
|
84
|
+
if (fs.existsSync(libCardDir) && isVersionIndexed(db, name, version)) {
|
|
85
|
+
// Already up-to-date — skip
|
|
86
|
+
indexedCount++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Prune old version if the version changed
|
|
91
|
+
pruneOldVersion(db, libsDir, name, version);
|
|
92
|
+
|
|
93
|
+
// Create card directory
|
|
94
|
+
fs.mkdirSync(libCardDir, { recursive: true });
|
|
95
|
+
|
|
96
|
+
// Upsert library record
|
|
97
|
+
upsertLibrary(db, {
|
|
98
|
+
name,
|
|
99
|
+
version,
|
|
100
|
+
source: "node_modules",
|
|
101
|
+
dts_path: lib.dtsPath,
|
|
102
|
+
readme: lib.readmePath ? readFirstLines(lib.readmePath, 5) : null,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const symbols: LibSymbol[] = [];
|
|
106
|
+
|
|
107
|
+
// Extract from .d.ts if available
|
|
108
|
+
if (lib.dtsPath) {
|
|
109
|
+
try {
|
|
110
|
+
const source = fs.readFileSync(lib.dtsPath, "utf-8");
|
|
111
|
+
const extracted = extractDtsSymbols(source, name, version);
|
|
112
|
+
symbols.push(...extracted);
|
|
113
|
+
} catch {
|
|
114
|
+
// d.ts parse error — skip silently
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If no .d.ts symbols and we have README, create a module overview card
|
|
119
|
+
if (symbols.length === 0 && lib.readmePath) {
|
|
120
|
+
const readmeText = fs.readFileSync(lib.readmePath, "utf-8");
|
|
121
|
+
const summary = readmeText.split("\n").slice(0, 5).join("\n").trim() || `${name} library`;
|
|
122
|
+
symbols.push({
|
|
123
|
+
lib: name,
|
|
124
|
+
version,
|
|
125
|
+
name,
|
|
126
|
+
kind: "module",
|
|
127
|
+
signature: "",
|
|
128
|
+
doc: readmeText.slice(0, 2000),
|
|
129
|
+
summary,
|
|
130
|
+
card_path: "",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Write cards and insert symbols
|
|
135
|
+
for (const sym of symbols) {
|
|
136
|
+
const cardFileName = `${sanitizeName(sym.name)}.md`;
|
|
137
|
+
const cardPath = path.join(libCardDir, cardFileName);
|
|
138
|
+
|
|
139
|
+
const card = renderLibCard(sym);
|
|
140
|
+
const tmpPath = cardPath + ".tmp";
|
|
141
|
+
fs.writeFileSync(tmpPath, card, { encoding: "utf-8", mode: 0o600 });
|
|
142
|
+
fs.renameSync(tmpPath, cardPath);
|
|
143
|
+
|
|
144
|
+
sym.card_path = cardPath;
|
|
145
|
+
upsertLibSymbol(db, sym);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
indexedCount++;
|
|
149
|
+
symbolCount += symbols.length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setMeta(db, "last_libs_index", new Date().toISOString());
|
|
153
|
+
} catch (e) {
|
|
154
|
+
errorCount++;
|
|
155
|
+
} finally {
|
|
156
|
+
db.close();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { indexed: indexedCount, symbols: symbolCount, errors: errorCount };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Rebuild the lib_symbols DB tables from cards alone.
|
|
164
|
+
* Used for the disposable-index property.
|
|
165
|
+
*/
|
|
166
|
+
export function rebuildLibDbFromCards(
|
|
167
|
+
dbPath: string,
|
|
168
|
+
libsDir: string,
|
|
169
|
+
): void {
|
|
170
|
+
if (!fs.existsSync(libsDir)) return;
|
|
171
|
+
|
|
172
|
+
const db = openDb(dbPath);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
db.exec("BEGIN TRANSACTION");
|
|
176
|
+
|
|
177
|
+
// Walk all <pkg>@<version>/ directories
|
|
178
|
+
for (const libDir of fs.readdirSync(libsDir, { withFileTypes: true })) {
|
|
179
|
+
if (!libDir.isDirectory()) continue;
|
|
180
|
+
|
|
181
|
+
const fullLibDir = path.join(libsDir, libDir.name);
|
|
182
|
+
const atIndex = libDir.name.lastIndexOf("@");
|
|
183
|
+
if (atIndex < 0) continue;
|
|
184
|
+
|
|
185
|
+
const libName = libDir.name.slice(0, atIndex);
|
|
186
|
+
const version = libDir.name.slice(atIndex + 1);
|
|
187
|
+
|
|
188
|
+
// Upsert library record
|
|
189
|
+
upsertLibrary(db, { name: libName, version, source: "node_modules", dts_path: null, readme: null });
|
|
190
|
+
|
|
191
|
+
// Walk .md cards
|
|
192
|
+
for (const entry of fs.readdirSync(fullLibDir, { withFileTypes: true })) {
|
|
193
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
194
|
+
|
|
195
|
+
const cardPath = path.join(fullLibDir, entry.name);
|
|
196
|
+
const cardText = fs.readFileSync(cardPath, "utf-8");
|
|
197
|
+
const parsed = parseCard(cardText);
|
|
198
|
+
if (!parsed) continue;
|
|
199
|
+
|
|
200
|
+
const { head } = parsed;
|
|
201
|
+
|
|
202
|
+
upsertLibSymbol(db, {
|
|
203
|
+
lib: libName,
|
|
204
|
+
version,
|
|
205
|
+
name: head.name,
|
|
206
|
+
kind: head.kind,
|
|
207
|
+
signature: head.signature,
|
|
208
|
+
doc: parsed.body,
|
|
209
|
+
summary: head.summary,
|
|
210
|
+
card_path: cardPath,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
db.exec("COMMIT");
|
|
216
|
+
} catch (e) {
|
|
217
|
+
db.exec("ROLLBACK");
|
|
218
|
+
throw e;
|
|
219
|
+
} finally {
|
|
220
|
+
db.close();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Internal helpers ───────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
function parseDependenciesFromPkg(
|
|
227
|
+
pkgJson: Record<string, any>,
|
|
228
|
+
includeDev: boolean,
|
|
229
|
+
): string[] {
|
|
230
|
+
const deps: string[] = [];
|
|
231
|
+
if (pkgJson.dependencies) {
|
|
232
|
+
deps.push(...Object.keys(pkgJson.dependencies));
|
|
233
|
+
}
|
|
234
|
+
if (includeDev && pkgJson.devDependencies) {
|
|
235
|
+
deps.push(...Object.keys(pkgJson.devDependencies));
|
|
236
|
+
}
|
|
237
|
+
return deps;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Check if a library+version is already in the DB (idempotent guard). */
|
|
241
|
+
function isVersionIndexed(db: any, libName: string, version: string): boolean {
|
|
242
|
+
try {
|
|
243
|
+
const row = db.prepare(
|
|
244
|
+
"SELECT 1 FROM libraries WHERE name = ? AND version = ?",
|
|
245
|
+
).get(libName, version);
|
|
246
|
+
return !!row;
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Remove old card dir and DB rows for a library when version changes. */
|
|
253
|
+
function pruneOldVersion(
|
|
254
|
+
db: any,
|
|
255
|
+
libsDir: string,
|
|
256
|
+
libName: string,
|
|
257
|
+
newVersion: string,
|
|
258
|
+
): void {
|
|
259
|
+
// Find any existing card dir for this lib with a different version
|
|
260
|
+
if (!fs.existsSync(libsDir)) return;
|
|
261
|
+
|
|
262
|
+
for (const dir of fs.readdirSync(libsDir, { withFileTypes: true })) {
|
|
263
|
+
if (!dir.isDirectory()) continue;
|
|
264
|
+
const prefix = `${libName}@`;
|
|
265
|
+
if (dir.name.startsWith(prefix) && dir.name !== `${libName}@${newVersion}`) {
|
|
266
|
+
// Remove old card dir
|
|
267
|
+
const oldDir = path.join(libsDir, dir.name);
|
|
268
|
+
try {
|
|
269
|
+
fs.rmSync(oldDir, { recursive: true, force: true });
|
|
270
|
+
} catch { /* best effort */ }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Delete old DB rows for this lib (all versions — we'll re-insert with new version)
|
|
275
|
+
try {
|
|
276
|
+
deleteLibrary(db, libName);
|
|
277
|
+
} catch { /* best effort */ }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function sanitizeName(name: string): string {
|
|
281
|
+
return name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function readFirstLines(filePath: string, n: number): string {
|
|
285
|
+
try {
|
|
286
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
287
|
+
return content.split("\n").slice(0, n).join("\n").trim();
|
|
288
|
+
} catch {
|
|
289
|
+
return "";
|
|
290
|
+
}
|
|
291
|
+
}
|