@meet-im/lxcli 0.0.4 → 0.0.5
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.
|
@@ -2,13 +2,14 @@ import { outputCliError } from '../../../core/error.js';
|
|
|
2
2
|
import { request } from '../../../core/http.js';
|
|
3
3
|
import { getNextRequestId } from './utils.js';
|
|
4
4
|
export async function jsonRpcCall(gateway, method, params, pat) {
|
|
5
|
+
const requestId = getNextRequestId();
|
|
5
6
|
const requestBody = {
|
|
6
7
|
jsonrpc: '2.0',
|
|
7
8
|
method,
|
|
8
|
-
id:
|
|
9
|
+
id: requestId,
|
|
9
10
|
params,
|
|
10
11
|
};
|
|
11
|
-
const
|
|
12
|
+
const result = await request({
|
|
12
13
|
gateway,
|
|
13
14
|
path: '/jsonrpc.php',
|
|
14
15
|
method: 'POST',
|
|
@@ -16,11 +17,26 @@ export async function jsonRpcCall(gateway, method, params, pat) {
|
|
|
16
17
|
headers: {
|
|
17
18
|
Authorization: `meet ${pat}`,
|
|
18
19
|
},
|
|
20
|
+
throwOnError: false,
|
|
19
21
|
});
|
|
22
|
+
// HTTP 层错误
|
|
23
|
+
if (result.error) {
|
|
24
|
+
outputCliError('NETWORK_ERROR', result.error.message, {
|
|
25
|
+
code: result.error.code,
|
|
26
|
+
details: result.error.details,
|
|
27
|
+
requestId,
|
|
28
|
+
});
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const response = result.data;
|
|
32
|
+
// JSON-RPC 层错误
|
|
20
33
|
if (response.error) {
|
|
21
34
|
outputCliError('NETWORK_ERROR', response.error.message, {
|
|
22
35
|
code: response.error.code,
|
|
23
36
|
data: response.error.data,
|
|
37
|
+
requestId,
|
|
38
|
+
// 服务端返回的 id(可能为 null)
|
|
39
|
+
responseId: response.id,
|
|
24
40
|
});
|
|
25
41
|
return undefined;
|
|
26
42
|
}
|
|
@@ -28,6 +44,7 @@ export async function jsonRpcCall(gateway, method, params, pat) {
|
|
|
28
44
|
outputCliError('NETWORK_ERROR', 'No result returned from Kanboard API', {
|
|
29
45
|
method,
|
|
30
46
|
params,
|
|
47
|
+
requestId,
|
|
31
48
|
});
|
|
32
49
|
return undefined;
|
|
33
50
|
}
|
|
@@ -33,6 +33,7 @@ export interface KanboardTask {
|
|
|
33
33
|
date_due?: string;
|
|
34
34
|
date_started?: string;
|
|
35
35
|
priority: string;
|
|
36
|
+
priority_name?: string;
|
|
36
37
|
score: string;
|
|
37
38
|
reference: string;
|
|
38
39
|
url: string;
|
|
@@ -43,6 +44,17 @@ export interface KanboardTask {
|
|
|
43
44
|
};
|
|
44
45
|
tags?: string[];
|
|
45
46
|
}
|
|
47
|
+
export interface KanboardSubtask {
|
|
48
|
+
id: string;
|
|
49
|
+
title: string;
|
|
50
|
+
status: string;
|
|
51
|
+
status_name?: string;
|
|
52
|
+
time_estimated: string;
|
|
53
|
+
time_spent: string;
|
|
54
|
+
task_id: string;
|
|
55
|
+
user_id: string;
|
|
56
|
+
position: string;
|
|
57
|
+
}
|
|
46
58
|
export interface GetTaskParams {
|
|
47
59
|
task_id: number;
|
|
48
60
|
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// src/commands/upgrade.ts
|
|
2
|
+
import { spawn } from 'child_process';
|
|
2
3
|
import { outputCliError } from '../core/error.js';
|
|
3
4
|
import { outputJson } from '../utils/index.js';
|
|
4
5
|
const PACKAGE_NAME = '@meet-im/lxcli';
|
|
5
6
|
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
7
|
+
export const upgradeDeps = {
|
|
8
|
+
spawn,
|
|
9
|
+
};
|
|
6
10
|
async function getLatestVersion() {
|
|
7
11
|
const res = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`);
|
|
8
12
|
if (!res.ok) {
|
|
@@ -11,22 +15,52 @@ async function getLatestVersion() {
|
|
|
11
15
|
const data = await res.json();
|
|
12
16
|
return data.version;
|
|
13
17
|
}
|
|
18
|
+
async function runNpmUpgrade() {
|
|
19
|
+
const command = process.platform === 'win32' ? 'cmd.exe' : 'npm';
|
|
20
|
+
const args = process.platform === 'win32'
|
|
21
|
+
? ['/c', 'npm', 'install', '-g', `${PACKAGE_NAME}@latest`]
|
|
22
|
+
: ['install', '-g', `${PACKAGE_NAME}@latest`];
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
const child = upgradeDeps.spawn(command, args, {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
});
|
|
27
|
+
child.on('error', reject);
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
resolve();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
reject(new Error(`npm install exited with code ${code ?? 'unknown'}`));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
14
37
|
export function registerUpgradeCommand(program) {
|
|
15
38
|
program
|
|
16
39
|
.command('upgrade')
|
|
17
40
|
.description('Check for updates and show upgrade instructions')
|
|
18
|
-
.
|
|
41
|
+
.option('--check', 'Only check for updates without installing')
|
|
42
|
+
.action(async (options) => {
|
|
19
43
|
try {
|
|
20
44
|
const currentVersion = program.version() || '1.0.0';
|
|
21
45
|
const latestVersion = await getLatestVersion();
|
|
22
46
|
const isLatest = currentVersion === latestVersion;
|
|
47
|
+
if (options.check || isLatest) {
|
|
48
|
+
outputJson({
|
|
49
|
+
currentVersion,
|
|
50
|
+
latestVersion,
|
|
51
|
+
isLatest,
|
|
52
|
+
message: isLatest
|
|
53
|
+
? 'Already on the latest version'
|
|
54
|
+
: `Run "npm install -g ${PACKAGE_NAME}@latest" to upgrade`,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await runNpmUpgrade();
|
|
23
59
|
outputJson({
|
|
24
|
-
currentVersion,
|
|
60
|
+
previousVersion: currentVersion,
|
|
25
61
|
latestVersion,
|
|
26
|
-
|
|
27
|
-
message:
|
|
28
|
-
? 'Already on the latest version'
|
|
29
|
-
: `Run "npm install -g ${PACKAGE_NAME}@latest" to upgrade`,
|
|
62
|
+
upgraded: true,
|
|
63
|
+
message: `Upgraded ${PACKAGE_NAME} to ${latestVersion}`,
|
|
30
64
|
});
|
|
31
65
|
}
|
|
32
66
|
catch (error) {
|
package/dist/core/http.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface RequestResult<T> {
|
|
2
|
+
/** HTTP 状态码 */
|
|
3
|
+
status: number;
|
|
4
|
+
/** 响应数据(成功时) */
|
|
5
|
+
data?: T;
|
|
6
|
+
/** 错误信息(失败时) */
|
|
7
|
+
error?: {
|
|
8
|
+
code: number;
|
|
9
|
+
message: string;
|
|
10
|
+
details?: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface RequestOptionsWithThrow {
|
|
2
14
|
/** gateway 地址(必填) */
|
|
3
15
|
gateway: string;
|
|
4
16
|
/** 请求路径(不含域名) */
|
|
@@ -15,7 +27,30 @@ export interface RequestOptions {
|
|
|
15
27
|
userAgent?: string;
|
|
16
28
|
/** 最大重试次数,默认 3 */
|
|
17
29
|
maxRetries?: number;
|
|
30
|
+
/** 错误时抛出异常而非直接退出,默认 true */
|
|
31
|
+
throwOnError?: true;
|
|
18
32
|
}
|
|
33
|
+
export interface RequestOptionsWithoutThrow {
|
|
34
|
+
/** gateway 地址(必填) */
|
|
35
|
+
gateway: string;
|
|
36
|
+
/** 请求路径(不含域名) */
|
|
37
|
+
path: string;
|
|
38
|
+
/** HTTP 方法,默认 GET */
|
|
39
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
40
|
+
/** URL query 参数 */
|
|
41
|
+
query?: Record<string, string | number | boolean>;
|
|
42
|
+
/** 请求体 */
|
|
43
|
+
body?: Record<string, unknown>;
|
|
44
|
+
/** 自定义 headers(会覆盖默认 headers) */
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
/** User-Agent,默认 lxcli/版本号 */
|
|
47
|
+
userAgent?: string;
|
|
48
|
+
/** 最大重试次数,默认 3 */
|
|
49
|
+
maxRetries?: number;
|
|
50
|
+
/** 错误时返回 RequestResult 而非退出 */
|
|
51
|
+
throwOnError: false;
|
|
52
|
+
}
|
|
53
|
+
export type RequestOptions = RequestOptionsWithThrow | RequestOptionsWithoutThrow;
|
|
19
54
|
/**
|
|
20
55
|
* 通用请求函数
|
|
21
56
|
*
|
|
@@ -24,5 +59,8 @@ export interface RequestOptions {
|
|
|
24
59
|
* 支持自动重试:
|
|
25
60
|
* - 429 (rate limit) 响应时重试,尊重 Retry-After header
|
|
26
61
|
* - 网络错误时重试,使用指数退避
|
|
62
|
+
*
|
|
63
|
+
* 当 throwOnError 为 false 时,返回 RequestResult<T> 而非直接退出。
|
|
27
64
|
*/
|
|
28
|
-
export declare function request<T>(options:
|
|
65
|
+
export declare function request<T>(options: RequestOptionsWithThrow): Promise<T>;
|
|
66
|
+
export declare function request<T>(options: RequestOptionsWithoutThrow): Promise<RequestResult<T>>;
|
package/dist/core/http.js
CHANGED
|
@@ -40,17 +40,8 @@ function joinUrl(gateway, path) {
|
|
|
40
40
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
41
41
|
return `${normalizedGateway}${normalizedPath}`;
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
44
|
-
* 通用请求函数
|
|
45
|
-
*
|
|
46
|
-
* 支持灵活传递 gateway、路径、方法、参数、UA 等。
|
|
47
|
-
*
|
|
48
|
-
* 支持自动重试:
|
|
49
|
-
* - 429 (rate limit) 响应时重试,尊重 Retry-After header
|
|
50
|
-
* - 网络错误时重试,使用指数退避
|
|
51
|
-
*/
|
|
52
43
|
export async function request(options) {
|
|
53
|
-
const { gateway, path, method = 'GET', query, body, headers = {}, userAgent = `lxcli/${pkg.version}`, maxRetries = MAX_RETRIES, } = options;
|
|
44
|
+
const { gateway, path, method = 'GET', query, body, headers = {}, userAgent = `lxcli/${pkg.version}`, maxRetries = MAX_RETRIES, throwOnError = true, } = options;
|
|
54
45
|
// 构建 URL(gateway + path + query)
|
|
55
46
|
let url = joinUrl(gateway, path);
|
|
56
47
|
if (query && Object.keys(query).length > 0) {
|
|
@@ -65,6 +56,13 @@ export async function request(options) {
|
|
|
65
56
|
};
|
|
66
57
|
const requestBody = body ? JSON.stringify(body) : undefined;
|
|
67
58
|
let lastError = null;
|
|
59
|
+
// 辅助函数:处理错误响应
|
|
60
|
+
const makeErrorResult = (status, code, message, details) => {
|
|
61
|
+
if (throwOnError) {
|
|
62
|
+
outputApiError(code, message, details, status);
|
|
63
|
+
}
|
|
64
|
+
return { status, error: { code, message, details } };
|
|
65
|
+
};
|
|
68
66
|
// 发送请求(带重试)
|
|
69
67
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
70
68
|
try {
|
|
@@ -94,19 +92,32 @@ export async function request(options) {
|
|
|
94
92
|
await sleep(delay);
|
|
95
93
|
continue;
|
|
96
94
|
}
|
|
97
|
-
|
|
95
|
+
const result = makeErrorResult(response.status, errorResponse?.error?.code || response.status, errorResponse?.error?.message || 'Request failed', errorResponse?.error?.details);
|
|
96
|
+
if (!throwOnError)
|
|
97
|
+
return result;
|
|
98
|
+
// throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
|
|
98
99
|
}
|
|
99
100
|
let responseData;
|
|
100
101
|
try {
|
|
101
102
|
responseData = parseJsonResponse(responseText);
|
|
102
103
|
}
|
|
103
104
|
catch {
|
|
104
|
-
|
|
105
|
+
const result = makeErrorResult(response.status, response.status, `Invalid JSON response: ${responseText}`);
|
|
106
|
+
if (!throwOnError)
|
|
107
|
+
return result;
|
|
108
|
+
// throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
|
|
105
109
|
}
|
|
106
110
|
logger.debug('Response', { status: response.status, data: responseData, attempt });
|
|
107
111
|
if (!response.ok) {
|
|
108
112
|
const errorResponse = responseData;
|
|
109
|
-
|
|
113
|
+
const result = makeErrorResult(response.status, errorResponse?.error?.code || response.status, errorResponse?.error?.message || 'Request failed', errorResponse?.error?.details);
|
|
114
|
+
if (!throwOnError)
|
|
115
|
+
return result;
|
|
116
|
+
// throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
|
|
117
|
+
}
|
|
118
|
+
// throwOnError 为 false 时返回完整结果
|
|
119
|
+
if (!throwOnError) {
|
|
120
|
+
return { status: response.status, data: responseData };
|
|
110
121
|
}
|
|
111
122
|
return responseData;
|
|
112
123
|
}
|
|
@@ -125,7 +136,9 @@ export async function request(options) {
|
|
|
125
136
|
}
|
|
126
137
|
if (isNetworkError) {
|
|
127
138
|
logger.error('Request failed', error);
|
|
128
|
-
|
|
139
|
+
const result = makeErrorResult(500, 500, error.message);
|
|
140
|
+
if (!throwOnError)
|
|
141
|
+
return result;
|
|
129
142
|
}
|
|
130
143
|
throw error;
|
|
131
144
|
}
|
|
@@ -133,7 +146,9 @@ export async function request(options) {
|
|
|
133
146
|
// 所有重试都失败了
|
|
134
147
|
if (lastError) {
|
|
135
148
|
logger.error('All retries failed', lastError);
|
|
136
|
-
|
|
149
|
+
const result = makeErrorResult(500, 500, lastError.message);
|
|
150
|
+
if (!throwOnError)
|
|
151
|
+
return result;
|
|
137
152
|
}
|
|
138
153
|
// 不应该到达这里,但 TypeScript 需要返回值
|
|
139
154
|
throw new Error('Request failed after all retries');
|