coding-friend-cli 1.0.3 → 1.1.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.
@@ -62,7 +62,7 @@ async function hostCommand(path, opts) {
62
62
  log.success("Done.");
63
63
  console.log();
64
64
  }
65
- log.step("Building static site...");
65
+ log.step("Building site...");
66
66
  const buildCode = await streamExec("npm", ["run", "build", "--silent"], {
67
67
  cwd: hostDir,
68
68
  env: { ...process.env, DOCS_DIR: docsDir }
@@ -72,11 +72,15 @@ async function hostCommand(path, opts) {
72
72
  process.exit(1);
73
73
  }
74
74
  console.log();
75
- log.step("Starting local preview server...");
75
+ log.step("Starting server (ISR enabled)...");
76
76
  log.info(`Site: http://localhost:${port}`);
77
+ log.dim("New/changed docs will auto-update on page refresh.");
77
78
  log.dim("Press Ctrl+C to stop.");
78
79
  console.log();
79
- await streamExec("npx", ["serve", "out", "-p", port], { cwd: hostDir });
80
+ await streamExec("npx", ["next", "start", "-p", port], {
81
+ cwd: hostDir,
82
+ env: { ...process.env, DOCS_DIR: docsDir }
83
+ });
80
84
  }
81
85
  export {
82
86
  hostCommand
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ program.command("init").description("Initialize coding-friend in current project
16
16
  await initCommand();
17
17
  });
18
18
  program.command("host").description("Build and serve learning docs as a static website").argument("[path]", "path to docs folder").option("-p, --port <port>", "port number", "3333").action(async (path, opts) => {
19
- const { hostCommand } = await import("./host-4CEAT6TL.js");
19
+ const { hostCommand } = await import("./host-JBTJCWM2.js");
20
20
  await hostCommand(path, opts);
21
21
  });
22
22
  program.command("mcp").description("Setup MCP server for learning docs").argument("[path]", "path to docs folder").action(async (path) => {
@@ -1,7 +1,6 @@
1
1
  import type { NextConfig } from "next";
2
2
 
3
3
  const nextConfig: NextConfig = {
4
- output: "export",
5
4
  trailingSlash: true,
6
5
  images: { unoptimized: true },
7
6
  };
@@ -23,8 +23,8 @@
23
23
  "@types/node": "^22.0.0",
24
24
  "@types/react": "^19.0.0",
25
25
  "@types/react-dom": "^19.0.0",
26
+ "pagefind": "^1.3.0",
26
27
  "tailwindcss": "^4.0.0",
27
- "tsx": "^4.0.0",
28
28
  "typescript": "^5.7.0"
29
29
  }
30
30
  },
@@ -51,448 +51,6 @@
51
51
  "tslib": "^2.4.0"
52
52
  }
53
53
  },
54
- "node_modules/@esbuild/aix-ppc64": {
55
- "version": "0.27.3",
56
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
57
- "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
58
- "cpu": [
59
- "ppc64"
60
- ],
61
- "dev": true,
62
- "license": "MIT",
63
- "optional": true,
64
- "os": [
65
- "aix"
66
- ],
67
- "engines": {
68
- "node": ">=18"
69
- }
70
- },
71
- "node_modules/@esbuild/android-arm": {
72
- "version": "0.27.3",
73
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
74
- "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
75
- "cpu": [
76
- "arm"
77
- ],
78
- "dev": true,
79
- "license": "MIT",
80
- "optional": true,
81
- "os": [
82
- "android"
83
- ],
84
- "engines": {
85
- "node": ">=18"
86
- }
87
- },
88
- "node_modules/@esbuild/android-arm64": {
89
- "version": "0.27.3",
90
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
91
- "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
92
- "cpu": [
93
- "arm64"
94
- ],
95
- "dev": true,
96
- "license": "MIT",
97
- "optional": true,
98
- "os": [
99
- "android"
100
- ],
101
- "engines": {
102
- "node": ">=18"
103
- }
104
- },
105
- "node_modules/@esbuild/android-x64": {
106
- "version": "0.27.3",
107
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
108
- "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
109
- "cpu": [
110
- "x64"
111
- ],
112
- "dev": true,
113
- "license": "MIT",
114
- "optional": true,
115
- "os": [
116
- "android"
117
- ],
118
- "engines": {
119
- "node": ">=18"
120
- }
121
- },
122
- "node_modules/@esbuild/darwin-arm64": {
123
- "version": "0.27.3",
124
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
125
- "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
126
- "cpu": [
127
- "arm64"
128
- ],
129
- "dev": true,
130
- "license": "MIT",
131
- "optional": true,
132
- "os": [
133
- "darwin"
134
- ],
135
- "engines": {
136
- "node": ">=18"
137
- }
138
- },
139
- "node_modules/@esbuild/darwin-x64": {
140
- "version": "0.27.3",
141
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
142
- "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
143
- "cpu": [
144
- "x64"
145
- ],
146
- "dev": true,
147
- "license": "MIT",
148
- "optional": true,
149
- "os": [
150
- "darwin"
151
- ],
152
- "engines": {
153
- "node": ">=18"
154
- }
155
- },
156
- "node_modules/@esbuild/freebsd-arm64": {
157
- "version": "0.27.3",
158
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
159
- "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
160
- "cpu": [
161
- "arm64"
162
- ],
163
- "dev": true,
164
- "license": "MIT",
165
- "optional": true,
166
- "os": [
167
- "freebsd"
168
- ],
169
- "engines": {
170
- "node": ">=18"
171
- }
172
- },
173
- "node_modules/@esbuild/freebsd-x64": {
174
- "version": "0.27.3",
175
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
176
- "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
177
- "cpu": [
178
- "x64"
179
- ],
180
- "dev": true,
181
- "license": "MIT",
182
- "optional": true,
183
- "os": [
184
- "freebsd"
185
- ],
186
- "engines": {
187
- "node": ">=18"
188
- }
189
- },
190
- "node_modules/@esbuild/linux-arm": {
191
- "version": "0.27.3",
192
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
193
- "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
194
- "cpu": [
195
- "arm"
196
- ],
197
- "dev": true,
198
- "license": "MIT",
199
- "optional": true,
200
- "os": [
201
- "linux"
202
- ],
203
- "engines": {
204
- "node": ">=18"
205
- }
206
- },
207
- "node_modules/@esbuild/linux-arm64": {
208
- "version": "0.27.3",
209
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
210
- "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
211
- "cpu": [
212
- "arm64"
213
- ],
214
- "dev": true,
215
- "license": "MIT",
216
- "optional": true,
217
- "os": [
218
- "linux"
219
- ],
220
- "engines": {
221
- "node": ">=18"
222
- }
223
- },
224
- "node_modules/@esbuild/linux-ia32": {
225
- "version": "0.27.3",
226
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
227
- "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
228
- "cpu": [
229
- "ia32"
230
- ],
231
- "dev": true,
232
- "license": "MIT",
233
- "optional": true,
234
- "os": [
235
- "linux"
236
- ],
237
- "engines": {
238
- "node": ">=18"
239
- }
240
- },
241
- "node_modules/@esbuild/linux-loong64": {
242
- "version": "0.27.3",
243
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
244
- "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
245
- "cpu": [
246
- "loong64"
247
- ],
248
- "dev": true,
249
- "license": "MIT",
250
- "optional": true,
251
- "os": [
252
- "linux"
253
- ],
254
- "engines": {
255
- "node": ">=18"
256
- }
257
- },
258
- "node_modules/@esbuild/linux-mips64el": {
259
- "version": "0.27.3",
260
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
261
- "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
262
- "cpu": [
263
- "mips64el"
264
- ],
265
- "dev": true,
266
- "license": "MIT",
267
- "optional": true,
268
- "os": [
269
- "linux"
270
- ],
271
- "engines": {
272
- "node": ">=18"
273
- }
274
- },
275
- "node_modules/@esbuild/linux-ppc64": {
276
- "version": "0.27.3",
277
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
278
- "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
279
- "cpu": [
280
- "ppc64"
281
- ],
282
- "dev": true,
283
- "license": "MIT",
284
- "optional": true,
285
- "os": [
286
- "linux"
287
- ],
288
- "engines": {
289
- "node": ">=18"
290
- }
291
- },
292
- "node_modules/@esbuild/linux-riscv64": {
293
- "version": "0.27.3",
294
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
295
- "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
296
- "cpu": [
297
- "riscv64"
298
- ],
299
- "dev": true,
300
- "license": "MIT",
301
- "optional": true,
302
- "os": [
303
- "linux"
304
- ],
305
- "engines": {
306
- "node": ">=18"
307
- }
308
- },
309
- "node_modules/@esbuild/linux-s390x": {
310
- "version": "0.27.3",
311
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
312
- "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
313
- "cpu": [
314
- "s390x"
315
- ],
316
- "dev": true,
317
- "license": "MIT",
318
- "optional": true,
319
- "os": [
320
- "linux"
321
- ],
322
- "engines": {
323
- "node": ">=18"
324
- }
325
- },
326
- "node_modules/@esbuild/linux-x64": {
327
- "version": "0.27.3",
328
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
329
- "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
330
- "cpu": [
331
- "x64"
332
- ],
333
- "dev": true,
334
- "license": "MIT",
335
- "optional": true,
336
- "os": [
337
- "linux"
338
- ],
339
- "engines": {
340
- "node": ">=18"
341
- }
342
- },
343
- "node_modules/@esbuild/netbsd-arm64": {
344
- "version": "0.27.3",
345
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
346
- "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
347
- "cpu": [
348
- "arm64"
349
- ],
350
- "dev": true,
351
- "license": "MIT",
352
- "optional": true,
353
- "os": [
354
- "netbsd"
355
- ],
356
- "engines": {
357
- "node": ">=18"
358
- }
359
- },
360
- "node_modules/@esbuild/netbsd-x64": {
361
- "version": "0.27.3",
362
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
363
- "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
364
- "cpu": [
365
- "x64"
366
- ],
367
- "dev": true,
368
- "license": "MIT",
369
- "optional": true,
370
- "os": [
371
- "netbsd"
372
- ],
373
- "engines": {
374
- "node": ">=18"
375
- }
376
- },
377
- "node_modules/@esbuild/openbsd-arm64": {
378
- "version": "0.27.3",
379
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
380
- "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
381
- "cpu": [
382
- "arm64"
383
- ],
384
- "dev": true,
385
- "license": "MIT",
386
- "optional": true,
387
- "os": [
388
- "openbsd"
389
- ],
390
- "engines": {
391
- "node": ">=18"
392
- }
393
- },
394
- "node_modules/@esbuild/openbsd-x64": {
395
- "version": "0.27.3",
396
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
397
- "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
398
- "cpu": [
399
- "x64"
400
- ],
401
- "dev": true,
402
- "license": "MIT",
403
- "optional": true,
404
- "os": [
405
- "openbsd"
406
- ],
407
- "engines": {
408
- "node": ">=18"
409
- }
410
- },
411
- "node_modules/@esbuild/openharmony-arm64": {
412
- "version": "0.27.3",
413
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
414
- "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
415
- "cpu": [
416
- "arm64"
417
- ],
418
- "dev": true,
419
- "license": "MIT",
420
- "optional": true,
421
- "os": [
422
- "openharmony"
423
- ],
424
- "engines": {
425
- "node": ">=18"
426
- }
427
- },
428
- "node_modules/@esbuild/sunos-x64": {
429
- "version": "0.27.3",
430
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
431
- "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
432
- "cpu": [
433
- "x64"
434
- ],
435
- "dev": true,
436
- "license": "MIT",
437
- "optional": true,
438
- "os": [
439
- "sunos"
440
- ],
441
- "engines": {
442
- "node": ">=18"
443
- }
444
- },
445
- "node_modules/@esbuild/win32-arm64": {
446
- "version": "0.27.3",
447
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
448
- "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
449
- "cpu": [
450
- "arm64"
451
- ],
452
- "dev": true,
453
- "license": "MIT",
454
- "optional": true,
455
- "os": [
456
- "win32"
457
- ],
458
- "engines": {
459
- "node": ">=18"
460
- }
461
- },
462
- "node_modules/@esbuild/win32-ia32": {
463
- "version": "0.27.3",
464
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
465
- "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
466
- "cpu": [
467
- "ia32"
468
- ],
469
- "dev": true,
470
- "license": "MIT",
471
- "optional": true,
472
- "os": [
473
- "win32"
474
- ],
475
- "engines": {
476
- "node": ">=18"
477
- }
478
- },
479
- "node_modules/@esbuild/win32-x64": {
480
- "version": "0.27.3",
481
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
482
- "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
483
- "cpu": [
484
- "x64"
485
- ],
486
- "dev": true,
487
- "license": "MIT",
488
- "optional": true,
489
- "os": [
490
- "win32"
491
- ],
492
- "engines": {
493
- "node": ">=18"
494
- }
495
- },
496
54
  "node_modules/@img/colour": {
497
55
  "version": "1.0.0",
498
56
  "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz",
@@ -1143,6 +701,90 @@
1143
701
  "node": ">= 10"
1144
702
  }
1145
703
  },
704
+ "node_modules/@pagefind/darwin-arm64": {
705
+ "version": "1.4.0",
706
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz",
707
+ "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==",
708
+ "cpu": [
709
+ "arm64"
710
+ ],
711
+ "dev": true,
712
+ "license": "MIT",
713
+ "optional": true,
714
+ "os": [
715
+ "darwin"
716
+ ]
717
+ },
718
+ "node_modules/@pagefind/darwin-x64": {
719
+ "version": "1.4.0",
720
+ "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz",
721
+ "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==",
722
+ "cpu": [
723
+ "x64"
724
+ ],
725
+ "dev": true,
726
+ "license": "MIT",
727
+ "optional": true,
728
+ "os": [
729
+ "darwin"
730
+ ]
731
+ },
732
+ "node_modules/@pagefind/freebsd-x64": {
733
+ "version": "1.4.0",
734
+ "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz",
735
+ "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==",
736
+ "cpu": [
737
+ "x64"
738
+ ],
739
+ "dev": true,
740
+ "license": "MIT",
741
+ "optional": true,
742
+ "os": [
743
+ "freebsd"
744
+ ]
745
+ },
746
+ "node_modules/@pagefind/linux-arm64": {
747
+ "version": "1.4.0",
748
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz",
749
+ "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==",
750
+ "cpu": [
751
+ "arm64"
752
+ ],
753
+ "dev": true,
754
+ "license": "MIT",
755
+ "optional": true,
756
+ "os": [
757
+ "linux"
758
+ ]
759
+ },
760
+ "node_modules/@pagefind/linux-x64": {
761
+ "version": "1.4.0",
762
+ "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz",
763
+ "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==",
764
+ "cpu": [
765
+ "x64"
766
+ ],
767
+ "dev": true,
768
+ "license": "MIT",
769
+ "optional": true,
770
+ "os": [
771
+ "linux"
772
+ ]
773
+ },
774
+ "node_modules/@pagefind/windows-x64": {
775
+ "version": "1.4.0",
776
+ "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz",
777
+ "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==",
778
+ "cpu": [
779
+ "x64"
780
+ ],
781
+ "dev": true,
782
+ "license": "MIT",
783
+ "optional": true,
784
+ "os": [
785
+ "win32"
786
+ ]
787
+ },
1146
788
  "node_modules/@swc/helpers": {
1147
789
  "version": "0.5.15",
1148
790
  "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1725,48 +1367,6 @@
1725
1367
  "node": ">=10.13.0"
1726
1368
  }
1727
1369
  },
1728
- "node_modules/esbuild": {
1729
- "version": "0.27.3",
1730
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
1731
- "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
1732
- "dev": true,
1733
- "hasInstallScript": true,
1734
- "license": "MIT",
1735
- "bin": {
1736
- "esbuild": "bin/esbuild"
1737
- },
1738
- "engines": {
1739
- "node": ">=18"
1740
- },
1741
- "optionalDependencies": {
1742
- "@esbuild/aix-ppc64": "0.27.3",
1743
- "@esbuild/android-arm": "0.27.3",
1744
- "@esbuild/android-arm64": "0.27.3",
1745
- "@esbuild/android-x64": "0.27.3",
1746
- "@esbuild/darwin-arm64": "0.27.3",
1747
- "@esbuild/darwin-x64": "0.27.3",
1748
- "@esbuild/freebsd-arm64": "0.27.3",
1749
- "@esbuild/freebsd-x64": "0.27.3",
1750
- "@esbuild/linux-arm": "0.27.3",
1751
- "@esbuild/linux-arm64": "0.27.3",
1752
- "@esbuild/linux-ia32": "0.27.3",
1753
- "@esbuild/linux-loong64": "0.27.3",
1754
- "@esbuild/linux-mips64el": "0.27.3",
1755
- "@esbuild/linux-ppc64": "0.27.3",
1756
- "@esbuild/linux-riscv64": "0.27.3",
1757
- "@esbuild/linux-s390x": "0.27.3",
1758
- "@esbuild/linux-x64": "0.27.3",
1759
- "@esbuild/netbsd-arm64": "0.27.3",
1760
- "@esbuild/netbsd-x64": "0.27.3",
1761
- "@esbuild/openbsd-arm64": "0.27.3",
1762
- "@esbuild/openbsd-x64": "0.27.3",
1763
- "@esbuild/openharmony-arm64": "0.27.3",
1764
- "@esbuild/sunos-x64": "0.27.3",
1765
- "@esbuild/win32-arm64": "0.27.3",
1766
- "@esbuild/win32-ia32": "0.27.3",
1767
- "@esbuild/win32-x64": "0.27.3"
1768
- }
1769
- },
1770
1370
  "node_modules/escape-string-regexp": {
1771
1371
  "version": "5.0.0",
1772
1372
  "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
@@ -1820,34 +1420,6 @@
1820
1420
  "node": ">=0.10.0"
1821
1421
  }
1822
1422
  },
1823
- "node_modules/fsevents": {
1824
- "version": "2.3.3",
1825
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1826
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1827
- "dev": true,
1828
- "hasInstallScript": true,
1829
- "license": "MIT",
1830
- "optional": true,
1831
- "os": [
1832
- "darwin"
1833
- ],
1834
- "engines": {
1835
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1836
- }
1837
- },
1838
- "node_modules/get-tsconfig": {
1839
- "version": "4.13.6",
1840
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
1841
- "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
1842
- "dev": true,
1843
- "license": "MIT",
1844
- "dependencies": {
1845
- "resolve-pkg-maps": "^1.0.0"
1846
- },
1847
- "funding": {
1848
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
1849
- }
1850
- },
1851
1423
  "node_modules/graceful-fs": {
1852
1424
  "version": "4.2.11",
1853
1425
  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3314,6 +2886,24 @@
3314
2886
  "node": "^10 || ^12 || >=14"
3315
2887
  }
3316
2888
  },
2889
+ "node_modules/pagefind": {
2890
+ "version": "1.4.0",
2891
+ "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz",
2892
+ "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==",
2893
+ "dev": true,
2894
+ "license": "MIT",
2895
+ "bin": {
2896
+ "pagefind": "lib/runner/bin.cjs"
2897
+ },
2898
+ "optionalDependencies": {
2899
+ "@pagefind/darwin-arm64": "1.4.0",
2900
+ "@pagefind/darwin-x64": "1.4.0",
2901
+ "@pagefind/freebsd-x64": "1.4.0",
2902
+ "@pagefind/linux-arm64": "1.4.0",
2903
+ "@pagefind/linux-x64": "1.4.0",
2904
+ "@pagefind/windows-x64": "1.4.0"
2905
+ }
2906
+ },
3317
2907
  "node_modules/parse-entities": {
3318
2908
  "version": "4.0.2",
3319
2909
  "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
@@ -3529,16 +3119,6 @@
3529
3119
  "url": "https://opencollective.com/unified"
3530
3120
  }
3531
3121
  },
3532
- "node_modules/resolve-pkg-maps": {
3533
- "version": "1.0.0",
3534
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
3535
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
3536
- "dev": true,
3537
- "license": "MIT",
3538
- "funding": {
3539
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
3540
- }
3541
- },
3542
3122
  "node_modules/scheduler": {
3543
3123
  "version": "0.27.0",
3544
3124
  "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -3752,26 +3332,6 @@
3752
3332
  "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
3753
3333
  "license": "0BSD"
3754
3334
  },
3755
- "node_modules/tsx": {
3756
- "version": "4.21.0",
3757
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
3758
- "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
3759
- "dev": true,
3760
- "license": "MIT",
3761
- "dependencies": {
3762
- "esbuild": "~0.27.0",
3763
- "get-tsconfig": "^4.7.5"
3764
- },
3765
- "bin": {
3766
- "tsx": "dist/cli.mjs"
3767
- },
3768
- "engines": {
3769
- "node": ">=18.0.0"
3770
- },
3771
- "optionalDependencies": {
3772
- "fsevents": "~2.3.3"
3773
- }
3774
- },
3775
3335
  "node_modules/typescript": {
3776
3336
  "version": "5.9.3",
3777
3337
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -4,9 +4,9 @@
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",
7
- "prebuild": "tsx scripts/build-search-index.ts",
8
- "build": "npm run prebuild && next build",
9
- "start": "npx serve out"
7
+ "build": "next build",
8
+ "postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
9
+ "start": "next start"
10
10
  },
11
11
  "dependencies": {
12
12
  "next": "^15.1.0",
@@ -26,6 +26,6 @@
26
26
  "tailwindcss": "^4.0.0",
27
27
  "@tailwindcss/postcss": "^4.0.0",
28
28
  "@tailwindcss/typography": "^0.5.0",
29
- "tsx": "^4.0.0"
29
+ "pagefind": "^1.3.0"
30
30
  }
31
31
  }
@@ -11,7 +11,8 @@ export async function generateStaticParams() {
11
11
  }));
12
12
  }
13
13
 
14
- export const dynamicParams = false;
14
+ export const dynamicParams = true;
15
+ export const revalidate = 10;
15
16
 
16
17
  export default async function DocPage({
17
18
  params,
@@ -25,7 +26,7 @@ export default async function DocPage({
25
26
  const categoryDisplay = category.replace(/[_-]/g, " ");
26
27
 
27
28
  return (
28
- <article>
29
+ <article data-pagefind-body>
29
30
  <Breadcrumbs
30
31
  crumbs={[
31
32
  { label: categoryDisplay, href: `/${category}/` },
@@ -6,7 +6,8 @@ export async function generateStaticParams() {
6
6
  return getAllCategories().map((c) => ({ category: c.name }));
7
7
  }
8
8
 
9
- export const dynamicParams = false;
9
+ export const dynamicParams = true;
10
+ export const revalidate = 10;
10
11
 
11
12
  export default async function CategoryPage({
12
13
  params,
@@ -6,6 +6,8 @@ import MobileNav from "@/components/MobileNav";
6
6
  import "./globals.css";
7
7
  import "highlight.js/styles/github-dark.css";
8
8
 
9
+ export const revalidate = 10;
10
+
9
11
  export const metadata: Metadata = {
10
12
  title: "Learning Notes",
11
13
  description: "Personal learning knowledge base",
@@ -18,9 +20,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
18
20
  <html lang="en" suppressHydrationWarning>
19
21
  <body className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
20
22
  <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
21
- <MobileNav categories={categories} />
23
+ <div data-pagefind-ignore>
24
+ <MobileNav categories={categories} />
25
+ </div>
22
26
  <div className="flex">
23
- <Sidebar categories={categories} />
27
+ <div data-pagefind-ignore>
28
+ <Sidebar categories={categories} />
29
+ </div>
24
30
  <main className="flex-1 min-h-screen p-6 md:p-8 max-w-4xl">
25
31
  {children}
26
32
  </main>
@@ -3,6 +3,8 @@ import DocCard from "@/components/DocCard";
3
3
  import TagBadge from "@/components/TagBadge";
4
4
  import Link from "next/link";
5
5
 
6
+ export const revalidate = 10;
7
+
6
8
  export default function HomePage() {
7
9
  const categories = getAllCategories();
8
10
  const docs = getAllDocs();
@@ -1,94 +1,19 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect } from "react";
4
- import { useSearchParams } from "next/navigation";
5
3
  import { Suspense } from "react";
6
- import type { SearchIndexEntry } from "@/lib/types";
7
-
8
- // Search index embedded at build time
9
- import searchIndexData from "./search-index.json";
4
+ import { useSearchParams } from "next/navigation";
5
+ import PagefindSearch from "@/components/PagefindSearch";
10
6
 
11
- function SearchContent() {
7
+ function SearchWithParams() {
12
8
  const searchParams = useSearchParams();
13
- const initialQuery = searchParams.get("q") ?? "";
14
- const [query, setQuery] = useState(initialQuery);
15
- const [results, setResults] = useState<SearchIndexEntry[]>([]);
16
-
17
- useEffect(() => {
18
- if (!query.trim()) {
19
- setResults([]);
20
- return;
21
- }
22
- const lower = query.toLowerCase();
23
- const filtered = (searchIndexData as SearchIndexEntry[]).filter(
24
- (entry) =>
25
- entry.title.toLowerCase().includes(lower) ||
26
- entry.tags.some((t) => t.toLowerCase().includes(lower)) ||
27
- entry.excerpt.toLowerCase().includes(lower) ||
28
- entry.category.toLowerCase().includes(lower),
29
- );
30
- setResults(filtered);
31
- }, [query]);
32
-
33
- useEffect(() => {
34
- setQuery(initialQuery);
35
- }, [initialQuery]);
36
-
37
- return (
38
- <div>
39
- <h1 className="text-2xl font-bold mb-4">Search</h1>
40
- <input
41
- type="text"
42
- value={query}
43
- onChange={(e) => setQuery(e.target.value)}
44
- placeholder="Search docs..."
45
- className="w-full px-4 py-2 mb-6 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
46
- autoFocus
47
- />
48
-
49
- {query.trim() && (
50
- <p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
51
- {results.length} {results.length === 1 ? "result" : "results"} for &ldquo;{query}&rdquo;
52
- </p>
53
- )}
54
-
55
- <div className="grid gap-3">
56
- {results.map((entry) => (
57
- <a
58
- key={`${entry.category}/${entry.slug}`}
59
- href={`/${entry.category}/${entry.slug}/`}
60
- className="block p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600 hover:shadow-sm transition-all bg-white dark:bg-gray-800/50"
61
- >
62
- <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
63
- {entry.title}
64
- </h3>
65
- <p className="text-sm text-gray-500 dark:text-gray-400 mb-2 line-clamp-2">
66
- {entry.excerpt}
67
- </p>
68
- <div className="flex items-center gap-2">
69
- <span className="text-xs px-2 py-0.5 bg-gray-100 dark:bg-gray-800 rounded capitalize">
70
- {entry.category.replace(/[_-]/g, " ")}
71
- </span>
72
- {entry.tags.slice(0, 3).map((tag) => (
73
- <span
74
- key={tag}
75
- className="text-xs px-2 py-0.5 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full"
76
- >
77
- {tag}
78
- </span>
79
- ))}
80
- </div>
81
- </a>
82
- ))}
83
- </div>
84
- </div>
85
- );
9
+ const query = searchParams.get("q") ?? "";
10
+ return <PagefindSearch initialQuery={query} />;
86
11
  }
87
12
 
88
13
  export default function SearchPage() {
89
14
  return (
90
15
  <Suspense fallback={<div>Loading...</div>}>
91
- <SearchContent />
16
+ <SearchWithParams />
92
17
  </Suspense>
93
18
  );
94
19
  }
@@ -0,0 +1,134 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useRef, useCallback } from "react";
4
+
5
+ interface PagefindResult {
6
+ id: string;
7
+ data: () => Promise<{
8
+ url: string;
9
+ meta: { title?: string };
10
+ excerpt: string;
11
+ sub_results?: { url: string; title: string; excerpt: string }[];
12
+ }>;
13
+ }
14
+
15
+ interface SearchResult {
16
+ id: string;
17
+ url: string;
18
+ title: string;
19
+ excerpt: string;
20
+ }
21
+
22
+ interface Pagefind {
23
+ init: () => Promise<void>;
24
+ search: (query: string) => Promise<{ results: PagefindResult[] }>;
25
+ debouncedSearch: (
26
+ query: string,
27
+ options?: Record<string, unknown>,
28
+ ms?: number,
29
+ ) => Promise<{ results: PagefindResult[] } | null>;
30
+ }
31
+
32
+ export default function PagefindSearch({ initialQuery = "" }: { initialQuery?: string }) {
33
+ const [query, setQuery] = useState(initialQuery);
34
+ const [results, setResults] = useState<SearchResult[]>([]);
35
+ const [loading, setLoading] = useState(false);
36
+ const [ready, setReady] = useState(false);
37
+ const pagefindRef = useRef<Pagefind | null>(null);
38
+
39
+ useEffect(() => {
40
+ async function load() {
41
+ try {
42
+ const pf = await import(
43
+ // @ts-expect-error pagefind.js generated after build
44
+ /* webpackIgnore: true */ "/_pagefind/pagefind.js"
45
+ );
46
+ await pf.init();
47
+ pagefindRef.current = pf;
48
+ setReady(true);
49
+ } catch {
50
+ // Pagefind not available (dev mode or first run)
51
+ }
52
+ }
53
+ load();
54
+ }, []);
55
+
56
+ const doSearch = useCallback(async (q: string) => {
57
+ const pf = pagefindRef.current;
58
+ if (!pf || !q.trim()) {
59
+ setResults([]);
60
+ return;
61
+ }
62
+
63
+ setLoading(true);
64
+ try {
65
+ const response = await pf.debouncedSearch(q, {}, 200);
66
+ if (!response) return; // debounced away
67
+
68
+ const items: SearchResult[] = [];
69
+ for (const result of response.results.slice(0, 20)) {
70
+ const data = await result.data();
71
+ items.push({
72
+ id: result.id,
73
+ url: data.url,
74
+ title: data.meta?.title ?? "Untitled",
75
+ excerpt: data.excerpt,
76
+ });
77
+ }
78
+ setResults(items);
79
+ } finally {
80
+ setLoading(false);
81
+ }
82
+ }, []);
83
+
84
+ useEffect(() => {
85
+ if (ready) doSearch(query);
86
+ }, [query, ready, doSearch]);
87
+
88
+ return (
89
+ <div>
90
+ <h1 className="text-2xl font-bold mb-4">Search</h1>
91
+ <input
92
+ type="text"
93
+ value={query}
94
+ onChange={(e) => setQuery(e.target.value)}
95
+ placeholder="Search docs..."
96
+ className="w-full px-4 py-2 mb-6 text-sm rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
97
+ autoFocus
98
+ />
99
+
100
+ {!ready && query.trim() && (
101
+ <p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
102
+ Search index not available. Run a build first.
103
+ </p>
104
+ )}
105
+
106
+ {ready && query.trim() && !loading && (
107
+ <p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
108
+ {results.length} {results.length === 1 ? "result" : "results"} for &ldquo;{query}&rdquo;
109
+ </p>
110
+ )}
111
+
112
+ <div className="grid gap-3">
113
+ {results.map((entry) => {
114
+ // Pagefind excerpts contain only <mark> tags for highlighting — safe to render
115
+ return (
116
+ <a
117
+ key={entry.id}
118
+ href={entry.url}
119
+ className="block p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600 hover:shadow-sm transition-all bg-white dark:bg-gray-800/50"
120
+ >
121
+ <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
122
+ {entry.title}
123
+ </h3>
124
+ <p
125
+ className="text-sm text-gray-500 dark:text-gray-400 mb-2 line-clamp-2 [&_mark]:bg-yellow-200 dark:[&_mark]:bg-yellow-800 [&_mark]:rounded [&_mark]:px-0.5"
126
+ dangerouslySetInnerHTML={{ __html: entry.excerpt }}
127
+ />
128
+ </a>
129
+ );
130
+ })}
131
+ </div>
132
+ </div>
133
+ );
134
+ }
@@ -20,12 +20,4 @@ export interface Doc extends DocMeta {
20
20
  export interface CategoryInfo {
21
21
  name: string;
22
22
  docCount: number;
23
- }
24
-
25
- export interface SearchIndexEntry {
26
- slug: string;
27
- category: string;
28
- title: string;
29
- tags: string[];
30
- excerpt: string;
31
- }
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-friend-cli",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "CLI for coding-friend — host learning docs, setup MCP server, initialize projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { buildSearchIndex } from "../src/lib/search";
4
-
5
- const index = buildSearchIndex();
6
- const outPath = path.join(process.cwd(), "src/app/search/search-index.json");
7
-
8
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
9
- fs.writeFileSync(outPath, JSON.stringify(index, null, 2));
10
-
11
- console.log(`Search index built: ${index.length} entries -> ${outPath}`);
@@ -1,42 +0,0 @@
1
- [
2
- {
3
- "slug": "claude-code-plugin-hooks-system",
4
- "category": "Tools_Workflow",
5
- "title": "Claude Code Plugin & Hooks System",
6
- "tags": [
7
- "claude-code",
8
- "plugins",
9
- "hooks",
10
- "configuration"
11
- ],
12
- "excerpt": "Claude Code has a plugin system that can load hooks (scripts that run on specific events like `SessionStart`, `PreToolUse`, etc.) from **multiple sources simult..."
13
- },
14
- {
15
- "slug": "tauri-desktop-app-gotchas",
16
- "category": "Mobile_Dev",
17
- "title": "Tauri Desktop App Gotchas",
18
- "tags": [
19
- "tauri",
20
- "desktop",
21
- "webview",
22
- "sidecar",
23
- "macos",
24
- "gatekeeper",
25
- "race-condition"
26
- ],
27
- "excerpt": "Tổng hợp các vấn đề thường gặp khi build và distribute desktop app bằng Tauri v2, đặc biệt trên macOS. **What:** Khi user tải app `.dmg` từ GitHub Releases và c..."
28
- },
29
- {
30
- "slug": "typescript-type-casting-patterns",
31
- "category": "Programming_Languages",
32
- "title": "TypeScript Type Casting Patterns & Gotchas",
33
- "tags": [
34
- "typescript",
35
- "type-casting",
36
- "type-narrowing",
37
- "window",
38
- "strict-mode"
39
- ],
40
- "excerpt": "Tổng hợp các pattern cast type phổ biến và gotchas khi dùng TypeScript strict mode. **What:** Khi TypeScript không cho phép cast trực tiếp giữa 2 type vì chúng ..."
41
- }
42
- ]
@@ -1,27 +0,0 @@
1
- import { getAllDocs } from "./docs";
2
- import type { SearchIndexEntry } from "./types";
3
-
4
- export function buildSearchIndex(): SearchIndexEntry[] {
5
- return getAllDocs().map((doc) => ({
6
- slug: doc.slug,
7
- category: doc.category,
8
- title: doc.frontmatter.title,
9
- tags: doc.frontmatter.tags,
10
- excerpt: doc.excerpt,
11
- }));
12
- }
13
-
14
- export function searchIndex(
15
- index: SearchIndexEntry[],
16
- query: string,
17
- ): SearchIndexEntry[] {
18
- const lower = query.toLowerCase();
19
- return index.filter((entry) => {
20
- return (
21
- entry.title.toLowerCase().includes(lower) ||
22
- entry.tags.some((t) => t.toLowerCase().includes(lower)) ||
23
- entry.excerpt.toLowerCase().includes(lower) ||
24
- entry.category.toLowerCase().includes(lower)
25
- );
26
- });
27
- }