@super-protocol/sp-cli 0.0.9 → 0.0.10-beta
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 +191 -163
- package/dist/commands/account/base.d.ts +3 -4
- package/dist/commands/account/base.js +8 -6
- package/dist/commands/account/forget.js +3 -3
- package/dist/commands/account/get-sppi.js +7 -11
- package/dist/commands/account/info.d.ts +1 -13
- package/dist/commands/account/info.js +20 -40
- package/dist/commands/account/list.js +6 -6
- package/dist/commands/account/login.d.ts +6 -5
- package/dist/commands/account/login.js +96 -145
- package/dist/commands/account/switch.js +2 -2
- package/dist/commands/assets/base.d.ts +39 -0
- package/dist/commands/assets/base.js +217 -0
- package/dist/commands/assets/create.d.ts +41 -0
- package/dist/commands/assets/create.js +277 -0
- package/dist/commands/assets/delete.d.ts +14 -0
- package/dist/commands/assets/delete.js +69 -0
- package/dist/commands/assets/get.d.ts +14 -0
- package/dist/commands/assets/get.js +79 -0
- package/dist/commands/assets/list.d.ts +7 -0
- package/dist/commands/assets/list.js +33 -0
- package/dist/commands/assets/update.d.ts +44 -0
- package/dist/commands/assets/update.js +321 -0
- package/dist/commands/base.d.ts +1 -0
- package/dist/commands/base.js +5 -0
- package/dist/config/config.schema.d.ts +2 -11
- package/dist/config/config.schema.js +2 -7
- package/dist/constants.d.ts +3 -3
- package/dist/constants.js +3 -3
- package/dist/errors.d.ts +0 -2
- package/dist/errors.js +0 -2
- package/dist/hooks/prerun/auth.js +3 -8
- package/dist/interfaces/config-manager.interface.d.ts +3 -1
- package/dist/lib/container.d.ts +4 -12
- package/dist/lib/container.js +28 -113
- package/dist/lib/swarm-client/fetch-api.d.ts +7 -0
- package/dist/lib/swarm-client/fetch-api.js +41 -0
- package/dist/lib/swarm-client/fetch-timeout.client.d.ts +1 -0
- package/dist/lib/swarm-client/fetch-timeout.client.js +32 -0
- package/dist/lib/swarm-client/index.d.ts +6 -0
- package/dist/lib/swarm-client/index.js +31 -0
- package/dist/lib/swarm-client/middlewares/authorization.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/authorization.middleware.js +12 -0
- package/dist/lib/swarm-client/middlewares/index.d.ts +6 -0
- package/dist/lib/swarm-client/middlewares/index.js +5 -0
- package/dist/lib/swarm-client/middlewares/logger.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/logger.middleware.js +30 -0
- package/dist/lib/swarm-client/middlewares/request-id.middleware.d.ts +2 -0
- package/dist/lib/swarm-client/middlewares/request-id.middleware.js +13 -0
- package/dist/lib/swarm-client/types.d.ts +23 -0
- package/dist/lib/swarm-client/types.js +1 -0
- package/dist/managers/account-manager.d.ts +1 -0
- package/dist/managers/account-manager.js +13 -18
- package/dist/managers/config-file-manager.d.ts +22 -9
- package/dist/managers/config-file-manager.js +247 -122
- package/dist/managers/config-manager.d.ts +6 -6
- package/dist/managers/config-manager.js +8 -8
- package/dist/services/account.service.d.ts +42 -0
- package/dist/services/account.service.js +140 -0
- package/dist/services/asset.service.d.ts +35 -0
- package/dist/services/asset.service.js +120 -0
- package/dist/services/auth.service.d.ts +4 -6
- package/dist/services/auth.service.js +108 -118
- package/dist/utils/helper.js +2 -2
- package/oclif.manifest.json +462 -212
- package/package.json +7 -8
- package/dist/commands/files/download.d.ts +0 -15
- package/dist/commands/files/download.js +0 -63
- package/dist/commands/files/upload.d.ts +0 -18
- package/dist/commands/files/upload.js +0 -83
- package/dist/commands/storage/base.d.ts +0 -13
- package/dist/commands/storage/base.js +0 -125
- package/dist/commands/storage/create.d.ts +0 -11
- package/dist/commands/storage/create.js +0 -53
- package/dist/commands/storage/select.d.ts +0 -9
- package/dist/commands/storage/select.js +0 -38
- package/dist/commands/storage/show.d.ts +0 -17
- package/dist/commands/storage/show.js +0 -34
- package/dist/commands/storage/update.d.ts +0 -14
- package/dist/commands/storage/update.js +0 -204
- package/dist/commands/workflows/extend-lease.d.ts +0 -17
- package/dist/commands/workflows/extend-lease.js +0 -102
- package/dist/hooks/finally/shutdown-blockchain.d.ts +0 -3
- package/dist/hooks/finally/shutdown-blockchain.js +0 -8
- package/dist/middlewares/auth-middleware.d.ts +0 -9
- package/dist/middlewares/auth-middleware.js +0 -91
- package/dist/middlewares/cookies-middleware.d.ts +0 -8
- package/dist/middlewares/cookies-middleware.js +0 -80
- package/dist/services/storage.service.d.ts +0 -73
- package/dist/services/storage.service.js +0 -378
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Value } from 'typebox/value';
|
|
4
4
|
import { cliConfigSchema } from '../config/config.schema.js';
|
|
5
|
-
import { DEFAULT_USER_CONFIG_FILE, DEFAULT_USER_CONFIG_NAME, DIR_ACCESS_PERMS, FILE_ACCESS_PERMS, } from '../constants.js';
|
|
6
|
-
import { getConfigName } from '../utils/helper.js';
|
|
5
|
+
import { DEFAULT_USER_CONFIG_FILE, DEFAULT_USER_CONFIG_NAME, DIR_ACCESS_PERMS, FILE_ACCESS_PERMS, SWARM_URL, } from '../constants.js';
|
|
6
|
+
import { getConfigName, getConfigNameFromCredentials } from '../utils/helper.js';
|
|
7
7
|
export class ConfigFileManager {
|
|
8
8
|
logger;
|
|
9
9
|
static CONFIG_FILE = 'config.json';
|
|
@@ -22,24 +22,29 @@ export class ConfigFileManager {
|
|
|
22
22
|
this.configsDir = path.join(this.configDir, ConfigFileManager.CONFIGS_DIR);
|
|
23
23
|
this.runtimeConfigFile = options.runtimeConfigFile;
|
|
24
24
|
}
|
|
25
|
-
async createConfig(configFileName, name, url, account) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
async createConfig(configFileName, name, url, account, authUrl) {
|
|
26
|
+
const normalizedName = name?.trim();
|
|
27
|
+
const resolvedFileName = normalizedName ? getConfigName(normalizedName) : configFileName;
|
|
28
|
+
const configPath = path.join(this.configsDir, resolvedFileName);
|
|
29
|
+
if (await this.pathExists(configPath)) {
|
|
30
|
+
throw new Error(`Account file already exists: ${resolvedFileName}`);
|
|
31
|
+
}
|
|
32
|
+
const config = {};
|
|
33
|
+
if (normalizedName) {
|
|
34
|
+
config.name = normalizedName;
|
|
29
35
|
}
|
|
30
|
-
const config = { name };
|
|
31
36
|
if (url) {
|
|
32
|
-
config.
|
|
37
|
+
config.swarmUrl = url;
|
|
33
38
|
}
|
|
34
39
|
if (account) {
|
|
35
40
|
config.account = account;
|
|
36
41
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
42
|
-
|
|
42
|
+
if (authUrl) {
|
|
43
|
+
config.authUrl = authUrl;
|
|
44
|
+
}
|
|
45
|
+
await this.writeJsonFile(configPath, config);
|
|
46
|
+
this.logger.info({ configFileName: resolvedFileName, name: normalizedName, url }, 'Created new configuration');
|
|
47
|
+
return resolvedFileName;
|
|
43
48
|
}
|
|
44
49
|
async updateConfigName(configFileName, name) {
|
|
45
50
|
const configPath = path.join(this.configsDir, configFileName);
|
|
@@ -47,41 +52,72 @@ export class ConfigFileManager {
|
|
|
47
52
|
if (!normalizedName) {
|
|
48
53
|
throw new Error('Configuration name cannot be empty');
|
|
49
54
|
}
|
|
50
|
-
if (!
|
|
55
|
+
if (!(await this.pathExists(configPath))) {
|
|
51
56
|
throw new Error(`Account file not found: ${configFileName}`);
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const config = await this.readJsonFileOrThrow(configPath, { configFileName }, 'Failed to read config data', `Failed to read configuration: ${configFileName}`);
|
|
59
|
+
const targetFileName = getConfigName(normalizedName);
|
|
60
|
+
const nameChanged = config.name !== normalizedName;
|
|
61
|
+
const fileChanged = targetFileName !== configFileName;
|
|
62
|
+
if (!nameChanged && !fileChanged) {
|
|
63
|
+
return { updated: false, fileName: configFileName };
|
|
64
|
+
}
|
|
65
|
+
if (fileChanged) {
|
|
66
|
+
const targetPath = path.join(this.configsDir, targetFileName);
|
|
67
|
+
if (await this.pathExists(targetPath)) {
|
|
68
|
+
throw new Error(`Account file already exists: ${targetFileName}`);
|
|
69
|
+
}
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
config.name = normalizedName;
|
|
72
|
+
if (fileChanged) {
|
|
73
|
+
const targetPath = path.join(this.configsDir, targetFileName);
|
|
74
|
+
await this.writeJsonFile(targetPath, config);
|
|
75
|
+
await fs.unlink(configPath);
|
|
76
|
+
if (this.configs.currentConfig === configFileName) {
|
|
77
|
+
this.configs.currentConfig = targetFileName;
|
|
78
|
+
await this.save();
|
|
79
|
+
}
|
|
61
80
|
}
|
|
62
|
-
|
|
81
|
+
else {
|
|
82
|
+
await this.writeJsonFile(configPath, config);
|
|
83
|
+
}
|
|
84
|
+
this.logger.info({ configFileName, renamedTo: fileChanged ? targetFileName : undefined, name: normalizedName }, 'Updated configuration name');
|
|
85
|
+
return { updated: nameChanged, fileName: fileChanged ? targetFileName : configFileName };
|
|
86
|
+
}
|
|
87
|
+
async updateConfigAuthUrl(configFileName, authUrl) {
|
|
88
|
+
const configPath = path.join(this.configsDir, configFileName);
|
|
89
|
+
const normalizedUrl = authUrl.trim();
|
|
90
|
+
if (!normalizedUrl) {
|
|
91
|
+
throw new Error('Auth URL cannot be empty');
|
|
92
|
+
}
|
|
93
|
+
if (!(await this.pathExists(configPath))) {
|
|
94
|
+
throw new Error(`Account file not found: ${configFileName}`);
|
|
95
|
+
}
|
|
96
|
+
const config = await this.readJsonFileOrThrow(configPath, { configFileName }, 'Failed to read config data', `Failed to read configuration: ${configFileName}`);
|
|
97
|
+
if (config.authUrl === normalizedUrl) {
|
|
63
98
|
return false;
|
|
64
99
|
}
|
|
65
|
-
config.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
flag: 'w',
|
|
69
|
-
mode: FILE_ACCESS_PERMS,
|
|
70
|
-
});
|
|
71
|
-
this.logger.info({ configFileName, name: normalizedName }, 'Updated configuration name');
|
|
100
|
+
config.authUrl = normalizedUrl;
|
|
101
|
+
await this.writeJsonFile(configPath, config);
|
|
102
|
+
this.logger.info({ configFileName, authUrl: normalizedUrl }, 'Updated auth URL');
|
|
72
103
|
return true;
|
|
73
104
|
}
|
|
74
105
|
async deleteConfig(configName) {
|
|
75
106
|
const configPath = path.join(this.configsDir, configName);
|
|
76
|
-
if (!
|
|
107
|
+
if (!(await this.pathExists(configPath))) {
|
|
77
108
|
throw new Error(`Account file not found: ${configName}`);
|
|
78
109
|
}
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
if (this.configs.currentConfig === configName) {
|
|
111
|
+
const configs = await this.listConfigFiles();
|
|
112
|
+
const remainingConfigs = configs.filter((c) => c !== configName);
|
|
113
|
+
this.configs.currentConfig = remainingConfigs.length > 0 ? remainingConfigs[0] : undefined;
|
|
114
|
+
await this.save();
|
|
115
|
+
}
|
|
116
|
+
await fs.unlink(configPath);
|
|
81
117
|
this.logger.info({ configName }, 'Deleted configuration file');
|
|
82
118
|
}
|
|
83
119
|
async deleteConfigByName(name) {
|
|
84
|
-
const configs = this.getConfigsWithNames();
|
|
120
|
+
const configs = await this.getConfigsWithNames();
|
|
85
121
|
if (configs.length === 0) {
|
|
86
122
|
throw new Error('No configurations found');
|
|
87
123
|
}
|
|
@@ -106,13 +142,13 @@ export class ConfigFileManager {
|
|
|
106
142
|
this.logger.info('Create a new account with: sp account login --name "Account Name" or sp account login');
|
|
107
143
|
}
|
|
108
144
|
}
|
|
109
|
-
getConfigData(configName) {
|
|
145
|
+
async getConfigData(configName) {
|
|
110
146
|
const configPath = path.join(this.configsDir, configName);
|
|
111
|
-
if (!
|
|
147
|
+
if (!(await this.pathExists(configPath))) {
|
|
112
148
|
return undefined;
|
|
113
149
|
}
|
|
114
150
|
try {
|
|
115
|
-
const raw = fs.
|
|
151
|
+
const raw = await fs.readFile(configPath, 'utf8');
|
|
116
152
|
return JSON.parse(raw);
|
|
117
153
|
}
|
|
118
154
|
catch (error) {
|
|
@@ -123,44 +159,49 @@ export class ConfigFileManager {
|
|
|
123
159
|
getConfigDir() {
|
|
124
160
|
return this.configsDir;
|
|
125
161
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
162
|
+
async configExists(configName) {
|
|
163
|
+
return this.pathExists(path.join(this.configsDir, configName));
|
|
164
|
+
}
|
|
165
|
+
async listConfigFiles() {
|
|
166
|
+
try {
|
|
167
|
+
const files = await fs.readdir(this.configsDir);
|
|
168
|
+
return files.filter((file) => file.endsWith('.json') || file.endsWith('.config.json'));
|
|
129
169
|
}
|
|
130
|
-
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (this.isNotFoundError(error)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
getConfigBaseName(file) {
|
|
178
|
+
if (file.endsWith('.config.json')) {
|
|
179
|
+
return file.slice(0, -'.config.json'.length);
|
|
180
|
+
}
|
|
181
|
+
return path.basename(file, path.extname(file));
|
|
131
182
|
}
|
|
132
|
-
getConfigWithName(name) {
|
|
133
|
-
const configs = this.getConfigsWithNames();
|
|
134
|
-
const config = configs.
|
|
183
|
+
async getConfigWithName(name) {
|
|
184
|
+
const configs = await this.getConfigsWithNames();
|
|
185
|
+
const config = configs.find((config) => config.name === name ||
|
|
186
|
+
config.file === name ||
|
|
187
|
+
this.getConfigBaseName(config.file) === name);
|
|
135
188
|
if (!config) {
|
|
136
189
|
throw new Error(`Account with name ${name} not found`);
|
|
137
190
|
}
|
|
138
191
|
return config;
|
|
139
192
|
}
|
|
140
|
-
getConfigsWithNames() {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
const configFiles = fs
|
|
145
|
-
.readdirSync(this.configsDir)
|
|
146
|
-
.filter((file) => file.endsWith('.config.json'));
|
|
147
|
-
return configFiles.map((configFile) => {
|
|
193
|
+
async getConfigsWithNames() {
|
|
194
|
+
const configFiles = await this.listConfigFiles();
|
|
195
|
+
return Promise.all(configFiles.map(async (configFile) => {
|
|
148
196
|
const configPath = path.join(this.configsDir, configFile);
|
|
149
|
-
let name = configFile;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (config.name) {
|
|
155
|
-
name = config.name;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
this.logger.warn({ configFile, err: error }, 'Failed to read config name');
|
|
197
|
+
let name = this.getConfigBaseName(configFile);
|
|
198
|
+
const config = await this.tryReadJsonFile(configPath, { configFile }, 'Failed to read config name');
|
|
199
|
+
const normalizedName = config?.name?.trim();
|
|
200
|
+
if (normalizedName) {
|
|
201
|
+
name = normalizedName;
|
|
161
202
|
}
|
|
162
203
|
return { file: configFile, name };
|
|
163
|
-
});
|
|
204
|
+
}));
|
|
164
205
|
}
|
|
165
206
|
getCurrentConfigFile() {
|
|
166
207
|
if (this.runtimeConfigFile) {
|
|
@@ -173,14 +214,18 @@ export class ConfigFileManager {
|
|
|
173
214
|
return this.runtimeConfigFile;
|
|
174
215
|
}
|
|
175
216
|
if (!this.configs.currentConfig) {
|
|
217
|
+
let defaultConfigFile = DEFAULT_USER_CONFIG_FILE;
|
|
176
218
|
try {
|
|
177
|
-
await this.createConfig(DEFAULT_USER_CONFIG_FILE, DEFAULT_USER_CONFIG_NAME);
|
|
219
|
+
defaultConfigFile = await this.createConfig(DEFAULT_USER_CONFIG_FILE, DEFAULT_USER_CONFIG_NAME);
|
|
178
220
|
}
|
|
179
|
-
catch {
|
|
180
|
-
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (!this.isAlreadyExistsError(error)) {
|
|
223
|
+
this.logger.error({ err: error }, 'Failed to create default config');
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
181
226
|
}
|
|
182
227
|
try {
|
|
183
|
-
await this.setCurrentConfig(
|
|
228
|
+
await this.setCurrentConfig(defaultConfigFile);
|
|
184
229
|
}
|
|
185
230
|
catch (error) {
|
|
186
231
|
this.logger.error({ err: error }, 'Failed to set default config');
|
|
@@ -190,12 +235,12 @@ export class ConfigFileManager {
|
|
|
190
235
|
return path.join(this.configsDir, this.configs.currentConfig || DEFAULT_USER_CONFIG_FILE);
|
|
191
236
|
}
|
|
192
237
|
async importConfig(sourcePath, targetName) {
|
|
193
|
-
if (!
|
|
238
|
+
if (!(await this.pathExists(sourcePath))) {
|
|
194
239
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
195
240
|
}
|
|
196
241
|
let sourceConfig;
|
|
197
242
|
try {
|
|
198
|
-
const raw = fs.
|
|
243
|
+
const raw = await fs.readFile(sourcePath, 'utf8');
|
|
199
244
|
sourceConfig = JSON.parse(raw);
|
|
200
245
|
if (!Value.Check(cliConfigSchema, sourceConfig)) {
|
|
201
246
|
throw new Error("Configuration doesn't match required schema");
|
|
@@ -204,23 +249,15 @@ export class ConfigFileManager {
|
|
|
204
249
|
catch (error) {
|
|
205
250
|
throw new Error(`Invalid configuration file: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
251
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
else if (sourceConfig.name) {
|
|
212
|
-
targetFileName = getConfigName(sourceConfig.name);
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
const basename = path.basename(sourcePath, path.extname(sourcePath));
|
|
216
|
-
targetFileName = getConfigName(basename);
|
|
217
|
-
}
|
|
252
|
+
const targetFileName = targetName
|
|
253
|
+
? getConfigName(targetName)
|
|
254
|
+
: this.resolveConfigFileName(sourceConfig, path.basename(sourcePath, path.extname(sourcePath)));
|
|
218
255
|
const targetPath = path.join(this.configsDir, targetFileName);
|
|
219
|
-
if (
|
|
256
|
+
if (await this.pathExists(targetPath)) {
|
|
220
257
|
throw new Error(`Configuration file already exists: ${targetFileName}`);
|
|
221
258
|
}
|
|
222
|
-
fs.
|
|
223
|
-
fs.
|
|
259
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
260
|
+
await fs.chmod(targetPath, FILE_ACCESS_PERMS);
|
|
224
261
|
this.logger.info({ sourcePath, targetFileName }, 'Imported configuration');
|
|
225
262
|
return targetFileName;
|
|
226
263
|
}
|
|
@@ -229,20 +266,13 @@ export class ConfigFileManager {
|
|
|
229
266
|
this.logger.debug({ runtimeConfigFile: this.runtimeConfigFile }, 'Runtime config provided, skipping config file initialization');
|
|
230
267
|
return;
|
|
231
268
|
}
|
|
232
|
-
this.ensureDirectories();
|
|
269
|
+
await this.ensureDirectories();
|
|
233
270
|
await this.load();
|
|
234
|
-
|
|
235
|
-
async removeConfig(configName) {
|
|
236
|
-
if (this.configs.currentConfig === configName) {
|
|
237
|
-
const configs = this.getConfigs();
|
|
238
|
-
const remainingConfigs = configs.filter((c) => c !== configName);
|
|
239
|
-
this.configs.currentConfig = remainingConfigs.length > 0 ? remainingConfigs[0] : undefined;
|
|
240
|
-
await this.save();
|
|
241
|
-
}
|
|
271
|
+
await this.normalizeConfigFiles();
|
|
242
272
|
}
|
|
243
273
|
async setCurrentConfig(configName) {
|
|
244
274
|
const configPath = path.join(this.configsDir, configName);
|
|
245
|
-
if (!
|
|
275
|
+
if (!(await this.pathExists(configPath))) {
|
|
246
276
|
throw new Error(`Configuration file not found: ${configName}`);
|
|
247
277
|
}
|
|
248
278
|
this.configs.currentConfig = configName;
|
|
@@ -251,47 +281,142 @@ export class ConfigFileManager {
|
|
|
251
281
|
setRuntimeConfigFile(runtimeConfigFile) {
|
|
252
282
|
this.runtimeConfigFile = runtimeConfigFile;
|
|
253
283
|
}
|
|
254
|
-
ensureDirectories() {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
284
|
+
async ensureDirectories() {
|
|
285
|
+
await fs.mkdir(this.configDir, {
|
|
286
|
+
mode: DIR_ACCESS_PERMS,
|
|
287
|
+
recursive: true,
|
|
288
|
+
});
|
|
289
|
+
await fs.mkdir(this.configsDir, {
|
|
290
|
+
mode: DIR_ACCESS_PERMS,
|
|
291
|
+
recursive: true,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async normalizeConfigFiles() {
|
|
295
|
+
const configFiles = await this.listConfigFiles();
|
|
296
|
+
if (configFiles.length === 0) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
let updatedCurrent = false;
|
|
300
|
+
for (const configFile of configFiles) {
|
|
301
|
+
const configPath = path.join(this.configsDir, configFile);
|
|
302
|
+
const config = await this.tryReadJsonFile(configPath, { configFile }, 'Failed to read config for normalization');
|
|
303
|
+
if (!config) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const desiredFileName = this.resolveConfigFileName(config, this.getConfigBaseName(configFile));
|
|
307
|
+
if (desiredFileName === configFile) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const targetPath = path.join(this.configsDir, desiredFileName);
|
|
311
|
+
if (await this.pathExists(targetPath)) {
|
|
312
|
+
this.logger.warn({ configFile, desiredFileName }, 'Config file name collision; skipping rename');
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
await fs.rename(configPath, targetPath);
|
|
316
|
+
if (this.configs.currentConfig === configFile) {
|
|
317
|
+
this.configs.currentConfig = desiredFileName;
|
|
318
|
+
updatedCurrent = true;
|
|
319
|
+
}
|
|
320
|
+
this.logger.info({ from: configFile, to: desiredFileName }, 'Renamed configuration file');
|
|
321
|
+
}
|
|
322
|
+
if (updatedCurrent) {
|
|
323
|
+
await this.save();
|
|
266
324
|
}
|
|
267
325
|
}
|
|
268
326
|
async load() {
|
|
269
327
|
try {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
328
|
+
const raw = await fs.readFile(this.configFilePath, 'utf8');
|
|
329
|
+
this.configs = JSON.parse(raw);
|
|
330
|
+
this.logger.debug({ configs: this.configs }, 'Configs loaded');
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
if (this.isNotFoundError(error)) {
|
|
334
|
+
this.logger.debug('Config manager file not found, using defaults');
|
|
335
|
+
await this.save();
|
|
274
336
|
}
|
|
275
337
|
else {
|
|
276
|
-
|
|
338
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
339
|
+
const backupPath = `${this.configFilePath}.corrupt-${timestamp}`;
|
|
340
|
+
this.logger.warn({ err: error, backupPath }, 'Config manager file appears corrupted; backing up and recreating');
|
|
341
|
+
try {
|
|
342
|
+
await fs.rename(this.configFilePath, backupPath);
|
|
343
|
+
}
|
|
344
|
+
catch (backupError) {
|
|
345
|
+
this.logger.error({ err: backupError, path: this.configFilePath, backupPath }, 'Failed to back up corrupted config manager file');
|
|
346
|
+
throw backupError;
|
|
347
|
+
}
|
|
348
|
+
this.configs = {
|
|
349
|
+
currentConfig: undefined,
|
|
350
|
+
};
|
|
277
351
|
await this.save();
|
|
278
352
|
}
|
|
279
353
|
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
this.logger.error({ err: error }, 'Failed to load config manager file, using defaults');
|
|
282
|
-
this.configs = {
|
|
283
|
-
currentConfig: undefined,
|
|
284
|
-
};
|
|
285
|
-
await this.save();
|
|
286
|
-
}
|
|
287
354
|
}
|
|
288
355
|
async save() {
|
|
289
|
-
this.ensureDirectories();
|
|
356
|
+
await this.ensureDirectories();
|
|
290
357
|
this.logger.debug({ configs: this.configs, path: this.configFilePath }, 'Saving config manager file');
|
|
291
|
-
|
|
358
|
+
await this.writeJsonFile(this.configFilePath, this.configs);
|
|
359
|
+
}
|
|
360
|
+
async pathExists(targetPath) {
|
|
361
|
+
try {
|
|
362
|
+
await fs.access(targetPath);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async readJsonFileOrThrow(filePath, context, warnMessage, errorMessage) {
|
|
370
|
+
try {
|
|
371
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
372
|
+
return JSON.parse(raw);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
this.logger.warn({ ...context, err: error }, warnMessage);
|
|
376
|
+
throw new Error(errorMessage);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async tryReadJsonFile(filePath, context, warnMessage) {
|
|
380
|
+
try {
|
|
381
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
382
|
+
return JSON.parse(raw);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
this.logger.warn({ ...context, err: error }, warnMessage);
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async writeJsonFile(filePath, data) {
|
|
390
|
+
await fs.writeFile(filePath, JSON.stringify(data, undefined, 2), {
|
|
292
391
|
encoding: 'utf8',
|
|
293
392
|
flag: 'w',
|
|
294
393
|
mode: FILE_ACCESS_PERMS,
|
|
295
394
|
});
|
|
296
395
|
}
|
|
396
|
+
resolveConfigFileName(config, fallbackName) {
|
|
397
|
+
const normalizedName = config.name?.trim();
|
|
398
|
+
if (normalizedName) {
|
|
399
|
+
return getConfigName(normalizedName);
|
|
400
|
+
}
|
|
401
|
+
if (config.account?.privateKey) {
|
|
402
|
+
const swarmUrl = (config.swarmUrl ?? SWARM_URL).trim();
|
|
403
|
+
return getConfigNameFromCredentials(config.account.privateKey, swarmUrl);
|
|
404
|
+
}
|
|
405
|
+
return getConfigName(fallbackName);
|
|
406
|
+
}
|
|
407
|
+
isNotFoundError(error) {
|
|
408
|
+
return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT');
|
|
409
|
+
}
|
|
410
|
+
isAlreadyExistsError(error) {
|
|
411
|
+
if (!error || typeof error !== 'object') {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
if ('code' in error && error.code === 'EEXIST') {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
if (error instanceof Error) {
|
|
418
|
+
return /already exists/i.test(error.message);
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
297
422
|
}
|
|
@@ -7,18 +7,18 @@ export declare class ConfigManager implements IConfigManager {
|
|
|
7
7
|
private config;
|
|
8
8
|
private readonly configDir;
|
|
9
9
|
constructor(configFile: string, logger: pino.BaseLogger);
|
|
10
|
-
get<K extends keyof CliConfig, V extends CliConfig[K]>(key: K):
|
|
10
|
+
get<K extends keyof CliConfig, V extends CliConfig[K]>(key: K): undefined | V;
|
|
11
11
|
private getConfig;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
accessKey: string;
|
|
15
|
-
} | undefined>;
|
|
12
|
+
getAuthUrl(): string;
|
|
13
|
+
getSwarmUrl(): string;
|
|
16
14
|
init(): Promise<void>;
|
|
17
15
|
load(): Promise<CliConfig>;
|
|
18
16
|
private makeBackup;
|
|
19
17
|
save(): Promise<void>;
|
|
20
18
|
set<K extends keyof CliConfig, V extends CliConfig[K]>(key: K, value: V): Promise<void>;
|
|
21
|
-
|
|
19
|
+
getCredentials(): {
|
|
20
|
+
accessKey: string;
|
|
21
|
+
} | undefined;
|
|
22
22
|
setCredentials(auth: Auth): Promise<void>;
|
|
23
23
|
private getConfigPath;
|
|
24
24
|
private hasConfig;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ux } from '@oclif/core';
|
|
4
|
-
import { DIR_ACCESS_PERMS, FILE_ACCESS_PERMS } from '../constants.js';
|
|
4
|
+
import { DIR_ACCESS_PERMS, FILE_ACCESS_PERMS, SWARM_URL } from '../constants.js';
|
|
5
5
|
import { ConfigurationNotFoundError } from '../errors.js';
|
|
6
6
|
export class ConfigManager {
|
|
7
7
|
configFile;
|
|
@@ -13,7 +13,7 @@ export class ConfigManager {
|
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
this.configDir = path.dirname(configFile);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
get(key) {
|
|
17
17
|
return this.config[key];
|
|
18
18
|
}
|
|
19
19
|
getConfig() {
|
|
@@ -24,11 +24,11 @@ export class ConfigManager {
|
|
|
24
24
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
25
25
|
return JSON.parse(raw);
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
return this.
|
|
27
|
+
getAuthUrl() {
|
|
28
|
+
return this.config.authUrl || this.getSwarmUrl();
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
return this.
|
|
30
|
+
getSwarmUrl() {
|
|
31
|
+
return this.config.swarmUrl || SWARM_URL;
|
|
32
32
|
}
|
|
33
33
|
async init() {
|
|
34
34
|
try {
|
|
@@ -78,8 +78,8 @@ export class ConfigManager {
|
|
|
78
78
|
this.logger.debug({ key }, 'Saving parameters to file');
|
|
79
79
|
await this.save();
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
getCredentials() {
|
|
82
|
+
return this.get('auth');
|
|
83
83
|
}
|
|
84
84
|
async setCredentials(auth) {
|
|
85
85
|
await this.set('auth', auth);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Account, type CliConfig } from '../config/config.schema.js';
|
|
2
|
+
import type { ConfigFileManager } from '../managers/index.js';
|
|
3
|
+
export type CliConfigWithAccount = CliConfig & {
|
|
4
|
+
account: Account;
|
|
5
|
+
};
|
|
6
|
+
export declare class AccountServiceError extends Error {
|
|
7
|
+
}
|
|
8
|
+
export declare class InvalidPrivateKeyError extends AccountServiceError {
|
|
9
|
+
}
|
|
10
|
+
export declare class InvalidAccountConfigError extends AccountServiceError {
|
|
11
|
+
}
|
|
12
|
+
export type DisplayNameUpdateStatus = 'updated' | 'unchanged' | 'skipped';
|
|
13
|
+
export declare class AccountService {
|
|
14
|
+
private readonly configFileManager;
|
|
15
|
+
constructor(configFileManager: ConfigFileManager);
|
|
16
|
+
createAccountFromKey(privateKey: string): Account;
|
|
17
|
+
loadConfigFromPath(configPath: string): Promise<CliConfigWithAccount>;
|
|
18
|
+
resolveConfigFileName(configPath: string, config: CliConfig, name?: string): string;
|
|
19
|
+
configExists(configFile: string): Promise<boolean>;
|
|
20
|
+
getConfigsWithNames(): Promise<Array<{
|
|
21
|
+
file: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}>>;
|
|
24
|
+
getConfigData(configFile: string): Promise<CliConfig | undefined>;
|
|
25
|
+
setCurrentConfig(configFile: string): Promise<void>;
|
|
26
|
+
importConfig(sourcePath: string, name?: string): Promise<string>;
|
|
27
|
+
createConfigIfMissing(configFile: string, displayName: string, swarmUrl: string, account: Account, authUrl?: string): Promise<{
|
|
28
|
+
created: boolean;
|
|
29
|
+
updatedName: boolean;
|
|
30
|
+
updatedAuthUrl: boolean;
|
|
31
|
+
configFile: string;
|
|
32
|
+
}>;
|
|
33
|
+
updateConfigDisplayName(configFile: string, displayName?: string): Promise<{
|
|
34
|
+
status: DisplayNameUpdateStatus;
|
|
35
|
+
configFile: string;
|
|
36
|
+
}>;
|
|
37
|
+
updateConfigAuthUrl(configFile: string, authUrl?: string): Promise<DisplayNameUpdateStatus>;
|
|
38
|
+
findConfigsByPrivateKey(privateKey: string, swarmUrl: string, matchSwarmUrl: boolean): Promise<Array<{
|
|
39
|
+
file: string;
|
|
40
|
+
name: string;
|
|
41
|
+
}>>;
|
|
42
|
+
}
|