@super-protocol/sp-cli 0.0.1-alpha.35 → 0.0.2
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 +260 -65
- package/bin/dev.cmd +1 -1
- package/bin/dev.js +5 -3
- package/bin/run.js +3 -2
- package/dist/commands/auth/login.d.ts +3 -2
- package/dist/commands/auth/login.js +52 -24
- package/dist/commands/auth/me.d.ts +3 -2
- package/dist/commands/auth/me.js +9 -4
- package/dist/commands/base.d.ts +18 -0
- package/dist/commands/base.js +45 -0
- package/dist/commands/config/add.d.ts +13 -0
- package/dist/commands/config/add.js +84 -0
- package/dist/commands/config/base.d.ts +14 -0
- package/dist/commands/config/base.js +62 -0
- package/dist/commands/config/create.d.ts +11 -0
- package/dist/commands/config/create.js +51 -0
- package/dist/commands/config/delete.d.ts +10 -0
- package/dist/commands/config/delete.js +25 -0
- package/dist/commands/config/index.d.ts +6 -0
- package/dist/commands/config/index.js +22 -0
- package/dist/commands/config/list.d.ts +6 -0
- package/dist/commands/config/list.js +39 -0
- package/dist/commands/config/show.d.ts +6 -0
- package/dist/commands/config/show.js +10 -0
- package/dist/commands/config/use.d.ts +6 -0
- package/dist/commands/config/use.js +25 -0
- package/dist/config/config-file.schema.d.ts +6 -0
- package/dist/config/config-file.schema.js +5 -0
- package/dist/config/config.schema.d.ts +2 -0
- package/dist/config/config.schema.js +12 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +5 -0
- package/dist/hooks/init/init-container.d.ts +3 -0
- package/dist/hooks/init/init-container.js +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +23 -1
- package/dist/interfaces/index.d.ts +0 -1
- package/dist/interfaces/index.js +0 -1
- package/dist/interfaces/manager.interface.d.ts +1 -1
- package/dist/lib/container.d.ts +40 -0
- package/dist/lib/container.js +177 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +62 -0
- package/dist/managers/account-manager.d.ts +2 -2
- package/dist/managers/account-manager.js +18 -9
- package/dist/managers/config-file-manager.d.ts +50 -0
- package/dist/managers/config-file-manager.js +278 -0
- package/dist/managers/config-manager.d.ts +12 -6
- package/dist/managers/config-manager.js +38 -14
- package/dist/managers/index.d.ts +1 -2
- package/dist/managers/index.js +1 -2
- package/dist/middlewares/auth-middleware.d.ts +9 -0
- package/dist/middlewares/auth-middleware.js +87 -0
- package/dist/middlewares/cookies-middleware.d.ts +8 -0
- package/dist/middlewares/cookies-middleware.js +80 -0
- package/dist/utils/helper.d.ts +1 -0
- package/dist/utils/helper.js +1 -0
- package/oclif.manifest.json +545 -7
- package/package.json +32 -14
- package/dist/commands/refresh.d.ts +0 -4
- package/dist/commands/refresh.js +0 -7
- package/dist/config/constants.d.ts +0 -1
- package/dist/config/constants.js +0 -2
- package/dist/interfaces/abstract.command.d.ts +0 -16
- package/dist/interfaces/abstract.command.js +0 -59
- package/dist/managers/auth-manager.d.ts +0 -19
- package/dist/managers/auth-manager.js +0 -101
- package/dist/managers/cookies-manager.d.ts +0 -15
- package/dist/managers/cookies-manager.js +0 -92
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createProviderClient, loggerMiddleware, requestIdMiddleware, } from '@super-protocol/provider-client';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { PROVIDER_URL } from '../constants.js';
|
|
4
|
+
import logger from '../logger.js';
|
|
5
|
+
import { AccountManager, ConfigFileManager, ConfigManager, } from '../managers/index.js';
|
|
6
|
+
import { createAuthMiddleware } from '../middlewares/auth-middleware.js';
|
|
7
|
+
import { createCookieMiddleware } from '../middlewares/cookies-middleware.js';
|
|
8
|
+
export class AppContainer {
|
|
9
|
+
cwdDir;
|
|
10
|
+
oclifConfig;
|
|
11
|
+
static appContainer;
|
|
12
|
+
_accountManager;
|
|
13
|
+
_configFileManager;
|
|
14
|
+
_configManager;
|
|
15
|
+
_providerClient;
|
|
16
|
+
initPromises = [];
|
|
17
|
+
initQueue = new Map();
|
|
18
|
+
logger;
|
|
19
|
+
runtimeConfigFile;
|
|
20
|
+
runtimeProviderUrl;
|
|
21
|
+
constructor(cwdDir, oclifConfig) {
|
|
22
|
+
this.cwdDir = cwdDir;
|
|
23
|
+
this.oclifConfig = oclifConfig;
|
|
24
|
+
this.logger = logger.getPino();
|
|
25
|
+
}
|
|
26
|
+
static initialize(currentDir, config) {
|
|
27
|
+
if (!AppContainer.appContainer) {
|
|
28
|
+
AppContainer.appContainer = new AppContainer(currentDir, config);
|
|
29
|
+
}
|
|
30
|
+
return AppContainer.appContainer;
|
|
31
|
+
}
|
|
32
|
+
get accountManager() {
|
|
33
|
+
if (!this._accountManager) {
|
|
34
|
+
throw new Error('Account manager not initialized');
|
|
35
|
+
}
|
|
36
|
+
return this._accountManager;
|
|
37
|
+
}
|
|
38
|
+
get configFileManager() {
|
|
39
|
+
if (!this._configFileManager) {
|
|
40
|
+
throw new Error('ConfigFile manager not initialized');
|
|
41
|
+
}
|
|
42
|
+
return this._configFileManager;
|
|
43
|
+
}
|
|
44
|
+
get configManager() {
|
|
45
|
+
if (!this._configManager) {
|
|
46
|
+
throw new Error('Config manager not initialized');
|
|
47
|
+
}
|
|
48
|
+
return this._configManager;
|
|
49
|
+
}
|
|
50
|
+
static get container() {
|
|
51
|
+
if (!AppContainer.appContainer) {
|
|
52
|
+
throw new Error('AppContainer not initialized');
|
|
53
|
+
}
|
|
54
|
+
return AppContainer.appContainer;
|
|
55
|
+
}
|
|
56
|
+
get providerClient() {
|
|
57
|
+
if (!this._providerClient) {
|
|
58
|
+
throw new Error('Provider client not initialized');
|
|
59
|
+
}
|
|
60
|
+
return this._providerClient;
|
|
61
|
+
}
|
|
62
|
+
async build() {
|
|
63
|
+
await Promise.all(this.initPromises);
|
|
64
|
+
}
|
|
65
|
+
initAccountManager() {
|
|
66
|
+
this.initConfigManager();
|
|
67
|
+
return this.queueInit('accountManager', async () => {
|
|
68
|
+
await this.waitFor('configManager');
|
|
69
|
+
if (this._accountManager) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.logger.info('Initializing account manager');
|
|
73
|
+
const accountManager = new AccountManager(this.configManager, this.logger);
|
|
74
|
+
await accountManager.init();
|
|
75
|
+
this._accountManager = accountManager;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
initConfigFileManager() {
|
|
79
|
+
return this.queueInit('configFileManager', async () => {
|
|
80
|
+
if (this._configFileManager) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const configFileManager = new ConfigFileManager(this.oclifConfig.configDir, this.logger, {
|
|
84
|
+
runtimeConfigFile: this.runtimeConfigFile,
|
|
85
|
+
});
|
|
86
|
+
await configFileManager.init();
|
|
87
|
+
this._configFileManager = configFileManager;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
initConfigManager() {
|
|
91
|
+
this.initConfigFileManager();
|
|
92
|
+
return this.queueInit('configManager', async () => {
|
|
93
|
+
await this.waitFor('configFileManager');
|
|
94
|
+
if (this._configManager) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const configFilePath = this.configFileManager.getCurrentConfigPath();
|
|
98
|
+
if (!configFilePath) {
|
|
99
|
+
throw new Error('Config file not found');
|
|
100
|
+
}
|
|
101
|
+
this.logger.info('Initializing config manager');
|
|
102
|
+
const configManager = new ConfigManager(configFilePath, this.logger);
|
|
103
|
+
await configManager.init();
|
|
104
|
+
this._configManager = configManager;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
initProviderClient({ enableAuth = true, enableCookies = true } = {}) {
|
|
108
|
+
this.initConfigManager();
|
|
109
|
+
return this.queueInit('providerClient', async () => {
|
|
110
|
+
if (this._providerClient) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await this.waitFor('configManager');
|
|
114
|
+
const providerUrl = this.runtimeProviderUrl || await this.configManager.get('providerUrl') || PROVIDER_URL;
|
|
115
|
+
this.logger.info('Initializing provider client');
|
|
116
|
+
const providerClient = createProviderClient({
|
|
117
|
+
baseUrl: providerUrl,
|
|
118
|
+
logger: this.logger,
|
|
119
|
+
middlewares: [requestIdMiddleware, loggerMiddleware],
|
|
120
|
+
});
|
|
121
|
+
providerClient.use({
|
|
122
|
+
onRequest: async ({ request }) => {
|
|
123
|
+
const req = request.clone();
|
|
124
|
+
const readBody = await req.body?.getReader().read();
|
|
125
|
+
let body;
|
|
126
|
+
if (readBody && readBody.value) {
|
|
127
|
+
body = Buffer.from(readBody.value).toString('utf8');
|
|
128
|
+
}
|
|
129
|
+
this.logger.debug({
|
|
130
|
+
body,
|
|
131
|
+
url: req.url,
|
|
132
|
+
}, 'Requesting');
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
if (enableCookies) {
|
|
136
|
+
this.logger.info('Initializing cookie middleware');
|
|
137
|
+
const cookiesMiddleware = await createCookieMiddleware(this.configManager, this.logger, providerUrl);
|
|
138
|
+
providerClient.use(cookiesMiddleware);
|
|
139
|
+
}
|
|
140
|
+
if (enableAuth) {
|
|
141
|
+
this.logger.info('Initializing auth middleware');
|
|
142
|
+
const authMiddleware = await createAuthMiddleware(this.configManager, providerClient, this.logger);
|
|
143
|
+
providerClient.use(authMiddleware);
|
|
144
|
+
}
|
|
145
|
+
this._providerClient = providerClient;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
setupRuntimeConfig(config) {
|
|
149
|
+
if (this.initQueue.size > 0) {
|
|
150
|
+
throw new Error('Cannot change runtime config after initialization has started');
|
|
151
|
+
}
|
|
152
|
+
this.runtimeProviderUrl = config.url;
|
|
153
|
+
if (config.configFile) {
|
|
154
|
+
this.runtimeConfigFile = path.isAbsolute(config.configFile)
|
|
155
|
+
? config.configFile
|
|
156
|
+
: path.join(this.cwdDir, config.configFile);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.runtimeConfigFile = undefined;
|
|
160
|
+
}
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
queueInit(name, task) {
|
|
164
|
+
if (!this.initQueue.has(name)) {
|
|
165
|
+
const promise = task();
|
|
166
|
+
this.initQueue.set(name, promise);
|
|
167
|
+
this.initPromises.push(promise);
|
|
168
|
+
}
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
async waitFor(name) {
|
|
172
|
+
const promise = this.initQueue.get(name);
|
|
173
|
+
if (promise) {
|
|
174
|
+
await promise;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
const formatMsgPrefix = (namespace) => namespace ? `[${namespace}] ` : undefined;
|
|
3
|
+
class PinoLogger {
|
|
4
|
+
rootLogger;
|
|
5
|
+
namespaceValue;
|
|
6
|
+
logger;
|
|
7
|
+
constructor(rootLogger, namespaceValue) {
|
|
8
|
+
this.rootLogger = rootLogger;
|
|
9
|
+
this.namespaceValue = namespaceValue;
|
|
10
|
+
this.logger = namespaceValue
|
|
11
|
+
? rootLogger.child({ namespace: namespaceValue }, { msgPrefix: formatMsgPrefix(namespaceValue) })
|
|
12
|
+
: rootLogger;
|
|
13
|
+
}
|
|
14
|
+
get namespace() {
|
|
15
|
+
return this.namespaceValue;
|
|
16
|
+
}
|
|
17
|
+
child(namespace) {
|
|
18
|
+
const normalizedNamespace = (namespace || '').trim();
|
|
19
|
+
if (!normalizedNamespace) {
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
const childNamespace = this.namespaceValue
|
|
23
|
+
? `${this.namespaceValue}:${normalizedNamespace}`
|
|
24
|
+
: normalizedNamespace;
|
|
25
|
+
return new PinoLogger(this.rootLogger, childNamespace);
|
|
26
|
+
}
|
|
27
|
+
debug(formatter, ...args) {
|
|
28
|
+
this.forward('debug', formatter, args);
|
|
29
|
+
}
|
|
30
|
+
error(formatter, ...args) {
|
|
31
|
+
this.forward('error', formatter, args);
|
|
32
|
+
}
|
|
33
|
+
getPino() {
|
|
34
|
+
return this.logger;
|
|
35
|
+
}
|
|
36
|
+
info(formatter, ...args) {
|
|
37
|
+
this.forward('info', formatter, args);
|
|
38
|
+
}
|
|
39
|
+
trace(formatter, ...args) {
|
|
40
|
+
this.forward('trace', formatter, args);
|
|
41
|
+
}
|
|
42
|
+
warn(formatter, ...args) {
|
|
43
|
+
this.forward('warn', formatter, args);
|
|
44
|
+
}
|
|
45
|
+
forward(level, formatter, args) {
|
|
46
|
+
this.logger[level].apply(this.logger, [formatter, ...args]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const logger = (namespace) => {
|
|
50
|
+
const normalizedNamespace = (namespace || '').trim();
|
|
51
|
+
const pinoLogger = pino({
|
|
52
|
+
level: process.env.SP_LOG_LEVEL || 'silent',
|
|
53
|
+
transport: {
|
|
54
|
+
options: {
|
|
55
|
+
colorize: true,
|
|
56
|
+
},
|
|
57
|
+
target: 'pino-pretty',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return new PinoLogger(pinoLogger, normalizedNamespace);
|
|
61
|
+
};
|
|
62
|
+
export default logger('sp');
|
|
@@ -8,8 +8,8 @@ export declare class AccountManager implements IAccountManager {
|
|
|
8
8
|
account?: Account;
|
|
9
9
|
private privateKey?;
|
|
10
10
|
constructor(configManager: IConfigManager, logger: pino.BaseLogger);
|
|
11
|
-
create(): Account
|
|
12
|
-
createFromKey(privateKey: string): Account
|
|
11
|
+
create(): Promise<Account>;
|
|
12
|
+
createFromKey(privateKey: string): Promise<Account>;
|
|
13
13
|
createSign(nonce: string): Promise<string>;
|
|
14
14
|
getAddress(): string;
|
|
15
15
|
init(): Promise<void>;
|
|
@@ -20,25 +20,25 @@ export class AccountManager {
|
|
|
20
20
|
this.configManager = configManager;
|
|
21
21
|
this.logger = logger;
|
|
22
22
|
}
|
|
23
|
-
create() {
|
|
23
|
+
async create() {
|
|
24
24
|
this.privateKey = generatePrivateKey();
|
|
25
25
|
const account = privateKeyToAccount(this.privateKey);
|
|
26
26
|
this.account = {
|
|
27
27
|
address: account.address,
|
|
28
28
|
privateKey: this.privateKey,
|
|
29
29
|
};
|
|
30
|
-
this.configManager.set('account', this.account);
|
|
30
|
+
await this.configManager.set('account', this.account);
|
|
31
31
|
this.logger.debug('Created new account');
|
|
32
32
|
return this.account;
|
|
33
33
|
}
|
|
34
|
-
createFromKey(privateKey) {
|
|
34
|
+
async createFromKey(privateKey) {
|
|
35
35
|
this.privateKey = privateKey;
|
|
36
36
|
const account = privateKeyToAccount(this.privateKey);
|
|
37
37
|
this.account = {
|
|
38
38
|
address: account.address,
|
|
39
39
|
privateKey: this.privateKey,
|
|
40
40
|
};
|
|
41
|
-
this.configManager.set('account', this.account);
|
|
41
|
+
await this.configManager.set('account', this.account);
|
|
42
42
|
this.logger.debug('Created new account from key');
|
|
43
43
|
return this.account;
|
|
44
44
|
}
|
|
@@ -48,7 +48,7 @@ export class AccountManager {
|
|
|
48
48
|
}
|
|
49
49
|
const account = privateKeyToAccount(this.privateKey);
|
|
50
50
|
const sign = await account.signMessage({ message: nonce });
|
|
51
|
-
this.logger.debug({
|
|
51
|
+
this.logger.debug({ address: account.address, nonce }, 'Created new signature for nonce');
|
|
52
52
|
return sign;
|
|
53
53
|
}
|
|
54
54
|
getAddress() {
|
|
@@ -64,8 +64,17 @@ export class AccountManager {
|
|
|
64
64
|
this.privateKey = this.account.privateKey;
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
this.logger.warn({ err: error }, 'Incorrect config file');
|
|
68
|
+
ux.warn('Config not found or have errors');
|
|
69
|
+
const ask = await confirm({
|
|
70
|
+
message: 'Do you want to create new one?',
|
|
71
|
+
});
|
|
72
|
+
if (ask) {
|
|
73
|
+
await this.promptAccount();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
ux.error('Credentials not generated please check config file and rerun command again', { exit: 1 });
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
async promptAccount() {
|
|
@@ -75,7 +84,7 @@ export class AccountManager {
|
|
|
75
84
|
if (askPrivateKey) {
|
|
76
85
|
const privateKey = await password({ message: 'Please input your privateKey for using in account' });
|
|
77
86
|
try {
|
|
78
|
-
const account = this.createFromKey(privateKey);
|
|
87
|
+
const account = await this.createFromKey(privateKey);
|
|
79
88
|
ux.stdout('Your privateKey will be used in config. Keypair created');
|
|
80
89
|
return account;
|
|
81
90
|
}
|
|
@@ -84,7 +93,7 @@ export class AccountManager {
|
|
|
84
93
|
ux.error('Invalid private key format. Please try again.');
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
|
-
const account = this.create();
|
|
96
|
+
const account = await this.create();
|
|
88
97
|
ux.stdout('Generated new keyPair for new account.');
|
|
89
98
|
return account;
|
|
90
99
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { confirm, select } from '@inquirer/prompts';
|
|
2
|
+
import { ux } from '@oclif/core';
|
|
3
|
+
import pino from 'pino';
|
|
4
|
+
import { CliConfig } from '../config/config.schema.js';
|
|
5
|
+
import { 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
|
+
interface ConfigFileManagerOptions {
|
|
10
|
+
confirmPrompt?: ConfirmPrompt;
|
|
11
|
+
runtimeConfigFile?: string;
|
|
12
|
+
selectPrompt?: SelectPrompt;
|
|
13
|
+
ux?: UxLike;
|
|
14
|
+
}
|
|
15
|
+
export declare class ConfigFileManager implements IManager {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private static readonly CONFIG_FILE;
|
|
18
|
+
private static readonly CONFIGS_DIR;
|
|
19
|
+
private readonly configDir;
|
|
20
|
+
private readonly configFilePath;
|
|
21
|
+
private configs;
|
|
22
|
+
private readonly configsDir;
|
|
23
|
+
private readonly confirmPrompt;
|
|
24
|
+
private runtimeConfigFile?;
|
|
25
|
+
private readonly selectPrompt;
|
|
26
|
+
private readonly ux;
|
|
27
|
+
constructor(configDir: string, logger: pino.BaseLogger, options?: ConfigFileManagerOptions);
|
|
28
|
+
addConfig(configName: string): Promise<void>;
|
|
29
|
+
createConfig(configFileName: string, name: string, url?: string): Promise<void>;
|
|
30
|
+
deleteConfig(configName: string): Promise<void>;
|
|
31
|
+
deleteConfigByName(name?: string, force?: boolean): Promise<void>;
|
|
32
|
+
getConfigData(configName: string): CliConfig | undefined;
|
|
33
|
+
getConfigDir(): string;
|
|
34
|
+
getConfigs(): string[];
|
|
35
|
+
getConfigsWithNames(): Array<{
|
|
36
|
+
file: string;
|
|
37
|
+
name: string;
|
|
38
|
+
}>;
|
|
39
|
+
getCurrentConfigFile(): string | undefined;
|
|
40
|
+
getCurrentConfigPath(): string | undefined;
|
|
41
|
+
importConfig(sourcePath: string, targetName?: string): Promise<string>;
|
|
42
|
+
init(): Promise<void>;
|
|
43
|
+
removeConfig(configName: string): Promise<void>;
|
|
44
|
+
setCurrentConfig(configName: string): Promise<void>;
|
|
45
|
+
setRuntimeConfigFile(runtimeConfigFile?: string): void;
|
|
46
|
+
private ensureDirectories;
|
|
47
|
+
private load;
|
|
48
|
+
private save;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { confirm, select } from '@inquirer/prompts';
|
|
2
|
+
import { ux } from '@oclif/core';
|
|
3
|
+
import { Value } from '@sinclair/typebox/value';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { cliConfigSchema } from '../config/config.schema.js';
|
|
7
|
+
import { DIR_ACCESS_PERMS, FILE_ACCESS_PERMS } from '../constants.js';
|
|
8
|
+
import { getConfigName } from '../utils/helper.js';
|
|
9
|
+
export class ConfigFileManager {
|
|
10
|
+
logger;
|
|
11
|
+
static CONFIG_FILE = 'config.json';
|
|
12
|
+
static CONFIGS_DIR = 'configs';
|
|
13
|
+
configDir;
|
|
14
|
+
configFilePath;
|
|
15
|
+
configs = {
|
|
16
|
+
configs: [],
|
|
17
|
+
currentConfig: undefined,
|
|
18
|
+
};
|
|
19
|
+
configsDir;
|
|
20
|
+
confirmPrompt;
|
|
21
|
+
runtimeConfigFile;
|
|
22
|
+
selectPrompt;
|
|
23
|
+
ux;
|
|
24
|
+
constructor(configDir, logger, options = {}) {
|
|
25
|
+
this.logger = logger;
|
|
26
|
+
this.configDir = configDir;
|
|
27
|
+
this.configFilePath = path.join(this.configDir, ConfigFileManager.CONFIG_FILE);
|
|
28
|
+
this.configsDir = path.join(this.configDir, ConfigFileManager.CONFIGS_DIR);
|
|
29
|
+
this.confirmPrompt = options.confirmPrompt ?? confirm;
|
|
30
|
+
this.selectPrompt = options.selectPrompt ?? select;
|
|
31
|
+
this.ux = options.ux ?? ux;
|
|
32
|
+
this.runtimeConfigFile = options.runtimeConfigFile;
|
|
33
|
+
}
|
|
34
|
+
async addConfig(configName) {
|
|
35
|
+
if (!this.configs.configs.includes(configName)) {
|
|
36
|
+
this.configs.configs.push(configName);
|
|
37
|
+
await this.save();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async createConfig(configFileName, name, url) {
|
|
41
|
+
const configPath = path.join(this.configsDir, configFileName);
|
|
42
|
+
if (fs.existsSync(configPath)) {
|
|
43
|
+
throw new Error(`Configuration file already exists: ${configFileName}`);
|
|
44
|
+
}
|
|
45
|
+
const config = { name };
|
|
46
|
+
if (url) {
|
|
47
|
+
config.providerUrl = url;
|
|
48
|
+
}
|
|
49
|
+
fs.writeFileSync(configPath, JSON.stringify(config, undefined, 2), {
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
flag: 'w',
|
|
52
|
+
mode: FILE_ACCESS_PERMS,
|
|
53
|
+
});
|
|
54
|
+
await this.addConfig(configFileName);
|
|
55
|
+
this.logger.info({ configFileName, name, url }, 'Created new configuration');
|
|
56
|
+
}
|
|
57
|
+
async deleteConfig(configName) {
|
|
58
|
+
const configPath = path.join(this.configsDir, configName);
|
|
59
|
+
if (!fs.existsSync(configPath)) {
|
|
60
|
+
throw new Error(`Configuration file not found: ${configName}`);
|
|
61
|
+
}
|
|
62
|
+
await this.removeConfig(configName);
|
|
63
|
+
fs.unlinkSync(configPath);
|
|
64
|
+
this.logger.info({ configName }, 'Deleted configuration file');
|
|
65
|
+
}
|
|
66
|
+
async deleteConfigByName(name, force) {
|
|
67
|
+
const configs = this.getConfigsWithNames();
|
|
68
|
+
if (configs.length === 0) {
|
|
69
|
+
this.ux.stdout('No configurations found');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
let configToDelete;
|
|
73
|
+
if (name) {
|
|
74
|
+
const config = configs.find(c => c.name === name);
|
|
75
|
+
if (!config) {
|
|
76
|
+
this.ux.error(`Configuration not found: ${name}`);
|
|
77
|
+
}
|
|
78
|
+
configToDelete = config.file;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
configToDelete = await this.selectPrompt({
|
|
82
|
+
choices: configs.map(config => ({
|
|
83
|
+
name: config.name,
|
|
84
|
+
value: config.file,
|
|
85
|
+
})),
|
|
86
|
+
message: 'Select configuration to delete:',
|
|
87
|
+
});
|
|
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
|
+
default: false,
|
|
94
|
+
message: `Are you sure you want to delete configuration "${displayName}"?`,
|
|
95
|
+
});
|
|
96
|
+
if (!confirmed) {
|
|
97
|
+
this.ux.stdout('Deletion cancelled');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
await this.deleteConfig(configToDelete);
|
|
103
|
+
this.ux.stdout(`Successfully deleted configuration: ${displayName}`);
|
|
104
|
+
const newCurrent = this.getCurrentConfigFile();
|
|
105
|
+
if (newCurrent) {
|
|
106
|
+
const newCurrentConfig = configs.find(c => c.file === newCurrent);
|
|
107
|
+
this.ux.stdout(`Current configuration is now: ${newCurrentConfig?.name || newCurrent}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.ux.stdout('No configurations remaining');
|
|
111
|
+
this.ux.stdout('Create a new configuration with: sp config create --name "Config Name" or sp auth login');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.ux.error(`Failed to delete configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
getConfigData(configName) {
|
|
119
|
+
const configPath = path.join(this.configsDir, configName);
|
|
120
|
+
if (!fs.existsSync(configPath)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
125
|
+
return JSON.parse(raw);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.logger.warn({ configName, err: error }, 'Failed to read config data');
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
getConfigDir() {
|
|
133
|
+
return this.configsDir;
|
|
134
|
+
}
|
|
135
|
+
getConfigs() {
|
|
136
|
+
return [...this.configs.configs];
|
|
137
|
+
}
|
|
138
|
+
getConfigsWithNames() {
|
|
139
|
+
return this.configs.configs.map(configFile => {
|
|
140
|
+
const configPath = path.join(this.configsDir, configFile);
|
|
141
|
+
let name = configFile;
|
|
142
|
+
try {
|
|
143
|
+
if (fs.existsSync(configPath)) {
|
|
144
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
145
|
+
const config = JSON.parse(raw);
|
|
146
|
+
if (config.name) {
|
|
147
|
+
name = config.name;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
this.logger.warn({ configFile, err: error }, 'Failed to read config name');
|
|
153
|
+
}
|
|
154
|
+
return { file: configFile, name };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
getCurrentConfigFile() {
|
|
158
|
+
if (this.runtimeConfigFile) {
|
|
159
|
+
return path.basename(this.runtimeConfigFile);
|
|
160
|
+
}
|
|
161
|
+
return this.configs.currentConfig;
|
|
162
|
+
}
|
|
163
|
+
getCurrentConfigPath() {
|
|
164
|
+
if (this.runtimeConfigFile) {
|
|
165
|
+
return this.runtimeConfigFile;
|
|
166
|
+
}
|
|
167
|
+
if (!this.configs.currentConfig) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
return path.join(this.configsDir, this.configs.currentConfig);
|
|
171
|
+
}
|
|
172
|
+
async importConfig(sourcePath, targetName) {
|
|
173
|
+
if (!fs.existsSync(sourcePath)) {
|
|
174
|
+
throw new Error(`Source file not found: ${sourcePath}`);
|
|
175
|
+
}
|
|
176
|
+
let sourceConfig;
|
|
177
|
+
try {
|
|
178
|
+
const raw = fs.readFileSync(sourcePath, 'utf8');
|
|
179
|
+
sourceConfig = JSON.parse(raw);
|
|
180
|
+
if (!Value.Check(cliConfigSchema, sourceConfig)) {
|
|
181
|
+
throw new Error("Configuration doesn't match required schema");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
throw new Error(`Invalid configuration file: ${error instanceof Error ? error.message : String(error)}`);
|
|
186
|
+
}
|
|
187
|
+
let targetFileName;
|
|
188
|
+
if (targetName) {
|
|
189
|
+
targetFileName = getConfigName(targetName);
|
|
190
|
+
}
|
|
191
|
+
else if (sourceConfig.name) {
|
|
192
|
+
targetFileName = getConfigName(sourceConfig.name);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const basename = path.basename(sourcePath, path.extname(sourcePath));
|
|
196
|
+
targetFileName = getConfigName(basename);
|
|
197
|
+
}
|
|
198
|
+
const targetPath = path.join(this.configsDir, targetFileName);
|
|
199
|
+
if (fs.existsSync(targetPath)) {
|
|
200
|
+
throw new Error(`Configuration file already exists: ${targetFileName}`);
|
|
201
|
+
}
|
|
202
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
203
|
+
fs.chmodSync(targetPath, FILE_ACCESS_PERMS);
|
|
204
|
+
await this.addConfig(targetFileName);
|
|
205
|
+
this.logger.info({ sourcePath, targetFileName }, 'Imported configuration');
|
|
206
|
+
return targetFileName;
|
|
207
|
+
}
|
|
208
|
+
async init() {
|
|
209
|
+
if (this.runtimeConfigFile) {
|
|
210
|
+
this.logger.debug({ runtimeConfigFile: this.runtimeConfigFile }, 'Runtime config provided, skipping config file initialization');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.ensureDirectories();
|
|
214
|
+
await this.load();
|
|
215
|
+
}
|
|
216
|
+
async removeConfig(configName) {
|
|
217
|
+
const index = this.configs.configs.indexOf(configName);
|
|
218
|
+
if (index !== -1) {
|
|
219
|
+
this.configs.configs.splice(index, 1);
|
|
220
|
+
if (this.configs.currentConfig === configName) {
|
|
221
|
+
this.configs.currentConfig = this.configs.configs[0];
|
|
222
|
+
}
|
|
223
|
+
await this.save();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async setCurrentConfig(configName) {
|
|
227
|
+
const configPath = path.join(this.configsDir, configName);
|
|
228
|
+
if (!fs.existsSync(configPath)) {
|
|
229
|
+
throw new Error(`Configuration file not found: ${configName}`);
|
|
230
|
+
}
|
|
231
|
+
if (!this.configs.configs.includes(configName)) {
|
|
232
|
+
this.configs.configs.push(configName);
|
|
233
|
+
}
|
|
234
|
+
this.configs.currentConfig = configName;
|
|
235
|
+
await this.save();
|
|
236
|
+
}
|
|
237
|
+
setRuntimeConfigFile(runtimeConfigFile) {
|
|
238
|
+
this.runtimeConfigFile = runtimeConfigFile;
|
|
239
|
+
}
|
|
240
|
+
ensureDirectories() {
|
|
241
|
+
if (!fs.existsSync(this.configDir)) {
|
|
242
|
+
fs.mkdirSync(this.configDir, { mode: DIR_ACCESS_PERMS, recursive: true });
|
|
243
|
+
}
|
|
244
|
+
if (!fs.existsSync(this.configsDir)) {
|
|
245
|
+
fs.mkdirSync(this.configsDir, { mode: DIR_ACCESS_PERMS, recursive: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async load() {
|
|
249
|
+
try {
|
|
250
|
+
if (fs.existsSync(this.configFilePath)) {
|
|
251
|
+
const raw = fs.readFileSync(this.configFilePath, 'utf8');
|
|
252
|
+
this.configs = JSON.parse(raw);
|
|
253
|
+
this.logger.debug({ configs: this.configs }, 'Configs loaded');
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
this.logger.debug('Config manager file not found, using defaults');
|
|
257
|
+
await this.save();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.logger.error({ err: error }, 'Failed to load config manager file, using defaults');
|
|
262
|
+
this.configs = {
|
|
263
|
+
configs: [],
|
|
264
|
+
currentConfig: undefined,
|
|
265
|
+
};
|
|
266
|
+
await this.save();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async save() {
|
|
270
|
+
this.ensureDirectories();
|
|
271
|
+
this.logger.debug({ configs: this.configs, path: this.configFilePath }, 'Saving config manager file');
|
|
272
|
+
fs.writeFileSync(this.configFilePath, JSON.stringify(this.configs, undefined, 2), {
|
|
273
|
+
encoding: 'utf8',
|
|
274
|
+
flag: 'w',
|
|
275
|
+
mode: FILE_ACCESS_PERMS,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|