@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
|
@@ -1,12 +1,49 @@
|
|
|
1
|
-
import { deleteCredentials } from '../lib/config.js';
|
|
2
|
-
import
|
|
1
|
+
import { assertAllowedApiUrl, deleteCredentials, readCredentials } from '../lib/config.js';
|
|
2
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
3
|
+
import { consoleIo } from '../lib/logger.js';
|
|
3
4
|
export function registerLogoutCommand(program) {
|
|
4
5
|
program
|
|
5
6
|
.command('logout')
|
|
6
|
-
.description('Remove stored
|
|
7
|
-
.action(() => {
|
|
8
|
-
|
|
9
|
-
log.success('Logged out. Credentials removed.');
|
|
7
|
+
.description('Remove the stored credential (a CLI credential is also revoked on the server)')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
process.exitCode = await runLogout({});
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
+
export async function runLogout(_options, deps = {}) {
|
|
13
|
+
const io = deps.io ?? consoleIo;
|
|
14
|
+
const env = deps.env ?? process.env;
|
|
15
|
+
const state = readCredentials(deps.configDir);
|
|
16
|
+
let code = 0;
|
|
17
|
+
if (state.status === 'none') {
|
|
18
|
+
io.out('未登录,没有要删除的凭据。');
|
|
19
|
+
}
|
|
20
|
+
else if (state.status === 'legacy') {
|
|
21
|
+
deleteCredentials(deps.configDir);
|
|
22
|
+
io.out('已删除旧版凭据文件。');
|
|
23
|
+
}
|
|
24
|
+
else if (state.credential.kind === 'cli') {
|
|
25
|
+
// Revoke at the API that issued the credential, never at an overridden URL.
|
|
26
|
+
try {
|
|
27
|
+
const client = new ApiClient({ apiUrl: assertAllowedApiUrl(state.credential.apiUrl), credential: state.credential.credential });
|
|
28
|
+
await client.revokeCurrentCredential();
|
|
29
|
+
io.out('已在服务器端撤销 CLI 凭据。');
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
io.err('本地已删除,服务器端未撤销,可在账号页设备列表撤销。');
|
|
33
|
+
code = 1;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
deleteCredentials(deps.configDir);
|
|
37
|
+
}
|
|
38
|
+
if (code === 0)
|
|
39
|
+
io.out('已登出,本地凭据已删除。');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
deleteCredentials(deps.configDir);
|
|
43
|
+
io.out('已登出,本地凭据已删除。开发者密钥本身仍然有效;如需作废,请在开发者中心轮换密钥。');
|
|
44
|
+
}
|
|
45
|
+
if (env.ULTIKITS_TOKEN)
|
|
46
|
+
io.out('注意:环境变量 ULTIKITS_TOKEN 仍已设置,命令会继续使用它。');
|
|
47
|
+
return code;
|
|
48
|
+
}
|
|
12
49
|
//# sourceMappingURL=logout.js.map
|
|
@@ -1,2 +1,37 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import type { CommandDeps } from './publish.js';
|
|
3
|
+
/** Exit code for a usage error or a refused confirmation; nothing was sent. */
|
|
4
|
+
export declare const EXIT_USAGE = 2;
|
|
5
|
+
export interface ManageDeps extends CommandDeps {
|
|
6
|
+
/** Overrides terminal detection (tests). */
|
|
7
|
+
interactive?: boolean;
|
|
8
|
+
/** Asks one question on the terminal (tests pass a stub). */
|
|
9
|
+
ask?: (question: string) => Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
interface ApiUrlOption {
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CreateOptions extends ApiUrlOption {
|
|
15
|
+
id?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
shortDescription?: string;
|
|
18
|
+
categoryId?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface UpdateOptions extends ApiUrlOption {
|
|
21
|
+
name?: string;
|
|
22
|
+
shortDescription?: string;
|
|
23
|
+
descriptionFile?: string;
|
|
24
|
+
categoryId?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DestructiveOptions extends ApiUrlOption {
|
|
27
|
+
confirm?: string;
|
|
28
|
+
yes?: boolean;
|
|
29
|
+
}
|
|
2
30
|
export declare function registerModulesCommand(program: Command): void;
|
|
31
|
+
export declare function runModulesList(options: ApiUrlOption, deps?: ManageDeps): Promise<number>;
|
|
32
|
+
export declare function runModulesInfo(identifier: string, options: ApiUrlOption, deps?: ManageDeps): Promise<number>;
|
|
33
|
+
export declare function runModulesCreate(options: CreateOptions, deps?: ManageDeps): Promise<number>;
|
|
34
|
+
export declare function runModulesUpdate(identifier: string, options: UpdateOptions, deps?: ManageDeps): Promise<number>;
|
|
35
|
+
export declare function runModulesDelete(identifier: string, options: DestructiveOptions, deps?: ManageDeps): Promise<number>;
|
|
36
|
+
export declare function runModulesRestore(identifier: string, options: ApiUrlOption, deps?: ManageDeps): Promise<number>;
|
|
37
|
+
export {};
|
|
@@ -1,311 +1,254 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import
|
|
3
|
-
import { readCredentials } from '../lib/config.js';
|
|
2
|
+
import { resolveAuth } from '../lib/config.js';
|
|
4
3
|
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
import { readProjectConfig } from '../lib/project-config.js';
|
|
5
|
+
import { redact } from '../lib/mask.js';
|
|
6
|
+
import { consoleIo } from '../lib/logger.js';
|
|
7
|
+
import { fetchAllPages, MAX_PAGE_SIZE } from '../lib/paging.js';
|
|
8
|
+
import { isInteractiveTerminal, requireConfirmation } from '../lib/confirm.js';
|
|
9
|
+
import { describeCommandError } from '../lib/command-errors.js';
|
|
10
|
+
/** Exit code for a usage error or a refused confirmation; nothing was sent. */
|
|
11
|
+
export const EXIT_USAGE = 2;
|
|
12
|
+
const API_URL_HELP = 'UltiCloud API URL (default: ULTIKITS_API_URL, stored login, or https://api.ultikits.com)';
|
|
13
|
+
const MANAGE_NOTE = '(needs a manage credential: `ultikits login`, or a developer key rotated to the manage scope)';
|
|
14
14
|
export function registerModulesCommand(program) {
|
|
15
15
|
const modules = program
|
|
16
16
|
.command('modules')
|
|
17
|
-
.description('Manage your
|
|
17
|
+
.description('Manage your UltiCloud modules')
|
|
18
|
+
.addHelpText('after', '\nDeleted modules can be restored for 30 days with `ultikits modules restore <identifier>`.\n' +
|
|
19
|
+
'The web panel has no restore: the CLI (or the API) is the only way back.');
|
|
18
20
|
modules
|
|
19
21
|
.command('list')
|
|
20
22
|
.description('List your modules')
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
spin.start();
|
|
25
|
-
const res = await client.get('/developer/modules');
|
|
26
|
-
if (res.code !== '200') {
|
|
27
|
-
spin.fail('Failed to list modules: ' + res.msg);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
spin.stop();
|
|
31
|
-
const modulesList = res.data;
|
|
32
|
-
if (modulesList.length === 0) {
|
|
33
|
-
log.info('No modules found. Create one with `ultikits publish`.');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
console.log();
|
|
37
|
-
for (const mod of modulesList) {
|
|
38
|
-
const version = mod.latestVersion?.version ?? 'no versions';
|
|
39
|
-
console.log(` ${mod.identifyString} — ${mod.name} (${version})`);
|
|
40
|
-
}
|
|
41
|
-
console.log();
|
|
42
|
-
log.info(`${modulesList.length} module(s)`);
|
|
23
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
process.exitCode = await runModulesList(options);
|
|
43
26
|
});
|
|
44
27
|
modules
|
|
45
|
-
.command('info <
|
|
46
|
-
.description('Show
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (res.code !== '200' || !res.data) {
|
|
51
|
-
log.error('Module not found: ' + identifyString);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
const mod = res.data;
|
|
55
|
-
const category = mod.categoryId ? (CATEGORY_NAMES[mod.categoryId] ?? `#${mod.categoryId}`) : 'N/A';
|
|
56
|
-
console.log();
|
|
57
|
-
console.log(` Name: ${mod.name}`);
|
|
58
|
-
console.log(` ID: ${mod.identifyString}`);
|
|
59
|
-
console.log(` Summary: ${mod.shortDescription || 'N/A'}`);
|
|
60
|
-
console.log(` Category: ${category}`);
|
|
61
|
-
console.log(` Compatible: ${mod.compatibleVersions || 'N/A'}`);
|
|
62
|
-
console.log(` Tags: ${mod.tags?.length ? mod.tags.join(', ') : 'N/A'}`);
|
|
63
|
-
console.log(` Downloads: ${mod.downloads ?? 0}`);
|
|
64
|
-
console.log(` Version: ${mod.latestVersion?.version ?? 'N/A'}`);
|
|
65
|
-
if (mod.description) {
|
|
66
|
-
const preview = mod.description.length > 200
|
|
67
|
-
? mod.description.slice(0, 200) + '...'
|
|
68
|
-
: mod.description;
|
|
69
|
-
console.log(` Description: ${preview.split('\n')[0]}`);
|
|
70
|
-
const lines = preview.split('\n').slice(1);
|
|
71
|
-
for (const line of lines) {
|
|
72
|
-
console.log(` ${line}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
console.log(` Description: N/A`);
|
|
77
|
-
}
|
|
78
|
-
console.log();
|
|
28
|
+
.command('info <identifier>')
|
|
29
|
+
.description('Show one of your modules')
|
|
30
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
31
|
+
.action(async (identifier, options) => {
|
|
32
|
+
process.exitCode = await runModulesInfo(identifier, options);
|
|
79
33
|
});
|
|
80
34
|
modules
|
|
81
|
-
.command('
|
|
82
|
-
.description(
|
|
83
|
-
.option('--
|
|
84
|
-
.option('--
|
|
85
|
-
.option('--description <text>', '
|
|
86
|
-
.option('--
|
|
87
|
-
.option('--
|
|
88
|
-
.
|
|
89
|
-
.
|
|
90
|
-
.option('-y, --yes', 'Skip confirmation')
|
|
91
|
-
.action(async (identifyString, options) => {
|
|
92
|
-
const client = getClient();
|
|
93
|
-
const spin = log.spinner('Loading module...');
|
|
94
|
-
spin.start();
|
|
95
|
-
const infoRes = await client.get(`/plugin/get?identifyString=${identifyString}`);
|
|
96
|
-
if (infoRes.code !== '200' || !infoRes.data) {
|
|
97
|
-
spin.fail('Module not found: ' + identifyString);
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
spin.succeed(`Found module: ${infoRes.data.name}`);
|
|
101
|
-
const mod = infoRes.data;
|
|
102
|
-
const hasFlags = options.name || options.shortDescription || options.description
|
|
103
|
-
|| options.descriptionFile || options.category || options.compatibleVersions || options.tags;
|
|
104
|
-
let updates;
|
|
105
|
-
if (hasFlags) {
|
|
106
|
-
updates = buildUpdatesFromFlags(options);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
updates = await promptForUpdates(mod);
|
|
110
|
-
}
|
|
111
|
-
if (Object.keys(updates).length === 0) {
|
|
112
|
-
log.info('No changes to apply.');
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// Show summary
|
|
116
|
-
console.log();
|
|
117
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
118
|
-
const display = key === 'description'
|
|
119
|
-
? value.split('\n')[0].slice(0, 80) + (value.length > 80 ? '...' : '')
|
|
120
|
-
: key === 'tags'
|
|
121
|
-
? value.join(', ')
|
|
122
|
-
: String(value);
|
|
123
|
-
console.log(` ${key}: ${display}`);
|
|
124
|
-
}
|
|
125
|
-
console.log();
|
|
126
|
-
if (!options.yes) {
|
|
127
|
-
const { confirm } = await inquirer.prompt([
|
|
128
|
-
{
|
|
129
|
-
type: 'confirm',
|
|
130
|
-
name: 'confirm',
|
|
131
|
-
message: `Update module "${identifyString}"?`,
|
|
132
|
-
default: true,
|
|
133
|
-
},
|
|
134
|
-
]);
|
|
135
|
-
if (!confirm)
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const updateSpin = log.spinner('Updating module...');
|
|
139
|
-
updateSpin.start();
|
|
140
|
-
const res = await client.put(`/developer/modules/${mod.id}`, updates);
|
|
141
|
-
if (res.code !== '200') {
|
|
142
|
-
updateSpin.fail('Failed to update: ' + res.msg);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
updateSpin.succeed(`Updated module: ${identifyString}`);
|
|
35
|
+
.command('create')
|
|
36
|
+
.description(`Create a module ${MANAGE_NOTE}`)
|
|
37
|
+
.option('--id <identifier>', 'Module identifier (default: identifyString in ultikits.json)')
|
|
38
|
+
.option('--name <name>', 'Display name (default: name in ultikits.json)')
|
|
39
|
+
.option('--short-description <text>', 'Short description, up to 200 characters')
|
|
40
|
+
.option('--category-id <id>', 'Category id')
|
|
41
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
process.exitCode = await runModulesCreate(options);
|
|
146
44
|
});
|
|
147
45
|
modules
|
|
148
|
-
.command('
|
|
149
|
-
.description(
|
|
150
|
-
.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
log.success(`Deleted module: ${identifyString}`);
|
|
46
|
+
.command('update <identifier>')
|
|
47
|
+
.description(`Change a module's profile; only the options given are sent ${MANAGE_NOTE}`)
|
|
48
|
+
.option('--name <name>', 'New display name')
|
|
49
|
+
.option('--short-description <text>', 'New short description')
|
|
50
|
+
.option('--description-file <path>', 'File whose content becomes the long description')
|
|
51
|
+
.option('--category-id <id>', 'New category id')
|
|
52
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
53
|
+
.action(async (identifier, options) => {
|
|
54
|
+
process.exitCode = await runModulesUpdate(identifier, options);
|
|
55
|
+
});
|
|
56
|
+
modules
|
|
57
|
+
.command('delete <identifier>')
|
|
58
|
+
.description(`Delete a module; restorable for 30 days with \`ultikits modules restore\` ${MANAGE_NOTE}`)
|
|
59
|
+
.option('--confirm <identifier>', 'Required with -y or without a terminal: the same identifier again')
|
|
60
|
+
.option('-y, --yes', 'Do not prompt (still requires --confirm)')
|
|
61
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
62
|
+
.action(async (identifier, options) => {
|
|
63
|
+
process.exitCode = await runModulesDelete(identifier, options);
|
|
64
|
+
});
|
|
65
|
+
modules
|
|
66
|
+
.command('restore <identifier>')
|
|
67
|
+
.description(`Restore a module deleted in the last 30 days ${MANAGE_NOTE}`)
|
|
68
|
+
.option('--api-url <url>', API_URL_HELP)
|
|
69
|
+
.action(async (identifier, options) => {
|
|
70
|
+
process.exitCode = await runModulesRestore(identifier, options);
|
|
174
71
|
});
|
|
175
72
|
}
|
|
176
|
-
function
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
73
|
+
function authenticate(options, deps) {
|
|
74
|
+
const io = deps.io ?? consoleIo;
|
|
75
|
+
const auth = resolveAuth({ apiUrl: options.apiUrl, env: deps.env ?? process.env, configDir: deps.configDir });
|
|
76
|
+
if (!auth.ok) {
|
|
77
|
+
io.err(auth.message);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return { auth, client: new ApiClient({ apiUrl: auth.apiUrl, credential: auth.credential }), io };
|
|
81
|
+
}
|
|
82
|
+
function fail(a, err, ctx) {
|
|
83
|
+
a.io.err(redact(describeCommandError(err, { ...ctx, apiUrl: a.auth.apiUrl }), a.auth.credential));
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
function listAll(client) {
|
|
87
|
+
return fetchAllPages((page) => client.listModules({ page, pageSize: MAX_PAGE_SIZE }));
|
|
88
|
+
}
|
|
89
|
+
/** Parses a positive integer option; returns undefined when absent, null when invalid. */
|
|
90
|
+
function parseCategoryId(value) {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
return /^[1-9][0-9]{0,15}$/.test(value) ? Number(value) : null;
|
|
94
|
+
}
|
|
95
|
+
export async function runModulesList(options, deps = {}) {
|
|
96
|
+
const a = authenticate(options, deps);
|
|
97
|
+
if (!a)
|
|
98
|
+
return 1;
|
|
99
|
+
let items;
|
|
100
|
+
try {
|
|
101
|
+
items = await listAll(a.client);
|
|
189
102
|
}
|
|
190
|
-
|
|
191
|
-
|
|
103
|
+
catch (err) {
|
|
104
|
+
return fail(a, err, { op: 'list' });
|
|
192
105
|
}
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
log.error('Category must be 1-6');
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
updates.categoryId = id;
|
|
106
|
+
if (items.length === 0) {
|
|
107
|
+
a.io.out('还没有模块:用 ultikits publish 发布(模块不存在时会自动创建),或 ultikits modules create。');
|
|
108
|
+
return 0;
|
|
200
109
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (options.tags) {
|
|
204
|
-
const tags = options.tags.split(',').map((t) => t.trim()).filter(Boolean);
|
|
205
|
-
if (tags.length > 5) {
|
|
206
|
-
log.error('Maximum 5 tags allowed');
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
updates.tags = tags;
|
|
110
|
+
for (const mod of items) {
|
|
111
|
+
a.io.out(` ${mod.identifier} — ${mod.name} (${mod.latestVersion ?? '无已发布版本 / no versions'})`);
|
|
210
112
|
}
|
|
211
|
-
|
|
113
|
+
a.io.out(`共 ${items.length} 个模块 / ${items.length} module(s)`);
|
|
114
|
+
return 0;
|
|
212
115
|
}
|
|
213
|
-
async function
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}]);
|
|
221
|
-
if (name !== mod.name)
|
|
222
|
-
updates.name = name;
|
|
223
|
-
const { shortDesc } = await inquirer.prompt([{
|
|
224
|
-
type: 'input',
|
|
225
|
-
name: 'shortDesc',
|
|
226
|
-
message: 'Short description:',
|
|
227
|
-
default: mod.shortDescription || '',
|
|
228
|
-
}]);
|
|
229
|
-
if (shortDesc !== (mod.shortDescription || ''))
|
|
230
|
-
updates.shortDescription = shortDesc;
|
|
231
|
-
const { descMode } = await inquirer.prompt([{
|
|
232
|
-
type: 'list',
|
|
233
|
-
name: 'descMode',
|
|
234
|
-
message: 'Full description:',
|
|
235
|
-
choices: [
|
|
236
|
-
{ name: 'Keep current', value: 'keep' },
|
|
237
|
-
{ name: 'Enter text', value: 'text' },
|
|
238
|
-
{ name: 'Read from file', value: 'file' },
|
|
239
|
-
],
|
|
240
|
-
}]);
|
|
241
|
-
if (descMode === 'text') {
|
|
242
|
-
const { desc } = await inquirer.prompt([{
|
|
243
|
-
type: 'editor',
|
|
244
|
-
name: 'desc',
|
|
245
|
-
message: 'Enter description (opens editor):',
|
|
246
|
-
default: mod.description || '',
|
|
247
|
-
}]);
|
|
248
|
-
if (desc.trim())
|
|
249
|
-
updates.description = desc.trim();
|
|
116
|
+
export async function runModulesInfo(identifier, options, deps = {}) {
|
|
117
|
+
const a = authenticate(options, deps);
|
|
118
|
+
if (!a)
|
|
119
|
+
return 1;
|
|
120
|
+
let items;
|
|
121
|
+
try {
|
|
122
|
+
items = await listAll(a.client);
|
|
250
123
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
124
|
+
catch (err) {
|
|
125
|
+
return fail(a, err, { op: 'list', identifier });
|
|
126
|
+
}
|
|
127
|
+
const mod = items.find((m) => m.identifier === identifier);
|
|
128
|
+
if (!mod) {
|
|
129
|
+
a.io.err(`你的模块里没有 ${identifier}(用 ultikits modules list 查看;删除后 30 天内可 ultikits modules restore ${identifier})。`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
a.io.out(` 名称 / Name: ${mod.name}`);
|
|
133
|
+
a.io.out(` 标识符 / Identifier: ${mod.identifier}`);
|
|
134
|
+
a.io.out(` 简介 / Summary: ${mod.shortDescription || '-'}`);
|
|
135
|
+
a.io.out(` 分类 / Category: ${mod.category ? `${mod.category.name} (#${mod.category.id})` : '-'}`);
|
|
136
|
+
a.io.out(` 最新版本 / Latest: ${mod.latestVersion ?? '无已发布版本 / none'}`);
|
|
137
|
+
a.io.out(` 下载 / Downloads: ${mod.downloads}`);
|
|
138
|
+
a.io.out(` 更新 / Updated: ${mod.updatedAt}`);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
export async function runModulesCreate(options, deps = {}) {
|
|
142
|
+
const io = deps.io ?? consoleIo;
|
|
143
|
+
const config = readProjectConfig(deps.cwd ?? process.cwd());
|
|
144
|
+
const identifier = options.id ?? config?.identifyString;
|
|
145
|
+
const name = options.name ?? config?.name;
|
|
146
|
+
if (!identifier || !name) {
|
|
147
|
+
io.err('需要模块标识符与名称:用 --id 与 --name 指定,或写进 ultikits.json(identifyString、name)。未发送任何请求。');
|
|
148
|
+
return EXIT_USAGE;
|
|
149
|
+
}
|
|
150
|
+
const flagCategory = parseCategoryId(options.categoryId);
|
|
151
|
+
if (flagCategory === null) {
|
|
152
|
+
io.err(`--category-id 必须是正整数:${options.categoryId}。未发送任何请求。`);
|
|
153
|
+
return EXIT_USAGE;
|
|
154
|
+
}
|
|
155
|
+
const a = authenticate(options, deps);
|
|
156
|
+
if (!a)
|
|
157
|
+
return 1;
|
|
158
|
+
try {
|
|
159
|
+
const created = await a.client.createModule({
|
|
160
|
+
name,
|
|
161
|
+
identifier,
|
|
162
|
+
shortDescription: options.shortDescription ?? config?.shortDescription ?? '',
|
|
163
|
+
description: null,
|
|
164
|
+
categoryId: flagCategory ?? config?.categoryId ?? null,
|
|
165
|
+
compatibleApiVersions: null,
|
|
166
|
+
tags: [],
|
|
167
|
+
});
|
|
168
|
+
a.io.out(`已创建模块 ${created.identifier}(${created.name})。用 ultikits publish 发布第一个版本。`);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
return fail(a, err, { op: 'create', identifier });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export async function runModulesUpdate(identifier, options, deps = {}) {
|
|
176
|
+
const io = deps.io ?? consoleIo;
|
|
177
|
+
const body = {};
|
|
178
|
+
if (options.name !== undefined)
|
|
179
|
+
body.name = options.name;
|
|
180
|
+
if (options.shortDescription !== undefined)
|
|
181
|
+
body.shortDescription = options.shortDescription;
|
|
182
|
+
if (options.descriptionFile !== undefined) {
|
|
183
|
+
try {
|
|
184
|
+
body.description = fs.readFileSync(options.descriptionFile, 'utf8');
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
io.err(`读不到描述文件:${options.descriptionFile}。未发送任何请求。`);
|
|
188
|
+
return EXIT_USAGE;
|
|
262
189
|
}
|
|
263
190
|
}
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
choices: [
|
|
269
|
-
{ name: `Keep current (${CATEGORY_NAMES[mod.categoryId] ?? 'N/A'})`, value: 0 },
|
|
270
|
-
...Object.entries(CATEGORY_NAMES).map(([id, label]) => ({
|
|
271
|
-
name: `${id}. ${label}`,
|
|
272
|
-
value: parseInt(id),
|
|
273
|
-
})),
|
|
274
|
-
],
|
|
275
|
-
}]);
|
|
276
|
-
if (category !== 0 && category !== mod.categoryId)
|
|
277
|
-
updates.categoryId = category;
|
|
278
|
-
const { compat } = await inquirer.prompt([{
|
|
279
|
-
type: 'input',
|
|
280
|
-
name: 'compat',
|
|
281
|
-
message: 'Compatible MC versions:',
|
|
282
|
-
default: mod.compatibleVersions || '',
|
|
283
|
-
}]);
|
|
284
|
-
if (compat !== (mod.compatibleVersions || ''))
|
|
285
|
-
updates.compatibleVersions = compat;
|
|
286
|
-
const currentTags = mod.tags?.join(', ') || '';
|
|
287
|
-
const { tags } = await inquirer.prompt([{
|
|
288
|
-
type: 'input',
|
|
289
|
-
name: 'tags',
|
|
290
|
-
message: 'Tags (comma-separated, max 5):',
|
|
291
|
-
default: currentTags,
|
|
292
|
-
}]);
|
|
293
|
-
const tagList = tags.split(',').map((t) => t.trim()).filter(Boolean);
|
|
294
|
-
if (tags !== currentTags && tagList.length <= 5) {
|
|
295
|
-
updates.tags = tagList;
|
|
191
|
+
const category = parseCategoryId(options.categoryId);
|
|
192
|
+
if (category === null) {
|
|
193
|
+
io.err(`--category-id 必须是正整数:${options.categoryId}。未发送任何请求。`);
|
|
194
|
+
return EXIT_USAGE;
|
|
296
195
|
}
|
|
297
|
-
|
|
298
|
-
|
|
196
|
+
if (category !== undefined)
|
|
197
|
+
body.categoryId = category;
|
|
198
|
+
if (Object.keys(body).length === 0) {
|
|
199
|
+
io.err('没有要改的内容:至少给出 --name、--short-description、--description-file 或 --category-id 之一。未发送任何请求。');
|
|
200
|
+
return EXIT_USAGE;
|
|
201
|
+
}
|
|
202
|
+
const a = authenticate(options, deps);
|
|
203
|
+
if (!a)
|
|
204
|
+
return 1;
|
|
205
|
+
try {
|
|
206
|
+
await a.client.updateModule(identifier, body);
|
|
207
|
+
a.io.out(`已更新模块 ${identifier}:${Object.keys(body).join(', ')}。`);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
return fail(a, err, { op: 'update', identifier });
|
|
299
212
|
}
|
|
300
|
-
return updates;
|
|
301
213
|
}
|
|
302
|
-
function
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
214
|
+
export async function runModulesDelete(identifier, options, deps = {}) {
|
|
215
|
+
const io = deps.io ?? consoleIo;
|
|
216
|
+
const confirmed = await requireConfirmation({
|
|
217
|
+
expected: identifier,
|
|
218
|
+
flagValue: options.confirm,
|
|
219
|
+
yes: options.yes,
|
|
220
|
+
interactive: deps.interactive ?? isInteractiveTerminal(),
|
|
221
|
+
warning: `将删除模块 ${identifier}:它会立刻从目录、下载与你的模块列表中消失。` +
|
|
222
|
+
`30 天内可用 ultikits modules restore ${identifier} 恢复(网页面板不提供恢复),之后永久清除。`,
|
|
223
|
+
ask: deps.ask,
|
|
224
|
+
});
|
|
225
|
+
if (!confirmed.ok) {
|
|
226
|
+
io.err(confirmed.reason);
|
|
227
|
+
return EXIT_USAGE;
|
|
228
|
+
}
|
|
229
|
+
const a = authenticate(options, deps);
|
|
230
|
+
if (!a)
|
|
231
|
+
return 1;
|
|
232
|
+
try {
|
|
233
|
+
const result = await a.client.deleteModule(identifier);
|
|
234
|
+
a.io.out(`已删除 ${identifier}。${result.restorableUntil} 前可用 ultikits modules restore ${identifier} 恢复(网页面板不提供恢复)。`);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return fail(a, err, { op: 'delete', identifier });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
export async function runModulesRestore(identifier, options, deps = {}) {
|
|
242
|
+
const a = authenticate(options, deps);
|
|
243
|
+
if (!a)
|
|
244
|
+
return 1;
|
|
245
|
+
try {
|
|
246
|
+
const mod = await a.client.restoreModule(identifier);
|
|
247
|
+
a.io.out(`已恢复 ${mod.identifier}(${mod.name})。`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return fail(a, err, { op: 'restore', identifier });
|
|
308
252
|
}
|
|
309
|
-
return client;
|
|
310
253
|
}
|
|
311
254
|
//# sourceMappingURL=modules.js.map
|