@jackwener/opencli 1.5.0 → 1.5.2
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/dist/browser/cdp.js +5 -0
- package/dist/browser/discover.js +11 -7
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/page.d.ts +4 -0
- package/dist/browser/page.js +52 -3
- package/dist/browser.test.js +5 -0
- package/dist/cli-manifest.json +460 -1
- package/dist/cli.js +34 -3
- package/dist/clis/apple-podcasts/commands.test.js +26 -3
- package/dist/clis/apple-podcasts/top.js +4 -1
- package/dist/clis/bluesky/feeds.yaml +29 -0
- package/dist/clis/bluesky/followers.yaml +33 -0
- package/dist/clis/bluesky/following.yaml +33 -0
- package/dist/clis/bluesky/profile.yaml +27 -0
- package/dist/clis/bluesky/search.yaml +34 -0
- package/dist/clis/bluesky/starter-packs.yaml +34 -0
- package/dist/clis/bluesky/thread.yaml +32 -0
- package/dist/clis/bluesky/trending.yaml +27 -0
- package/dist/clis/bluesky/user.yaml +34 -0
- package/dist/clis/twitter/trending.js +29 -61
- package/dist/clis/weread/shelf.js +132 -9
- package/dist/clis/weread/utils.js +5 -1
- package/dist/clis/xiaohongshu/publish.js +78 -42
- package/dist/clis/xiaohongshu/publish.test.js +20 -8
- package/dist/clis/xiaohongshu/search.d.ts +8 -1
- package/dist/clis/xiaohongshu/search.js +20 -1
- package/dist/clis/xiaohongshu/search.test.d.ts +1 -1
- package/dist/clis/xiaohongshu/search.test.js +32 -1
- package/dist/daemon.js +1 -0
- package/dist/discovery.js +40 -28
- package/dist/doctor.d.ts +1 -2
- package/dist/doctor.js +9 -5
- package/dist/engine.test.js +42 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +2 -2
- package/dist/execution.js +45 -13
- package/dist/execution.test.d.ts +1 -0
- package/dist/execution.test.js +40 -0
- package/dist/extension-manifest-regression.test.d.ts +1 -0
- package/dist/extension-manifest-regression.test.js +12 -0
- package/dist/external.js +6 -1
- package/dist/main.js +1 -0
- package/dist/plugin-scaffold.d.ts +28 -0
- package/dist/plugin-scaffold.js +142 -0
- package/dist/plugin-scaffold.test.d.ts +4 -0
- package/dist/plugin-scaffold.test.js +83 -0
- package/dist/plugin.d.ts +55 -17
- package/dist/plugin.js +706 -154
- package/dist/plugin.test.js +836 -38
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/weread-private-api-regression.test.js +185 -0
- package/docs/adapters/browser/bluesky.md +53 -0
- package/docs/guide/plugins.md +10 -0
- package/extension/dist/background.js +4 -2
- package/extension/manifest.json +4 -1
- package/extension/package-lock.json +2 -2
- package/extension/package.json +1 -1
- package/extension/src/background.ts +2 -1
- package/package.json +1 -1
- package/src/browser/cdp.ts +6 -0
- package/src/browser/discover.ts +10 -7
- package/src/browser/index.ts +2 -0
- package/src/browser/page.ts +49 -3
- package/src/browser.test.ts +6 -0
- package/src/cli.ts +34 -3
- package/src/clis/apple-podcasts/commands.test.ts +30 -2
- package/src/clis/apple-podcasts/top.ts +4 -1
- package/src/clis/bluesky/feeds.yaml +29 -0
- package/src/clis/bluesky/followers.yaml +33 -0
- package/src/clis/bluesky/following.yaml +33 -0
- package/src/clis/bluesky/profile.yaml +27 -0
- package/src/clis/bluesky/search.yaml +34 -0
- package/src/clis/bluesky/starter-packs.yaml +34 -0
- package/src/clis/bluesky/thread.yaml +32 -0
- package/src/clis/bluesky/trending.yaml +27 -0
- package/src/clis/bluesky/user.yaml +34 -0
- package/src/clis/twitter/trending.ts +29 -77
- package/src/clis/weread/shelf.ts +169 -9
- package/src/clis/weread/utils.ts +6 -1
- package/src/clis/xiaohongshu/publish.test.ts +22 -8
- package/src/clis/xiaohongshu/publish.ts +93 -52
- package/src/clis/xiaohongshu/search.test.ts +39 -1
- package/src/clis/xiaohongshu/search.ts +19 -1
- package/src/daemon.ts +1 -0
- package/src/discovery.ts +41 -33
- package/src/doctor.ts +11 -8
- package/src/engine.test.ts +38 -0
- package/src/errors.ts +6 -2
- package/src/execution.test.ts +47 -0
- package/src/execution.ts +39 -15
- package/src/extension-manifest-regression.test.ts +17 -0
- package/src/external.ts +6 -1
- package/src/main.ts +1 -0
- package/src/plugin-scaffold.test.ts +98 -0
- package/src/plugin-scaffold.ts +170 -0
- package/src/plugin.test.ts +881 -38
- package/src/plugin.ts +871 -158
- package/src/runtime.ts +2 -2
- package/src/types.ts +2 -0
- package/src/weread-private-api-regression.test.ts +207 -0
- package/tests/e2e/browser-public.test.ts +1 -1
- package/tests/e2e/output-formats.test.ts +10 -14
- package/tests/e2e/plugin-management.test.ts +4 -1
- package/tests/e2e/public-commands.test.ts +12 -1
- package/vitest.config.ts +1 -15
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for plugin scaffold: create new plugin directories.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { createPluginScaffold } from './plugin-scaffold.js';
|
|
9
|
+
describe('createPluginScaffold', () => {
|
|
10
|
+
const createdDirs = [];
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
for (const dir of createdDirs) {
|
|
13
|
+
try {
|
|
14
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
}
|
|
18
|
+
createdDirs.length = 0;
|
|
19
|
+
});
|
|
20
|
+
it('creates all expected files', () => {
|
|
21
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
22
|
+
createdDirs.push(dir);
|
|
23
|
+
const result = createPluginScaffold('my-test', { dir });
|
|
24
|
+
expect(result.name).toBe('my-test');
|
|
25
|
+
expect(result.dir).toBe(dir);
|
|
26
|
+
expect(result.files).toContain('opencli-plugin.json');
|
|
27
|
+
expect(result.files).toContain('package.json');
|
|
28
|
+
expect(result.files).toContain('hello.yaml');
|
|
29
|
+
expect(result.files).toContain('greet.ts');
|
|
30
|
+
expect(result.files).toContain('README.md');
|
|
31
|
+
// All files exist
|
|
32
|
+
for (const f of result.files) {
|
|
33
|
+
expect(fs.existsSync(path.join(dir, f))).toBe(true);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it('generates valid opencli-plugin.json', () => {
|
|
37
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
38
|
+
createdDirs.push(dir);
|
|
39
|
+
createPluginScaffold('test-manifest', { dir, description: 'Test desc' });
|
|
40
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'opencli-plugin.json'), 'utf-8'));
|
|
41
|
+
expect(manifest.name).toBe('test-manifest');
|
|
42
|
+
expect(manifest.version).toBe('0.1.0');
|
|
43
|
+
expect(manifest.description).toBe('Test desc');
|
|
44
|
+
expect(manifest.opencli).toMatch(/^>=/);
|
|
45
|
+
});
|
|
46
|
+
it('generates ESM package.json', () => {
|
|
47
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
48
|
+
createdDirs.push(dir);
|
|
49
|
+
createPluginScaffold('test-pkg', { dir });
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
51
|
+
expect(pkg.type).toBe('module');
|
|
52
|
+
expect(pkg.peerDependencies?.['@jackwener/opencli']).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
it('generates a TS sample that matches the current plugin API', () => {
|
|
55
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
56
|
+
createdDirs.push(dir);
|
|
57
|
+
createPluginScaffold('test-ts', { dir });
|
|
58
|
+
const tsSample = fs.readFileSync(path.join(dir, 'greet.ts'), 'utf-8');
|
|
59
|
+
expect(tsSample).toContain(`import { cli, Strategy } from '@jackwener/opencli/registry';`);
|
|
60
|
+
expect(tsSample).toContain(`strategy: Strategy.PUBLIC`);
|
|
61
|
+
expect(tsSample).toContain(`help: 'Name to greet'`);
|
|
62
|
+
expect(tsSample).toContain(`func: async (_page, kwargs)`);
|
|
63
|
+
expect(tsSample).not.toContain('async run(');
|
|
64
|
+
});
|
|
65
|
+
it('documents a supported local install flow', () => {
|
|
66
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
67
|
+
createdDirs.push(dir);
|
|
68
|
+
createPluginScaffold('test-readme', { dir });
|
|
69
|
+
const readme = fs.readFileSync(path.join(dir, 'README.md'), 'utf-8');
|
|
70
|
+
expect(readme).toContain(`opencli plugin install file://${dir}`);
|
|
71
|
+
});
|
|
72
|
+
it('rejects invalid names', () => {
|
|
73
|
+
expect(() => createPluginScaffold('Bad_Name')).toThrow('Invalid plugin name');
|
|
74
|
+
expect(() => createPluginScaffold('123start')).toThrow('Invalid plugin name');
|
|
75
|
+
});
|
|
76
|
+
it('rejects non-empty directory', () => {
|
|
77
|
+
const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
|
|
78
|
+
createdDirs.push(dir);
|
|
79
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
fs.writeFileSync(path.join(dir, 'existing.txt'), 'x');
|
|
81
|
+
expect(() => createPluginScaffold('test', { dir })).toThrow('not empty');
|
|
82
|
+
});
|
|
83
|
+
});
|
package/dist/plugin.d.ts
CHANGED
|
@@ -3,26 +3,31 @@
|
|
|
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"
|
|
6
|
+
* Install source format: "github:user/repo", "github:user/repo/subplugin",
|
|
7
|
+
* "https://github.com/user/repo", "file:///local/plugin", or a local directory path.
|
|
7
8
|
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
8
10
|
/** Path to the lock file that tracks installed plugin versions. */
|
|
9
11
|
export declare function getLockFilePath(): string;
|
|
10
12
|
/** Monorepo clones directory: ~/.opencli/monorepos/ */
|
|
11
13
|
export declare function getMonoreposDir(): string;
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
+
export type PluginSourceRecord = {
|
|
15
|
+
kind: 'git';
|
|
16
|
+
url: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'local';
|
|
19
|
+
path: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'monorepo';
|
|
22
|
+
url: string;
|
|
23
|
+
repoName: string;
|
|
24
|
+
subPath: string;
|
|
25
|
+
};
|
|
14
26
|
export interface LockEntry {
|
|
15
|
-
source:
|
|
27
|
+
source: PluginSourceRecord;
|
|
16
28
|
commitHash: string;
|
|
17
29
|
installedAt: string;
|
|
18
30
|
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
|
-
};
|
|
26
31
|
}
|
|
27
32
|
export interface PluginInfo {
|
|
28
33
|
name: string;
|
|
@@ -36,11 +41,40 @@ export interface PluginInfo {
|
|
|
36
41
|
/** Description from opencli-plugin.json. */
|
|
37
42
|
description?: string;
|
|
38
43
|
}
|
|
44
|
+
interface ParsedSource {
|
|
45
|
+
type: 'git' | 'local';
|
|
46
|
+
name: string;
|
|
47
|
+
subPlugin?: string;
|
|
48
|
+
cloneUrl?: string;
|
|
49
|
+
localPath?: string;
|
|
50
|
+
}
|
|
51
|
+
declare function isLocalPluginSource(source?: string): boolean;
|
|
52
|
+
declare function toStoredPluginSource(source: PluginSourceRecord): string;
|
|
53
|
+
declare function toLocalPluginSource(pluginDir: string): string;
|
|
54
|
+
declare function resolvePluginSource(lockEntry: LockEntry | undefined, pluginDir: string): PluginSourceRecord | undefined;
|
|
55
|
+
declare function resolveStoredPluginSource(lockEntry: LockEntry | undefined, pluginDir: string): string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Move a directory, with EXDEV fallback.
|
|
58
|
+
* fs.renameSync fails when source and destination are on different
|
|
59
|
+
* filesystems (e.g. /tmp → ~/.opencli). In that case we copy then remove.
|
|
60
|
+
*/
|
|
61
|
+
type MoveDirFsOps = Pick<typeof fs, 'renameSync' | 'cpSync' | 'rmSync'>;
|
|
62
|
+
declare function moveDir(src: string, dest: string, fsOps?: MoveDirFsOps): void;
|
|
63
|
+
type PromoteDirFsOps = MoveDirFsOps & Pick<typeof fs, 'existsSync' | 'mkdirSync'>;
|
|
64
|
+
/**
|
|
65
|
+
* Promote a prepared staging directory into its final location.
|
|
66
|
+
* The final path is only exposed after the directory has been fully prepared.
|
|
67
|
+
*/
|
|
68
|
+
declare function promoteDir(stagingDir: string, dest: string, fsOps?: PromoteDirFsOps): void;
|
|
69
|
+
declare function replaceDir(stagingDir: string, dest: string, fsOps?: PromoteDirFsOps): void;
|
|
39
70
|
export interface ValidationResult {
|
|
40
71
|
valid: boolean;
|
|
41
72
|
errors: string[];
|
|
42
73
|
}
|
|
74
|
+
declare function readLockFileWithWriter(writeLock?: (lock: Record<string, LockEntry>) => void): Record<string, LockEntry>;
|
|
43
75
|
export declare function readLockFile(): Record<string, LockEntry>;
|
|
76
|
+
type WriteLockFileFsOps = Pick<typeof fs, 'mkdirSync' | 'writeFileSync' | 'renameSync' | 'rmSync'>;
|
|
77
|
+
declare function writeLockFileWithFs(lock: Record<string, LockEntry>, fsOps?: WriteLockFileFsOps): void;
|
|
44
78
|
export declare function writeLockFile(lock: Record<string, LockEntry>): void;
|
|
45
79
|
/** Get the HEAD commit hash of a git repo directory. */
|
|
46
80
|
export declare function getCommitHash(dir: string): string | undefined;
|
|
@@ -61,10 +95,18 @@ declare function postInstallMonorepoLifecycle(repoDir: string, pluginDirs: strin
|
|
|
61
95
|
* "github:user/repo" — single plugin or full monorepo
|
|
62
96
|
* "github:user/repo/subplugin" — specific sub-plugin from a monorepo
|
|
63
97
|
* "https://github.com/user/repo"
|
|
98
|
+
* "file:///absolute/path" — local plugin directory (symlinked)
|
|
99
|
+
* "/absolute/path" — local plugin directory (symlinked)
|
|
64
100
|
*
|
|
65
101
|
* Returns the installed plugin name(s).
|
|
66
102
|
*/
|
|
67
103
|
export declare function installPlugin(source: string): string | string[];
|
|
104
|
+
/**
|
|
105
|
+
* Install a local plugin by creating a symlink.
|
|
106
|
+
* Used for plugin development: the source directory is symlinked into
|
|
107
|
+
* the plugins dir so changes are reflected immediately.
|
|
108
|
+
*/
|
|
109
|
+
declare function installLocalPlugin(localPath: string, name: string): string;
|
|
68
110
|
/**
|
|
69
111
|
* Uninstall a plugin by name.
|
|
70
112
|
* For monorepo sub-plugins: removes symlink and cleans up the monorepo
|
|
@@ -95,13 +137,9 @@ export declare function updateAllPlugins(): UpdateResult[];
|
|
|
95
137
|
*/
|
|
96
138
|
export declare function listPlugins(): PluginInfo[];
|
|
97
139
|
/** Parse a plugin source string into clone URL, repo name, and optional sub-plugin. */
|
|
98
|
-
declare function parseSource(source: string):
|
|
99
|
-
cloneUrl: string;
|
|
100
|
-
name: string;
|
|
101
|
-
subPlugin?: string;
|
|
102
|
-
} | null;
|
|
140
|
+
declare function parseSource(source: string): ParsedSource | null;
|
|
103
141
|
/**
|
|
104
142
|
* Resolve the path to the esbuild CLI executable with fallback strategies.
|
|
105
143
|
*/
|
|
106
144
|
export declare function resolveEsbuildBin(): string | null;
|
|
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, };
|
|
145
|
+
export { resolveEsbuildBin as _resolveEsbuildBin, getCommitHash as _getCommitHash, installDependencies as _installDependencies, parseSource as _parseSource, postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle, readLockFile as _readLockFile, readLockFileWithWriter as _readLockFileWithWriter, updateAllPlugins as _updateAllPlugins, validatePluginStructure as _validatePluginStructure, writeLockFile as _writeLockFile, writeLockFileWithFs as _writeLockFileWithFs, isSymlinkSync as _isSymlinkSync, getMonoreposDir as _getMonoreposDir, installLocalPlugin as _installLocalPlugin, isLocalPluginSource as _isLocalPluginSource, moveDir as _moveDir, promoteDir as _promoteDir, replaceDir as _replaceDir, resolvePluginSource as _resolvePluginSource, resolveStoredPluginSource as _resolveStoredPluginSource, toStoredPluginSource as _toStoredPluginSource, toLocalPluginSource as _toLocalPluginSource, };
|