@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.
@@ -1,12 +1,49 @@
1
- import { deleteCredentials } from '../lib/config.js';
2
- import * as log from '../lib/logger.js';
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 credentials')
7
- .action(() => {
8
- deleteCredentials();
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 inquirer from 'inquirer';
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 * as log from '../lib/logger.js';
6
- const CATEGORY_NAMES = {
7
- 1: 'Utilities',
8
- 2: 'Economy',
9
- 3: 'World Management',
10
- 4: 'Social & Chat',
11
- 5: 'Permissions & Security',
12
- 6: 'Gameplay',
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 marketplace modules');
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
- .action(async () => {
22
- const client = getClient();
23
- const spin = log.spinner('Loading modules...');
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 <identifyString>')
46
- .description('Show module details')
47
- .action(async (identifyString) => {
48
- const client = getClient();
49
- const res = await client.get(`/plugin/get?identifyString=${identifyString}`);
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('update <identifyString>')
82
- .description('Update module metadata')
83
- .option('--name <name>', 'Display name')
84
- .option('--short-description <text>', 'Short description (one-liner)')
85
- .option('--description <text>', 'Full description (Markdown)')
86
- .option('--description-file <path>', 'Read full description from a file')
87
- .option('--category <id>', 'Category ID (1-6)')
88
- .option('--compatible-versions <range>', 'Compatible MC versions (e.g. "1.8-1.21+")')
89
- .option('--tags <csv>', 'Comma-separated tags (max 5)')
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('delete <identifyString>')
149
- .description('Delete a module')
150
- .action(async (identifyString) => {
151
- const client = getClient();
152
- // Get module ID first
153
- const infoRes = await client.get(`/plugin/get?identifyString=${identifyString}`);
154
- if (infoRes.code !== '200' || !infoRes.data) {
155
- log.error('Module not found: ' + identifyString);
156
- process.exit(1);
157
- }
158
- const { confirm } = await inquirer.prompt([
159
- {
160
- type: 'confirm',
161
- name: 'confirm',
162
- message: `Delete module "${identifyString}"? This cannot be undone.`,
163
- default: false,
164
- },
165
- ]);
166
- if (!confirm)
167
- return;
168
- const res = await client.delete(`/developer/modules/${infoRes.data.id}`);
169
- if (res.code !== '200') {
170
- log.error('Failed to delete: ' + res.msg);
171
- process.exit(1);
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 buildUpdatesFromFlags(options) {
177
- const updates = {};
178
- if (options.name)
179
- updates.name = options.name;
180
- if (options.shortDescription)
181
- updates.shortDescription = options.shortDescription;
182
- if (options.descriptionFile) {
183
- const filePath = options.descriptionFile;
184
- if (!fs.existsSync(filePath)) {
185
- log.error(`File not found: ${filePath}`);
186
- process.exit(1);
187
- }
188
- updates.description = fs.readFileSync(filePath, 'utf-8');
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
- else if (options.description) {
191
- updates.description = options.description;
103
+ catch (err) {
104
+ return fail(a, err, { op: 'list' });
192
105
  }
193
- if (options.category) {
194
- const id = parseInt(options.category);
195
- if (isNaN(id) || id < 1 || id > 6) {
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
- if (options.compatibleVersions)
202
- updates.compatibleVersions = options.compatibleVersions;
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
- return updates;
113
+ a.io.out(`共 ${items.length} 个模块 / ${items.length} module(s)`);
114
+ return 0;
212
115
  }
213
- async function promptForUpdates(mod) {
214
- const updates = {};
215
- const { name } = await inquirer.prompt([{
216
- type: 'input',
217
- name: 'name',
218
- message: 'Display name:',
219
- default: mod.name,
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
- else if (descMode === 'file') {
252
- const { filePath } = await inquirer.prompt([{
253
- type: 'input',
254
- name: 'filePath',
255
- message: 'File path:',
256
- }]);
257
- if (filePath && fs.existsSync(filePath)) {
258
- updates.description = fs.readFileSync(filePath, 'utf-8');
259
- }
260
- else if (filePath) {
261
- log.warn('File not found, skipping description.');
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 { category } = await inquirer.prompt([{
265
- type: 'list',
266
- name: 'category',
267
- message: 'Category:',
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
- else if (tagList.length > 5) {
298
- log.warn('Too many tags (max 5), skipping.');
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 getClient() {
303
- const credentials = readCredentials();
304
- const client = ApiClient.fromEnvOrCredentials(credentials);
305
- if (!client) {
306
- log.error('Not logged in. Run `ultikits login` or set ULTIKITS_TOKEN.');
307
- process.exit(1);
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