@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.
Files changed (90) hide show
  1. package/README.md +191 -163
  2. package/dist/commands/account/base.d.ts +3 -4
  3. package/dist/commands/account/base.js +8 -6
  4. package/dist/commands/account/forget.js +3 -3
  5. package/dist/commands/account/get-sppi.js +7 -11
  6. package/dist/commands/account/info.d.ts +1 -13
  7. package/dist/commands/account/info.js +20 -40
  8. package/dist/commands/account/list.js +6 -6
  9. package/dist/commands/account/login.d.ts +6 -5
  10. package/dist/commands/account/login.js +96 -145
  11. package/dist/commands/account/switch.js +2 -2
  12. package/dist/commands/assets/base.d.ts +39 -0
  13. package/dist/commands/assets/base.js +217 -0
  14. package/dist/commands/assets/create.d.ts +41 -0
  15. package/dist/commands/assets/create.js +277 -0
  16. package/dist/commands/assets/delete.d.ts +14 -0
  17. package/dist/commands/assets/delete.js +69 -0
  18. package/dist/commands/assets/get.d.ts +14 -0
  19. package/dist/commands/assets/get.js +79 -0
  20. package/dist/commands/assets/list.d.ts +7 -0
  21. package/dist/commands/assets/list.js +33 -0
  22. package/dist/commands/assets/update.d.ts +44 -0
  23. package/dist/commands/assets/update.js +321 -0
  24. package/dist/commands/base.d.ts +1 -0
  25. package/dist/commands/base.js +5 -0
  26. package/dist/config/config.schema.d.ts +2 -11
  27. package/dist/config/config.schema.js +2 -7
  28. package/dist/constants.d.ts +3 -3
  29. package/dist/constants.js +3 -3
  30. package/dist/errors.d.ts +0 -2
  31. package/dist/errors.js +0 -2
  32. package/dist/hooks/prerun/auth.js +3 -8
  33. package/dist/interfaces/config-manager.interface.d.ts +3 -1
  34. package/dist/lib/container.d.ts +4 -12
  35. package/dist/lib/container.js +28 -113
  36. package/dist/lib/swarm-client/fetch-api.d.ts +7 -0
  37. package/dist/lib/swarm-client/fetch-api.js +41 -0
  38. package/dist/lib/swarm-client/fetch-timeout.client.d.ts +1 -0
  39. package/dist/lib/swarm-client/fetch-timeout.client.js +32 -0
  40. package/dist/lib/swarm-client/index.d.ts +6 -0
  41. package/dist/lib/swarm-client/index.js +31 -0
  42. package/dist/lib/swarm-client/middlewares/authorization.middleware.d.ts +2 -0
  43. package/dist/lib/swarm-client/middlewares/authorization.middleware.js +12 -0
  44. package/dist/lib/swarm-client/middlewares/index.d.ts +6 -0
  45. package/dist/lib/swarm-client/middlewares/index.js +5 -0
  46. package/dist/lib/swarm-client/middlewares/logger.middleware.d.ts +2 -0
  47. package/dist/lib/swarm-client/middlewares/logger.middleware.js +30 -0
  48. package/dist/lib/swarm-client/middlewares/request-id.middleware.d.ts +2 -0
  49. package/dist/lib/swarm-client/middlewares/request-id.middleware.js +13 -0
  50. package/dist/lib/swarm-client/types.d.ts +23 -0
  51. package/dist/lib/swarm-client/types.js +1 -0
  52. package/dist/managers/account-manager.d.ts +1 -0
  53. package/dist/managers/account-manager.js +13 -18
  54. package/dist/managers/config-file-manager.d.ts +22 -9
  55. package/dist/managers/config-file-manager.js +247 -122
  56. package/dist/managers/config-manager.d.ts +6 -6
  57. package/dist/managers/config-manager.js +8 -8
  58. package/dist/services/account.service.d.ts +42 -0
  59. package/dist/services/account.service.js +140 -0
  60. package/dist/services/asset.service.d.ts +35 -0
  61. package/dist/services/asset.service.js +120 -0
  62. package/dist/services/auth.service.d.ts +4 -6
  63. package/dist/services/auth.service.js +108 -118
  64. package/dist/utils/helper.js +2 -2
  65. package/oclif.manifest.json +462 -212
  66. package/package.json +7 -8
  67. package/dist/commands/files/download.d.ts +0 -15
  68. package/dist/commands/files/download.js +0 -63
  69. package/dist/commands/files/upload.d.ts +0 -18
  70. package/dist/commands/files/upload.js +0 -83
  71. package/dist/commands/storage/base.d.ts +0 -13
  72. package/dist/commands/storage/base.js +0 -125
  73. package/dist/commands/storage/create.d.ts +0 -11
  74. package/dist/commands/storage/create.js +0 -53
  75. package/dist/commands/storage/select.d.ts +0 -9
  76. package/dist/commands/storage/select.js +0 -38
  77. package/dist/commands/storage/show.d.ts +0 -17
  78. package/dist/commands/storage/show.js +0 -34
  79. package/dist/commands/storage/update.d.ts +0 -14
  80. package/dist/commands/storage/update.js +0 -204
  81. package/dist/commands/workflows/extend-lease.d.ts +0 -17
  82. package/dist/commands/workflows/extend-lease.js +0 -102
  83. package/dist/hooks/finally/shutdown-blockchain.d.ts +0 -3
  84. package/dist/hooks/finally/shutdown-blockchain.js +0 -8
  85. package/dist/middlewares/auth-middleware.d.ts +0 -9
  86. package/dist/middlewares/auth-middleware.js +0 -91
  87. package/dist/middlewares/cookies-middleware.d.ts +0 -8
  88. package/dist/middlewares/cookies-middleware.js +0 -80
  89. package/dist/services/storage.service.d.ts +0 -73
  90. package/dist/services/storage.service.js +0 -378
@@ -1,9 +1,9 @@
1
- import * as fs from 'node:fs';
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 configPath = path.join(this.configsDir, configFileName);
27
- if (fs.existsSync(configPath)) {
28
- throw new Error(`Account file already exists: ${configFileName}`);
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.providerUrl = url;
37
+ config.swarmUrl = url;
33
38
  }
34
39
  if (account) {
35
40
  config.account = account;
36
41
  }
37
- fs.writeFileSync(configPath, JSON.stringify(config, undefined, 2), {
38
- encoding: 'utf8',
39
- flag: 'w',
40
- mode: FILE_ACCESS_PERMS,
41
- });
42
- this.logger.info({ configFileName, name, url }, 'Created new configuration');
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 (!fs.existsSync(configPath)) {
55
+ if (!(await this.pathExists(configPath))) {
51
56
  throw new Error(`Account file not found: ${configFileName}`);
52
57
  }
53
- let config;
54
- try {
55
- const raw = fs.readFileSync(configPath, 'utf8');
56
- config = JSON.parse(raw);
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
- catch (error) {
59
- this.logger.warn({ configFileName, err: error }, 'Failed to read config data');
60
- throw new Error(`Failed to read configuration: ${configFileName}`);
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
- if (config.name === normalizedName) {
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.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');
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 (!fs.existsSync(configPath)) {
107
+ if (!(await this.pathExists(configPath))) {
77
108
  throw new Error(`Account file not found: ${configName}`);
78
109
  }
79
- await this.removeConfig(configName);
80
- fs.unlinkSync(configPath);
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 (!fs.existsSync(configPath)) {
147
+ if (!(await this.pathExists(configPath))) {
112
148
  return undefined;
113
149
  }
114
150
  try {
115
- const raw = fs.readFileSync(configPath, 'utf8');
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
- getConfigs() {
127
- if (!fs.existsSync(this.configsDir)) {
128
- return [];
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
- return fs.readdirSync(this.configsDir).filter((file) => file.endsWith('.config.json'));
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.filter((config) => config.name === name || config.file === name).at(0);
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
- if (!fs.existsSync(this.configsDir)) {
142
- return [];
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
- try {
151
- if (fs.existsSync(configPath)) {
152
- const raw = fs.readFileSync(configPath, 'utf8');
153
- const config = JSON.parse(raw);
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(DEFAULT_USER_CONFIG_FILE);
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 (!fs.existsSync(sourcePath)) {
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.readFileSync(sourcePath, 'utf8');
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
- let targetFileName;
208
- if (targetName) {
209
- targetFileName = getConfigName(targetName);
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 (fs.existsSync(targetPath)) {
256
+ if (await this.pathExists(targetPath)) {
220
257
  throw new Error(`Configuration file already exists: ${targetFileName}`);
221
258
  }
222
- fs.copyFileSync(sourcePath, targetPath);
223
- fs.chmodSync(targetPath, FILE_ACCESS_PERMS);
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 (!fs.existsSync(configPath)) {
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
- if (!fs.existsSync(this.configDir)) {
256
- fs.mkdirSync(this.configDir, {
257
- mode: DIR_ACCESS_PERMS,
258
- recursive: true,
259
- });
260
- }
261
- if (!fs.existsSync(this.configsDir)) {
262
- fs.mkdirSync(this.configsDir, {
263
- mode: DIR_ACCESS_PERMS,
264
- recursive: true,
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
- if (fs.existsSync(this.configFilePath)) {
271
- const raw = fs.readFileSync(this.configFilePath, 'utf8');
272
- this.configs = JSON.parse(raw);
273
- this.logger.debug({ configs: this.configs }, 'Configs loaded');
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
- this.logger.debug('Config manager file not found, using defaults');
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
- fs.writeFileSync(this.configFilePath, JSON.stringify(this.configs, undefined, 2), {
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): Promise<undefined | V>;
10
+ get<K extends keyof CliConfig, V extends CliConfig[K]>(key: K): undefined | V;
11
11
  private getConfig;
12
- getCookies(): Promise<unknown>;
13
- getCredentials(): Promise<{
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
- setCookies(cookies: unknown): Promise<void>;
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
- async get(key) {
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
- getCookies() {
28
- return this.get('cookies');
27
+ getAuthUrl() {
28
+ return this.config.authUrl || this.getSwarmUrl();
29
29
  }
30
- getCredentials() {
31
- return this.get('auth');
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
- async setCookies(cookies) {
82
- await this.set('cookies', cookies);
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
+ }