@jackwener/opencli 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +2 -6
- package/.github/workflows/ci.yml +21 -1
- package/README.md +35 -6
- package/README.zh-CN.md +12 -5
- package/SKILL.md +2 -0
- package/dist/browser/cdp.d.ts +2 -1
- package/dist/browser/discover.d.ts +4 -1
- package/dist/browser/discover.js +6 -2
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +4 -12
- package/dist/browser/mcp.d.ts +2 -1
- package/dist/build-manifest.d.ts +2 -0
- package/dist/build-manifest.js +39 -14
- package/dist/build-manifest.test.js +21 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +2 -1
- package/dist/cli-manifest.json +1111 -112
- package/dist/cli.js +34 -3
- package/dist/clis/36kr/article.d.ts +1 -0
- package/dist/clis/36kr/article.js +62 -0
- package/dist/clis/36kr/hot.d.ts +3 -0
- package/dist/clis/36kr/hot.js +80 -0
- package/dist/clis/36kr/hot.test.d.ts +1 -0
- package/dist/clis/36kr/hot.test.js +15 -0
- package/dist/clis/36kr/news.d.ts +1 -0
- package/dist/clis/36kr/news.js +51 -0
- package/dist/clis/36kr/news.test.d.ts +1 -0
- package/dist/clis/36kr/news.test.js +85 -0
- package/dist/clis/36kr/search.d.ts +1 -0
- package/dist/clis/36kr/search.js +72 -0
- package/dist/clis/bilibili/comments.d.ts +5 -0
- package/dist/clis/bilibili/comments.js +40 -0
- package/dist/clis/bilibili/comments.test.d.ts +1 -0
- package/dist/clis/bilibili/comments.test.js +82 -0
- package/dist/clis/chatgpt/ask.js +29 -14
- package/dist/clis/chatgpt/ax.d.ts +6 -0
- package/dist/clis/chatgpt/ax.js +172 -1
- package/dist/clis/chatgpt/model.d.ts +1 -0
- package/dist/clis/chatgpt/model.js +24 -0
- package/dist/clis/chatgpt/send.js +12 -3
- package/dist/clis/douban/download.d.ts +1 -0
- package/dist/clis/douban/download.js +67 -0
- package/dist/clis/douban/download.test.d.ts +1 -0
- package/dist/clis/douban/download.test.js +170 -0
- package/dist/clis/douban/photos.d.ts +1 -0
- package/dist/clis/douban/photos.js +34 -0
- package/dist/clis/douban/utils.d.ts +25 -0
- package/dist/clis/douban/utils.js +190 -1
- package/dist/clis/douban/utils.test.d.ts +1 -0
- package/dist/clis/douban/utils.test.js +64 -0
- package/dist/clis/imdb/person.d.ts +1 -0
- package/dist/clis/imdb/person.js +203 -0
- package/dist/clis/imdb/reviews.d.ts +1 -0
- package/dist/clis/imdb/reviews.js +88 -0
- package/dist/clis/imdb/search.d.ts +1 -0
- package/dist/clis/imdb/search.js +161 -0
- package/dist/clis/imdb/title.d.ts +1 -0
- package/dist/clis/imdb/title.js +93 -0
- package/dist/clis/imdb/top.d.ts +1 -0
- package/dist/clis/imdb/top.js +53 -0
- package/dist/clis/imdb/trending.d.ts +1 -0
- package/dist/clis/imdb/trending.js +52 -0
- package/dist/clis/imdb/utils.d.ts +46 -0
- package/dist/clis/imdb/utils.js +285 -0
- package/dist/clis/imdb/utils.test.d.ts +1 -0
- package/dist/clis/imdb/utils.test.js +88 -0
- package/dist/clis/jd/item.d.ts +4 -0
- package/dist/clis/jd/item.js +16 -15
- package/dist/clis/jd/item.test.js +16 -1
- package/dist/clis/linux-do/categories.yaml +38 -9
- package/dist/clis/linux-do/category.d.ts +1 -0
- package/dist/clis/linux-do/category.js +36 -0
- package/dist/clis/linux-do/feed.d.ts +45 -0
- package/dist/clis/linux-do/feed.js +397 -0
- package/dist/clis/linux-do/feed.test.d.ts +1 -0
- package/dist/clis/linux-do/feed.test.js +118 -0
- package/dist/clis/linux-do/hot.d.ts +1 -0
- package/dist/clis/linux-do/hot.js +25 -0
- package/dist/clis/linux-do/latest.d.ts +1 -0
- package/dist/clis/linux-do/latest.js +18 -0
- package/dist/clis/linux-do/tags.yaml +41 -0
- package/dist/clis/linux-do/topic.yaml +41 -3
- package/dist/clis/linux-do/user-posts.yaml +67 -0
- package/dist/clis/linux-do/user-topics.yaml +54 -0
- package/dist/clis/paperreview/commands.test.d.ts +3 -0
- package/dist/clis/paperreview/commands.test.js +243 -0
- package/dist/clis/paperreview/feedback.d.ts +1 -0
- package/dist/clis/paperreview/feedback.js +52 -0
- package/dist/clis/paperreview/review.d.ts +1 -0
- package/dist/clis/paperreview/review.js +37 -0
- package/dist/clis/paperreview/submit.d.ts +1 -0
- package/dist/clis/paperreview/submit.js +85 -0
- package/dist/clis/paperreview/utils.d.ts +46 -0
- package/dist/clis/paperreview/utils.js +197 -0
- package/dist/clis/paperreview/utils.test.d.ts +1 -0
- package/dist/clis/paperreview/utils.test.js +49 -0
- package/dist/clis/producthunt/browse.d.ts +1 -0
- package/dist/clis/producthunt/browse.js +99 -0
- package/dist/clis/producthunt/hot.d.ts +1 -0
- package/dist/clis/producthunt/hot.js +110 -0
- package/dist/clis/producthunt/posts.d.ts +1 -0
- package/dist/clis/producthunt/posts.js +28 -0
- package/dist/clis/producthunt/today.d.ts +1 -0
- package/dist/clis/producthunt/today.js +35 -0
- package/dist/clis/producthunt/utils.d.ts +29 -0
- package/dist/clis/producthunt/utils.js +99 -0
- package/dist/clis/producthunt/utils.test.d.ts +1 -0
- package/dist/clis/producthunt/utils.test.js +64 -0
- package/dist/clis/twitter/article.js +4 -28
- package/dist/clis/twitter/likes.d.ts +24 -0
- package/dist/clis/twitter/likes.js +217 -0
- package/dist/clis/twitter/likes.test.d.ts +1 -0
- package/dist/clis/twitter/likes.test.js +85 -0
- package/dist/clis/twitter/profile.js +4 -28
- package/dist/clis/twitter/search.js +2 -1
- package/dist/clis/twitter/search.test.js +2 -0
- package/dist/clis/twitter/shared.d.ts +6 -0
- package/dist/clis/twitter/shared.js +35 -0
- package/dist/clis/twitter/timeline.js +2 -13
- package/dist/clis/weixin/download.d.ts +17 -0
- package/dist/clis/weixin/download.js +88 -20
- package/dist/clis/weread/book.js +2 -2
- package/dist/clis/weread/commands.test.d.ts +3 -0
- package/dist/clis/weread/commands.test.js +43 -0
- package/dist/clis/weread/highlights.js +2 -2
- package/dist/clis/weread/notebooks.js +2 -2
- package/dist/clis/weread/notes.js +3 -3
- package/dist/clis/weread/shelf.js +2 -2
- package/dist/clis/weread/utils.d.ts +4 -4
- package/dist/clis/weread/utils.js +32 -14
- package/dist/clis/weread/utils.test.js +1 -28
- package/dist/clis/xiaohongshu/comments.d.ts +5 -0
- package/dist/clis/xiaohongshu/comments.js +74 -0
- package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/comments.test.js +79 -0
- package/dist/clis/xiaohongshu/publish.js +114 -18
- package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +119 -0
- package/dist/commanderAdapter.d.ts +1 -0
- package/dist/commanderAdapter.js +176 -29
- package/dist/commanderAdapter.test.d.ts +1 -0
- package/dist/commanderAdapter.test.js +62 -0
- package/dist/daemon.js +17 -1
- package/dist/discovery.js +8 -14
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +9 -2
- package/dist/download/index.js +63 -51
- package/dist/download/index.test.js +17 -4
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +15 -32
- package/dist/execution.d.ts +1 -3
- package/dist/execution.js +21 -1
- package/dist/hooks.js +2 -0
- package/dist/main.js +5 -0
- package/dist/output.js +5 -1
- package/dist/pipeline/executor.js +3 -4
- package/dist/plugin-manifest.d.ts +70 -0
- package/dist/plugin-manifest.js +160 -0
- package/dist/plugin-manifest.test.d.ts +4 -0
- package/dist/plugin-manifest.test.js +179 -0
- package/dist/plugin.d.ts +38 -5
- package/dist/plugin.js +267 -33
- package/dist/plugin.test.js +220 -3
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +2 -0
- package/dist/runtime-detect.d.ts +21 -0
- package/dist/runtime-detect.js +32 -0
- package/dist/runtime-detect.test.d.ts +1 -0
- package/dist/runtime-detect.test.js +27 -0
- package/dist/runtime.js +1 -1
- package/dist/serialization.d.ts +2 -0
- package/dist/serialization.js +6 -0
- package/dist/types.d.ts +1 -0
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +112 -0
- package/dist/weixin-download.test.d.ts +1 -0
- package/dist/weixin-download.test.js +30 -0
- package/dist/weread-private-api-regression.test.d.ts +1 -0
- package/dist/weread-private-api-regression.test.js +122 -0
- package/dist/yaml-schema.d.ts +3 -0
- package/dist/yaml-schema.js +18 -1
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/36kr.md +47 -0
- package/docs/adapters/browser/douban.md +14 -0
- package/docs/adapters/browser/imdb.md +47 -0
- package/docs/adapters/browser/jd.md +2 -2
- package/docs/adapters/browser/linux-do.md +181 -20
- package/docs/adapters/browser/paperreview.md +43 -0
- package/docs/adapters/browser/producthunt.md +49 -0
- package/docs/adapters/desktop/chatgpt.md +5 -0
- package/docs/adapters/index.md +6 -2
- package/docs/advanced/download.md +4 -0
- package/docs/advanced/rate-limiter-plugin.md +99 -0
- package/docs/guide/electron-app-cli.md +200 -0
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +87 -0
- package/docs/zh/guide/electron-app-cli.md +188 -0
- package/docs/zh/guide/getting-started.md +1 -0
- package/docs/zh/guide/plugins.md +65 -0
- package/extension/package.json +1 -0
- package/extension/scripts/package-release.mjs +179 -0
- package/extension/src/background.ts +2 -0
- package/package.json +4 -1
- package/scripts/postinstall.js +10 -0
- package/src/browser/cdp.ts +2 -1
- package/src/browser/discover.ts +8 -3
- package/src/browser/errors.ts +13 -14
- package/src/browser/mcp.ts +2 -1
- package/src/build-manifest.test.ts +23 -0
- package/src/build-manifest.ts +40 -15
- package/src/capabilityRouting.ts +2 -1
- package/src/cli.ts +35 -3
- package/src/clis/36kr/article.ts +69 -0
- package/src/clis/36kr/hot.test.ts +19 -0
- package/src/clis/36kr/hot.ts +100 -0
- package/src/clis/36kr/news.test.ts +90 -0
- package/src/clis/36kr/news.ts +54 -0
- package/src/clis/36kr/search.ts +78 -0
- package/src/clis/bilibili/comments.test.ts +102 -0
- package/src/clis/bilibili/comments.ts +44 -0
- package/src/clis/chatgpt/ask.ts +28 -14
- package/src/clis/chatgpt/ax.ts +180 -1
- package/src/clis/chatgpt/model.ts +27 -0
- package/src/clis/chatgpt/send.ts +16 -6
- package/src/clis/douban/download.test.ts +196 -0
- package/src/clis/douban/download.ts +78 -0
- package/src/clis/douban/photos.ts +36 -0
- package/src/clis/douban/utils.test.ts +97 -0
- package/src/clis/douban/utils.ts +232 -1
- package/src/clis/imdb/person.ts +232 -0
- package/src/clis/imdb/reviews.ts +111 -0
- package/src/clis/imdb/search.ts +179 -0
- package/src/clis/imdb/title.ts +121 -0
- package/src/clis/imdb/top.ts +67 -0
- package/src/clis/imdb/trending.ts +66 -0
- package/src/clis/imdb/utils.test.ts +117 -0
- package/src/clis/imdb/utils.ts +305 -0
- package/src/clis/jd/item.test.ts +18 -1
- package/src/clis/jd/item.ts +18 -15
- package/src/clis/linux-do/categories.yaml +38 -9
- package/src/clis/linux-do/category.ts +37 -0
- package/src/clis/linux-do/feed.test.ts +132 -0
- package/src/clis/linux-do/feed.ts +501 -0
- package/src/clis/linux-do/hot.ts +26 -0
- package/src/clis/linux-do/latest.ts +19 -0
- package/src/clis/linux-do/tags.yaml +41 -0
- package/src/clis/linux-do/topic.yaml +41 -3
- package/src/clis/linux-do/user-posts.yaml +67 -0
- package/src/clis/linux-do/user-topics.yaml +54 -0
- package/src/clis/paperreview/commands.test.ts +283 -0
- package/src/clis/paperreview/feedback.ts +64 -0
- package/src/clis/paperreview/review.ts +47 -0
- package/src/clis/paperreview/submit.ts +119 -0
- package/src/clis/paperreview/utils.test.ts +68 -0
- package/src/clis/paperreview/utils.ts +276 -0
- package/src/clis/producthunt/browse.ts +109 -0
- package/src/clis/producthunt/hot.ts +127 -0
- package/src/clis/producthunt/posts.ts +29 -0
- package/src/clis/producthunt/today.ts +37 -0
- package/src/clis/producthunt/utils.test.ts +72 -0
- package/src/clis/producthunt/utils.ts +122 -0
- package/src/clis/twitter/article.ts +5 -28
- package/src/clis/twitter/likes.test.ts +91 -0
- package/src/clis/twitter/likes.ts +256 -0
- package/src/clis/twitter/profile.ts +5 -28
- package/src/clis/twitter/search.test.ts +2 -0
- package/src/clis/twitter/search.ts +3 -1
- package/src/clis/twitter/shared.ts +45 -0
- package/src/clis/twitter/timeline.ts +2 -13
- package/src/clis/weixin/download.ts +114 -20
- package/src/clis/weread/book.ts +2 -2
- package/src/clis/weread/commands.test.ts +57 -0
- package/src/clis/weread/highlights.ts +2 -2
- package/src/clis/weread/notebooks.ts +2 -2
- package/src/clis/weread/notes.ts +3 -3
- package/src/clis/weread/shelf.ts +2 -2
- package/src/clis/weread/utils.test.ts +1 -32
- package/src/clis/weread/utils.ts +41 -16
- package/src/clis/xiaohongshu/comments.test.ts +96 -0
- package/src/clis/xiaohongshu/comments.ts +81 -0
- package/src/clis/xiaohongshu/publish.test.ts +137 -0
- package/src/clis/xiaohongshu/publish.ts +129 -18
- package/src/commanderAdapter.test.ts +78 -0
- package/src/commanderAdapter.ts +188 -24
- package/src/daemon.ts +19 -1
- package/src/discovery.ts +8 -15
- package/src/doctor.ts +13 -2
- package/src/download/index.test.ts +14 -4
- package/src/download/index.ts +67 -55
- package/src/errors.ts +25 -66
- package/src/execution.ts +28 -3
- package/src/hooks.ts +1 -0
- package/src/main.ts +6 -0
- package/src/output.ts +3 -1
- package/src/pipeline/executor.ts +4 -6
- package/src/plugin-manifest.test.ts +223 -0
- package/src/plugin-manifest.ts +206 -0
- package/src/plugin.test.ts +246 -2
- package/src/plugin.ts +338 -36
- package/src/registry.ts +6 -1
- package/src/runtime-detect.test.ts +30 -0
- package/src/runtime-detect.ts +36 -0
- package/src/runtime.ts +1 -1
- package/src/serialization.ts +4 -0
- package/src/types.ts +1 -0
- package/src/update-check.ts +114 -0
- package/src/weixin-download.test.ts +64 -0
- package/src/weread-private-api-regression.test.ts +150 -0
- package/src/yaml-schema.ts +20 -0
- package/tests/e2e/browser-auth.test.ts +13 -9
- package/tests/e2e/browser-public-extended.test.ts +1 -1
- package/tests/e2e/browser-public.test.ts +62 -4
- package/tests/e2e/helpers.ts +2 -1
- package/tests/e2e/public-commands.test.ts +37 -3
- package/tests/smoke/api-health.test.ts +1 -1
- package/vitest.config.ts +10 -0
- package/dist/clis/linux-do/category.yaml +0 -51
- package/dist/clis/linux-do/hot.yaml +0 -50
- package/dist/clis/linux-do/latest.yaml +0 -40
- package/src/clis/linux-do/category.yaml +0 -51
- package/src/clis/linux-do/hot.yaml +0 -50
- package/src/clis/linux-do/latest.yaml +0 -40
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin manifest: reads and validates opencli-plugin.json files.
|
|
3
|
+
*
|
|
4
|
+
* Supports two modes:
|
|
5
|
+
* 1. Single plugin: repo root IS the plugin directory.
|
|
6
|
+
* 2. Monorepo: repo contains multiple plugins declared in `plugins` field.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { PKG_VERSION } from './version.js';
|
|
11
|
+
export const MANIFEST_FILENAME = 'opencli-plugin.json';
|
|
12
|
+
// ── Read / Validate ─────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Read and parse opencli-plugin.json from a directory.
|
|
15
|
+
* Returns null if the file does not exist or is unparseable.
|
|
16
|
+
*/
|
|
17
|
+
export function readPluginManifest(dir) {
|
|
18
|
+
const manifestPath = path.join(dir, MANIFEST_FILENAME);
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Returns true when the manifest declares a monorepo (has `plugins` field). */
|
|
32
|
+
export function isMonorepo(manifest) {
|
|
33
|
+
return (manifest.plugins !== undefined &&
|
|
34
|
+
manifest.plugins !== null &&
|
|
35
|
+
typeof manifest.plugins === 'object' &&
|
|
36
|
+
Object.keys(manifest.plugins).length > 0);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the list of enabled sub-plugins from a monorepo manifest.
|
|
40
|
+
* Returns entries sorted by key name.
|
|
41
|
+
*/
|
|
42
|
+
export function getEnabledPlugins(manifest) {
|
|
43
|
+
if (!manifest.plugins)
|
|
44
|
+
return [];
|
|
45
|
+
return Object.entries(manifest.plugins)
|
|
46
|
+
.filter(([, entry]) => !entry.disabled)
|
|
47
|
+
.map(([name, entry]) => ({ name, entry }))
|
|
48
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
49
|
+
}
|
|
50
|
+
// ── Version compatibility ───────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Check if the current opencli version satisfies a semver range string.
|
|
53
|
+
*
|
|
54
|
+
* Supports a simplified subset of semver ranges:
|
|
55
|
+
* ">=1.0.0" – greater than or equal
|
|
56
|
+
* "<=1.5.0" – less than or equal
|
|
57
|
+
* ">1.0.0" – strictly greater
|
|
58
|
+
* "<2.0.0" – strictly less
|
|
59
|
+
* "^1.2.0" – compatible (>=1.2.0 and <2.0.0)
|
|
60
|
+
* "~1.2.0" – patch-level (>=1.2.0 and <1.3.0)
|
|
61
|
+
* "1.2.0" – exact match
|
|
62
|
+
* ">=1.0.0 <2.0.0" – multiple constraints (space-separated, all must match)
|
|
63
|
+
*
|
|
64
|
+
* Returns true if compatible, false if not, and true for empty/undefined
|
|
65
|
+
* ranges (no constraint = always compatible).
|
|
66
|
+
*/
|
|
67
|
+
export function checkCompatibility(range) {
|
|
68
|
+
if (!range || range.trim() === '')
|
|
69
|
+
return true;
|
|
70
|
+
return satisfiesRange(PKG_VERSION, range);
|
|
71
|
+
}
|
|
72
|
+
/** Parse a version string ("1.2.3") into [major, minor, patch]. */
|
|
73
|
+
export function parseVersion(version) {
|
|
74
|
+
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
75
|
+
if (!match)
|
|
76
|
+
return null;
|
|
77
|
+
return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
|
|
78
|
+
}
|
|
79
|
+
/** Compare two version tuples: -1 if a<b, 0 if equal, 1 if a>b. */
|
|
80
|
+
function compareVersions(a, b) {
|
|
81
|
+
for (let i = 0; i < 3; i++) {
|
|
82
|
+
if (a[i] < b[i])
|
|
83
|
+
return -1;
|
|
84
|
+
if (a[i] > b[i])
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
/** Check if a version satisfies a single constraint like ">=1.2.0". */
|
|
90
|
+
function satisfiesSingleConstraint(version, constraint) {
|
|
91
|
+
const trimmed = constraint.trim();
|
|
92
|
+
if (!trimmed)
|
|
93
|
+
return true;
|
|
94
|
+
// ^1.2.0 → >=1.2.0 <2.0.0
|
|
95
|
+
if (trimmed.startsWith('^')) {
|
|
96
|
+
const target = parseVersion(trimmed.slice(1));
|
|
97
|
+
if (!target)
|
|
98
|
+
return true;
|
|
99
|
+
const upper = [target[0] + 1, 0, 0];
|
|
100
|
+
return compareVersions(version, target) >= 0 && compareVersions(version, upper) < 0;
|
|
101
|
+
}
|
|
102
|
+
// ~1.2.0 → >=1.2.0 <1.3.0
|
|
103
|
+
if (trimmed.startsWith('~')) {
|
|
104
|
+
const target = parseVersion(trimmed.slice(1));
|
|
105
|
+
if (!target)
|
|
106
|
+
return true;
|
|
107
|
+
const upper = [target[0], target[1] + 1, 0];
|
|
108
|
+
return compareVersions(version, target) >= 0 && compareVersions(version, upper) < 0;
|
|
109
|
+
}
|
|
110
|
+
// >=, <=, >, <, =
|
|
111
|
+
if (trimmed.startsWith('>=')) {
|
|
112
|
+
const target = parseVersion(trimmed.slice(2));
|
|
113
|
+
if (!target)
|
|
114
|
+
return true;
|
|
115
|
+
return compareVersions(version, target) >= 0;
|
|
116
|
+
}
|
|
117
|
+
if (trimmed.startsWith('<=')) {
|
|
118
|
+
const target = parseVersion(trimmed.slice(2));
|
|
119
|
+
if (!target)
|
|
120
|
+
return true;
|
|
121
|
+
return compareVersions(version, target) <= 0;
|
|
122
|
+
}
|
|
123
|
+
if (trimmed.startsWith('>')) {
|
|
124
|
+
const target = parseVersion(trimmed.slice(1));
|
|
125
|
+
if (!target)
|
|
126
|
+
return true;
|
|
127
|
+
return compareVersions(version, target) > 0;
|
|
128
|
+
}
|
|
129
|
+
if (trimmed.startsWith('<')) {
|
|
130
|
+
const target = parseVersion(trimmed.slice(1));
|
|
131
|
+
if (!target)
|
|
132
|
+
return true;
|
|
133
|
+
return compareVersions(version, target) < 0;
|
|
134
|
+
}
|
|
135
|
+
if (trimmed.startsWith('=')) {
|
|
136
|
+
const target = parseVersion(trimmed.slice(1));
|
|
137
|
+
if (!target)
|
|
138
|
+
return true;
|
|
139
|
+
return compareVersions(version, target) === 0;
|
|
140
|
+
}
|
|
141
|
+
// Exact match
|
|
142
|
+
const target = parseVersion(trimmed);
|
|
143
|
+
if (!target)
|
|
144
|
+
return true;
|
|
145
|
+
return compareVersions(version, target) === 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if a version string satisfies a range expression.
|
|
149
|
+
* Space-separated constraints are ANDed together.
|
|
150
|
+
*/
|
|
151
|
+
export function satisfiesRange(versionStr, range) {
|
|
152
|
+
const version = parseVersion(versionStr);
|
|
153
|
+
if (!version)
|
|
154
|
+
return true; // Can't parse our own version → assume ok
|
|
155
|
+
// Split on whitespace for multi-constraint ranges (e.g. ">=1.0.0 <2.0.0")
|
|
156
|
+
const constraints = range.trim().split(/\s+/);
|
|
157
|
+
return constraints.every((c) => satisfiesSingleConstraint(version, c));
|
|
158
|
+
}
|
|
159
|
+
// ── Exports for testing ─────────────────────────────────────────────────────
|
|
160
|
+
export { readPluginManifest as _readPluginManifest, isMonorepo as _isMonorepo, getEnabledPlugins as _getEnabledPlugins, checkCompatibility as _checkCompatibility, parseVersion as _parseVersion, satisfiesRange as _satisfiesRange, };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for plugin manifest: reading, validating, and compatibility checks.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import { _readPluginManifest as readPluginManifest, _isMonorepo as isMonorepo, _getEnabledPlugins as getEnabledPlugins, _parseVersion as parseVersion, _satisfiesRange as satisfiesRange, MANIFEST_FILENAME, } from './plugin-manifest.js';
|
|
9
|
+
// ── readPluginManifest ──────────────────────────────────────────────────────
|
|
10
|
+
describe('readPluginManifest', () => {
|
|
11
|
+
let tmpDir;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-test-'));
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
it('returns null when no manifest file exists', () => {
|
|
19
|
+
expect(readPluginManifest(tmpDir)).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
it('returns null for malformed JSON', () => {
|
|
22
|
+
fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), 'not json {{{');
|
|
23
|
+
expect(readPluginManifest(tmpDir)).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it('returns null for non-object JSON (array)', () => {
|
|
26
|
+
fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), '["a","b"]');
|
|
27
|
+
expect(readPluginManifest(tmpDir)).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('returns null for non-object JSON (string)', () => {
|
|
30
|
+
fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), '"hello"');
|
|
31
|
+
expect(readPluginManifest(tmpDir)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
it('reads a single-plugin manifest', () => {
|
|
34
|
+
const manifest = {
|
|
35
|
+
name: 'polymarket',
|
|
36
|
+
version: '1.2.0',
|
|
37
|
+
opencli: '>=1.0.0',
|
|
38
|
+
description: 'Prediction market analysis',
|
|
39
|
+
};
|
|
40
|
+
fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), JSON.stringify(manifest));
|
|
41
|
+
const result = readPluginManifest(tmpDir);
|
|
42
|
+
expect(result).toEqual(manifest);
|
|
43
|
+
});
|
|
44
|
+
it('reads a monorepo manifest', () => {
|
|
45
|
+
const manifest = {
|
|
46
|
+
version: '1.0.0',
|
|
47
|
+
opencli: '>=0.9.0',
|
|
48
|
+
description: 'My plugin collection',
|
|
49
|
+
plugins: {
|
|
50
|
+
polymarket: {
|
|
51
|
+
path: 'packages/polymarket',
|
|
52
|
+
description: 'Prediction market',
|
|
53
|
+
version: '1.2.0',
|
|
54
|
+
},
|
|
55
|
+
defi: {
|
|
56
|
+
path: 'packages/defi',
|
|
57
|
+
description: 'DeFi data',
|
|
58
|
+
version: '0.8.0',
|
|
59
|
+
disabled: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
fs.writeFileSync(path.join(tmpDir, MANIFEST_FILENAME), JSON.stringify(manifest));
|
|
64
|
+
const result = readPluginManifest(tmpDir);
|
|
65
|
+
expect(result).toEqual(manifest);
|
|
66
|
+
expect(result.plugins.polymarket.path).toBe('packages/polymarket');
|
|
67
|
+
expect(result.plugins.defi.disabled).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
// ── isMonorepo ──────────────────────────────────────────────────────────────
|
|
71
|
+
describe('isMonorepo', () => {
|
|
72
|
+
it('returns false for single-plugin manifest', () => {
|
|
73
|
+
expect(isMonorepo({ name: 'test', version: '1.0.0' })).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('returns false for empty plugins object', () => {
|
|
76
|
+
expect(isMonorepo({ plugins: {} })).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it('returns true for manifest with plugins', () => {
|
|
79
|
+
expect(isMonorepo({
|
|
80
|
+
plugins: {
|
|
81
|
+
foo: { path: 'packages/foo' },
|
|
82
|
+
},
|
|
83
|
+
})).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// ── getEnabledPlugins ───────────────────────────────────────────────────────
|
|
87
|
+
describe('getEnabledPlugins', () => {
|
|
88
|
+
it('returns empty array for no plugins', () => {
|
|
89
|
+
expect(getEnabledPlugins({ name: 'test' })).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
it('filters out disabled plugins', () => {
|
|
92
|
+
const manifest = {
|
|
93
|
+
plugins: {
|
|
94
|
+
foo: { path: 'packages/foo' },
|
|
95
|
+
bar: { path: 'packages/bar', disabled: true },
|
|
96
|
+
baz: { path: 'packages/baz' },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
const result = getEnabledPlugins(manifest);
|
|
100
|
+
expect(result).toHaveLength(2);
|
|
101
|
+
expect(result.map((r) => r.name)).toEqual(['baz', 'foo']); // sorted
|
|
102
|
+
});
|
|
103
|
+
it('returns all when none disabled', () => {
|
|
104
|
+
const manifest = {
|
|
105
|
+
plugins: {
|
|
106
|
+
charlie: { path: 'packages/charlie' },
|
|
107
|
+
alpha: { path: 'packages/alpha' },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
const result = getEnabledPlugins(manifest);
|
|
111
|
+
expect(result).toHaveLength(2);
|
|
112
|
+
expect(result[0].name).toBe('alpha');
|
|
113
|
+
expect(result[1].name).toBe('charlie');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
// ── parseVersion ────────────────────────────────────────────────────────────
|
|
117
|
+
describe('parseVersion', () => {
|
|
118
|
+
it('parses standard versions', () => {
|
|
119
|
+
expect(parseVersion('1.2.3')).toEqual([1, 2, 3]);
|
|
120
|
+
expect(parseVersion('0.0.0')).toEqual([0, 0, 0]);
|
|
121
|
+
expect(parseVersion('10.20.30')).toEqual([10, 20, 30]);
|
|
122
|
+
});
|
|
123
|
+
it('parses versions with prerelease suffix', () => {
|
|
124
|
+
expect(parseVersion('1.2.3-beta.1')).toEqual([1, 2, 3]);
|
|
125
|
+
});
|
|
126
|
+
it('returns null for invalid versions', () => {
|
|
127
|
+
expect(parseVersion('abc')).toBeNull();
|
|
128
|
+
expect(parseVersion('')).toBeNull();
|
|
129
|
+
expect(parseVersion('1.2')).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
// ── satisfiesRange ──────────────────────────────────────────────────────────
|
|
133
|
+
describe('satisfiesRange', () => {
|
|
134
|
+
it('handles >= constraint', () => {
|
|
135
|
+
expect(satisfiesRange('1.4.1', '>=1.0.0')).toBe(true);
|
|
136
|
+
expect(satisfiesRange('1.0.0', '>=1.0.0')).toBe(true);
|
|
137
|
+
expect(satisfiesRange('0.9.9', '>=1.0.0')).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
it('handles <= constraint', () => {
|
|
140
|
+
expect(satisfiesRange('1.0.0', '<=1.0.0')).toBe(true);
|
|
141
|
+
expect(satisfiesRange('0.9.0', '<=1.0.0')).toBe(true);
|
|
142
|
+
expect(satisfiesRange('1.0.1', '<=1.0.0')).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
it('handles > constraint', () => {
|
|
145
|
+
expect(satisfiesRange('1.0.1', '>1.0.0')).toBe(true);
|
|
146
|
+
expect(satisfiesRange('1.0.0', '>1.0.0')).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
it('handles < constraint', () => {
|
|
149
|
+
expect(satisfiesRange('0.9.9', '<1.0.0')).toBe(true);
|
|
150
|
+
expect(satisfiesRange('1.0.0', '<1.0.0')).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
it('handles ^ (caret) constraint', () => {
|
|
153
|
+
expect(satisfiesRange('1.2.0', '^1.2.0')).toBe(true);
|
|
154
|
+
expect(satisfiesRange('1.9.9', '^1.2.0')).toBe(true);
|
|
155
|
+
expect(satisfiesRange('2.0.0', '^1.2.0')).toBe(false);
|
|
156
|
+
expect(satisfiesRange('1.1.0', '^1.2.0')).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
it('handles ~ (tilde) constraint', () => {
|
|
159
|
+
expect(satisfiesRange('1.2.0', '~1.2.0')).toBe(true);
|
|
160
|
+
expect(satisfiesRange('1.2.9', '~1.2.0')).toBe(true);
|
|
161
|
+
expect(satisfiesRange('1.3.0', '~1.2.0')).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
it('handles exact match', () => {
|
|
164
|
+
expect(satisfiesRange('1.2.3', '1.2.3')).toBe(true);
|
|
165
|
+
expect(satisfiesRange('1.2.4', '1.2.3')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it('handles compound range (AND)', () => {
|
|
168
|
+
expect(satisfiesRange('1.5.0', '>=1.0.0 <2.0.0')).toBe(true);
|
|
169
|
+
expect(satisfiesRange('2.0.0', '>=1.0.0 <2.0.0')).toBe(false);
|
|
170
|
+
expect(satisfiesRange('0.9.0', '>=1.0.0 <2.0.0')).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
it('returns true for empty range', () => {
|
|
173
|
+
expect(satisfiesRange('1.0.0', '')).toBe(true);
|
|
174
|
+
expect(satisfiesRange('1.0.0', ' ')).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
it('returns true for unparseable version', () => {
|
|
177
|
+
expect(satisfiesRange('dev', '>=1.0.0')).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
});
|
package/dist/plugin.d.ts
CHANGED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
* Plugin management: install, uninstall, and list plugins.
|
|
3
3
|
*
|
|
4
4
|
* Plugins live in ~/.opencli/plugins/<name>/.
|
|
5
|
-
*
|
|
5
|
+
* Monorepo clones live in ~/.opencli/monorepos/<repo-name>/.
|
|
6
|
+
* Install source format: "github:user/repo" or "github:user/repo/subplugin"
|
|
6
7
|
*/
|
|
7
8
|
/** Path to the lock file that tracks installed plugin versions. */
|
|
8
9
|
export declare function getLockFilePath(): string;
|
|
10
|
+
/** Monorepo clones directory: ~/.opencli/monorepos/ */
|
|
11
|
+
export declare function getMonoreposDir(): string;
|
|
9
12
|
export declare const LOCK_FILE: string;
|
|
13
|
+
export declare const MONOREPOS_DIR: string;
|
|
10
14
|
export interface LockEntry {
|
|
11
15
|
source: string;
|
|
12
16
|
commitHash: string;
|
|
13
17
|
installedAt: string;
|
|
14
18
|
updatedAt?: string;
|
|
19
|
+
/** Present when this plugin comes from a monorepo. */
|
|
20
|
+
monorepo?: {
|
|
21
|
+
/** Monorepo directory name under ~/.opencli/monorepos/ */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Relative path of this sub-plugin within the monorepo. */
|
|
24
|
+
subPath: string;
|
|
25
|
+
};
|
|
15
26
|
}
|
|
16
27
|
export interface PluginInfo {
|
|
17
28
|
name: string;
|
|
@@ -20,6 +31,10 @@ export interface PluginInfo {
|
|
|
20
31
|
source?: string;
|
|
21
32
|
version?: string;
|
|
22
33
|
installedAt?: string;
|
|
34
|
+
/** If from a monorepo, the monorepo name. */
|
|
35
|
+
monorepoName?: string;
|
|
36
|
+
/** Description from opencli-plugin.json. */
|
|
37
|
+
description?: string;
|
|
23
38
|
}
|
|
24
39
|
export interface ValidationResult {
|
|
25
40
|
valid: boolean;
|
|
@@ -35,17 +50,33 @@ export declare function getCommitHash(dir: string): string | undefined;
|
|
|
35
50
|
* package.json if it contains .ts files.
|
|
36
51
|
*/
|
|
37
52
|
export declare function validatePluginStructure(pluginDir: string): ValidationResult;
|
|
53
|
+
declare function installDependencies(dir: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Monorepo lifecycle: install shared deps once at repo root, then finalize each sub-plugin.
|
|
56
|
+
*/
|
|
57
|
+
declare function postInstallMonorepoLifecycle(repoDir: string, pluginDirs: string[]): void;
|
|
38
58
|
/**
|
|
39
59
|
* Install a plugin from a source.
|
|
40
|
-
*
|
|
60
|
+
* Supports:
|
|
61
|
+
* "github:user/repo" — single plugin or full monorepo
|
|
62
|
+
* "github:user/repo/subplugin" — specific sub-plugin from a monorepo
|
|
63
|
+
* "https://github.com/user/repo"
|
|
64
|
+
*
|
|
65
|
+
* Returns the installed plugin name(s).
|
|
41
66
|
*/
|
|
42
|
-
export declare function installPlugin(source: string): string;
|
|
67
|
+
export declare function installPlugin(source: string): string | string[];
|
|
43
68
|
/**
|
|
44
69
|
* Uninstall a plugin by name.
|
|
70
|
+
* For monorepo sub-plugins: removes symlink and cleans up the monorepo
|
|
71
|
+
* directory when no more sub-plugins reference it.
|
|
45
72
|
*/
|
|
46
73
|
export declare function uninstallPlugin(name: string): void;
|
|
74
|
+
/** Synchronous check if a path is a symlink. */
|
|
75
|
+
declare function isSymlinkSync(p: string): boolean;
|
|
47
76
|
/**
|
|
48
77
|
* Update a plugin by name (git pull + re-install lifecycle).
|
|
78
|
+
* For monorepo sub-plugins: pulls the monorepo root and re-runs lifecycle
|
|
79
|
+
* for all sub-plugins from the same monorepo.
|
|
49
80
|
*/
|
|
50
81
|
export declare function updatePlugin(name: string): void;
|
|
51
82
|
export interface UpdateResult {
|
|
@@ -60,15 +91,17 @@ export interface UpdateResult {
|
|
|
60
91
|
export declare function updateAllPlugins(): UpdateResult[];
|
|
61
92
|
/**
|
|
62
93
|
* List all installed plugins.
|
|
94
|
+
* Reads opencli-plugin.json for description/version when available.
|
|
63
95
|
*/
|
|
64
96
|
export declare function listPlugins(): PluginInfo[];
|
|
65
|
-
/** Parse a plugin source string into clone URL
|
|
97
|
+
/** Parse a plugin source string into clone URL, repo name, and optional sub-plugin. */
|
|
66
98
|
declare function parseSource(source: string): {
|
|
67
99
|
cloneUrl: string;
|
|
68
100
|
name: string;
|
|
101
|
+
subPlugin?: string;
|
|
69
102
|
} | null;
|
|
70
103
|
/**
|
|
71
104
|
* Resolve the path to the esbuild CLI executable with fallback strategies.
|
|
72
105
|
*/
|
|
73
106
|
export declare function resolveEsbuildBin(): string | null;
|
|
74
|
-
export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, parseSource as _parseSource, readLockFile as _readLockFile, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, };
|
|
107
|
+
export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, installDependencies as _installDependencies, parseSource as _parseSource, postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle, readLockFile as _readLockFile, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, isSymlinkSync as _isSymlinkSync, getMonoreposDir as _getMonoreposDir, };
|