@super-protocol/sp-cli 0.0.8 → 0.0.9
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 +25 -20
- package/dist/commands/account/base.js +4 -3
- package/dist/commands/account/forget.d.ts +1 -1
- package/dist/commands/account/forget.js +65 -16
- package/dist/commands/account/info.js +5 -5
- package/dist/commands/account/login.d.ts +6 -0
- package/dist/commands/account/login.js +119 -14
- package/dist/commands/account/switch.d.ts +3 -0
- package/dist/commands/account/switch.js +30 -10
- package/dist/commands/base.js +1 -0
- package/dist/hooks/prerun/auth.js +2 -1
- package/dist/managers/config-file-manager.d.ts +6 -12
- package/dist/managers/config-file-manager.js +58 -59
- package/dist/utils/progress.js +1 -0
- package/dist/utils/prompt.service.d.ts +8 -1
- package/dist/utils/prompt.service.js +33 -1
- package/oclif.manifest.json +18 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ $ npm install -g @super-protocol/sp-cli
|
|
|
21
21
|
$ sp COMMAND
|
|
22
22
|
running command...
|
|
23
23
|
$ sp (--version)
|
|
24
|
-
@super-protocol/sp-cli/0.0.
|
|
24
|
+
@super-protocol/sp-cli/0.0.9 linux-x64 node-v22.22.0
|
|
25
25
|
$ sp --help [COMMAND]
|
|
26
26
|
USAGE
|
|
27
27
|
$ sp COMMAND
|
|
@@ -53,11 +53,11 @@ Purge all account details
|
|
|
53
53
|
|
|
54
54
|
```
|
|
55
55
|
USAGE
|
|
56
|
-
$ sp account forget
|
|
56
|
+
$ sp account forget [--json] [--tty] [-f] [-n <value>]
|
|
57
57
|
|
|
58
58
|
FLAGS
|
|
59
|
-
-f, --force Force
|
|
60
|
-
-n, --name=<value>
|
|
59
|
+
-f, --force Force forget without confirmation
|
|
60
|
+
-n, --name=<value> Account name to forget
|
|
61
61
|
|
|
62
62
|
GLOBAL FLAGS
|
|
63
63
|
--json Format output as json.
|
|
@@ -74,7 +74,7 @@ EXAMPLES
|
|
|
74
74
|
$ sp account forget --name "My Account" --force
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
_See code: [src/commands/account/forget.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
77
|
+
_See code: [src/commands/account/forget.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/forget.ts)_
|
|
78
78
|
|
|
79
79
|
## `sp account get-sppi`
|
|
80
80
|
|
|
@@ -95,7 +95,7 @@ EXAMPLES
|
|
|
95
95
|
$ sp account get-sppi
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
_See code: [src/commands/account/get-sppi.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
98
|
+
_See code: [src/commands/account/get-sppi.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/get-sppi.ts)_
|
|
99
99
|
|
|
100
100
|
## `sp account info`
|
|
101
101
|
|
|
@@ -116,7 +116,7 @@ DESCRIPTION
|
|
|
116
116
|
Information about current authorized user
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
_See code: [src/commands/account/info.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
119
|
+
_See code: [src/commands/account/info.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/info.ts)_
|
|
120
120
|
|
|
121
121
|
## `sp account list`
|
|
122
122
|
|
|
@@ -137,7 +137,7 @@ EXAMPLES
|
|
|
137
137
|
$ sp account list
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
_See code: [src/commands/account/list.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
140
|
+
_See code: [src/commands/account/list.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/list.ts)_
|
|
141
141
|
|
|
142
142
|
## `sp account login`
|
|
143
143
|
|
|
@@ -145,11 +145,12 @@ Login to SuperProtocol (sign up or sign in).
|
|
|
145
145
|
|
|
146
146
|
```
|
|
147
147
|
USAGE
|
|
148
|
-
$ sp account login [--json] [--tty] [--name <value>] [--privateKey <value>] [-y]
|
|
148
|
+
$ sp account login [--json] [--tty] [--name <value>] [--privateKey <value>] [--path <value>] [-y]
|
|
149
149
|
|
|
150
150
|
FLAGS
|
|
151
151
|
-y, --yes Skip questions (generate keys when needed).
|
|
152
152
|
--name=<value> Account readable name
|
|
153
|
+
--path=<value> Path to account(configuration) file to import
|
|
153
154
|
--privateKey=<value> Account private key used for authentication
|
|
154
155
|
|
|
155
156
|
GLOBAL FLAGS
|
|
@@ -166,7 +167,7 @@ EXAMPLES
|
|
|
166
167
|
$ sp account login
|
|
167
168
|
```
|
|
168
169
|
|
|
169
|
-
_See code: [src/commands/account/login.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
170
|
+
_See code: [src/commands/account/login.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/login.ts)_
|
|
170
171
|
|
|
171
172
|
## `sp account switch`
|
|
172
173
|
|
|
@@ -174,7 +175,10 @@ Switch to a different account
|
|
|
174
175
|
|
|
175
176
|
```
|
|
176
177
|
USAGE
|
|
177
|
-
$ sp account switch [--json] [--tty]
|
|
178
|
+
$ sp account switch [--json] [--tty] [-n <value>]
|
|
179
|
+
|
|
180
|
+
FLAGS
|
|
181
|
+
-n, --name=<value> Account name to switch
|
|
178
182
|
|
|
179
183
|
GLOBAL FLAGS
|
|
180
184
|
--json Format output as json.
|
|
@@ -187,7 +191,7 @@ EXAMPLES
|
|
|
187
191
|
$ sp account switch
|
|
188
192
|
```
|
|
189
193
|
|
|
190
|
-
_See code: [src/commands/account/switch.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
194
|
+
_See code: [src/commands/account/switch.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/account/switch.ts)_
|
|
191
195
|
|
|
192
196
|
## `sp autocomplete [SHELL]`
|
|
193
197
|
|
|
@@ -246,7 +250,7 @@ EXAMPLES
|
|
|
246
250
|
$ sp files download ./resource.json ./pathToDownload
|
|
247
251
|
```
|
|
248
252
|
|
|
249
|
-
_See code: [src/commands/files/download.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
253
|
+
_See code: [src/commands/files/download.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/files/download.ts)_
|
|
250
254
|
|
|
251
255
|
## `sp files upload PATH`
|
|
252
256
|
|
|
@@ -280,7 +284,7 @@ EXAMPLES
|
|
|
280
284
|
$ sp files upload ./file.txt
|
|
281
285
|
```
|
|
282
286
|
|
|
283
|
-
_See code: [src/commands/files/upload.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
287
|
+
_See code: [src/commands/files/upload.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/files/upload.ts)_
|
|
284
288
|
|
|
285
289
|
## `sp help [COMMAND]`
|
|
286
290
|
|
|
@@ -308,11 +312,12 @@ Login to SuperProtocol (sign up or sign in).
|
|
|
308
312
|
|
|
309
313
|
```
|
|
310
314
|
USAGE
|
|
311
|
-
$ sp login [--json] [--tty] [--name <value>] [--privateKey <value>] [-y]
|
|
315
|
+
$ sp login [--json] [--tty] [--name <value>] [--privateKey <value>] [--path <value>] [-y]
|
|
312
316
|
|
|
313
317
|
FLAGS
|
|
314
318
|
-y, --yes Skip questions (generate keys when needed).
|
|
315
319
|
--name=<value> Account readable name
|
|
320
|
+
--path=<value> Path to account(configuration) file to import
|
|
316
321
|
--privateKey=<value> Account private key used for authentication
|
|
317
322
|
|
|
318
323
|
GLOBAL FLAGS
|
|
@@ -356,7 +361,7 @@ EXAMPLES
|
|
|
356
361
|
$ sp storage create --not-default
|
|
357
362
|
```
|
|
358
363
|
|
|
359
|
-
_See code: [src/commands/storage/create.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
364
|
+
_See code: [src/commands/storage/create.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/storage/create.ts)_
|
|
360
365
|
|
|
361
366
|
## `sp storage select`
|
|
362
367
|
|
|
@@ -377,7 +382,7 @@ EXAMPLES
|
|
|
377
382
|
$ sp storage select
|
|
378
383
|
```
|
|
379
384
|
|
|
380
|
-
_See code: [src/commands/storage/select.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
385
|
+
_See code: [src/commands/storage/select.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/storage/select.ts)_
|
|
381
386
|
|
|
382
387
|
## `sp storage show`
|
|
383
388
|
|
|
@@ -398,7 +403,7 @@ EXAMPLES
|
|
|
398
403
|
$ sp storage show
|
|
399
404
|
```
|
|
400
405
|
|
|
401
|
-
_See code: [src/commands/storage/show.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
406
|
+
_See code: [src/commands/storage/show.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/storage/show.ts)_
|
|
402
407
|
|
|
403
408
|
## `sp storage update`
|
|
404
409
|
|
|
@@ -425,7 +430,7 @@ EXAMPLES
|
|
|
425
430
|
$ sp storage update --id=2de3e3a4-0000-1111-2222-333344445555
|
|
426
431
|
```
|
|
427
432
|
|
|
428
|
-
_See code: [src/commands/storage/update.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
433
|
+
_See code: [src/commands/storage/update.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/storage/update.ts)_
|
|
429
434
|
|
|
430
435
|
## `sp workflows extend-lease ORDERID`
|
|
431
436
|
|
|
@@ -458,5 +463,5 @@ EXAMPLES
|
|
|
458
463
|
$ sp workflows extend-lease <orderId> --sppi 2 --yes
|
|
459
464
|
```
|
|
460
465
|
|
|
461
|
-
_See code: [src/commands/workflows/extend-lease.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.
|
|
466
|
+
_See code: [src/commands/workflows/extend-lease.ts](https://github.com/Super-Protocol/sp-cli/blob/v0.0.9/src/commands/workflows/extend-lease.ts)_
|
|
462
467
|
<!-- commandsstop -->
|
|
@@ -15,15 +15,16 @@ export class BaseAccountCommand extends BaseCommand {
|
|
|
15
15
|
async interactWithUser(accountManager) {
|
|
16
16
|
this.warn("Account doesn't exist in current configuration");
|
|
17
17
|
const ask = await promptService.confirm({
|
|
18
|
-
message: 'Do you
|
|
18
|
+
message: 'Do you already have an account?',
|
|
19
19
|
});
|
|
20
|
-
if (ask) {
|
|
20
|
+
if (!ask) {
|
|
21
21
|
const account = await accountManager.create();
|
|
22
22
|
this.log('Generated new keyPair for new account.');
|
|
23
23
|
return account;
|
|
24
24
|
}
|
|
25
25
|
const privateKeyInput = await promptService.password({
|
|
26
|
-
message: 'Please input your privateKey
|
|
26
|
+
message: 'Please input your privateKey',
|
|
27
|
+
validate: (value) => value?.trim(),
|
|
27
28
|
});
|
|
28
29
|
const privateKey = privateKeyInput.trim();
|
|
29
30
|
try {
|
|
@@ -5,7 +5,7 @@ export default class AccountForget extends BaseAccountCommand<typeof AccountForg
|
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static flags: {
|
|
7
7
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
};
|
|
10
10
|
private configFileManager;
|
|
11
11
|
init(): Promise<void>;
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { AppContainer } from '../../lib/container.js';
|
|
3
|
+
import { promptService } from '../../utils/prompt.service.js';
|
|
3
4
|
import { BaseAccountCommand } from './base.js';
|
|
4
|
-
const buildConfigNameFlag = (options = []) => options.length
|
|
5
|
-
? Flags.option({
|
|
6
|
-
char: 'n',
|
|
7
|
-
description: 'Configuration name to delete',
|
|
8
|
-
options,
|
|
9
|
-
required: true,
|
|
10
|
-
})()
|
|
11
|
-
: Flags.string({
|
|
12
|
-
char: 'n',
|
|
13
|
-
description: 'Configuration name to delete',
|
|
14
|
-
required: true,
|
|
15
|
-
});
|
|
16
5
|
export default class AccountForget extends BaseAccountCommand {
|
|
17
6
|
static authenticate = false;
|
|
18
7
|
static description = 'Purge all account details';
|
|
@@ -25,9 +14,12 @@ export default class AccountForget extends BaseAccountCommand {
|
|
|
25
14
|
force: Flags.boolean({
|
|
26
15
|
char: 'f',
|
|
27
16
|
default: false,
|
|
28
|
-
description: 'Force
|
|
17
|
+
description: 'Force forget without confirmation',
|
|
18
|
+
}),
|
|
19
|
+
name: Flags.string({
|
|
20
|
+
char: 'n',
|
|
21
|
+
description: 'Account name to forget',
|
|
29
22
|
}),
|
|
30
|
-
name: buildConfigNameFlag(),
|
|
31
23
|
};
|
|
32
24
|
configFileManager;
|
|
33
25
|
async init() {
|
|
@@ -38,12 +30,69 @@ export default class AccountForget extends BaseAccountCommand {
|
|
|
38
30
|
}
|
|
39
31
|
async run() {
|
|
40
32
|
const { force, name } = this.flags;
|
|
41
|
-
|
|
33
|
+
const configs = this.configFileManager.getConfigsWithNames();
|
|
34
|
+
if (configs.length === 0) {
|
|
35
|
+
this.error('Not found any account', { exit: 1 });
|
|
36
|
+
}
|
|
37
|
+
let configToDelete;
|
|
38
|
+
let displayName;
|
|
39
|
+
if (name) {
|
|
40
|
+
const config = configs.find((item) => item.name === name || item.file === name);
|
|
41
|
+
if (!config) {
|
|
42
|
+
this.error(`Account with name: ${name} not found`);
|
|
43
|
+
}
|
|
44
|
+
configToDelete = config.file;
|
|
45
|
+
displayName = config.name || config.file;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const selection = await promptService.select({
|
|
49
|
+
message: 'Select account to forget:',
|
|
50
|
+
options: configs.map((config) => ({
|
|
51
|
+
label: config.name || config.file,
|
|
52
|
+
value: config.file,
|
|
53
|
+
})),
|
|
54
|
+
});
|
|
55
|
+
const config = configs.find((item) => item.file === selection);
|
|
56
|
+
configToDelete = selection;
|
|
57
|
+
displayName = config?.name || selection;
|
|
58
|
+
}
|
|
59
|
+
if (!force) {
|
|
60
|
+
const confirmed = await promptService.confirm({
|
|
61
|
+
initialValue: false,
|
|
62
|
+
message: `Are you sure you want to forget "${displayName}"?`,
|
|
63
|
+
});
|
|
64
|
+
if (!confirmed) {
|
|
65
|
+
this.log('Cancelled. Use --force to skip confirmation.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await this.configFileManager.deleteConfigByName(configToDelete);
|
|
71
|
+
this.log(`Successfully forgot account: ${displayName}`);
|
|
72
|
+
const newCurrent = this.configFileManager.getCurrentConfigFile();
|
|
73
|
+
if (newCurrent) {
|
|
74
|
+
const configsAfter = this.configFileManager.getConfigsWithNames();
|
|
75
|
+
const newCurrentConfig = configsAfter.find((c) => c.file === newCurrent);
|
|
76
|
+
this.log(`Current account is now: ${newCurrentConfig?.name || newCurrent}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.log('No accounts remaining');
|
|
80
|
+
this.log('Create a new account with: sp account login --name "Account Name" or sp account login');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
this.error(`Failed to forget account: ${message}`, { exit: 1 });
|
|
86
|
+
}
|
|
42
87
|
}
|
|
43
88
|
async updateNameFlagOptions() {
|
|
44
89
|
const { configFileManager } = await AppContainer.container.initConfigFileManager().build();
|
|
45
90
|
const configs = configFileManager.getConfigsWithNames();
|
|
46
91
|
const options = configs.map((config) => config.name);
|
|
47
|
-
this.constructor.flags.name =
|
|
92
|
+
this.constructor.flags.name = Flags.option({
|
|
93
|
+
char: 'n',
|
|
94
|
+
description: 'Account name to forget',
|
|
95
|
+
options,
|
|
96
|
+
})();
|
|
48
97
|
}
|
|
49
98
|
}
|
|
@@ -55,11 +55,11 @@ export class InfoCommand extends BaseAccountCommand {
|
|
|
55
55
|
const currentConfig = configs.find((c) => c.file === current);
|
|
56
56
|
const displayName = currentConfig?.name || current;
|
|
57
57
|
const configData = configFileManager.getConfigData(current);
|
|
58
|
-
this.log(`Current
|
|
58
|
+
this.log(`Current account: ${displayName}`);
|
|
59
59
|
this.log(`Configuration file: ${current}`);
|
|
60
60
|
this.log(`Configuration directory: ${configDir}`);
|
|
61
61
|
if (configData) {
|
|
62
|
-
this.log('\
|
|
62
|
+
this.log('\nAccount details:');
|
|
63
63
|
if (configData.providerUrl) {
|
|
64
64
|
this.log(` Provider URL: ${configData.providerUrl}`);
|
|
65
65
|
}
|
|
@@ -81,9 +81,9 @@ export class InfoCommand extends BaseAccountCommand {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
this.log('No
|
|
85
|
-
this.log(`
|
|
86
|
-
this.log('Create a new
|
|
84
|
+
this.log('No account is currently set');
|
|
85
|
+
this.log(`Account directory: ${configDir}`);
|
|
86
|
+
this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name"');
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
return {
|
|
@@ -7,6 +7,7 @@ export default class AccountLoginCommand extends BaseAccountCommand<typeof Accou
|
|
|
7
7
|
static flags: {
|
|
8
8
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
privateKey: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
path: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
};
|
|
@@ -26,9 +27,14 @@ export default class AccountLoginCommand extends BaseAccountCommand<typeof Accou
|
|
|
26
27
|
private confirmPrompt;
|
|
27
28
|
private createAccountFromKey;
|
|
28
29
|
private createConfigIfMissing;
|
|
30
|
+
private updateConfigDisplayName;
|
|
29
31
|
private findConfigsByPrivateKey;
|
|
30
32
|
private getPrivateKeyOrGenerate;
|
|
31
33
|
private resolveByName;
|
|
34
|
+
private assertConfigHasAccount;
|
|
35
|
+
private loadConfigFromPath;
|
|
36
|
+
private resolveConfigFileName;
|
|
37
|
+
private resolveByPath;
|
|
32
38
|
private resolveByPrivateKey;
|
|
33
39
|
private resolveConfiguration;
|
|
34
40
|
private static maskPrivateKey;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Flags } from '@oclif/core';
|
|
4
|
+
import { Value } from 'typebox/value';
|
|
4
5
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
6
|
+
import { cliConfigSchema } from '../../config/config.schema.js';
|
|
5
7
|
import { PROVIDER_URL } from '../../constants.js';
|
|
6
8
|
import { AuthService } from '../../services/auth.service.js';
|
|
7
9
|
import { StorageService } from '../../services/storage.service.js';
|
|
8
|
-
import { getConfigNameFromCredentials } from '../../utils/helper.js';
|
|
10
|
+
import { getConfigName, getConfigNameFromCredentials, preparePath, readJsonFile, } from '../../utils/helper.js';
|
|
9
11
|
import { promptService } from '../../utils/prompt.service.js';
|
|
10
12
|
import { BaseAccountCommand } from './base.js';
|
|
11
13
|
export default class AccountLoginCommand extends BaseAccountCommand {
|
|
@@ -23,6 +25,9 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
23
25
|
privateKey: Flags.string({
|
|
24
26
|
description: 'Account private key used for authentication',
|
|
25
27
|
}),
|
|
28
|
+
path: Flags.string({
|
|
29
|
+
description: 'Path to account(configuration) file to import',
|
|
30
|
+
}),
|
|
26
31
|
url: Flags.string({
|
|
27
32
|
description: 'Provider base URL',
|
|
28
33
|
hidden: true,
|
|
@@ -90,7 +95,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
90
95
|
}
|
|
91
96
|
async askName(fallback) {
|
|
92
97
|
const value = await this.inputPrompt({
|
|
93
|
-
message: '
|
|
98
|
+
message: 'Account name:',
|
|
94
99
|
placeholder: fallback,
|
|
95
100
|
});
|
|
96
101
|
const trimmed = value.trim();
|
|
@@ -115,10 +120,22 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
115
120
|
async createConfigIfMissing(configFile, displayName, providerUrl, account) {
|
|
116
121
|
const configPath = path.join(this.configFileManager.getConfigDir(), configFile);
|
|
117
122
|
if (existsSync(configPath)) {
|
|
123
|
+
await this.configFileManager.updateConfigName(configFile, displayName);
|
|
118
124
|
return;
|
|
119
125
|
}
|
|
120
126
|
await this.configFileManager.createConfig(configFile, displayName, providerUrl, account);
|
|
121
|
-
this.log(`Created
|
|
127
|
+
this.log(`Created account: ${displayName}`);
|
|
128
|
+
}
|
|
129
|
+
async updateConfigDisplayName(configFile, displayName) {
|
|
130
|
+
if (!displayName) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const updated = await this.configFileManager.updateConfigName(configFile, displayName);
|
|
134
|
+
if (updated) {
|
|
135
|
+
this.log(`Updated account name to "${displayName}"`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.log(`Account name: "${displayName}"`);
|
|
122
139
|
}
|
|
123
140
|
findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl) {
|
|
124
141
|
const normalizedKey = privateKey.trim().toLowerCase();
|
|
@@ -159,7 +176,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
159
176
|
}
|
|
160
177
|
let shouldCreate = true;
|
|
161
178
|
if (!this.flags.yes) {
|
|
162
|
-
shouldCreate = await this.confirmPrompt(`
|
|
179
|
+
shouldCreate = await this.confirmPrompt(`Account with "${name}" not found. Create it?`, true);
|
|
163
180
|
}
|
|
164
181
|
if (!shouldCreate) {
|
|
165
182
|
this.shouldSkipLogin = true;
|
|
@@ -171,7 +188,7 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
171
188
|
await this.createConfigIfMissing(configFile, name, providerUrl, account);
|
|
172
189
|
let shouldSwitch = true;
|
|
173
190
|
if (!this.flags.yes) {
|
|
174
|
-
shouldSwitch = await this.confirmPrompt(`Switch to
|
|
191
|
+
shouldSwitch = await this.confirmPrompt(`Switch to account "${name}" now?`, true);
|
|
175
192
|
}
|
|
176
193
|
if (!shouldSwitch) {
|
|
177
194
|
this.shouldSkipLogin = true;
|
|
@@ -179,41 +196,120 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
179
196
|
}
|
|
180
197
|
await this.configFileManager.setCurrentConfig(configFile);
|
|
181
198
|
}
|
|
182
|
-
|
|
199
|
+
assertConfigHasAccount(config) {
|
|
200
|
+
if (!config.account?.privateKey) {
|
|
201
|
+
throw new Error('Account private key is required for import.');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async loadConfigFromPath(configPath) {
|
|
205
|
+
try {
|
|
206
|
+
const config = await readJsonFile({ path: configPath });
|
|
207
|
+
if (!Value.Check(cliConfigSchema, config)) {
|
|
208
|
+
throw new Error("Account doesn't match required schema");
|
|
209
|
+
}
|
|
210
|
+
this.assertConfigHasAccount(config);
|
|
211
|
+
return config;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.error(`Invalid account file: ${error instanceof Error ? error.message : String(error)}`, { exit: 1 });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
resolveConfigFileName(configPath, config, name) {
|
|
218
|
+
if (name) {
|
|
219
|
+
return getConfigName(name);
|
|
220
|
+
}
|
|
221
|
+
if (config.name) {
|
|
222
|
+
return getConfigName(config.name);
|
|
223
|
+
}
|
|
224
|
+
const baseName = path.basename(configPath, path.extname(configPath));
|
|
225
|
+
return getConfigName(baseName);
|
|
226
|
+
}
|
|
227
|
+
async resolveByPath(rawPath, name) {
|
|
228
|
+
const sourcePath = preparePath(rawPath);
|
|
229
|
+
const config = await this.loadConfigFromPath(sourcePath);
|
|
230
|
+
const targetFile = this.resolveConfigFileName(sourcePath, config, name);
|
|
231
|
+
const targetPath = path.join(this.configFileManager.getConfigDir(), targetFile);
|
|
232
|
+
const desiredName = name?.trim();
|
|
233
|
+
const privateKey = config.account.privateKey;
|
|
234
|
+
const providerUrl = (config.providerUrl ?? PROVIDER_URL).trim();
|
|
235
|
+
const matches = this.findConfigsByPrivateKey(privateKey, providerUrl, true);
|
|
236
|
+
if (matches.length > 0) {
|
|
237
|
+
let selectedFile = matches[0].file;
|
|
238
|
+
if (matches.length > 1) {
|
|
239
|
+
if (this.flags.yes) {
|
|
240
|
+
this.log(`Too many matched accounts: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
selectedFile = await this.selectPrompt({
|
|
244
|
+
message: 'Multiple accounts found for this private key. Select one:',
|
|
245
|
+
options: matches.map((match) => ({ label: match.name, value: match.file })),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
this.log('Account already exists. Nothing to import.');
|
|
250
|
+
await this.updateConfigDisplayName(selectedFile, desiredName);
|
|
251
|
+
await this.configFileManager.setCurrentConfig(selectedFile);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (existsSync(targetPath)) {
|
|
255
|
+
const existingConfig = this.configFileManager.getConfigData(targetFile);
|
|
256
|
+
const existingKey = existingConfig?.account?.privateKey?.trim().toLowerCase();
|
|
257
|
+
const existingUrl = (existingConfig?.providerUrl ?? PROVIDER_URL).trim();
|
|
258
|
+
const importedKey = privateKey.trim().toLowerCase();
|
|
259
|
+
if (!existingKey || existingKey !== importedKey || existingUrl !== providerUrl) {
|
|
260
|
+
this.error(`Account file "${targetFile}" already exists with different credentials. Use a different name or remove the existing file before importing.`, { exit: 1 });
|
|
261
|
+
}
|
|
262
|
+
this.log(`Account already exists. Switching to ${name || targetFile}`);
|
|
263
|
+
await this.configFileManager.setCurrentConfig(targetFile);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const importedFile = await this.configFileManager.importConfig(sourcePath, name);
|
|
268
|
+
await this.configFileManager.setCurrentConfig(importedFile);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
this.error(error instanceof Error ? error.message : String(error), { exit: 1 });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async resolveByPrivateKey(privateKey, providerUrl, matchProviderUrl, name) {
|
|
183
275
|
const matches = this.findConfigsByPrivateKey(privateKey, providerUrl, matchProviderUrl);
|
|
276
|
+
const desiredName = name?.trim();
|
|
184
277
|
if (matches.length === 1) {
|
|
278
|
+
await this.updateConfigDisplayName(matches[0].file, desiredName);
|
|
185
279
|
await this.configFileManager.setCurrentConfig(matches[0].file);
|
|
186
280
|
return;
|
|
187
281
|
}
|
|
188
282
|
if (matches.length > 1) {
|
|
189
283
|
if (this.flags.yes) {
|
|
190
|
-
this.log(`Too many matched
|
|
284
|
+
this.log(`Too many matched accounts: ${matches.map((conf) => conf.name).join(',')} Selected first: ${matches[0].name}`);
|
|
285
|
+
await this.updateConfigDisplayName(matches[0].file, desiredName);
|
|
191
286
|
await this.configFileManager.setCurrentConfig(matches[0].file);
|
|
192
287
|
return;
|
|
193
288
|
}
|
|
194
289
|
const selected = await this.selectPrompt({
|
|
195
|
-
message: 'Multiple
|
|
290
|
+
message: 'Multiple accounts found for this private key. Select one:',
|
|
196
291
|
options: matches.map((match) => ({ label: match.name, value: match.file })),
|
|
197
292
|
});
|
|
293
|
+
await this.updateConfigDisplayName(selected, desiredName);
|
|
198
294
|
await this.configFileManager.setCurrentConfig(selected);
|
|
199
295
|
return;
|
|
200
296
|
}
|
|
201
297
|
let shouldCreate = true;
|
|
202
298
|
if (!this.flags.yes) {
|
|
203
|
-
shouldCreate = await this.confirmPrompt('No
|
|
299
|
+
shouldCreate = await this.confirmPrompt('No accounts found for this private key. Create a new one?', true);
|
|
204
300
|
}
|
|
205
301
|
if (!shouldCreate) {
|
|
206
302
|
this.shouldSkipLogin = true;
|
|
207
303
|
return;
|
|
208
304
|
}
|
|
209
305
|
const account = this.createAccountFromKey(privateKey);
|
|
210
|
-
const suggestedName =
|
|
306
|
+
const suggestedName = desiredName || account.address;
|
|
211
307
|
const displayName = this.flags.yes ? suggestedName : await this.askName(suggestedName);
|
|
212
308
|
const configFile = getConfigNameFromCredentials(privateKey, providerUrl);
|
|
213
309
|
await this.createConfigIfMissing(configFile, displayName, providerUrl, account);
|
|
214
310
|
let shouldSwitch = true;
|
|
215
311
|
if (!this.flags.yes) {
|
|
216
|
-
shouldSwitch = await this.confirmPrompt(`Switch to
|
|
312
|
+
shouldSwitch = await this.confirmPrompt(`Switch to account "${displayName}" now?`, true);
|
|
217
313
|
}
|
|
218
314
|
if (!shouldSwitch) {
|
|
219
315
|
this.shouldSkipLogin = true;
|
|
@@ -223,18 +319,27 @@ export default class AccountLoginCommand extends BaseAccountCommand {
|
|
|
223
319
|
}
|
|
224
320
|
async resolveConfiguration() {
|
|
225
321
|
const name = this.flags.name?.trim();
|
|
322
|
+
const configPath = this.flags.path?.trim();
|
|
323
|
+
if (configPath !== undefined && !configPath) {
|
|
324
|
+
this.error('Account path cannot be empty.', { exit: 1 });
|
|
325
|
+
}
|
|
226
326
|
const privateKey = this.flags.privateKey?.trim();
|
|
227
327
|
const url = this.flags.url?.trim();
|
|
228
328
|
if (url !== undefined && !url) {
|
|
229
329
|
this.error('Provider URL cannot be empty.', { exit: 1 });
|
|
230
330
|
}
|
|
231
331
|
const providerUrl = (url || PROVIDER_URL).trim();
|
|
232
|
-
if (
|
|
233
|
-
await this.
|
|
332
|
+
if (configPath) {
|
|
333
|
+
await this.resolveByPath(configPath, name);
|
|
234
334
|
return;
|
|
235
335
|
}
|
|
236
336
|
if (privateKey) {
|
|
237
|
-
await this.resolveByPrivateKey(privateKey, providerUrl, Boolean(url));
|
|
337
|
+
await this.resolveByPrivateKey(privateKey, providerUrl, Boolean(url), name);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (name) {
|
|
341
|
+
await this.resolveByName(name, privateKey, providerUrl);
|
|
342
|
+
return;
|
|
238
343
|
}
|
|
239
344
|
}
|
|
240
345
|
static maskPrivateKey(privateKey) {
|
|
@@ -6,6 +6,9 @@ export default class AccountSwitchCommand extends BaseAccountCommand<typeof Acco
|
|
|
6
6
|
static examples: string[];
|
|
7
7
|
protected configFileManager: ConfigFileManager;
|
|
8
8
|
protected configManager: ConfigManager;
|
|
9
|
+
static flags: {
|
|
10
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
9
12
|
init(): Promise<void>;
|
|
10
13
|
run(): Promise<void>;
|
|
11
14
|
private switchConfig;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
1
2
|
import { BaseAccountCommand } from './base.js';
|
|
2
3
|
export default class AccountSwitchCommand extends BaseAccountCommand {
|
|
3
4
|
static authenticate = false;
|
|
@@ -5,6 +6,12 @@ export default class AccountSwitchCommand extends BaseAccountCommand {
|
|
|
5
6
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
6
7
|
configFileManager;
|
|
7
8
|
configManager;
|
|
9
|
+
static flags = {
|
|
10
|
+
name: Flags.string({
|
|
11
|
+
char: 'n',
|
|
12
|
+
description: 'Account name to switch',
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
8
15
|
async init() {
|
|
9
16
|
await super.init();
|
|
10
17
|
await this.container.initConfigFileManager().build();
|
|
@@ -12,28 +19,41 @@ export default class AccountSwitchCommand extends BaseAccountCommand {
|
|
|
12
19
|
await this.container.initConfigManager().build();
|
|
13
20
|
}
|
|
14
21
|
async run() {
|
|
22
|
+
const { name } = this.flags;
|
|
15
23
|
const configs = this.configFileManager.getConfigsWithNames();
|
|
16
24
|
const currentConfig = this.configFileManager.getCurrentConfigFile();
|
|
17
25
|
if (configs.length === 0) {
|
|
18
26
|
this.log('No accounts found');
|
|
19
|
-
this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name"');
|
|
27
|
+
this.log('Create a new account with: sp account login --privateKey "<PRIVATE_KEY>" --name "Account Name" or sp account login');
|
|
20
28
|
return;
|
|
21
29
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
let selected;
|
|
31
|
+
if (name) {
|
|
32
|
+
try {
|
|
33
|
+
const config = this.configFileManager.getConfigWithName(name);
|
|
34
|
+
selected = config.file;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.error(`Account with name ${name} not found`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
selected = await this.selectPrompt({
|
|
42
|
+
initialValue: currentConfig,
|
|
43
|
+
message: 'Select an account:',
|
|
44
|
+
options: configs.map((config) => ({
|
|
45
|
+
label: config.name ?? config.file,
|
|
46
|
+
value: config.file,
|
|
47
|
+
})),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
30
50
|
const selectedConfig = configs.find((c) => c.file === selected);
|
|
31
51
|
await this.switchConfig(selected, selectedConfig?.name || selected);
|
|
32
52
|
}
|
|
33
53
|
async switchConfig(configFile, displayName) {
|
|
34
54
|
await this.configFileManager.setCurrentConfig(configFile);
|
|
35
55
|
this.log(`Switched to account: ${displayName || configFile}`);
|
|
36
|
-
await this.
|
|
56
|
+
await this.container.initConfigManager(true).build();
|
|
37
57
|
this.configFileManager = this.container.configFileManager;
|
|
38
58
|
this.configManager = this.container.configManager;
|
|
39
59
|
}
|
package/dist/commands/base.js
CHANGED
|
@@ -63,6 +63,7 @@ export class BaseCommand extends Command {
|
|
|
63
63
|
strict: false,
|
|
64
64
|
});
|
|
65
65
|
const isInteractive = isInteractiveMode(parsedResult.flags.tty);
|
|
66
|
+
promptService.setInteractiveMode(isInteractive);
|
|
66
67
|
const missingFlags = await findMissingRequiredFlags(parsedResult.flags, allFlags);
|
|
67
68
|
const missingArgs = await findMissingRequiredArgs(parsedResult.args, this.ctor.args);
|
|
68
69
|
if (missingFlags.length > 0 || missingArgs.length > 0) {
|
|
@@ -2,6 +2,7 @@ import { BaseCommand } from '../../commands/base.js';
|
|
|
2
2
|
import { AppContainer } from '../../lib/container.js';
|
|
3
3
|
import logger from '../../logger.js';
|
|
4
4
|
import { AuthService } from '../../services/auth.service.js';
|
|
5
|
+
import { isInteractiveMode } from '../../utils/tty.js';
|
|
5
6
|
const isBaseCommandClass = (commandClass) => commandClass.prototype instanceof BaseCommand;
|
|
6
7
|
const getConfigFlag = (commandClass, argv) => {
|
|
7
8
|
if (!isBaseCommandClass(commandClass)) {
|
|
@@ -47,7 +48,7 @@ const hook = async (opts) => {
|
|
|
47
48
|
const { configManager, accountManager, providerClient } = container;
|
|
48
49
|
const credentials = await configManager.get('auth');
|
|
49
50
|
if (!credentials?.accessKey) {
|
|
50
|
-
if (!
|
|
51
|
+
if (!isInteractiveMode()) {
|
|
51
52
|
context.error('Authorization not found. Please run: sp account login', { exit: 1 });
|
|
52
53
|
}
|
|
53
54
|
const args = configFile ? ['--config', configFile] : [];
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import { confirm, select } from '@clack/prompts';
|
|
2
|
-
import { ux } from '@oclif/core';
|
|
3
1
|
import type pino from 'pino';
|
|
4
2
|
import { type Account, type CliConfig } from '../config/config.schema.js';
|
|
5
3
|
import type { IManager } from '../interfaces/manager.interface.js';
|
|
6
|
-
type ConfirmPrompt = typeof confirm;
|
|
7
|
-
type SelectPrompt = typeof select;
|
|
8
|
-
type UxLike = Pick<typeof ux, 'error' | 'stdout'>;
|
|
9
4
|
interface ConfigFileManagerOptions {
|
|
10
|
-
confirmPrompt?: ConfirmPrompt;
|
|
11
5
|
runtimeConfigFile?: string;
|
|
12
|
-
selectPrompt?: SelectPrompt;
|
|
13
|
-
ux?: UxLike;
|
|
14
6
|
}
|
|
15
7
|
export declare class ConfigFileManager implements IManager {
|
|
16
8
|
private readonly logger;
|
|
@@ -20,17 +12,19 @@ export declare class ConfigFileManager implements IManager {
|
|
|
20
12
|
private readonly configFilePath;
|
|
21
13
|
private configs;
|
|
22
14
|
private readonly configsDir;
|
|
23
|
-
private readonly confirmPrompt;
|
|
24
15
|
private runtimeConfigFile?;
|
|
25
|
-
private readonly selectPrompt;
|
|
26
|
-
private readonly ux;
|
|
27
16
|
constructor(configDir: string, logger: pino.BaseLogger, options?: ConfigFileManagerOptions);
|
|
28
17
|
createConfig(configFileName: string, name: string, url?: string, account?: Account): Promise<void>;
|
|
18
|
+
updateConfigName(configFileName: string, name: string): Promise<boolean>;
|
|
29
19
|
deleteConfig(configName: string): Promise<void>;
|
|
30
|
-
deleteConfigByName(name?: string
|
|
20
|
+
deleteConfigByName(name?: string): Promise<void>;
|
|
31
21
|
getConfigData(configName: string): CliConfig | undefined;
|
|
32
22
|
getConfigDir(): string;
|
|
33
23
|
getConfigs(): string[];
|
|
24
|
+
getConfigWithName(name: string): {
|
|
25
|
+
file: string;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
34
28
|
getConfigsWithNames(): Array<{
|
|
35
29
|
file: string;
|
|
36
30
|
name: string;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { confirm, isCancel, select } from '@clack/prompts';
|
|
4
|
-
import { ux } from '@oclif/core';
|
|
5
3
|
import { Value } from 'typebox/value';
|
|
6
4
|
import { cliConfigSchema } from '../config/config.schema.js';
|
|
7
5
|
import { DEFAULT_USER_CONFIG_FILE, DEFAULT_USER_CONFIG_NAME, DIR_ACCESS_PERMS, FILE_ACCESS_PERMS, } from '../constants.js';
|
|
@@ -16,24 +14,18 @@ export class ConfigFileManager {
|
|
|
16
14
|
currentConfig: undefined,
|
|
17
15
|
};
|
|
18
16
|
configsDir;
|
|
19
|
-
confirmPrompt;
|
|
20
17
|
runtimeConfigFile;
|
|
21
|
-
selectPrompt;
|
|
22
|
-
ux;
|
|
23
18
|
constructor(configDir, logger, options = {}) {
|
|
24
19
|
this.logger = logger;
|
|
25
20
|
this.configDir = configDir;
|
|
26
21
|
this.configFilePath = path.join(this.configDir, ConfigFileManager.CONFIG_FILE);
|
|
27
22
|
this.configsDir = path.join(this.configDir, ConfigFileManager.CONFIGS_DIR);
|
|
28
|
-
this.confirmPrompt = options.confirmPrompt ?? confirm;
|
|
29
|
-
this.selectPrompt = options.selectPrompt ?? select;
|
|
30
|
-
this.ux = options.ux ?? ux;
|
|
31
23
|
this.runtimeConfigFile = options.runtimeConfigFile;
|
|
32
24
|
}
|
|
33
25
|
async createConfig(configFileName, name, url, account) {
|
|
34
26
|
const configPath = path.join(this.configsDir, configFileName);
|
|
35
27
|
if (fs.existsSync(configPath)) {
|
|
36
|
-
throw new Error(`
|
|
28
|
+
throw new Error(`Account file already exists: ${configFileName}`);
|
|
37
29
|
}
|
|
38
30
|
const config = { name };
|
|
39
31
|
if (url) {
|
|
@@ -49,70 +41,69 @@ export class ConfigFileManager {
|
|
|
49
41
|
});
|
|
50
42
|
this.logger.info({ configFileName, name, url }, 'Created new configuration');
|
|
51
43
|
}
|
|
44
|
+
async updateConfigName(configFileName, name) {
|
|
45
|
+
const configPath = path.join(this.configsDir, configFileName);
|
|
46
|
+
const normalizedName = name.trim();
|
|
47
|
+
if (!normalizedName) {
|
|
48
|
+
throw new Error('Configuration name cannot be empty');
|
|
49
|
+
}
|
|
50
|
+
if (!fs.existsSync(configPath)) {
|
|
51
|
+
throw new Error(`Account file not found: ${configFileName}`);
|
|
52
|
+
}
|
|
53
|
+
let config;
|
|
54
|
+
try {
|
|
55
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
56
|
+
config = JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.logger.warn({ configFileName, err: error }, 'Failed to read config data');
|
|
60
|
+
throw new Error(`Failed to read configuration: ${configFileName}`);
|
|
61
|
+
}
|
|
62
|
+
if (config.name === normalizedName) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
config.name = normalizedName;
|
|
66
|
+
fs.writeFileSync(configPath, JSON.stringify(config, undefined, 2), {
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
flag: 'w',
|
|
69
|
+
mode: FILE_ACCESS_PERMS,
|
|
70
|
+
});
|
|
71
|
+
this.logger.info({ configFileName, name: normalizedName }, 'Updated configuration name');
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
52
74
|
async deleteConfig(configName) {
|
|
53
75
|
const configPath = path.join(this.configsDir, configName);
|
|
54
76
|
if (!fs.existsSync(configPath)) {
|
|
55
|
-
throw new Error(`
|
|
77
|
+
throw new Error(`Account file not found: ${configName}`);
|
|
56
78
|
}
|
|
57
79
|
await this.removeConfig(configName);
|
|
58
80
|
fs.unlinkSync(configPath);
|
|
59
81
|
this.logger.info({ configName }, 'Deleted configuration file');
|
|
60
82
|
}
|
|
61
|
-
async deleteConfigByName(name
|
|
83
|
+
async deleteConfigByName(name) {
|
|
62
84
|
const configs = this.getConfigsWithNames();
|
|
63
85
|
if (configs.length === 0) {
|
|
64
|
-
|
|
65
|
-
return;
|
|
86
|
+
throw new Error('No configurations found');
|
|
66
87
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const config = configs.find((c) => c.name === name);
|
|
70
|
-
if (!config) {
|
|
71
|
-
this.ux.error(`Configuration not found: ${name}`);
|
|
72
|
-
}
|
|
73
|
-
configToDelete = config.file;
|
|
88
|
+
if (!name) {
|
|
89
|
+
throw new Error('Configuration name is required');
|
|
74
90
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
options: configs.map((config) => ({
|
|
79
|
-
label: config.name,
|
|
80
|
-
value: config.file,
|
|
81
|
-
})),
|
|
82
|
-
});
|
|
83
|
-
if (isCancel(selection)) {
|
|
84
|
-
this.ux.stdout('Deletion cancelled');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
configToDelete = selection;
|
|
88
|
-
}
|
|
89
|
-
const configToDeleteData = configs.find((c) => c.file === configToDelete);
|
|
90
|
-
const displayName = configToDeleteData?.name || configToDelete;
|
|
91
|
-
if (!force) {
|
|
92
|
-
const confirmed = await this.confirmPrompt({
|
|
93
|
-
initialValue: false,
|
|
94
|
-
message: `Are you sure you want to delete configuration "${displayName}"?`,
|
|
95
|
-
});
|
|
96
|
-
if (isCancel(confirmed) || !confirmed) {
|
|
97
|
-
this.ux.stdout('Deletion cancelled');
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
91
|
+
const configToDeleteData = configs.find((c) => c.name === name || c.file === name);
|
|
92
|
+
if (!configToDeleteData) {
|
|
93
|
+
throw new Error(`Configuration not found: ${name}`);
|
|
100
94
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
this.ux.stdout('No configurations remaining');
|
|
111
|
-
this.ux.stdout('Create a new account with: sp account login --name "Account Name" or sp auth login');
|
|
112
|
-
}
|
|
95
|
+
const configToDelete = configToDeleteData.file;
|
|
96
|
+
const displayName = configToDeleteData.name || configToDelete;
|
|
97
|
+
await this.deleteConfig(configToDelete);
|
|
98
|
+
this.logger.info({ displayName, configToDelete }, 'Successfully deleted configuration');
|
|
99
|
+
const newCurrent = this.getCurrentConfigFile();
|
|
100
|
+
if (newCurrent) {
|
|
101
|
+
const newCurrentConfig = configs.find((c) => c.file === newCurrent);
|
|
102
|
+
this.logger.info({ currentConfig: newCurrentConfig?.name || newCurrent }, 'Current configuration is now');
|
|
113
103
|
}
|
|
114
|
-
|
|
115
|
-
this.
|
|
104
|
+
else {
|
|
105
|
+
this.logger.info('No configurations remaining');
|
|
106
|
+
this.logger.info('Create a new account with: sp account login --name "Account Name" or sp account login');
|
|
116
107
|
}
|
|
117
108
|
}
|
|
118
109
|
getConfigData(configName) {
|
|
@@ -138,6 +129,14 @@ export class ConfigFileManager {
|
|
|
138
129
|
}
|
|
139
130
|
return fs.readdirSync(this.configsDir).filter((file) => file.endsWith('.config.json'));
|
|
140
131
|
}
|
|
132
|
+
getConfigWithName(name) {
|
|
133
|
+
const configs = this.getConfigsWithNames();
|
|
134
|
+
const config = configs.filter((config) => config.name === name || config.file === name).at(0);
|
|
135
|
+
if (!config) {
|
|
136
|
+
throw new Error(`Account with name ${name} not found`);
|
|
137
|
+
}
|
|
138
|
+
return config;
|
|
139
|
+
}
|
|
141
140
|
getConfigsWithNames() {
|
|
142
141
|
if (!fs.existsSync(this.configsDir)) {
|
|
143
142
|
return [];
|
package/dist/utils/progress.js
CHANGED
|
@@ -14,6 +14,7 @@ export const createProgressPrinter = ({ action, done = 'completed', start, }) =>
|
|
|
14
14
|
progressBar.stop(`${action} ${lastKey} ${done}`);
|
|
15
15
|
}
|
|
16
16
|
const progressBar = p.progress({ size: 100, style: 'block' });
|
|
17
|
+
lastKey = key;
|
|
17
18
|
progressBar.start(`${start} ${key}`);
|
|
18
19
|
progressBars[key] = progressBar;
|
|
19
20
|
return progressBar;
|
|
@@ -4,7 +4,14 @@ export type ConfirmOptions = ClackConfirmOptions;
|
|
|
4
4
|
export type SelectOptions<T> = ClackSelectOptions<T>;
|
|
5
5
|
export type PasswordOptions = ClackPasswordOptions;
|
|
6
6
|
type ParseValue = (input: string) => string | number | boolean;
|
|
7
|
-
export declare class
|
|
7
|
+
export declare class NonInteractiveError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
declare class PromptService {
|
|
11
|
+
private _isInteractive;
|
|
12
|
+
setInteractiveMode(value: boolean): void;
|
|
13
|
+
get isInteractive(): boolean;
|
|
14
|
+
private checkInteractive;
|
|
8
15
|
text(options: ClackTextOptions): Promise<string>;
|
|
9
16
|
confirm(options: ClackConfirmOptions): Promise<boolean>;
|
|
10
17
|
select<T>(options: ClackSelectOptions<T>): Promise<T>;
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { confirm as clackConfirm, password as clackPassword, select as clackSelect, text as clackText, isCancel, } from '@clack/prompts';
|
|
2
|
-
|
|
2
|
+
import { isInteractiveMode } from './tty.js';
|
|
3
|
+
export class NonInteractiveError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'NonInteractiveError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class PromptService {
|
|
10
|
+
_isInteractive = null;
|
|
11
|
+
setInteractiveMode(value) {
|
|
12
|
+
this._isInteractive = value;
|
|
13
|
+
}
|
|
14
|
+
get isInteractive() {
|
|
15
|
+
if (this._isInteractive === null) {
|
|
16
|
+
this._isInteractive = isInteractiveMode();
|
|
17
|
+
}
|
|
18
|
+
return this._isInteractive;
|
|
19
|
+
}
|
|
20
|
+
checkInteractive(operation) {
|
|
21
|
+
if (!this.isInteractive) {
|
|
22
|
+
throw new NonInteractiveError(`Cannot ${operation} in non-interactive mode. Use appropriate flags or run without --no-tty.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
3
25
|
async text(options) {
|
|
26
|
+
this.checkInteractive('prompt for text input');
|
|
4
27
|
const result = await clackText(options);
|
|
5
28
|
if (isCancel(result)) {
|
|
6
29
|
throw new Error('Operation cancelled.');
|
|
@@ -8,6 +31,12 @@ export class PromptService {
|
|
|
8
31
|
return result;
|
|
9
32
|
}
|
|
10
33
|
async confirm(options) {
|
|
34
|
+
if (!this.isInteractive) {
|
|
35
|
+
if (options.initialValue !== undefined) {
|
|
36
|
+
return options.initialValue;
|
|
37
|
+
}
|
|
38
|
+
throw new NonInteractiveError('Cannot prompt for confirmation in non-interactive mode. Use --force or run without --no-tty.');
|
|
39
|
+
}
|
|
11
40
|
const result = await clackConfirm(options);
|
|
12
41
|
if (isCancel(result)) {
|
|
13
42
|
throw new Error('Operation cancelled.');
|
|
@@ -15,6 +44,7 @@ export class PromptService {
|
|
|
15
44
|
return result;
|
|
16
45
|
}
|
|
17
46
|
async select(options) {
|
|
47
|
+
this.checkInteractive('show selection menu');
|
|
18
48
|
const result = await clackSelect(options);
|
|
19
49
|
if (isCancel(result)) {
|
|
20
50
|
throw new Error('Operation cancelled.');
|
|
@@ -22,6 +52,7 @@ export class PromptService {
|
|
|
22
52
|
return result;
|
|
23
53
|
}
|
|
24
54
|
async password(options) {
|
|
55
|
+
this.checkInteractive('prompt for password');
|
|
25
56
|
const result = await clackPassword(options);
|
|
26
57
|
if (isCancel(result)) {
|
|
27
58
|
throw new Error('Operation cancelled.');
|
|
@@ -35,6 +66,7 @@ export class PromptService {
|
|
|
35
66
|
return value;
|
|
36
67
|
}
|
|
37
68
|
async promptForValue(name, description, type, parseValue) {
|
|
69
|
+
this.checkInteractive('prompt for value');
|
|
38
70
|
const fullDescription = description ? ` (${description})` : '';
|
|
39
71
|
if (type === 'boolean') {
|
|
40
72
|
const message = `Enable ${name}${fullDescription}?`;
|
package/oclif.manifest.json
CHANGED
|
@@ -37,16 +37,15 @@
|
|
|
37
37
|
},
|
|
38
38
|
"force": {
|
|
39
39
|
"char": "f",
|
|
40
|
-
"description": "Force
|
|
40
|
+
"description": "Force forget without confirmation",
|
|
41
41
|
"name": "force",
|
|
42
42
|
"allowNo": false,
|
|
43
43
|
"type": "boolean"
|
|
44
44
|
},
|
|
45
45
|
"name": {
|
|
46
46
|
"char": "n",
|
|
47
|
-
"description": "
|
|
47
|
+
"description": "Account name to forget",
|
|
48
48
|
"name": "name",
|
|
49
|
-
"required": true,
|
|
50
49
|
"hasDynamicHelp": false,
|
|
51
50
|
"multiple": false,
|
|
52
51
|
"type": "option"
|
|
@@ -269,6 +268,13 @@
|
|
|
269
268
|
"multiple": false,
|
|
270
269
|
"type": "option"
|
|
271
270
|
},
|
|
271
|
+
"path": {
|
|
272
|
+
"description": "Path to account(configuration) file to import",
|
|
273
|
+
"name": "path",
|
|
274
|
+
"hasDynamicHelp": false,
|
|
275
|
+
"multiple": false,
|
|
276
|
+
"type": "option"
|
|
277
|
+
},
|
|
272
278
|
"url": {
|
|
273
279
|
"description": "Provider base URL",
|
|
274
280
|
"hidden": true,
|
|
@@ -333,6 +339,14 @@
|
|
|
333
339
|
"summary": "Force or disable interactive mode (use --no-tty to disable).",
|
|
334
340
|
"allowNo": true,
|
|
335
341
|
"type": "boolean"
|
|
342
|
+
},
|
|
343
|
+
"name": {
|
|
344
|
+
"char": "n",
|
|
345
|
+
"description": "Account name to switch",
|
|
346
|
+
"name": "name",
|
|
347
|
+
"hasDynamicHelp": false,
|
|
348
|
+
"multiple": false,
|
|
349
|
+
"type": "option"
|
|
336
350
|
}
|
|
337
351
|
},
|
|
338
352
|
"hasDynamicHelp": false,
|
|
@@ -839,5 +853,5 @@
|
|
|
839
853
|
]
|
|
840
854
|
}
|
|
841
855
|
},
|
|
842
|
-
"version": "0.0.
|
|
856
|
+
"version": "0.0.9"
|
|
843
857
|
}
|