Compare commits

...

10 Commits

Author SHA1 Message Date
xiongxiao
dc6e158462 feat: 更新版本至0.0.12 2026-03-15 21:05:06 +08:00
xiongxiao
2de68a9960 feat: 更新 @kevisual/router 依赖至 0.1.2,增强 git 路径解析功能并优化项目搜索逻辑 2026-03-15 20:57:40 +08:00
xiongxiao
609a0987e9 udpate 2026-03-14 17:43:14 +08:00
xiongxiao
df616df952 chore: update project version to 0.0.11 and enhance project management features
- Added new project initialization route to register git projects from a specified root directory.
- Enhanced project management to support project status (active, inactive, unlive) and conditional listening.
- Updated project listener to handle non-existent paths gracefully.
- Improved project store to manage project documents with optional fields.
- Refactored git utility functions for better pathname extraction from URLs.
- Added command-line interface support for project management.
- Updated project routes to handle new project types and improved error handling.
2026-03-14 17:22:00 +08:00
xiongxiao
1aa46b92c0 feat: 更新版本至0.0.9并增强文件哈希生成逻辑 2026-03-14 03:39:04 +08:00
xiongxiao
7689dd512d feat: 添加更多文本文件扩展名以增强文件类型识别 2026-03-14 00:53:20 +08:00
xiongxiao
b60f365a6b feat: update version to 0.0.8 and enhance search query filtering in ProjectSearch 2026-03-13 22:00:19 +08:00
xiongxiao
711aa221a5 update 0.0.6 2026-03-13 20:17:33 +08:00
xiongxiao
6e31e24887 feat: update package.json dependencies and enhance FileProjectData type documentation 2026-03-13 20:05:36 +08:00
xiongxiao
642b08a00b feat: update version to 0.0.5 in package.json 2026-03-13 18:46:15 +08:00
14 changed files with 539 additions and 76 deletions

125
bun.lock
View File

@@ -6,14 +6,15 @@
"name": "@kevisual/project-search",
"dependencies": {
"@parcel/watcher": "^2.5.6",
"es-toolkit": "^1.45.1",
"eventemitter3": "^5.0.4",
},
"devDependencies": {
"@kevisual/code-builder": "^0.0.6",
"@kevisual/context": "^0.0.8",
"@kevisual/dts": "^0.0.4",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/router": "^0.1.1",
"es-toolkit": "^1.45.1",
"eventemitter3": "^5.0.4",
"fast-glob": "^3.3.3",
"meilisearch": "^0.55.0",
"zod": "^4.3.6",
@@ -21,10 +22,26 @@
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "https://registry.npmmirror.com/@kevisual/code-builder/-/code-builder-0.0.6.tgz", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="],
"@kevisual/context": ["@kevisual/context@0.0.8", "https://registry.npmmirror.com/@kevisual/context/-/context-0.0.8.tgz", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
"@kevisual/dts": ["@kevisual/dts@0.0.4", "https://registry.npmmirror.com/@kevisual/dts/-/dts-0.0.4.tgz", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="],
"@kevisual/remote-app": ["@kevisual/remote-app@0.0.7", "https://registry.npmmirror.com/@kevisual/remote-app/-/remote-app-0.0.7.tgz", {}, "sha512-d0P8uyxoMnmyT8x1J9XC9ecDBbqW+jOP0ZM5fCgQRDUhWw35V/MnbCD4hNG4b6EmvoiS6a/PBC7RC5JGm3wpCg=="],
"@kevisual/router": ["@kevisual/router@0.1.1", "https://registry.npmmirror.com/@kevisual/router/-/router-0.1.1.tgz", { "dependencies": { "es-toolkit": "^1.45.1" } }, "sha512-+uaJc+Bf/T1mfxyfy9PmwuxJGPOLhVqrmsli2xUPqkkFvizrFIGB1vBTITuo5XP/FnwGqxgbjsitG57AMubm3w=="],
@@ -63,28 +80,116 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.2", "https://registry.npmmirror.com/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg=="],
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
"@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, "optionalPeers": ["rollup", "tslib"] }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/resolve": ["@types/resolve@1.20.2", "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
"braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"commondir": ["commondir@1.0.1", "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
"convert-source-map": ["convert-source-map@2.0.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
"estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"fast-glob": ["fast-glob@3.3.3", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fastq": ["fastq@1.20.1", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["fill-range@7.1.1", "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"is-core-module": ["is-core-module@2.16.1", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-extglob": ["is-extglob@2.1.1", "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-module": ["is-module@1.0.0", "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
"is-number": ["is-number@7.0.0", "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-reference": ["is-reference@1.2.1", "https://registry.npmmirror.com/is-reference/-/is-reference-1.2.1.tgz", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"meilisearch": ["meilisearch@0.55.0", "https://registry.npmmirror.com/meilisearch/-/meilisearch-0.55.0.tgz", {}, "sha512-qSMeiezfDgIqciIeYzh5E4pXDZZD7CtHeWDCs43kN3trLgl5FtfmBAIkljL3huFaOx08feYtC8FfIFUpVwq6rg=="],
"merge2": ["merge2@1.4.1", "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
@@ -93,16 +198,32 @@
"node-addon-api": ["node-addon-api@7.1.1", "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"queue-microtask": ["queue-microtask@1.2.3", "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"resolve": ["resolve@1.22.11", "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"reusify": ["reusify@1.1.0", "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rollup": ["rollup@4.59.0", "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"rollup-plugin-dts": ["rollup-plugin-dts@6.4.0", "https://registry.npmmirror.com/rollup-plugin-dts/-/rollup-plugin-dts-6.4.0.tgz", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.5.5", "convert-source-map": "^2.0.0", "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.29.0" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0 || ^6.0" } }, "sha512-2i00A5UoPCoDecLEs13Eu105QegSGfrbp1sDeUj/54LKGmv6XFHDxWKC6Wsb4BobGUWYVCWWjmjAc8bXXbXH/Q=="],
"run-parallel": ["run-parallel@1.2.0", "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"to-regex-range": ["to-regex-range@5.0.1", "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"micromatch/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],

View File

@@ -1,6 +1,6 @@
{
"name": "@kevisual/project-search",
"version": "0.0.4",
"version": "0.0.12",
"description": "",
"main": "index.js",
"scripts": {
@@ -20,12 +20,12 @@
"@kevisual/context": "^0.0.8",
"@kevisual/dts": "^0.0.4",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/router": "^0.1.1",
"@kevisual/router": "^0.1.2",
"es-toolkit": "^1.45.1",
"eventemitter3": "^5.0.4",
"fast-glob": "^3.3.3",
"meilisearch": "^0.55.0",
"zod": "^4.3.6",
"es-toolkit": "^1.45.1",
"eventemitter3": "^5.0.4"
"zod": "^4.3.6"
},
"dependencies": {
"@parcel/watcher": "^2.5.6"

4
src/commander.ts Normal file
View File

@@ -0,0 +1,4 @@
import { parse } from '@kevisual/router/commander'
import { app } from './index'
parse({ app: app as any });

View File

@@ -8,6 +8,8 @@ import './routes/auth';
import './routes/project';
import './routes/search';
import './routes/file';
import './routes/project-init';
import { manager } from './app';
if (import.meta.main) {

View File

@@ -4,28 +4,15 @@ import { ProjectStore, ProjectDoc } from "./project-store";
import { ProjectListener } from "./project-listener/listener";
import { EventEmitter } from "eventemitter3";
import fs from 'node:fs';
import path from 'node:path';
import { CustomError } from "@kevisual/router";
type Project = {
name?: string;
path: string;
/**
* 用 git remote url 唯一标识项目,方便搜索结果展示和过滤
*/
repo: string;
listener: ProjectListener;
};
} & ProjectDoc;
export type ProjectInput = Omit<Project, "listener">;
export type ProjectInfo = Omit<Project, "listener"> & {
status: "active" | "inactive";
id?: string;
title?: string;
tags?: string[];
summary?: string;
description?: string;
link?: string;
};
export type ProjectInput = ProjectDoc;
export type ProjectInfo = ProjectDoc
type ProjectManagerOpt = {
meiliSearchOptions?: {
@@ -57,7 +44,8 @@ export class ProjectManager implements ProjectManagerInterface {
}
/**
* 初始化:从 store 加载已持久化的项目,检查本地目录存在后启动监听
* 初始化:从 store 加载已持久化的项目,检查本地目录存在后
* 只对 status 为 active 的项目启动监听inactive 的只加载不监听
*/
async init(): Promise<void> {
await this.projectStore.init();
@@ -67,8 +55,13 @@ export class ProjectManager implements ProjectManagerInterface {
console.log(`[ProjectManager] init: skip missing path ${doc.path}`);
continue;
}
// 只对 active 状态的项目启动监听
const shouldWatch = doc.status === 'active';
try {
await this.addProject({ name: doc.name, path: doc.path, repo: doc.repo });
await this.addProject(
{ name: doc.name, path: doc.path, repo: doc.repo },
{ startListening: shouldWatch }
);
} catch (err) {
console.error(`[ProjectManager] init: failed to add project ${doc.path}`, err);
}
@@ -77,32 +70,46 @@ export class ProjectManager implements ProjectManagerInterface {
}
/**
* 添加项目:创建监听器、启动监听、触发全量初始同步
* 添加项目:创建监听器、可选启动监听、触发全量初始同步
*/
async addProject(input: ProjectInput): Promise<ProjectInfo> {
async addProject(input: ProjectDoc, options: { startListening?: boolean } = {}): Promise<ProjectInfo> {
const { startListening = true } = options;
// 检查本地目录是否存在
if (!fs.existsSync(input.path)) {
if (!fs.existsSync(input.path!)) {
throw new CustomError(`[ProjectManager] addProject: path does not exist: ${input.path}`);
}
// 若已存在则先停止旧监听
if (this.projects.has(input.path)) {
await this.stopProject(input.path);
if (this.projects.has(input.path!)) {
await this.stopProject(input.path!);
}
const listener = new ProjectListener({ projectPath: input.path, emitter: this.emitter });
const listener = new ProjectListener({ projectPath: input.path!, emitter: this.emitter });
const project: Project = { ...input, listener };
this.projects.set(input.path, project);
// 持久化存储项目信息
this.projects.set(input.path!, project);
const readmePath = path.join(input.path!, 'README.md');
let description = '';
if (!input.description && fs.existsSync(readmePath)) {
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
description = readmeContent;
}
// 持久化存储项目信息(默认 active
this.projectStore.ensureIndex().then(() =>
this.projectStore.addProject({ name: input.name, path: input.path, repo: input.repo })
this.projectStore.addProject({
name: input.name,
path: input.path,
repo: input.repo,
status: startListening ? 'active' : 'inactive',
description: input?.description || description,
})
).catch(err => {
console.error('[ProjectManager] projectStore.addProject failed:', err);
});
// 启动文件监听
// 根据选项启动文件监听
if (startListening) {
await listener.startListening();
console.log(`[ProjectManager] watching: ${input.path}`);
}
// 触发全量初始同步(不阻塞返回)
this.projectSearch.initialSync(input.path, input.repo).catch(err => {
@@ -136,15 +143,37 @@ export class ProjectManager implements ProjectManagerInterface {
const project = this.projects.get(projectPath);
if (!project) return;
await project.listener.stopListening();
// 更新 store 中的 status
this.projectStore.updateProject(projectPath, { status: 'inactive' }).catch(err => {
console.error('[ProjectManager] updateProject status failed:', err);
});
console.log(`[ProjectManager] stopped: ${projectPath}`);
}
/** 启动项目文件监听 */
async startProject(projectPath: string): Promise<void> {
const project = this.projects.get(projectPath);
if (!project) return;
await project.listener.startListening();
// 更新 store 中的 status
this.projectStore.updateProject(projectPath, { status: 'active' }).catch(err => {
console.error('[ProjectManager] updateProject status failed:', err);
});
console.log(`[ProjectManager] started: ${projectPath}`);
}
async getProjectInfo(project: Project): Promise<ProjectInfo> {
const { listener, ...info } = project;
const storeDoc: ProjectDoc | null = await this.projectStore.getProject(info.path).catch(() => null);
// 优先从 store 读取 status确保与 store 保持一致
let status: any = storeDoc?.status === 'active' ? 'active' : 'inactive';
const exists = fileIsExist(info.path);
if (!exists) {
status = 'unlive';
}
return {
...info,
status: listener.isWatching ? "active" : "inactive",
status,
id: storeDoc?.id,
title: storeDoc?.title,
tags: storeDoc?.tags,
@@ -171,6 +200,21 @@ export class ProjectManager implements ProjectManagerInterface {
}
}
async writeFile(filepath: string, content: string): Promise<boolean> {
try {
const buffer = Buffer.from(content, 'base64');
const dir = path.dirname(filepath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filepath, buffer);
return true;
} catch (error) {
console.error('写入文件失败:', error);
return false;
}
}
async deleteFile(filepath: string): Promise<boolean> {
if (!fileIsExist(filepath)) {
return false;

View File

@@ -1,8 +1,9 @@
import ParcelWatcher, { subscribe } from '@parcel/watcher';
import path from 'node:path';
import fs from 'node:fs';
import fs, { existsSync } from 'node:fs';
import { EventEmitter } from "eventemitter3";
import { normalizeIgnorePattern, defaultIgnorePatterns } from '../utils';
import { CustomError } from '@kevisual/router';
export class ProjectListener {
projectPath: string;
@@ -26,6 +27,10 @@ export class ProjectListener {
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
}
if (!existsSync(projectPath)) {
console.warn(`[ProjectListener] Project path does not exist: ${projectPath}`);
throw new CustomError(`Project path does not exist: ${projectPath}`);
}
const allIgnore = [...defaultIgnorePatterns, ...ignorePatterns];
const sub = await subscribe(
this.projectPath, // 监听路径(支持数组)

View File

@@ -5,6 +5,10 @@ import path from 'node:path';
const TEXT_FILE_EXTENSIONS = [
// 前端/脚本
'.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx', '.vue', '.svelte', '.astro',
// 文本网页
'.html', '.htm', '.xhtml', '.xml', '.svg', '.styl',
// 配置文件
'.json', '.yaml', '.yml', '.toml', '.ini', '.env', '.properties', '.conf', '.config',
// node 相关
'.npmrc', '.yarnrc', '.babelrc', '.eslintrc', '.prettierrc', '.stylelintrc',
// 样式
@@ -19,13 +23,23 @@ const TEXT_FILE_EXTENSIONS = [
'.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd', '.psm1',
// 其他
'.sql', '.graphql', '.gql', '.proto', '.dockerfile', '.gitignore', '.gitattributes', '.editorconfig', '.eslintrc', '.prettierrc', '.prettierignore', '.npmrc', '.yarnrc', '.babelrc', '.webpackrc',
// lock 文件
'.lock', '-lock.json', '-lock.yaml', '-lock.yml', '.lockb',
// 其他配置文件
'.pem', '.key', '.crt', '.cer', '.p12', '.pfx',
];
// 没有扩展名的配置文件
const CONFIG_FILE_NAMES = [
'.gitignore', '.gitattributes', '.dockerignore', '.editorconfig', '.env', '.env.local', '.env.development', '.env.production',
'.gitignore', '.gitattributes', '.dockerignore', '.editorconfig', '.env', '.env.local', '.env.development', '.env.production', '.env.test', '.env.staging', '.env.example', '.env.sample',
'Dockerfile', 'Makefile', 'CMakeLists.txt', 'Vagrantfile', 'Gemfile', 'Podfile', 'Cartfile',
'.bashrc', '.zshrc', '.vimrc', '.inputrc',
// lock 文件
'bun.lockb', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'pnpm-workspace.yaml',
'Gemfile.lock', 'Podfile.lock', 'Cartfile.resolved', 'composer.lock', 'requirements.txt',
'Pipfile.lock', 'poetry.lock', 'Cargo.lock', 'go.sum', 'go.mod',
// IDE 配置
'.vscode', '.idea',
];
export const isText = (filePath: string): boolean => {

View File

@@ -20,6 +20,7 @@ export type FileProjectData = {
* 文件对应相关信息
*
* 具体文件路径
* /workspace/projects/project-search/src/index.ts
*/
filepath: string;
content?: string;
@@ -27,6 +28,7 @@ export type FileProjectData = {
size: number;
/**
* 项目路径,文件所在的项目路径,方便搜索结果展示和过滤
* /workspace/projects/project-search
*/
projectPath: string;
repo: string;
@@ -51,9 +53,15 @@ function fileId(filePath: string): string {
return Buffer.from(filePath).toString('base64url');
}
/** 根据文件绝对路径生成 md5 hash */
/** 根据文件内容生成 md5 hash,用于检测文件变更 */
function fileHash(filePath: string): string {
try {
const content = fs.readFileSync(filePath);
return crypto.createHash('md5').update(content).digest('hex');
} catch {
// 文件不可读时回退为路径 hash
return crypto.createHash('md5').update(filePath).digest('hex');
}
}
export class ProjectSearch {
@@ -76,6 +84,7 @@ export class ProjectSearch {
async searchFiles(query: string = '', options?: {
projectPath?: string;
repo?: string;
filepath?: string;
title?: string;
tags?: string | string[];
summary?: string;
@@ -89,16 +98,21 @@ export class ProjectSearch {
getContent?: boolean;
}) {
const filter: string[] = [];
if (options?.projectPath) filter.push(`projectPath = "${options.projectPath}"`);
if (options?.projectPath) filter.push(`projectPath STARTS WITH "${options.projectPath}"`);
if (options?.repo) filter.push(`repo = "${options.repo}"`);
if (options?.title) filter.push(`title = "${options.title}"`);
if (options?.filepath) filter.push(`filepath STARTS WITH "${options.filepath}"`);
if (options?.link) filter.push(`link = "${options.link}"`);
// title、summary、description、tags 使用全文搜索(部分匹配)
const searchTerms: string[] = [];
if (options?.title) searchTerms.push(options.title);
if (options?.summary) searchTerms.push(options.summary);
if (options?.description) searchTerms.push(options.description);
if (options?.tags) {
const tags = Array.isArray(options.tags) ? options.tags : [options.tags];
tags.forEach(tag => filter.push(`tags = "${tag}"`));
tags.forEach(tag => searchTerms.push(tag));
}
if (options?.summary) filter.push(`summary = "${options.summary}"`);
if (options?.description) filter.push(`description = "${options.description}"`);
if (options?.link) filter.push(`link = "${options.link}"`);
// 合并到主搜索词
const fullQuery = [query, ...searchTerms].filter(Boolean).join(' ');
const limit = options?.limit ?? 1000;
const search = {
filter: filter.length ? filter.join(' AND ') : undefined,
@@ -110,7 +124,7 @@ export class ProjectSearch {
let allHits: FileProjectData[] = [];
let offset = 0;
while (true) {
const searchResults = await this.index.search(query, {
const searchResults = await this.index.search(fullQuery, {
...search,
limit: Math.min(limit - allHits.length, 1000),
offset,

View File

@@ -6,7 +6,7 @@ export type ProjectDoc = {
/**
* 文档 ID使用 path 的 base64url 编码
*/
id: string;
id?: string;
name?: string;
path: string;
/**
@@ -23,9 +23,9 @@ export type ProjectDoc = {
description?: string;
link?: string;
/**
* 项目状态active 活动中 | inactive 非活动
* 项目状态active 活动中 | inactive 非活动 | unlive 未初始化
*/
status?: 'active' | 'inactive';
status?: 'active' | 'inactive' | 'unlive';
};
function projectId(projectPath: string): string {
@@ -59,8 +59,8 @@ export class ProjectStore {
async init() {
await this.ensureIndex();
}
async addProject(input: { name?: string; path: string; repo: string; status?: 'active' | 'inactive' }): Promise<void> {
const id = projectId(input.path);
async addProject(input: Partial<ProjectDoc>): Promise<void> {
const id = projectId(input.path!);
// 先查询是否已存在
let existingDoc: ProjectDoc | null = null;
@@ -74,14 +74,14 @@ export class ProjectStore {
const doc: ProjectDoc = {
id,
name: input.name,
path: input.path,
repo: input.repo,
path: input.path!,
repo: input.repo!,
// 保留原有字段
title: existingDoc?.title,
tags: existingDoc?.tags,
summary: existingDoc?.summary,
description: existingDoc?.description,
link: existingDoc?.link,
title: input.title ?? existingDoc?.title,
tags: input.tags ?? existingDoc?.tags,
summary: input.summary ?? existingDoc?.summary,
description: input.description ?? existingDoc?.description,
link: input.link ?? existingDoc?.link,
status: input.status ?? existingDoc?.status ?? 'active',
};
@@ -109,7 +109,7 @@ export class ProjectStore {
}
}
async updateProject(projectPath: string, opts: { name?: string; repo?: string; title?: string; tags?: string[]; summary?: string; description?: string; link?: string; status?: 'active' | 'inactive' }): Promise<void> {
async updateProject(projectPath: string, opts: { name?: string; repo?: string; title?: string; tags?: string[]; summary?: string; description?: string; link?: string; status?: 'active' | 'inactive' | 'unlive' }): Promise<void> {
const partial: Record<string, any> = { id: projectId(projectPath) };
if (opts.name !== undefined) partial.name = opts.name;
if (opts.repo !== undefined) partial.repo = opts.repo;

View File

@@ -1,16 +1,25 @@
import { execSync } from 'node:child_process'
import path from 'node:path';
export const getGitPathname = (repoPath: string): string | null => {
export const getPathnameFromGitUrl = (url: string): { pathname: string, filename: string } => {
const _url = new URL(url.replace(/\.git$/, ''));
const _pathname = _url.pathname;
const pathname = _pathname.replace(/^\/+/, '').replace(/\/+$/, ''); // 去除开头和结尾的斜杠
const filename = path.basename(_pathname);
return { pathname, filename };
}
export const getGitPathname = (repoPath: string): { url: string, pathname: string, filename: string } | null => {
try {
const url = execSync('git config --get remote.origin.url', { cwd: repoPath, encoding: 'utf-8' }).trim();
if (url) {
const _url = new URL(url.replace(/\.git$/, ''));
const pathname = _url.pathname.replace(/^\/+/, '').replace(/\/+$/, ''); // 去除开头和结尾的斜杠
return pathname;
return {
url,
...getPathnameFromGitUrl(url),
}
}
return null;
} catch (err) {
console.warn(`[getGitPathname] Failed to get git remote url for ${repoPath}:`);
console.warn(`[getGitPathname] Failed to get git remote url for ${repoPath}:`, err);
return null;
}
};

View File

@@ -23,6 +23,39 @@ app.route({
}
}).addTo(app);
/**
* 更新文件内容base64 编码)
*/
app.route({
path: 'project-file',
key: 'update-content',
middleware: ['auth-admin'],
description: '将 base64 编码的内容写入指定文件路径,用于更新或创建文件',
metadata: {
args: {
filepath: z.string().nonempty().describe('要写入的文件绝对路径,必填'),
content: z.string().nonempty().describe('文件内容的 base64 编码,必填'),
}
}
}).define(async (ctx) => {
const { filepath, content } = ctx.query as {
filepath: string;
content: string;
};
if (!filepath) ctx.throw(400, 'filepath 不能为空');
if (!content) ctx.throw(400, 'content 不能为空');
try {
const success = await manager.writeFile(filepath, content);
if (!success) {
ctx.throw(500, '写入文件失败');
}
ctx.body = { success: true };
} catch (error) {
ctx.throw(500, '写入文件失败');
}
}).addTo(app);
/**
* 更新文件自定义信息title/tags/summary/description/link
*/

135
src/routes/project-init.ts Normal file
View File

@@ -0,0 +1,135 @@
import { z } from 'zod'
import { app, manager } from '../app.ts';
import { getGitPathname, getPathnameFromGitUrl } from '../project/util/git';
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
export const DEFAULT_ROOT_PATH = '/workspace/projects';
const getProjectPaths = (ctx: any): { rootPath: string; projectPaths: string[] } => {
const rootPath = path.resolve(ctx.query.rootPath || DEFAULT_ROOT_PATH);
// 1. 检测文件夹是否存在
if (!fs.existsSync(rootPath)) {
ctx.throw(400, 'rootPath 指定的目录不存在');
}
// 2. 搜索第一层子目录,找到包含 .git 的目录, 如果存在把这个子目录的路径和 git 远程地址注册到系统中
const projectPaths: string[] = [];
// 搜索第一层子目录中包含 .git 的目录
try {
const entries = fs.readdirSync(rootPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const fullPath = path.join(rootPath, entry.name);
// 跳过 node_modules 和隐藏目录
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
// 如果包含 .git 目录,则认为是 git 项目
if (fs.existsSync(path.join(fullPath, '.git'))) {
projectPaths.push(fullPath);
}
}
} catch (err) {
console.warn(`[project-init] Failed to read root directory ${rootPath}:`, err);
}
return {
rootPath,
projectPaths
};
}
app.route({
path: 'project',
key: 'init',
description: '初始化项目,通过root目录搜索当前有git的项目列表并将其注册到系统中',
middleware: ['auth-admin'],
metadata: {
args: {
rootPath: z.string().optional().describe('搜索项目的根目录绝对路径,默认为 /workspace/projects'),
}
}
}).define(async (ctx) => {
const { rootPath, projectPaths } = getProjectPaths(ctx);
// 3. 注册项目到系统中
const results: Array<{ path: string; repo: string | null; success: boolean; error?: string }> = [];
for (const projectPath of projectPaths) {
try {
const gitRepo = getGitPathname(projectPath);
const repo = gitRepo?.pathname || path.basename(projectPath);
const name = path.basename(projectPath);
await manager.addProject({ path: projectPath, repo, name, link: gitRepo?.url });
results.push({ path: projectPath, repo: repo, success: true });
console.log(`[project-init] Added project: ${projectPath}, repo: ${repo}`);
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
results.push({ path: projectPath, repo: null, success: false, error });
console.error(`[project-init] Failed to add project ${projectPath}:`, err);
}
}
ctx.body = {
rootPath,
totalFound: projectPaths.length,
results
};
}).addTo(app);
app.route({
path: 'project',
key: 'list-projects',
description: '列出当前工作区已注册的项目',
middleware: ['auth-admin'],
metadata: {
args: {
rootPath: z.string().optional().describe('项目根目录绝对路径,默认为 /workspace/projects指定后只列出该目录下的项目'),
}
}
}).define(async (ctx) => {
const { rootPath, projectPaths } = getProjectPaths(ctx);
ctx.body = {
rootPath,
list: projectPaths
};
}).addTo(app);
app.route({
path: 'project',
key: 'clone-cnb',
description: 'clone 一个项目到projects目录下并注册到系统中。',
middleware: ['auth-admin'],
metadata: {
args: {
filepath: z.string().optional().describe('新项目根目录的绝对路径,默认在 /workspace/projects 下以仓库名创建子目录'),
repo: z.string().describe('代码仓库标识,用于搜索结果展示和过滤,格式如 owner/repo例如 kevisual/cnb必填'),
}
}
}).define(async (ctx) => {
const { filepath = DEFAULT_ROOT_PATH, repo = '' } = ctx.query as { filepath: string; repo?: string; };
let cloneRepoUrl = ''
if (repo?.startsWith('http')) {
cloneRepoUrl = repo;
} else if (repo) {
cloneRepoUrl = `https://cnb.cool/${repo}`;
} else {
ctx.throw(400, 'repo 参数不能为空,且必须是完整的 URL 或者 owner/repo 格式');
}
const { filename: name } = getPathnameFromGitUrl(cloneRepoUrl);
if (!name) {
ctx.throw(400, '无法从 repo 参数解析出项目名称,请检查 repo 格式是否正确');
}
const targetPath = path.join(filepath, name);
if (fs.existsSync(targetPath)) {
ctx.throw(400, `目标路径已存在: ${targetPath}`);
}
const cloneCmd = `git clone ${cloneRepoUrl} ${targetPath}`;
try {
console.log(`[project-clone] Cloning repo ${cloneRepoUrl} to ${targetPath}...`);
execSync(cloneCmd, { stdio: 'inherit' });
console.log(`[project-clone] Clone successful, adding project...`);
const info = await manager.addProject({ path: targetPath, repo: name, name, link: cloneRepoUrl });
ctx.body = info;
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
console.error(`[project-clone] Failed to clone and add project from ${cloneRepoUrl}:`, err);
ctx.throw(500, `Failed to clone and add project: ${error}`);
}
}).addTo(app);

View File

@@ -17,22 +17,35 @@ app
filepath: z.string().describe('项目根目录的绝对路径,必填'),
repo: z.string().optional().describe('代码仓库标识,用于搜索结果展示和过滤,格式如 owner/repo例如 kevisual/cnb选填默认自动从 git 配置读取)'),
name: z.string().optional().describe('项目显示名称,用于搜索结果展示,选填(默认使用目录名)'),
type: z.string().optional().describe('项目类型,filepath或cnb-repo,默认为filepath'),
}
}
})
.define(async (ctx) => {
let { filepath, repo, name } = ctx.query as { filepath: string; repo?: string; name?: string };
let { filepath, repo, name, type = 'filepath' } = ctx.query as { filepath: string; repo?: string; name?: string; type?: string };
if (!filepath) ctx.throw(400, 'filepath 不能为空');
if (type === 'cnb-repo') {
const msg = {
path: 'project',
key: 'clone-cnb',
token: ctx.query.token,
}
const res = await app.run({ ...msg, payload: { repo: filepath } }, { state: ctx.state });
ctx.forward(res);
return
}
let link = '';
if (!repo) {
const gitPathname = getGitPathname(filepath);
if (gitPathname) {
repo = gitPathname;
repo = gitPathname.pathname;
link = gitPathname.url;
} else {
const pathParts = filepath.split('/');
repo = pathParts[pathParts.length - 1];
}
}
const info = await manager.addProject({ path: filepath, repo, name });
const info = await manager.addProject({ path: filepath, repo, name, link: link || (repo ? `https://cnb.cool/${repo}` : undefined) });
ctx.body = { success: true, data: info };
})
.addTo(app);

View File

@@ -13,7 +13,65 @@ app
metadata: {
args: {
q: z.string().optional().describe('搜索关键词,选填;留空或不传则返回全部文件'),
projectPath: z.string().optional().describe('按项目根目录路径过滤,仅返回该项目下的文件,选填'),
// projectPath: z.string().optional().describe('按项目根目录路径过滤,仅返回该项目下的文件,选填'),
filepath: z.string().optional().describe('按文件绝对路径过滤,选填'),
repo: z.string().optional().describe('按代码仓库标识过滤(如 owner/repo选填'),
title: z.string().optional().describe('按人工标注的标题字段过滤,选填'),
tags: z.array(z.string()).optional().describe('按人工标注的标签列表过滤,选填'),
summary: z.string().optional().describe('按人工标注的摘要字段过滤,选填'),
description: z.string().optional().describe('按人工标注的描述字段过滤,选填'),
link: z.string().optional().describe('按人工标注的外部链接字段过滤,选填'),
sort: z.array(z.string()).optional().describe('排序规则数组,格式为 ["字段:asc"] 或 ["字段:desc"],选填,当 q 为空时默认为 ["projectPath:asc"]'),
limit: z.number().optional().describe('返回结果数量上限,选填,当 q 为空时默认为 1000'),
getContent: z.boolean().optional().describe('是否返回文件内容,默认为 false如果为 true则在结果中包含 content 字段,内容以 base64 编码返回,适用于前端预览或下载场景'),
projects: z.array(z.string()).optional().describe('按项目名称列表过滤,选填,默认不穿,只过滤当前工作区的项目'),
}
}
})
.define(async (ctx) => {
type SearchQuery = { q?: string; projectPath?: string; filepath?: string; repo?: string; title?: string; tags?: string[]; summary?: string; description?: string; link?: string; sort?: string[]; limit?: number; getContent?: boolean; projects?: string[] }
let { q, projectPath, filepath, repo, title, tags, summary, description, link, sort, limit, getContent = false, projects } = ctx.query as SearchQuery;
if (!q) {
sort = sort ?? ['projectPath:asc'];
limit = limit ?? 1000;
}
let hits: any[] = [];
const getOnlyProjects = async (projectPaths: string[]) => {
if (projectPaths.length === 0) return [];
let searchPromises = projectPaths.map(projectPath => manager.projectSearch.searchFiles(q, { projectPath, filepath, repo, title, tags, summary, description, link, sort, limit, getContent }));
const results = await Promise.all(searchPromises);
return results.flat();
}
if (!projects || projects.length === 0) {
// 如果没有指定项目名称列表,则默认搜索当前工作区的所有项目
const projects = await manager.projectStore.listProjects();
const projectPaths = projects.filter(p => p.status === 'active').map(p => p.path);
hits = await getOnlyProjects(projectPaths);
} else {
// 如果指定了项目名称列表,则只搜索这些项目, 同时确保这些项目在当前工作区中是存在的
const _projects = await manager.projectStore.listProjects();
let _hasProjects = projects.filter(p => _projects.some(_p => _p.path === p));
hits = await getOnlyProjects(_hasProjects);
}
ctx.body = { list: hits };
})
.addTo(app);
/**
* 搜索文件
* query: { q, projectPath?, repo? }
*/
app
.route({
path: 'project-search',
key: 'search',
description: '在已索引的项目文件中执行全文搜索,支持按仓库、目录、标签等字段过滤,以及自定义排序和数量限制',
middleware: ['auth-admin'],
metadata: {
args: {
q: z.string().optional().describe('搜索关键词,选填;留空或不传则返回全部文件'),
projectPath: z.string().describe('按项目根目录路径过滤,仅返回该项目下的文件,必填'),
filepath: z.string().optional().describe('按文件绝对路径过滤,选填'),
repo: z.string().optional().describe('按代码仓库标识过滤(如 owner/repo选填'),
title: z.string().optional().describe('按人工标注的标题字段过滤,选填'),
tags: z.array(z.string()).optional().describe('按人工标注的标签列表过滤,选填'),
@@ -27,13 +85,24 @@ app
}
})
.define(async (ctx) => {
let { q, projectPath, repo, title, tags, summary, description, link, sort, limit, getContent = false } = ctx.query as { q?: string; projectPath?: string; repo?: string; title?: string; tags?: string[]; summary?: string; description?: string; link?: string; sort?: string[]; limit?: number; getContent?: boolean };
type SearchQuery = { q?: string; projectPath?: string; filepath?: string; repo?: string; title?: string; tags?: string[]; summary?: string; description?: string; link?: string; sort?: string[]; limit?: number; getContent?: boolean; projects?: string[] }
let { q, projectPath, filepath, repo, title, tags, summary, description, link, sort, limit, getContent = false, projects } = ctx.query as SearchQuery;
if (!q) {
sort = sort ?? ['projectPath:asc'];
limit = limit ?? 1000;
}
const projectSearch = manager.projectSearch;
const hits = await projectSearch.searchFiles(q, { projectPath, repo, title, tags, summary, description, link, sort, limit, getContent });
if (!projectPath) {
ctx.throw(400, 'projectPath 参数不能为空');
return;
}
let hits: any[] = [];
const getOnlyProjects = async (projectPaths: string[]) => {
if (projectPaths.length === 0) return [];
let searchPromises = projectPaths.map(projectPath => manager.projectSearch.searchFiles(q, { projectPath, filepath, repo, title, tags, summary, description, link, sort, limit, getContent }));
const results = await Promise.all(searchPromises);
return results.flat();
}
hits = await getOnlyProjects([projectPath!]);
ctx.body = { list: hits };
})
.addTo(app);