@mearl/client 2.17.1 → 2.19.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/README.md +86 -5
- package/dist/autoUpdate.d.ts +33 -0
- package/dist/autoUpdate.js +359 -0
- package/dist/autoUpdateWorker.d.ts +1 -0
- package/dist/autoUpdateWorker.js +5 -0
- package/dist/bin.js +3 -0
- package/dist/check.d.ts +11 -0
- package/dist/check.js +106 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -1
- package/dist/generated-browser-action-protocol.d.ts +4 -2
- package/dist/generated-browser-action-protocol.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/plugin.d.ts +11 -3
- package/dist/plugin.js +44 -7
- package/dist/pluginPageBundle.d.ts +8 -0
- package/dist/pluginPageBundle.js +680 -0
- package/dist/pluginRegistry.d.ts +39 -0
- package/dist/pluginRegistry.js +261 -1
- package/dist/pythonPlugin.d.ts +1 -0
- package/dist/pythonPlugin.js +6 -0
- package/dist/socket.js +8 -13
- package/dist/unifiedConfig.d.ts +2 -0
- package/dist/unifiedConfig.js +23 -0
- package/package.json +3 -3
package/dist/check.js
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
import { UnifiedClient, normalizeBrowserSelector, } from './unified.js';
|
|
2
|
+
const UPDATE_SOURCES = {
|
|
3
|
+
client: 'https://registry.npmjs.org/@mearl%2Fclient/latest',
|
|
4
|
+
'native-host': 'https://registry.npmjs.org/@mearl%2Fnative-host/latest',
|
|
5
|
+
'cloud-connector': 'https://registry.npmjs.org/@mearl%2Fcloud-connector/latest',
|
|
6
|
+
extension: 'https://mearl.alibaba-inc.com/config.json',
|
|
7
|
+
};
|
|
8
|
+
export function parseReleaseVersion(value) {
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return null;
|
|
11
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-beta\.(0|[1-9]\d*))?$/.exec(value);
|
|
12
|
+
if (!match)
|
|
13
|
+
return null;
|
|
14
|
+
const parts = [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4] ?? 0)];
|
|
15
|
+
if (!parts.every(Number.isSafeInteger))
|
|
16
|
+
return null;
|
|
17
|
+
return [...parts.slice(0, 3), match[4] === undefined ? 1 : 0, parts[3]];
|
|
18
|
+
}
|
|
19
|
+
export function isNewerRelease(latest, installed) {
|
|
20
|
+
const latestParts = parseReleaseVersion(latest);
|
|
21
|
+
const installedParts = parseReleaseVersion(installed);
|
|
22
|
+
if (!latestParts || !installedParts)
|
|
23
|
+
return false;
|
|
24
|
+
for (let index = 0; index < latestParts.length; index++) {
|
|
25
|
+
if (latestParts[index] !== installedParts[index]) {
|
|
26
|
+
return latestParts[index] > installedParts[index];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async function fetchLatestVersion(component) {
|
|
32
|
+
const response = await fetch(UPDATE_SOURCES[component], {
|
|
33
|
+
signal: AbortSignal.timeout(1500),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(`HTTP ${response.status}`);
|
|
37
|
+
const payload = (await response.json());
|
|
38
|
+
const version = component === 'extension' ? payload.latestVersion : payload.version;
|
|
39
|
+
if (!parseReleaseVersion(version))
|
|
40
|
+
throw new Error('Invalid release version');
|
|
41
|
+
return version;
|
|
42
|
+
}
|
|
2
43
|
function formatError(error) {
|
|
3
44
|
return error instanceof Error ? error.message : String(error);
|
|
4
45
|
}
|
|
@@ -98,6 +139,9 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
98
139
|
connectTimeoutSec: timeoutSec,
|
|
99
140
|
requestTimeoutSec: timeoutSec,
|
|
100
141
|
});
|
|
142
|
+
const latestVersions = options.checkUpdates
|
|
143
|
+
? Promise.allSettled(Object.keys(UPDATE_SOURCES).map(component => (dependencies.fetchLatestVersion ?? fetchLatestVersion)(component)))
|
|
144
|
+
: null;
|
|
101
145
|
const environment = [
|
|
102
146
|
{ status: 'ok', label: `Unified client v${options.clientVersion}` },
|
|
103
147
|
];
|
|
@@ -301,6 +345,67 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
301
345
|
});
|
|
302
346
|
}
|
|
303
347
|
}));
|
|
348
|
+
let updates;
|
|
349
|
+
if (latestVersions) {
|
|
350
|
+
const latestResults = await latestVersions;
|
|
351
|
+
const components = Object.keys(UPDATE_SOURCES);
|
|
352
|
+
const installed = new Map();
|
|
353
|
+
const addInstalled = (component, version) => {
|
|
354
|
+
if (!version)
|
|
355
|
+
return;
|
|
356
|
+
const versions = installed.get(component) ?? new Set();
|
|
357
|
+
versions.add(version);
|
|
358
|
+
installed.set(component, versions);
|
|
359
|
+
};
|
|
360
|
+
addInstalled('client', options.clientVersion);
|
|
361
|
+
for (const check of browserChecks) {
|
|
362
|
+
addInstalled('native-host', check.versions?.nativeHost);
|
|
363
|
+
addInstalled('extension', check.versions?.extension);
|
|
364
|
+
}
|
|
365
|
+
for (const connector of connectors)
|
|
366
|
+
addInstalled('cloud-connector', connector.version);
|
|
367
|
+
updates = components.flatMap((component, index) => {
|
|
368
|
+
const result = latestResults[index];
|
|
369
|
+
return [...(installed.get(component) ?? [])].map(version => ({
|
|
370
|
+
component,
|
|
371
|
+
installed: version,
|
|
372
|
+
...(result.status === 'fulfilled' ? { latest: result.value } : {}),
|
|
373
|
+
status: result.status === 'rejected' ||
|
|
374
|
+
!parseReleaseVersion(version) ||
|
|
375
|
+
!parseReleaseVersion(result.value)
|
|
376
|
+
? 'unavailable'
|
|
377
|
+
: isNewerRelease(result.value, version)
|
|
378
|
+
? 'available'
|
|
379
|
+
: 'current',
|
|
380
|
+
}));
|
|
381
|
+
});
|
|
382
|
+
const available = updates.filter(update => update.status === 'available');
|
|
383
|
+
const unavailable = updates.filter(update => update.status === 'unavailable');
|
|
384
|
+
const packageUpdateAvailable = available.some(update => update.component === 'client' || update.component === 'native-host');
|
|
385
|
+
const extensionUpdateAvailable = available.some(update => update.component === 'extension');
|
|
386
|
+
const connectorUpdateAvailable = available.some(update => update.component === 'cloud-connector');
|
|
387
|
+
environment.push({
|
|
388
|
+
status: available.length || unavailable.length ? 'warning' : 'ok',
|
|
389
|
+
label: available.length
|
|
390
|
+
? `${available.length} Mearl update${available.length === 1 ? '' : 's'} available`
|
|
391
|
+
: unavailable.length
|
|
392
|
+
? 'Mearl update check incomplete'
|
|
393
|
+
: 'Mearl packages are current',
|
|
394
|
+
details: [
|
|
395
|
+
...available.map(update => `${update.component}: ${update.installed} → ${update.latest}`),
|
|
396
|
+
...(packageUpdateAvailable
|
|
397
|
+
? ['Update on the affected machine: npx @mearl/setup update']
|
|
398
|
+
: []),
|
|
399
|
+
...(extensionUpdateAvailable
|
|
400
|
+
? ['Update a setup-managed extension: npx @mearl/setup extension']
|
|
401
|
+
: []),
|
|
402
|
+
...(connectorUpdateAvailable
|
|
403
|
+
? ['Update cloud-connector with its package manager on the connector machine.']
|
|
404
|
+
: []),
|
|
405
|
+
...unavailable.map(update => `${update.component}: latest version unavailable`),
|
|
406
|
+
],
|
|
407
|
+
});
|
|
408
|
+
}
|
|
304
409
|
const hasError = environment.some(item => item.status === 'error') ||
|
|
305
410
|
browserChecks.some(check => check.item.status === 'error');
|
|
306
411
|
const listedBrowsers = browserList?.browsers ?? [];
|
|
@@ -336,6 +441,7 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
336
441
|
connectors,
|
|
337
442
|
environment,
|
|
338
443
|
ok: !hasError,
|
|
444
|
+
...(updates ? { updates } : {}),
|
|
339
445
|
};
|
|
340
446
|
writeJson(JSON.stringify(result, null, 2));
|
|
341
447
|
return result;
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -77,6 +77,7 @@ const PLUGIN_HOST_OPTIONS = new Set([
|
|
|
77
77
|
'--help',
|
|
78
78
|
'--json',
|
|
79
79
|
'--local',
|
|
80
|
+
'--updates',
|
|
80
81
|
'--output',
|
|
81
82
|
'--payload',
|
|
82
83
|
'--payload-file',
|
|
@@ -266,6 +267,7 @@ function printCheckHelp(commandName) {
|
|
|
266
267
|
' --server <url> 覆盖 cloud-server WebSocket URL',
|
|
267
268
|
' --timeout <seconds> 单项检查超时时间,默认 5 秒',
|
|
268
269
|
' --json 输出机器可读 JSON',
|
|
270
|
+
' --updates 查询已安装组件的发布版本',
|
|
269
271
|
' -h, --help 查看帮助',
|
|
270
272
|
].join('\n'));
|
|
271
273
|
}
|
|
@@ -409,6 +411,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
409
411
|
.option('--browser <id-or-name>')
|
|
410
412
|
.option('--connector <id-or-name>')
|
|
411
413
|
.option('--local')
|
|
414
|
+
.option('--updates')
|
|
412
415
|
.option('--cdp')
|
|
413
416
|
.option('--server <url>');
|
|
414
417
|
if (plugin)
|
|
@@ -442,6 +445,8 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
442
445
|
const json = options.json === true;
|
|
443
446
|
if (json && action !== 'check')
|
|
444
447
|
throw new Error('--json 仅适用于 check');
|
|
448
|
+
if (options.updates && action !== 'check')
|
|
449
|
+
throw new Error('--updates 仅适用于 check');
|
|
445
450
|
const parsedPayload = parsePayload(options.payload, options.payloadFile);
|
|
446
451
|
const { payload, outputPath } = normalizeScreenshotOutput(action, parsedPayload, options.output);
|
|
447
452
|
const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
|
|
@@ -459,6 +464,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
459
464
|
forceCdp,
|
|
460
465
|
json,
|
|
461
466
|
local,
|
|
467
|
+
updates: options.updates === true,
|
|
462
468
|
...(outputPath ? { outputPath } : {}),
|
|
463
469
|
...(options.browser ? { browser: options.browser } : {}),
|
|
464
470
|
...(connector ? { connector } : {}),
|
|
@@ -504,7 +510,8 @@ async function runPluginCli(args, commandName) {
|
|
|
504
510
|
...invokeOptions,
|
|
505
511
|
...(parsed.verbose
|
|
506
512
|
? {
|
|
507
|
-
onProgress(
|
|
513
|
+
onProgress(progress) {
|
|
514
|
+
const message = typeof progress === 'string' ? progress : progress.message;
|
|
508
515
|
process.stderr.write(message.endsWith('\n') ? message : `${message}\n`);
|
|
509
516
|
},
|
|
510
517
|
}
|
|
@@ -644,6 +651,7 @@ export async function runMearlCli(args, options = {}) {
|
|
|
644
651
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
645
652
|
...(parsed.local ? { local: true } : {}),
|
|
646
653
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
654
|
+
...(parsed.updates ? { checkUpdates: true } : {}),
|
|
647
655
|
};
|
|
648
656
|
if (parsed.json) {
|
|
649
657
|
const result = await runUnifiedCheck({ ...checkOptions, json: true });
|
|
@@ -348,7 +348,7 @@ export interface PageScreenshotParams {
|
|
|
348
348
|
format?: 'png' | 'jpeg';
|
|
349
349
|
quality?: number;
|
|
350
350
|
}
|
|
351
|
-
export declare const BROWSER_DEVICE_PRESET_NAMES: readonly ["iphone-17-pro", "iphone-17e", "pixel-11", "ipad-mini-a17-pro", "galaxy-s26", "pura-x-max", "iphone-15-pro", "iphone-se", "pixel-8", "ipad-mini", "galaxy-s23"];
|
|
351
|
+
export declare const BROWSER_DEVICE_PRESET_NAMES: readonly ["iphone-duo-folded", "iphone-duo-unfolded", "iphone-17-pro", "iphone-17e", "pixel-11", "ipad-mini-a17-pro", "galaxy-s26", "pura-x-max", "iphone-15-pro", "iphone-se", "pixel-8", "ipad-mini", "galaxy-s23"];
|
|
352
352
|
export type BrowserDevicePresetName = (typeof BROWSER_DEVICE_PRESET_NAMES)[number];
|
|
353
353
|
export interface BrowserEmulationOptions {
|
|
354
354
|
preset?: BrowserDevicePresetName;
|
|
@@ -365,7 +365,7 @@ export interface BrowserEmulationOptions {
|
|
|
365
365
|
/** App 容器横屏时将根页面与业务页面分置左右 WebView;仅与 deviceShell、App 档案同时生效。 */
|
|
366
366
|
splitScreen?: boolean;
|
|
367
367
|
}
|
|
368
|
-
export type AppBridgeConfigBehavior = 'navigate' | 'closeAll' | 'closeBackData' | 'postMessage' | 'notifySubscribe' | 'notifyUnsubscribe' | 'notifyPost' | 'sharedFileRead' | 'sharedFileWrite' | 'sharedDataGet' | 'sharedDataSet' | 'legacyBroadcastCreate' | 'legacyBroadcastPost' | 'legacyBroadcastClose' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'networkInfo' | 'geolocation' | 'permission' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest' | 'voiceRecognition';
|
|
368
|
+
export type AppBridgeConfigBehavior = 'navigate' | 'closeAll' | 'closeBackData' | 'postMessage' | 'notifySubscribe' | 'notifyUnsubscribe' | 'notifyPost' | 'sharedFileRead' | 'sharedFileWrite' | 'sharedDataGet' | 'sharedDataSet' | 'legacyBroadcastCreate' | 'legacyBroadcastPost' | 'legacyBroadcastClose' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'authCode' | 'networkInfo' | 'geolocation' | 'permission' | 'imagePicker' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest' | 'voiceRecognition';
|
|
369
369
|
/** 单个桥方法的声明式覆盖;行为、固定回包、成功空实现三选一。 */
|
|
370
370
|
export interface AppBridgeMethodConfig {
|
|
371
371
|
behavior?: AppBridgeConfigBehavior;
|
|
@@ -421,6 +421,8 @@ export interface SetAppProfileParams {
|
|
|
421
421
|
profile?: AppProfileName;
|
|
422
422
|
os?: 'ios' | 'android';
|
|
423
423
|
appVersion?: string;
|
|
424
|
+
/** 支付宝容器语言;其他容器忽略。 */
|
|
425
|
+
language?: 'zh-CN' | 'zh-TW' | 'zh-HK' | 'en-US';
|
|
424
426
|
deviceModel?: string;
|
|
425
427
|
/** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
|
|
426
428
|
bridgeConfig?: AppBridgeConfig;
|
|
@@ -7,6 +7,8 @@ export const SEND_REQUEST_MAX_TOTAL_FILE_BYTES = 40 * 1024 * 1024;
|
|
|
7
7
|
// the 10 MiB cloud message limit.
|
|
8
8
|
export const SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES = 7 * 1024 * 1024;
|
|
9
9
|
export const BROWSER_DEVICE_PRESET_NAMES = [
|
|
10
|
+
'iphone-duo-folded',
|
|
11
|
+
'iphone-duo-unfolded',
|
|
10
12
|
'iphone-17-pro',
|
|
11
13
|
'iphone-17e',
|
|
12
14
|
'pixel-11',
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invoke, invokePlugin, listCloudConnectors, } from './unified.js';
|
|
8
8
|
export type { UnifiedClientOptions, UnifiedInvokeOptions, UnifiedInvokeOptions as InvokeOptions, } from './unified.js';
|
|
9
|
-
export type { PluginInvocation, PluginInvokeOptions } from './plugin.js';
|
|
9
|
+
export type { PluginInvocation, PluginInvokeOptions, PluginProgress, StructuredPluginProgress, } from './plugin.js';
|
|
10
10
|
export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCapabilityAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated-browser-action-protocol.js';
|
|
11
11
|
export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestDomainPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestFormValue, SendMtopRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from './generated-browser-action-protocol.js';
|
|
12
12
|
import { type UnifiedInvokeOptions } from './unified.js';
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { type OfficialPluginInstaller } from './officialPluginInstaller.js';
|
|
2
|
+
import { type PluginToolboxContribution } from './pluginRegistry.js';
|
|
2
3
|
import type { UnifiedInvokeOptions } from './unified.js';
|
|
3
|
-
export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, type PluginRegistration, type PluginRegistrationOptions, } from './pluginRegistry.js';
|
|
4
|
+
export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, readPluginPageBundleChunk, type PluginRegistration, type PluginRegistrationOptions, type PluginToolboxAgentConfig, type PluginPageBundleRegistration, type PluginToolboxContribution, type PluginToolboxLocale, type PluginToolboxSandboxPage, } from './pluginRegistry.js';
|
|
4
5
|
export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, type OfficialPluginDefinition, type OfficialPluginName, } from './officialPluginCatalog.js';
|
|
5
6
|
export type PluginOptions = UnifiedInvokeOptions;
|
|
7
|
+
export interface StructuredPluginProgress {
|
|
8
|
+
message: string;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export type PluginProgress = string | StructuredPluginProgress;
|
|
6
12
|
export interface PluginInvocation {
|
|
7
13
|
command: string;
|
|
8
14
|
input: Record<string, any>;
|
|
9
15
|
}
|
|
10
16
|
export interface PluginInvokeOptions extends UnifiedInvokeOptions {
|
|
11
|
-
onProgress?: (
|
|
17
|
+
onProgress?: (progress: PluginProgress) => void;
|
|
12
18
|
}
|
|
13
19
|
export interface PluginExecutionContext {
|
|
14
20
|
ensurePlugin?: OfficialPluginInstaller;
|
|
@@ -18,13 +24,14 @@ export interface PluginApp {
|
|
|
18
24
|
readonly options: Readonly<PluginOptions>;
|
|
19
25
|
command(signature: string, handler: PluginHandler, description?: string): void;
|
|
20
26
|
invoke(action: string, payload?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
|
|
21
|
-
progress(
|
|
27
|
+
progress(progress: PluginProgress): void;
|
|
22
28
|
}
|
|
23
29
|
export type PluginHandler = (input: Record<string, any>) => unknown | Promise<unknown>;
|
|
24
30
|
/** 注册和执行复用同一宿主能力边界;仅 handler 执行期间开放浏览器调用。 */
|
|
25
31
|
export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invoke' | 'options' | 'progress'>, pluginName?: string): Promise<{
|
|
26
32
|
name: string | undefined;
|
|
27
33
|
description: string | undefined;
|
|
34
|
+
toolbox: PluginToolboxContribution | undefined;
|
|
28
35
|
version: string | undefined;
|
|
29
36
|
commandHelp(namespace: string): string;
|
|
30
37
|
help(namespace: string, name?: string): string;
|
|
@@ -33,6 +40,7 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invo
|
|
|
33
40
|
export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
|
|
34
41
|
name: string | undefined;
|
|
35
42
|
description: string | undefined;
|
|
43
|
+
toolbox: PluginToolboxContribution | undefined;
|
|
36
44
|
version: string | undefined;
|
|
37
45
|
commandHelp(namespace: string): string;
|
|
38
46
|
help(namespace: string, name?: string): string;
|
package/dist/plugin.js
CHANGED
|
@@ -5,9 +5,9 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { COMMAND_MAP } from './commands.js';
|
|
6
6
|
import { findOfficialPlugin } from './officialPluginCatalog.js';
|
|
7
7
|
import { ensureOfficialPluginRegistered, } from './officialPluginInstaller.js';
|
|
8
|
-
import { findPluginRegistration, PluginError } from './pluginRegistry.js';
|
|
8
|
+
import { findPluginRegistration, normalizePluginToolboxContribution, PluginError, } from './pluginRegistry.js';
|
|
9
9
|
import { describePythonPlugin, runPythonPluginCommand } from './pythonPlugin.js';
|
|
10
|
-
export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, } from './pluginRegistry.js';
|
|
10
|
+
export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, readPluginPageBundleChunk, } from './pluginRegistry.js';
|
|
11
11
|
export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, } from './officialPluginCatalog.js';
|
|
12
12
|
function readPluginVersion(entry) {
|
|
13
13
|
try {
|
|
@@ -26,6 +26,36 @@ function isObject(value) {
|
|
|
26
26
|
function isPluginInvocation(value) {
|
|
27
27
|
return isObject(value) && typeof value.command === 'string' && isObject(value.input);
|
|
28
28
|
}
|
|
29
|
+
function normalizePluginProgress(progress) {
|
|
30
|
+
if (typeof progress === 'string') {
|
|
31
|
+
if (!progress)
|
|
32
|
+
throw new Error('app.progress 需要非空消息');
|
|
33
|
+
return progress;
|
|
34
|
+
}
|
|
35
|
+
if (!isObject(progress) || typeof progress.message !== 'string' || !progress.message) {
|
|
36
|
+
throw new Error('app.progress 需要非空消息');
|
|
37
|
+
}
|
|
38
|
+
if (progress.data !== undefined && !isObject(progress.data)) {
|
|
39
|
+
throw new Error('app.progress data 必须是对象');
|
|
40
|
+
}
|
|
41
|
+
let data;
|
|
42
|
+
if (progress.data) {
|
|
43
|
+
try {
|
|
44
|
+
const serialized = JSON.stringify(progress.data);
|
|
45
|
+
const parsed = serialized === undefined ? undefined : JSON.parse(serialized);
|
|
46
|
+
if (!isObject(parsed))
|
|
47
|
+
throw new Error('invalid object');
|
|
48
|
+
data = Object.freeze(parsed);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new Error('app.progress data 必须是可 JSON 序列化的对象');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
message: progress.message,
|
|
56
|
+
...(data ? { data } : {}),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
29
59
|
function missingPluginEntryMessage(entry, pluginName) {
|
|
30
60
|
const name = pluginName ?? '<name>';
|
|
31
61
|
return [
|
|
@@ -54,6 +84,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
54
84
|
let registering = true;
|
|
55
85
|
let name;
|
|
56
86
|
let description;
|
|
87
|
+
let toolbox;
|
|
57
88
|
const app = Object.freeze({
|
|
58
89
|
options: Object.freeze({ ...context.options }),
|
|
59
90
|
command(signature, handler, description = '') {
|
|
@@ -80,12 +111,10 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
80
111
|
throw new Error('app.invoke 只能在命令执行期间调用');
|
|
81
112
|
return context.invoke(action, payload, options);
|
|
82
113
|
},
|
|
83
|
-
progress(
|
|
114
|
+
progress(progress) {
|
|
84
115
|
if (!executing)
|
|
85
116
|
throw new Error('app.progress 只能在命令执行期间调用');
|
|
86
|
-
|
|
87
|
-
throw new Error('app.progress 需要非空字符串');
|
|
88
|
-
context.progress(message);
|
|
117
|
+
context.progress(normalizePluginProgress(progress));
|
|
89
118
|
},
|
|
90
119
|
});
|
|
91
120
|
try {
|
|
@@ -93,6 +122,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
93
122
|
const descriptor = await describePythonPlugin(entry, app.options);
|
|
94
123
|
name = descriptor.name;
|
|
95
124
|
description = descriptor.description;
|
|
125
|
+
toolbox = normalizePluginToolboxContribution(descriptor.toolbox);
|
|
96
126
|
for (const command of descriptor.commands) {
|
|
97
127
|
const commandName = command.signature.trim().split(/\s+/)[0];
|
|
98
128
|
app.command(command.signature, input => runPythonPluginCommand(entry, commandName, input, app), command.description);
|
|
@@ -108,12 +138,18 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
108
138
|
if (module.description !== undefined && typeof module.description !== 'string')
|
|
109
139
|
throw new Error('入口的 description 导出必须为字符串');
|
|
110
140
|
description = module.description;
|
|
141
|
+
toolbox = normalizePluginToolboxContribution(module.toolbox);
|
|
111
142
|
if (typeof module.default !== 'function')
|
|
112
143
|
throw new Error('入口必须默认导出 register(app) 函数');
|
|
113
144
|
await module.default(app);
|
|
114
145
|
}
|
|
115
146
|
if (commands.size === 0)
|
|
116
147
|
throw new Error('入口未注册任何命令');
|
|
148
|
+
const toolboxCommands = toolbox?.page.commands ?? [];
|
|
149
|
+
for (const command of toolboxCommands) {
|
|
150
|
+
if (!commands.has(command))
|
|
151
|
+
throw new Error(`toolbox 命令未注册: ${command}`);
|
|
152
|
+
}
|
|
117
153
|
}
|
|
118
154
|
catch (error) {
|
|
119
155
|
throw new PluginError('PLUGIN_LOAD_FAILED', `${entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -130,6 +166,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
130
166
|
return {
|
|
131
167
|
name,
|
|
132
168
|
description,
|
|
169
|
+
toolbox,
|
|
133
170
|
version: readPluginVersion(entry),
|
|
134
171
|
commandHelp(namespace) {
|
|
135
172
|
return [...commands.values()]
|
|
@@ -215,7 +252,7 @@ async function loadRegisteredPluginWithState(name, context, options = {}, state
|
|
|
215
252
|
}
|
|
216
253
|
let registration = findPluginRegistration(name);
|
|
217
254
|
if (!registration && findOfficialPlugin(name)) {
|
|
218
|
-
await (context.ensurePlugin ?? ensureOfficialPluginRegistered)(name, options.onProgress);
|
|
255
|
+
await (context.ensurePlugin ?? ensureOfficialPluginRegistered)(name, options.onProgress ? message => options.onProgress?.(message) : undefined);
|
|
219
256
|
registration = findPluginRegistration(name);
|
|
220
257
|
}
|
|
221
258
|
if (!registration) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PLUGIN_PAGE_CHUNK_BYTES: number;
|
|
2
|
+
export interface BuiltPluginPageBundle {
|
|
3
|
+
content: string;
|
|
4
|
+
hash: string;
|
|
5
|
+
size: number;
|
|
6
|
+
chunks: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildPluginPageBundle(entry: string, pageEntry: string): BuiltPluginPageBundle;
|