pointfree-docs 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -7
- package/dist/cli.js +89 -2
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +59 -2
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +78 -13
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +59 -19
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +27 -4
- package/dist/commands/stats.js +32 -5
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +33 -7
- package/dist/config.d.ts +32 -1
- package/dist/config.js +34 -1
- package/dist/lib/format.d.ts +12 -0
- package/dist/lib/format.js +30 -0
- package/dist/lib/index.d.ts +14 -2
- package/dist/lib/index.js +162 -25
- package/dist/lib/repos.d.ts +32 -0
- package/dist/lib/repos.js +133 -1
- package/package.json +3 -3
package/dist/lib/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import Database from "better-sqlite3";
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
6
6
|
import { glob } from "glob";
|
|
7
7
|
import { basename, join } from "path";
|
|
8
|
-
import { PATHS } from "../config.js";
|
|
9
|
-
import { getDocsPaths } from "./repos.js";
|
|
8
|
+
import { PATHS, EXAMPLES_CONFIG, EPISODES_CONFIG } from "../config.js";
|
|
9
|
+
import { getDocsPaths, getExamplesPaths, getEpisodesPath } from "./repos.js";
|
|
10
10
|
import { cleanMarkdown, extractTitle } from "./markdown.js";
|
|
11
11
|
let db = null;
|
|
12
12
|
/**
|
|
@@ -28,6 +28,7 @@ export function openIndex() {
|
|
|
28
28
|
path TEXT NOT NULL,
|
|
29
29
|
title TEXT,
|
|
30
30
|
content TEXT,
|
|
31
|
+
source TEXT NOT NULL DEFAULT 'docs',
|
|
31
32
|
UNIQUE(library, path)
|
|
32
33
|
);
|
|
33
34
|
|
|
@@ -36,26 +37,40 @@ export function openIndex() {
|
|
|
36
37
|
content,
|
|
37
38
|
library,
|
|
38
39
|
path,
|
|
40
|
+
source,
|
|
39
41
|
content='docs',
|
|
40
42
|
content_rowid='id'
|
|
41
43
|
);
|
|
44
|
+
`);
|
|
45
|
+
// Add source column if it doesn't exist (migration for existing databases)
|
|
46
|
+
try {
|
|
47
|
+
db.exec(`ALTER TABLE docs ADD COLUMN source TEXT NOT NULL DEFAULT 'docs'`);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Column already exists, ignore
|
|
51
|
+
}
|
|
52
|
+
// Drop and recreate triggers to ensure they include the source column
|
|
53
|
+
// This handles migration from older schema versions
|
|
54
|
+
db.exec(`
|
|
55
|
+
DROP TRIGGER IF EXISTS docs_ai;
|
|
56
|
+
DROP TRIGGER IF EXISTS docs_ad;
|
|
57
|
+
DROP TRIGGER IF EXISTS docs_au;
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
VALUES (new.id, new.title, new.content, new.library, new.path);
|
|
59
|
+
CREATE TRIGGER docs_ai AFTER INSERT ON docs BEGIN
|
|
60
|
+
INSERT INTO docs_fts(rowid, title, content, library, path, source)
|
|
61
|
+
VALUES (new.id, new.title, new.content, new.library, new.path, new.source);
|
|
47
62
|
END;
|
|
48
63
|
|
|
49
|
-
CREATE TRIGGER
|
|
50
|
-
INSERT INTO docs_fts(docs_fts, rowid, title, content, library, path)
|
|
51
|
-
VALUES ('delete', old.id, old.title, old.content, old.library, old.path);
|
|
64
|
+
CREATE TRIGGER docs_ad AFTER DELETE ON docs BEGIN
|
|
65
|
+
INSERT INTO docs_fts(docs_fts, rowid, title, content, library, path, source)
|
|
66
|
+
VALUES ('delete', old.id, old.title, old.content, old.library, old.path, old.source);
|
|
52
67
|
END;
|
|
53
68
|
|
|
54
|
-
CREATE TRIGGER
|
|
55
|
-
INSERT INTO docs_fts(docs_fts, rowid, title, content, library, path)
|
|
56
|
-
VALUES ('delete', old.id, old.title, old.content, old.library, old.path);
|
|
57
|
-
INSERT INTO docs_fts(rowid, title, content, library, path)
|
|
58
|
-
VALUES (new.id, new.title, new.content, new.library, new.path);
|
|
69
|
+
CREATE TRIGGER docs_au AFTER UPDATE ON docs BEGIN
|
|
70
|
+
INSERT INTO docs_fts(docs_fts, rowid, title, content, library, path, source)
|
|
71
|
+
VALUES ('delete', old.id, old.title, old.content, old.library, old.path, old.source);
|
|
72
|
+
INSERT INTO docs_fts(rowid, title, content, library, path, source)
|
|
73
|
+
VALUES (new.id, new.title, new.content, new.library, new.path, new.source);
|
|
59
74
|
END;
|
|
60
75
|
`);
|
|
61
76
|
return db;
|
|
@@ -67,8 +82,8 @@ export function indexLibrary(lib) {
|
|
|
67
82
|
const db = openIndex();
|
|
68
83
|
const docsPaths = getDocsPaths(lib);
|
|
69
84
|
const insertStmt = db.prepare(`
|
|
70
|
-
INSERT OR REPLACE INTO docs (library, path, title, content)
|
|
71
|
-
VALUES (?, ?, ?, ?)
|
|
85
|
+
INSERT OR REPLACE INTO docs (library, path, title, content, source)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?)
|
|
72
87
|
`);
|
|
73
88
|
let indexed = 0;
|
|
74
89
|
for (const docsPath of docsPaths) {
|
|
@@ -86,7 +101,7 @@ export function indexLibrary(lib) {
|
|
|
86
101
|
const title = extractTitle(content) || basename(file, ".md");
|
|
87
102
|
// Logical path like "tca/Testing" or "tca/Articles/Performance"
|
|
88
103
|
const docPath = `${lib.shortName}/${file.replace(/\.md$/, "")}`;
|
|
89
|
-
insertStmt.run(lib.shortName, docPath, title, cleanedContent);
|
|
104
|
+
insertStmt.run(lib.shortName, docPath, title, cleanedContent, "docs");
|
|
90
105
|
indexed++;
|
|
91
106
|
}
|
|
92
107
|
catch (error) {
|
|
@@ -96,9 +111,112 @@ export function indexLibrary(lib) {
|
|
|
96
111
|
}
|
|
97
112
|
return indexed;
|
|
98
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Index TCA examples (CaseStudies, etc.)
|
|
116
|
+
*/
|
|
117
|
+
export function indexExamples() {
|
|
118
|
+
const db = openIndex();
|
|
119
|
+
const examplesPaths = getExamplesPaths();
|
|
120
|
+
const insertStmt = db.prepare(`
|
|
121
|
+
INSERT OR REPLACE INTO docs (library, path, title, content, source)
|
|
122
|
+
VALUES (?, ?, ?, ?, ?)
|
|
123
|
+
`);
|
|
124
|
+
let indexed = 0;
|
|
125
|
+
for (const examplesPath of examplesPaths) {
|
|
126
|
+
if (!existsSync(examplesPath)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Find all Swift files
|
|
130
|
+
for (const pattern of EXAMPLES_CONFIG.filePatterns) {
|
|
131
|
+
const files = glob.sync(pattern, { cwd: examplesPath });
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
const fullPath = join(examplesPath, file);
|
|
134
|
+
try {
|
|
135
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
136
|
+
// Extract title from filename or first struct/class
|
|
137
|
+
const title = extractSwiftTitle(content) || basename(file, ".swift");
|
|
138
|
+
// Get the example category from the path (e.g., "CaseStudies", "SyncUps")
|
|
139
|
+
const category = basename(examplesPath);
|
|
140
|
+
const docPath = `examples/${category}/${file}`;
|
|
141
|
+
insertStmt.run("examples", docPath, title, content, "examples");
|
|
142
|
+
indexed++;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(` Warning: Failed to index ${fullPath}:`, error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return indexed;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Index episode code samples
|
|
154
|
+
*/
|
|
155
|
+
export function indexEpisodes() {
|
|
156
|
+
const db = openIndex();
|
|
157
|
+
const episodesPath = getEpisodesPath();
|
|
158
|
+
if (!existsSync(episodesPath)) {
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
const insertStmt = db.prepare(`
|
|
162
|
+
INSERT OR REPLACE INTO docs (library, path, title, content, source)
|
|
163
|
+
VALUES (?, ?, ?, ?, ?)
|
|
164
|
+
`);
|
|
165
|
+
let indexed = 0;
|
|
166
|
+
// Find all episode directories (0001-xxx, 0002-xxx, etc.)
|
|
167
|
+
const episodeDirs = glob.sync("[0-9][0-9][0-9][0-9]-*", { cwd: episodesPath });
|
|
168
|
+
for (const episodeDir of episodeDirs) {
|
|
169
|
+
const episodePath = join(episodesPath, episodeDir);
|
|
170
|
+
// Find all Swift files in this episode
|
|
171
|
+
for (const pattern of EPISODES_CONFIG.filePatterns) {
|
|
172
|
+
const files = glob.sync(pattern, { cwd: episodePath });
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const fullPath = join(episodePath, file);
|
|
175
|
+
try {
|
|
176
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
177
|
+
// Title: episode name + file name
|
|
178
|
+
const episodeName = formatEpisodeName(episodeDir);
|
|
179
|
+
const fileName = basename(file, ".swift");
|
|
180
|
+
const title = `${episodeName}: ${fileName}`;
|
|
181
|
+
const docPath = `episodes/${episodeDir}/${file}`;
|
|
182
|
+
insertStmt.run("episodes", docPath, title, content, "episodes");
|
|
183
|
+
indexed++;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(` Warning: Failed to index ${fullPath}:`, error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return indexed;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extract title from Swift file (first struct/class/enum name)
|
|
195
|
+
*/
|
|
196
|
+
function extractSwiftTitle(content) {
|
|
197
|
+
// Match struct, class, enum, or actor declarations
|
|
198
|
+
const match = content.match(/(?:struct|class|enum|actor)\s+(\w+)/);
|
|
199
|
+
return match ? match[1] : null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Format episode directory name to readable title
|
|
203
|
+
* "0156-testable-state-pt1" -> "Episode 156: Testable State Pt1"
|
|
204
|
+
*/
|
|
205
|
+
function formatEpisodeName(dirName) {
|
|
206
|
+
const match = dirName.match(/^(\d+)-(.+)$/);
|
|
207
|
+
if (!match)
|
|
208
|
+
return dirName;
|
|
209
|
+
const num = parseInt(match[1], 10);
|
|
210
|
+
const name = match[2]
|
|
211
|
+
.split("-")
|
|
212
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
213
|
+
.join(" ");
|
|
214
|
+
return `Ep${num}: ${name}`;
|
|
215
|
+
}
|
|
99
216
|
export function search(query, options = {}) {
|
|
100
217
|
const db = openIndex();
|
|
101
218
|
const limit = options.limit || 10;
|
|
219
|
+
const source = options.source || "docs"; // Default to docs only
|
|
102
220
|
// Escape special FTS5 characters and format query
|
|
103
221
|
const ftsQuery = query
|
|
104
222
|
.replace(/['"]/g, "") // Remove quotes
|
|
@@ -115,7 +233,8 @@ export function search(query, options = {}) {
|
|
|
115
233
|
path,
|
|
116
234
|
title,
|
|
117
235
|
snippet(docs_fts, 1, '**', '**', '...', 32) as snippet,
|
|
118
|
-
bm25(docs_fts) as score
|
|
236
|
+
bm25(docs_fts) as score,
|
|
237
|
+
source
|
|
119
238
|
FROM docs_fts
|
|
120
239
|
WHERE docs_fts MATCH ?
|
|
121
240
|
`;
|
|
@@ -124,6 +243,11 @@ export function search(query, options = {}) {
|
|
|
124
243
|
sql += ` AND library = ?`;
|
|
125
244
|
params.push(options.lib);
|
|
126
245
|
}
|
|
246
|
+
// Filter by source unless "all" is specified
|
|
247
|
+
if (source !== "all") {
|
|
248
|
+
sql += ` AND source = ?`;
|
|
249
|
+
params.push(source);
|
|
250
|
+
}
|
|
127
251
|
sql += ` ORDER BY score LIMIT ?`;
|
|
128
252
|
params.push(limit);
|
|
129
253
|
try {
|
|
@@ -142,13 +266,15 @@ export function search(query, options = {}) {
|
|
|
142
266
|
function fallbackSearch(query, options = {}) {
|
|
143
267
|
const db = openIndex();
|
|
144
268
|
const limit = options.limit || 10;
|
|
269
|
+
const source = options.source || "docs";
|
|
145
270
|
let sql = `
|
|
146
271
|
SELECT
|
|
147
272
|
library,
|
|
148
273
|
path,
|
|
149
274
|
title,
|
|
150
275
|
substr(content, 1, 200) as snippet,
|
|
151
|
-
0 as score
|
|
276
|
+
0 as score,
|
|
277
|
+
source
|
|
152
278
|
FROM docs
|
|
153
279
|
WHERE (title LIKE ? OR content LIKE ?)
|
|
154
280
|
`;
|
|
@@ -158,6 +284,10 @@ function fallbackSearch(query, options = {}) {
|
|
|
158
284
|
sql += ` AND library = ?`;
|
|
159
285
|
params.push(options.lib);
|
|
160
286
|
}
|
|
287
|
+
if (source !== "all") {
|
|
288
|
+
sql += ` AND source = ?`;
|
|
289
|
+
params.push(source);
|
|
290
|
+
}
|
|
161
291
|
sql += ` LIMIT ?`;
|
|
162
292
|
params.push(limit);
|
|
163
293
|
try {
|
|
@@ -171,15 +301,19 @@ function fallbackSearch(query, options = {}) {
|
|
|
171
301
|
/**
|
|
172
302
|
* List all indexed documents
|
|
173
303
|
*/
|
|
174
|
-
export function listDocs(lib) {
|
|
304
|
+
export function listDocs(lib, source) {
|
|
175
305
|
const db = openIndex();
|
|
176
|
-
let sql = `SELECT library, path, title FROM docs`;
|
|
306
|
+
let sql = `SELECT library, path, title, source FROM docs WHERE 1=1`;
|
|
177
307
|
const params = [];
|
|
178
308
|
if (lib) {
|
|
179
|
-
sql += `
|
|
309
|
+
sql += ` AND library = ?`;
|
|
180
310
|
params.push(lib);
|
|
181
311
|
}
|
|
182
|
-
|
|
312
|
+
if (source && source !== "all") {
|
|
313
|
+
sql += ` AND source = ?`;
|
|
314
|
+
params.push(source);
|
|
315
|
+
}
|
|
316
|
+
sql += ` ORDER BY source, library, path`;
|
|
183
317
|
const stmt = db.prepare(sql);
|
|
184
318
|
return stmt.all(...params);
|
|
185
319
|
}
|
|
@@ -188,7 +322,7 @@ export function listDocs(lib) {
|
|
|
188
322
|
*/
|
|
189
323
|
export function getDoc(path) {
|
|
190
324
|
const db = openIndex();
|
|
191
|
-
const stmt = db.prepare(`SELECT library, path, title, content FROM docs WHERE path = ?`);
|
|
325
|
+
const stmt = db.prepare(`SELECT library, path, title, content, source FROM docs WHERE path = ?`);
|
|
192
326
|
return stmt.get(path);
|
|
193
327
|
}
|
|
194
328
|
/**
|
|
@@ -201,7 +335,10 @@ export function getStats() {
|
|
|
201
335
|
const byLibStmt = db.prepare(`SELECT library, COUNT(*) as count FROM docs GROUP BY library`);
|
|
202
336
|
const byLibRows = byLibStmt.all();
|
|
203
337
|
const byLibrary = Object.fromEntries(byLibRows.map((row) => [row.library, row.count]));
|
|
204
|
-
|
|
338
|
+
const bySourceStmt = db.prepare(`SELECT source, COUNT(*) as count FROM docs GROUP BY source`);
|
|
339
|
+
const bySourceRows = bySourceStmt.all();
|
|
340
|
+
const bySource = Object.fromEntries(bySourceRows.map((row) => [row.source, row.count]));
|
|
341
|
+
return { totalDocs: total, byLibrary, bySource };
|
|
205
342
|
}
|
|
206
343
|
/**
|
|
207
344
|
* Run a function with the index open, closing it automatically afterward.
|
package/dist/lib/repos.d.ts
CHANGED
|
@@ -6,15 +6,47 @@ import { LibraryConfig } from "../config.js";
|
|
|
6
6
|
* Clone a library repository (sparse checkout, docs only)
|
|
7
7
|
*/
|
|
8
8
|
export declare function cloneLibrary(lib: LibraryConfig): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Clone TCA examples (CaseStudies, SyncUps, etc.)
|
|
11
|
+
*/
|
|
12
|
+
export declare function cloneExamples(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Clone episode code samples
|
|
15
|
+
*/
|
|
16
|
+
export declare function cloneEpisodes(): Promise<void>;
|
|
9
17
|
/**
|
|
10
18
|
* Update a library repository
|
|
11
19
|
*/
|
|
12
20
|
export declare function updateLibrary(lib: LibraryConfig): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Update examples repository
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateExamples(): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Update episodes repository
|
|
27
|
+
*/
|
|
28
|
+
export declare function updateEpisodes(): Promise<boolean>;
|
|
13
29
|
/**
|
|
14
30
|
* Get all local paths to a library's docs
|
|
15
31
|
*/
|
|
16
32
|
export declare function getDocsPaths(lib: LibraryConfig): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Get all local paths to examples
|
|
35
|
+
*/
|
|
36
|
+
export declare function getExamplesPaths(): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Get local path to episodes
|
|
39
|
+
*/
|
|
40
|
+
export declare function getEpisodesPath(): string;
|
|
17
41
|
/**
|
|
18
42
|
* Check if a library is cloned
|
|
19
43
|
*/
|
|
20
44
|
export declare function isLibraryCloned(lib: LibraryConfig): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Check if examples are cloned
|
|
47
|
+
*/
|
|
48
|
+
export declare function areExamplesCloned(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check if episodes are cloned
|
|
51
|
+
*/
|
|
52
|
+
export declare function areEpisodesCloned(): boolean;
|
package/dist/lib/repos.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { simpleGit } from "simple-git";
|
|
5
5
|
import { existsSync, mkdirSync } from "fs";
|
|
6
6
|
import { join } from "path";
|
|
7
|
-
import { PATHS } from "../config.js";
|
|
7
|
+
import { PATHS, EXAMPLES_CONFIG, EPISODES_CONFIG } from "../config.js";
|
|
8
8
|
/**
|
|
9
9
|
* Clone a library repository (sparse checkout, docs only)
|
|
10
10
|
*/
|
|
@@ -34,6 +34,58 @@ export async function cloneLibrary(lib) {
|
|
|
34
34
|
await repoGit.raw(["sparse-checkout", "set", ...lib.docsPaths]);
|
|
35
35
|
console.log(` ✓ Cloned ${lib.shortName}`);
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Clone TCA examples (CaseStudies, SyncUps, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export async function cloneExamples() {
|
|
41
|
+
const repoDir = join(PATHS.reposDir, EXAMPLES_CONFIG.name);
|
|
42
|
+
// Ensure repos directory exists
|
|
43
|
+
if (!existsSync(PATHS.reposDir)) {
|
|
44
|
+
mkdirSync(PATHS.reposDir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(repoDir)) {
|
|
47
|
+
console.log(` Repository already exists: ${EXAMPLES_CONFIG.name}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(` Cloning ${EXAMPLES_CONFIG.repo} (examples)...`);
|
|
51
|
+
const git = simpleGit();
|
|
52
|
+
// Sparse checkout: only download examples directories
|
|
53
|
+
await git.clone(`https://github.com/${EXAMPLES_CONFIG.repo}.git`, repoDir, [
|
|
54
|
+
"--depth",
|
|
55
|
+
"1",
|
|
56
|
+
"--filter=blob:none",
|
|
57
|
+
"--sparse",
|
|
58
|
+
]);
|
|
59
|
+
const repoGit = simpleGit(repoDir);
|
|
60
|
+
// Set up sparse checkout to only get the examples folders
|
|
61
|
+
await repoGit.raw(["sparse-checkout", "init", "--cone"]);
|
|
62
|
+
await repoGit.raw(["sparse-checkout", "set", ...EXAMPLES_CONFIG.paths]);
|
|
63
|
+
console.log(` ✓ Cloned examples`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Clone episode code samples
|
|
67
|
+
*/
|
|
68
|
+
export async function cloneEpisodes() {
|
|
69
|
+
const repoDir = join(PATHS.reposDir, EPISODES_CONFIG.name);
|
|
70
|
+
// Ensure repos directory exists
|
|
71
|
+
if (!existsSync(PATHS.reposDir)) {
|
|
72
|
+
mkdirSync(PATHS.reposDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
if (existsSync(repoDir)) {
|
|
75
|
+
console.log(` Repository already exists: ${EPISODES_CONFIG.name}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(` Cloning ${EPISODES_CONFIG.repo} (episodes)...`);
|
|
79
|
+
console.log(` ⚠ Note: This may take a while (350+ episodes)...`);
|
|
80
|
+
const git = simpleGit();
|
|
81
|
+
// Full clone with depth 1 and blob filter for efficiency
|
|
82
|
+
await git.clone(`https://github.com/${EPISODES_CONFIG.repo}.git`, repoDir, [
|
|
83
|
+
"--depth",
|
|
84
|
+
"1",
|
|
85
|
+
"--filter=blob:none",
|
|
86
|
+
]);
|
|
87
|
+
console.log(` ✓ Cloned episodes`);
|
|
88
|
+
}
|
|
37
89
|
/**
|
|
38
90
|
* Update a library repository
|
|
39
91
|
*/
|
|
@@ -61,12 +113,78 @@ export async function updateLibrary(lib) {
|
|
|
61
113
|
return false;
|
|
62
114
|
}
|
|
63
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Update examples repository
|
|
118
|
+
*/
|
|
119
|
+
export async function updateExamples() {
|
|
120
|
+
const repoDir = join(PATHS.reposDir, EXAMPLES_CONFIG.name);
|
|
121
|
+
if (!existsSync(repoDir)) {
|
|
122
|
+
console.log(` Examples not found. Run 'pf-docs init --examples' first.`);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
console.log(` Updating examples...`);
|
|
126
|
+
const git = simpleGit(repoDir);
|
|
127
|
+
try {
|
|
128
|
+
const pullResult = await git.pull();
|
|
129
|
+
if (pullResult.summary.changes > 0) {
|
|
130
|
+
console.log(` ✓ Updated examples (${pullResult.summary.changes} changes)`);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.log(` ✓ Examples are up to date`);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error(` ✗ Failed to update examples:`, error);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Update episodes repository
|
|
145
|
+
*/
|
|
146
|
+
export async function updateEpisodes() {
|
|
147
|
+
const repoDir = join(PATHS.reposDir, EPISODES_CONFIG.name);
|
|
148
|
+
if (!existsSync(repoDir)) {
|
|
149
|
+
console.log(` Episodes not found. Run 'pf-docs init --episodes' first.`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
console.log(` Updating episodes...`);
|
|
153
|
+
const git = simpleGit(repoDir);
|
|
154
|
+
try {
|
|
155
|
+
const pullResult = await git.pull();
|
|
156
|
+
if (pullResult.summary.changes > 0) {
|
|
157
|
+
console.log(` ✓ Updated episodes (${pullResult.summary.changes} changes)`);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
console.log(` ✓ Episodes are up to date`);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error(` ✗ Failed to update episodes:`, error);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
64
170
|
/**
|
|
65
171
|
* Get all local paths to a library's docs
|
|
66
172
|
*/
|
|
67
173
|
export function getDocsPaths(lib) {
|
|
68
174
|
return lib.docsPaths.map((docsPath) => join(PATHS.reposDir, lib.name, docsPath));
|
|
69
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Get all local paths to examples
|
|
178
|
+
*/
|
|
179
|
+
export function getExamplesPaths() {
|
|
180
|
+
return EXAMPLES_CONFIG.paths.map((path) => join(PATHS.reposDir, EXAMPLES_CONFIG.name, path));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get local path to episodes
|
|
184
|
+
*/
|
|
185
|
+
export function getEpisodesPath() {
|
|
186
|
+
return join(PATHS.reposDir, EPISODES_CONFIG.name);
|
|
187
|
+
}
|
|
70
188
|
/**
|
|
71
189
|
* Check if a library is cloned
|
|
72
190
|
*/
|
|
@@ -74,3 +192,17 @@ export function isLibraryCloned(lib) {
|
|
|
74
192
|
const repoDir = join(PATHS.reposDir, lib.name);
|
|
75
193
|
return existsSync(repoDir);
|
|
76
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if examples are cloned
|
|
197
|
+
*/
|
|
198
|
+
export function areExamplesCloned() {
|
|
199
|
+
const repoDir = join(PATHS.reposDir, EXAMPLES_CONFIG.name);
|
|
200
|
+
return existsSync(repoDir);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if episodes are cloned
|
|
204
|
+
*/
|
|
205
|
+
export function areEpisodesCloned() {
|
|
206
|
+
const repoDir = join(PATHS.reposDir, EPISODES_CONFIG.name);
|
|
207
|
+
return existsSync(repoDir);
|
|
208
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pointfree-docs",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "CLI tool for searching Point-Free library documentation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"better-sqlite3": "^11.0.0",
|
|
36
|
+
"chalk": "^5.3.0",
|
|
36
37
|
"commander": "^12.0.0",
|
|
37
38
|
"glob": "^10.3.0",
|
|
38
|
-
"simple-git": "^3.22.0"
|
|
39
|
-
"chalk": "^5.3.0"
|
|
39
|
+
"simple-git": "^3.22.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "^7.6.9",
|