@ultikits/cli 1.2.0 → 2.0.1
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/CHANGELOG.md +41 -0
- package/README.md +179 -38
- package/dist/bin/ultikits.js +3 -1
- package/dist/src/commands/login.d.ts +13 -0
- package/dist/src/commands/login.js +118 -97
- package/dist/src/commands/logout.d.ts +2 -0
- package/dist/src/commands/logout.js +43 -6
- package/dist/src/commands/modules.d.ts +35 -0
- package/dist/src/commands/modules.js +224 -281
- package/dist/src/commands/publish.d.ts +28 -0
- package/dist/src/commands/publish.js +195 -131
- package/dist/src/commands/versions.d.ts +10 -0
- package/dist/src/commands/versions.js +101 -67
- package/dist/src/commands/whoami.d.ts +6 -0
- package/dist/src/commands/whoami.js +45 -26
- package/dist/src/lib/api-client.d.ts +56 -11
- package/dist/src/lib/api-client.js +146 -72
- package/dist/src/lib/command-errors.d.ts +8 -0
- package/dist/src/lib/command-errors.js +54 -0
- package/dist/src/lib/config.d.ts +41 -4
- package/dist/src/lib/config.js +93 -14
- package/dist/src/lib/confirm.d.ts +22 -0
- package/dist/src/lib/confirm.js +47 -0
- package/dist/src/lib/logger.d.ts +6 -0
- package/dist/src/lib/logger.js +4 -0
- package/dist/src/lib/mask.d.ts +7 -0
- package/dist/src/lib/mask.js +18 -0
- package/dist/src/lib/paging.d.ts +5 -0
- package/dist/src/lib/paging.js +16 -0
- package/dist/src/types.d.ts +143 -19
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Confirmation for destructive commands (modules delete, versions withdraw).
|
|
2
|
+
//
|
|
3
|
+
// The user must type the exact target string: the module identifier, or
|
|
4
|
+
// <identifier>@<version> for a withdrawal. On a terminal without -y the CLI asks
|
|
5
|
+
// for it; otherwise --confirm <same string> is required. -y alone is never enough,
|
|
6
|
+
// so a script that adds -y to every command cannot delete anything by accident.
|
|
7
|
+
// Callers must check the result before sending any request.
|
|
8
|
+
import readline from 'node:readline/promises';
|
|
9
|
+
export async function requireConfirmation(options) {
|
|
10
|
+
const { expected, flagValue } = options;
|
|
11
|
+
if (flagValue !== undefined) {
|
|
12
|
+
if (flagValue === expected)
|
|
13
|
+
return { ok: true };
|
|
14
|
+
return { ok: false, reason: `--confirm 的值必须与 ${expected} 完全一致(区分大小写),未发送任何请求。` };
|
|
15
|
+
}
|
|
16
|
+
if (!options.interactive || options.yes) {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
reason: `非交互模式或使用了 -y 时,必须加 --confirm ${expected} 确认(单有 -y 不够),未发送任何请求。`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const ask = options.ask ?? askOnTerminal;
|
|
23
|
+
let answer;
|
|
24
|
+
try {
|
|
25
|
+
answer = await ask(`${options.warning}\n请输入 ${expected} 确认(输入其他内容取消):`);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ctrl-C / Ctrl-D: readline rejects (AbortError on Node 24); treat it as a cancel.
|
|
29
|
+
return { ok: false, reason: '已取消(输入被中断),未发送任何请求。' };
|
|
30
|
+
}
|
|
31
|
+
if (answer === expected)
|
|
32
|
+
return { ok: true };
|
|
33
|
+
return { ok: false, reason: `输入与 ${expected} 不一致,已取消,未发送任何请求。` };
|
|
34
|
+
}
|
|
35
|
+
export function isInteractiveTerminal() {
|
|
36
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
37
|
+
}
|
|
38
|
+
async function askOnTerminal(question) {
|
|
39
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40
|
+
try {
|
|
41
|
+
return await rl.question(question);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
rl.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=confirm.js.map
|
package/dist/src/lib/logger.d.ts
CHANGED
|
@@ -3,3 +3,9 @@ export declare function error(msg: string): void;
|
|
|
3
3
|
export declare function info(msg: string): void;
|
|
4
4
|
export declare function warn(msg: string): void;
|
|
5
5
|
export declare function spinner(text: string): import("ora").Ora;
|
|
6
|
+
/** Output sink for commands; tests pass a capturing one. */
|
|
7
|
+
export interface Io {
|
|
8
|
+
out(msg: string): void;
|
|
9
|
+
err(msg: string): void;
|
|
10
|
+
}
|
|
11
|
+
export declare const consoleIo: Io;
|
package/dist/src/lib/logger.js
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Masked form of a credential for display: known prefix + first 4 + •••• + last 4.
|
|
3
|
+
* Anything that does not look like a UltiCloud credential is fully masked.
|
|
4
|
+
*/
|
|
5
|
+
export declare function maskCredential(value: string): string;
|
|
6
|
+
/** Replaces every occurrence of `secret` in `text` with its masked form. */
|
|
7
|
+
export declare function redact(text: string, secret: string | undefined | null): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Masked form of a credential for display: known prefix + first 4 + •••• + last 4.
|
|
3
|
+
* Anything that does not look like a UltiCloud credential is fully masked.
|
|
4
|
+
*/
|
|
5
|
+
export function maskCredential(value) {
|
|
6
|
+
const match = /^(uc[cdu]_)(.+)$/.exec(value);
|
|
7
|
+
if (!match || match[2].length < 12)
|
|
8
|
+
return '••••';
|
|
9
|
+
const body = match[2];
|
|
10
|
+
return `${match[1]}${body.slice(0, 4)}••••${body.slice(-4)}`;
|
|
11
|
+
}
|
|
12
|
+
/** Replaces every occurrence of `secret` in `text` with its masked form. */
|
|
13
|
+
export function redact(text, secret) {
|
|
14
|
+
if (!secret)
|
|
15
|
+
return text;
|
|
16
|
+
return text.split(secret).join(maskCredential(secret));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=mask.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Page } from '../types.js';
|
|
2
|
+
/** Largest pageSize the contract allows for the list routes. */
|
|
3
|
+
export declare const MAX_PAGE_SIZE = 100;
|
|
4
|
+
/** Fetches page 1, 2, … until `total` items are collected or a page comes back empty. */
|
|
5
|
+
export declare function fetchAllPages<T>(fetchPage: (page: number) => Promise<Page<T>>): Promise<T[]>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Largest pageSize the contract allows for the list routes. */
|
|
2
|
+
export const MAX_PAGE_SIZE = 100;
|
|
3
|
+
/** Hard stop so a server that keeps reporting a larger total cannot loop forever. */
|
|
4
|
+
const MAX_PAGES = 1000;
|
|
5
|
+
/** Fetches page 1, 2, … until `total` items are collected or a page comes back empty. */
|
|
6
|
+
export async function fetchAllPages(fetchPage) {
|
|
7
|
+
const items = [];
|
|
8
|
+
for (let page = 1; page <= MAX_PAGES; page++) {
|
|
9
|
+
const result = await fetchPage(page);
|
|
10
|
+
items.push(...result.items);
|
|
11
|
+
if (result.items.length === 0 || items.length >= result.total)
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
return items;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=paging.js.map
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
api_url: string;
|
|
12
|
-
}
|
|
13
|
-
export type Credentials = OAuthCredentials | AccessTokenCredentials;
|
|
1
|
+
/** Credential kinds the UltiCloud machine API accepts (contract: developer-client 1.1.0). */
|
|
2
|
+
export type CredentialKind = 'cli' | 'developer';
|
|
3
|
+
/** ~/.ultikits/credentials.json, format version 2. */
|
|
4
|
+
export interface StoredCredential {
|
|
5
|
+
version: 2;
|
|
6
|
+
kind: CredentialKind;
|
|
7
|
+
credential: string;
|
|
8
|
+
credentialId?: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
}
|
|
14
11
|
export interface ProjectConfig {
|
|
15
12
|
identifyString: string;
|
|
16
13
|
name: string;
|
|
@@ -25,17 +22,13 @@ export interface PluginYml {
|
|
|
25
22
|
}
|
|
26
23
|
export interface ModuleMetadata {
|
|
27
24
|
identifyString: string;
|
|
28
|
-
name
|
|
25
|
+
/** Display name; only needed when publish has to create the module. */
|
|
26
|
+
name?: string;
|
|
29
27
|
version: string;
|
|
30
28
|
shortDescription?: string;
|
|
31
29
|
changelog?: string;
|
|
32
30
|
categoryId?: number;
|
|
33
31
|
}
|
|
34
|
-
export interface ApiResponse<T = any> {
|
|
35
|
-
code: string;
|
|
36
|
-
msg: string;
|
|
37
|
-
data?: T;
|
|
38
|
-
}
|
|
39
32
|
export interface CreateParams {
|
|
40
33
|
moduleName: string;
|
|
41
34
|
packageName: string;
|
|
@@ -43,3 +36,134 @@ export interface CreateParams {
|
|
|
43
36
|
author: string;
|
|
44
37
|
apiVersion: string;
|
|
45
38
|
}
|
|
39
|
+
export interface DeviceAuthorization {
|
|
40
|
+
deviceCode: string;
|
|
41
|
+
userCode: string;
|
|
42
|
+
verificationUri: string;
|
|
43
|
+
verificationUriComplete: string;
|
|
44
|
+
expiresIn: number;
|
|
45
|
+
interval: number;
|
|
46
|
+
}
|
|
47
|
+
export interface DeviceTokenResult {
|
|
48
|
+
kind: 'cli';
|
|
49
|
+
credential: string;
|
|
50
|
+
credentialId: string;
|
|
51
|
+
userId: number;
|
|
52
|
+
}
|
|
53
|
+
export interface Whoami {
|
|
54
|
+
id: number;
|
|
55
|
+
username: string;
|
|
56
|
+
developerName: string;
|
|
57
|
+
/** Optional in the contract; treat absence as "unknown". */
|
|
58
|
+
credential?: {
|
|
59
|
+
kind: CredentialKind;
|
|
60
|
+
scope: 'upload' | 'manage';
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface UploadTicket {
|
|
64
|
+
uploadUrl: string;
|
|
65
|
+
method: 'PUT';
|
|
66
|
+
headers: Record<string, string>;
|
|
67
|
+
expiresAt: string;
|
|
68
|
+
maxBytes: number;
|
|
69
|
+
}
|
|
70
|
+
interface DevModuleVersionBase {
|
|
71
|
+
version: string;
|
|
72
|
+
sizeBytes: number;
|
|
73
|
+
sha256: string;
|
|
74
|
+
}
|
|
75
|
+
/** Status-specific fields from the frozen developer-client 1.1.0 export. */
|
|
76
|
+
export type DevModuleVersion = DevModuleVersionBase & ({
|
|
77
|
+
status: 'pending_upload';
|
|
78
|
+
withdrawnAt: null;
|
|
79
|
+
isLatest: false;
|
|
80
|
+
downloadUrl: null;
|
|
81
|
+
} | {
|
|
82
|
+
status: 'published';
|
|
83
|
+
withdrawnAt: null;
|
|
84
|
+
isLatest: boolean;
|
|
85
|
+
downloadUrl: string | null;
|
|
86
|
+
} | {
|
|
87
|
+
status: 'withdrawn';
|
|
88
|
+
withdrawnAt: string;
|
|
89
|
+
isLatest: false;
|
|
90
|
+
downloadUrl: null;
|
|
91
|
+
});
|
|
92
|
+
export interface CreateVersionBody {
|
|
93
|
+
version: string;
|
|
94
|
+
changelog: string | null;
|
|
95
|
+
sha256: string;
|
|
96
|
+
sizeBytes: number;
|
|
97
|
+
}
|
|
98
|
+
export interface CreateVersionResult {
|
|
99
|
+
version: Extract<DevModuleVersion, {
|
|
100
|
+
status: 'pending_upload';
|
|
101
|
+
}>;
|
|
102
|
+
upload: UploadTicket;
|
|
103
|
+
}
|
|
104
|
+
export interface CreateModuleBody {
|
|
105
|
+
name: string;
|
|
106
|
+
identifier: string;
|
|
107
|
+
shortDescription: string;
|
|
108
|
+
description: string | null;
|
|
109
|
+
categoryId: number | null;
|
|
110
|
+
compatibleApiVersions: string | null;
|
|
111
|
+
tags: string[];
|
|
112
|
+
}
|
|
113
|
+
export interface DeveloperModule {
|
|
114
|
+
id: string;
|
|
115
|
+
identifier: string;
|
|
116
|
+
name: string;
|
|
117
|
+
}
|
|
118
|
+
export interface Page<T> {
|
|
119
|
+
items: T[];
|
|
120
|
+
total: number;
|
|
121
|
+
page: number;
|
|
122
|
+
pageSize: number;
|
|
123
|
+
}
|
|
124
|
+
/** developer.modules.list item; also the body of update and restore. */
|
|
125
|
+
export interface ModuleListItem extends DeveloperModule {
|
|
126
|
+
shortDescription: string;
|
|
127
|
+
iconUrl: string | null;
|
|
128
|
+
category: {
|
|
129
|
+
id: number;
|
|
130
|
+
name: string;
|
|
131
|
+
} | null;
|
|
132
|
+
downloads: number;
|
|
133
|
+
latestVersion: string | null;
|
|
134
|
+
updatedAt: string;
|
|
135
|
+
}
|
|
136
|
+
/** PATCH body: every key optional, only the given ones are sent. */
|
|
137
|
+
export interface UpdateModuleBody {
|
|
138
|
+
name?: string;
|
|
139
|
+
shortDescription?: string;
|
|
140
|
+
description?: string | null;
|
|
141
|
+
categoryId?: number | null;
|
|
142
|
+
}
|
|
143
|
+
export interface ModuleDeletion {
|
|
144
|
+
deletedAt: string;
|
|
145
|
+
restorableUntil: string;
|
|
146
|
+
}
|
|
147
|
+
export type WithdrawnVersion = Extract<DevModuleVersion, {
|
|
148
|
+
status: 'withdrawn';
|
|
149
|
+
}>;
|
|
150
|
+
/** catalogue.modules.versions.list item. */
|
|
151
|
+
export interface PublicModuleVersion {
|
|
152
|
+
version: string;
|
|
153
|
+
isLatest: boolean;
|
|
154
|
+
publishedAt: string;
|
|
155
|
+
sizeBytes: number;
|
|
156
|
+
sha256: string;
|
|
157
|
+
changelog: string | null;
|
|
158
|
+
apiLevel: number | null;
|
|
159
|
+
download: {
|
|
160
|
+
kind: 'public';
|
|
161
|
+
url: string;
|
|
162
|
+
} | {
|
|
163
|
+
kind: 'license_required';
|
|
164
|
+
} | {
|
|
165
|
+
kind: 'withdrawn';
|
|
166
|
+
withdrawnAt: string;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export {};
|