@super-protocol/sp-cli 0.0.2-beta.1 → 0.0.2-beta.11

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 (47) hide show
  1. package/README.md +135 -321
  2. package/dist/commands/account/info.d.ts +21 -0
  3. package/dist/commands/account/info.js +43 -0
  4. package/dist/commands/auth/login.d.ts +1 -10
  5. package/dist/commands/auth/login.js +15 -84
  6. package/dist/commands/base.d.ts +2 -4
  7. package/dist/commands/base.js +8 -10
  8. package/dist/commands/config/add.js +9 -5
  9. package/dist/commands/config/base.d.ts +4 -3
  10. package/dist/commands/config/base.js +12 -14
  11. package/dist/commands/config/create.js +4 -4
  12. package/dist/commands/config/list.js +2 -2
  13. package/dist/commands/config/use.js +5 -3
  14. package/dist/commands/files/download.d.ts +15 -0
  15. package/dist/commands/files/download.js +53 -0
  16. package/dist/commands/files/upload.d.ts +18 -0
  17. package/dist/commands/files/upload.js +77 -0
  18. package/dist/commands/storage/base.d.ts +5 -0
  19. package/dist/commands/storage/base.js +113 -2
  20. package/dist/commands/storage/create.d.ts +0 -4
  21. package/dist/commands/storage/create.js +0 -116
  22. package/dist/commands/storage/select.js +12 -10
  23. package/dist/commands/storage/update.d.ts +0 -2
  24. package/dist/commands/storage/update.js +35 -87
  25. package/dist/config/config-file.schema.d.ts +4 -4
  26. package/dist/config/config-file.schema.js +1 -1
  27. package/dist/config/config.schema.d.ts +16 -16
  28. package/dist/config/config.schema.js +1 -10
  29. package/dist/config/resource.schema.d.ts +31 -0
  30. package/dist/config/resource.schema.js +14 -0
  31. package/dist/constants.d.ts +1 -0
  32. package/dist/constants.js +1 -0
  33. package/dist/lib/container.d.ts +4 -6
  34. package/dist/lib/container.js +21 -21
  35. package/dist/managers/account-manager.js +13 -3
  36. package/dist/managers/config-file-manager.d.ts +1 -1
  37. package/dist/managers/config-file-manager.js +13 -8
  38. package/dist/services/auth.service.d.ts +24 -0
  39. package/dist/services/auth.service.js +93 -0
  40. package/dist/services/storage.service.d.ts +44 -7
  41. package/dist/services/storage.service.js +207 -12
  42. package/dist/utils/helper.d.ts +5 -0
  43. package/dist/utils/helper.js +22 -0
  44. package/dist/utils/progress.d.ts +8 -0
  45. package/dist/utils/progress.js +27 -0
  46. package/oclif.manifest.json +180 -184
  47. package/package.json +9 -7
@@ -1,18 +1,35 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { StorageType, } from '@super-protocol/dto-js';
8
+ import { ResourceType, } from '@super-protocol/provider-client';
9
+ import { download, upload } from '@super-protocol/sp-files-addon';
10
+ import * as fs from 'node:fs/promises';
11
+ import { basename, dirname, join } from 'node:path';
12
+ import { Value } from 'typebox/value';
13
+ import { Retryable } from 'typescript-retry-decorator';
14
+ import { storageResourceSchema } from '../config/resource.schema.js';
15
+ import { S3_ENDPOINT } from '../constants.js';
1
16
  import { ProviderClientError } from '../errors.js';
17
+ import { preparePath, readJsonFile } from '../utils/helper.js';
2
18
  export class StorageError extends Error {
3
19
  }
4
- export class StoragesEmptyError extends StorageError {
20
+ export class StoragesUndefinedError extends StorageError {
5
21
  }
6
22
  export class StorageCreateError extends StorageError {
7
23
  }
8
24
  export class StorageUpdateError extends StorageError {
9
25
  }
26
+ export class StorageGetError extends StorageError {
27
+ }
28
+ export const generateExternalId = () => Array.from({ length: 16 }).map(() => Math.floor(Math.random() * 16).toString(16)).join('');
10
29
  export class StorageService {
11
- configManager;
12
30
  providerClient;
13
31
  logger;
14
- constructor(configManager, providerClient, logger) {
15
- this.configManager = configManager;
32
+ constructor(providerClient, logger) {
16
33
  this.providerClient = providerClient;
17
34
  this.logger = logger;
18
35
  }
@@ -39,10 +56,86 @@ export class StorageService {
39
56
  throw error_;
40
57
  }
41
58
  }
59
+ async download(params, progressCb) {
60
+ const resourceFile = await readJsonFile({ path: params.resourcePath });
61
+ if (!Value.Check(storageResourceSchema, resourceFile)) {
62
+ throw new StorageError('Invalid resource file format, please verify resource.json against the schema.');
63
+ }
64
+ const { resource } = resourceFile;
65
+ if (resource.type !== ResourceType.StorageProvider) {
66
+ throw new StorageError(`Resource type ${resource.type} is not supported, use StorageProvider type for this command`);
67
+ }
68
+ let localPath = preparePath(params.downloadPath);
69
+ if (resource.filepath) {
70
+ localPath = join(localPath, resource.filepath);
71
+ }
72
+ await download(resource, localPath, {
73
+ encryption: resourceFile.encryption,
74
+ progressCallback: progressCb,
75
+ threads: params.maximumConcurrent,
76
+ });
77
+ }
78
+ async getCentralizedStorage() {
79
+ try {
80
+ const storages = await this.requestStorages();
81
+ const centralized = storages.find(storage => storage.isCentralized);
82
+ if (!centralized) {
83
+ const storage = await this.initCentralizedStorage();
84
+ return storage;
85
+ }
86
+ return centralized;
87
+ }
88
+ catch (error) {
89
+ this.logger.error({ err: error }, 'Error getting centralized storage');
90
+ if (error instanceof StoragesUndefinedError) {
91
+ const storage = await this.initCentralizedStorage();
92
+ return storage;
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+ async getCurrentStorage() {
98
+ try {
99
+ const { data, error } = await this.providerClient.GET('/api/user-settings');
100
+ if (error) {
101
+ throw new StorageGetError('Error getting storage');
102
+ }
103
+ const { activeStorageId } = data || {};
104
+ const storages = await this.requestStorages();
105
+ const storage = storages.find(storage => storage.id === activeStorageId);
106
+ if (!storage) {
107
+ throw new StorageError('Selected storage not found please select storage first');
108
+ }
109
+ return storage;
110
+ }
111
+ catch (error) {
112
+ this.logger.error({ err: error }, 'Failed to get current storage');
113
+ const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
114
+ throw error_;
115
+ }
116
+ }
42
117
  async hasStorage() {
43
- return Boolean(await this.configManager.get('selectedStorage'));
118
+ try {
119
+ const { data, error } = await this.providerClient.GET('/api/user-settings');
120
+ if (error && error.statusCode === 404) {
121
+ return false;
122
+ }
123
+ if (error) {
124
+ this.logger.error({ err: error }, 'Error checking storage');
125
+ throw new StorageError(`Error request storage ${error}`);
126
+ }
127
+ if (!data) {
128
+ throw new StorageError('Error request storage');
129
+ }
130
+ return Boolean(data.activeStorageId);
131
+ }
132
+ catch (error) {
133
+ this.logger.error({ err: error }, 'Storage initialization failed');
134
+ const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
135
+ throw error_;
136
+ }
44
137
  }
45
- async initStorage() {
138
+ async initCentralizedStorage() {
46
139
  this.logger.info('Requesting storage initialization');
47
140
  try {
48
141
  const result = await this.providerClient.POST('/api/storages/centralized');
@@ -50,6 +143,10 @@ export class StorageService {
50
143
  this.logger.error({ err: result.error }, 'Failed to initialize storage');
51
144
  throw new StorageCreateError(result.error.message);
52
145
  }
146
+ if (!result.data) {
147
+ this.logger.error('Provider returned empty storage initialization response');
148
+ throw new StorageCreateError('Incorrect response');
149
+ }
53
150
  this.logger.info('Storage initialized successfully');
54
151
  return result.data;
55
152
  }
@@ -59,7 +156,7 @@ export class StorageService {
59
156
  throw error_;
60
157
  }
61
158
  }
62
- async requestStorage() {
159
+ async requestStorages() {
63
160
  let data;
64
161
  this.logger.info('Requesting available storages');
65
162
  try {
@@ -69,9 +166,9 @@ export class StorageService {
69
166
  this.logger.error({ err: result.error }, 'Failed to fetch storages');
70
167
  throw new StorageError(result.error.message);
71
168
  }
72
- if (!data || !data?.length) {
73
- this.logger.warn('Storages list is empty');
74
- throw new StoragesEmptyError('Storages is empty please create or import first one');
169
+ if (data === undefined) {
170
+ this.logger.warn('Storages list response is wrong');
171
+ throw new StoragesUndefinedError('Storages is empty please create or import first one');
75
172
  }
76
173
  this.logger.debug({ count: data.length }, 'Received storages list');
77
174
  return data;
@@ -84,7 +181,23 @@ export class StorageService {
84
181
  }
85
182
  async saveStorage(selectedStorage) {
86
183
  this.logger.info({ selectedStorage }, 'Saving selected storage');
87
- await this.configManager.set('selectedStorage', selectedStorage);
184
+ try {
185
+ const { data, error } = await this.providerClient.POST('/api/user-settings', {
186
+ body: {
187
+ activeStorageId: selectedStorage,
188
+ },
189
+ });
190
+ if (error) {
191
+ throw new StorageError(`User settings request error ${error.message}`);
192
+ }
193
+ this.logger.info({ selectedStorage: data?.activeStorageId }, 'Selected storage saved successfully');
194
+ return data;
195
+ }
196
+ catch (error) {
197
+ this.logger.error({ err: error }, 'Failed to save selected storage');
198
+ const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
199
+ throw error_;
200
+ }
88
201
  }
89
202
  async updateStorage(id, storage) {
90
203
  this.logger.info({ storageId: id }, 'Updating storage');
@@ -95,7 +208,7 @@ export class StorageService {
95
208
  });
96
209
  if (error) {
97
210
  this.logger.error({ err: error, storageId: id }, 'Failed to update storage');
98
- throw new StorageUpdateError(error?.message);
211
+ throw new StorageUpdateError(error.message);
99
212
  }
100
213
  if (!data) {
101
214
  this.logger.error({ storageId: id }, 'Provider returned empty storage update response');
@@ -110,4 +223,86 @@ export class StorageService {
110
223
  throw error_;
111
224
  }
112
225
  }
226
+ async upload(params, progressCb) {
227
+ const storage = await this.getCurrentStorage();
228
+ const remotePath = `${params.remotePath || generateExternalId()}`;
229
+ const resourceFilePath = preparePath(params.outputPath || '');
230
+ try {
231
+ await fs.stat(resourceFilePath);
232
+ throw new Error('Output file for resource file already exists');
233
+ }
234
+ catch (error) {
235
+ if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {
236
+ throw error;
237
+ }
238
+ }
239
+ let metadata = {};
240
+ if (params.metadataPath) {
241
+ metadata = await readJsonFile({ path: preparePath(params.metadataPath) });
242
+ }
243
+ const writeCredentials = storage.storageType === StorageType.StorJ
244
+ ? {
245
+ bucket: storage.bucket,
246
+ prefix: join(storage.prefix, remotePath),
247
+ token: storage.storjCredentials.writeAccessToken,
248
+ }
249
+ : {
250
+ accessKeyId: storage.s3Credentials.writeAccessKeyId,
251
+ bucket: storage.bucket,
252
+ endpoint: S3_ENDPOINT,
253
+ prefix: join(storage.prefix, remotePath),
254
+ secretKey: storage.s3Credentials.writeSecretAccessKey,
255
+ };
256
+ const readCredentials = storage.storageType === StorageType.StorJ
257
+ ? {
258
+ bucket: storage.bucket,
259
+ prefix: join(storage.prefix, remotePath),
260
+ token: storage.storjCredentials.readAccessToken,
261
+ }
262
+ : {
263
+ accessKeyId: storage.s3Credentials.readAccessKeyId,
264
+ bucket: storage.bucket,
265
+ endpoint: S3_ENDPOINT,
266
+ prefix: join(storage.prefix, remotePath),
267
+ secretKey: storage.s3Credentials.readSecretAccessKey,
268
+ };
269
+ const info = await fs.stat(params.localPath);
270
+ let { localPath } = params;
271
+ let sourcePath;
272
+ if (info.isFile()) {
273
+ sourcePath = basename(localPath);
274
+ localPath = dirname(localPath);
275
+ }
276
+ try {
277
+ const uploadResult = await upload(localPath, {
278
+ credentials: writeCredentials,
279
+ filepath: '',
280
+ storageType: storage.storageType,
281
+ type: ResourceType.StorageProvider,
282
+ }, {
283
+ encryption: params.withEncryption,
284
+ progressCallback: progressCb,
285
+ sourcePath,
286
+ sync: params.sync,
287
+ threads: params.maximumConcurrent,
288
+ });
289
+ uploadResult.resource.storageType = storage.storageType;
290
+ uploadResult.resource.credentials = readCredentials;
291
+ const result = {
292
+ ...metadata,
293
+ ...uploadResult,
294
+ };
295
+ await fs.writeFile(resourceFilePath, JSON.stringify(result, null, 2));
296
+ this.logger.info(`Resource file was created in ${resourceFilePath}`);
297
+ }
298
+ catch (error) {
299
+ this.logger.error({ err: error }, 'Somethong went wrong with upload');
300
+ console.log(error);
301
+ const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
302
+ throw error_;
303
+ }
304
+ }
113
305
  }
306
+ __decorate([
307
+ Retryable({ maxAttempts: 3, value: [StoragesUndefinedError, StorageCreateError, ProviderClientError] })
308
+ ], StorageService.prototype, "getCentralizedStorage", null);
@@ -1 +1,6 @@
1
1
  export declare const getConfigName: (name: string) => string;
2
+ export declare const preparePath: (rawPath: string) => string;
3
+ export type ReadJsonFileParams = {
4
+ path: string;
5
+ };
6
+ export declare const readJsonFile: <T>(params: ReadJsonFileParams) => Promise<T>;
@@ -1 +1,23 @@
1
+ import { existsSync } from 'node:fs';
2
+ import * as fs from 'node:fs/promises';
3
+ import path from 'node:path';
1
4
  export const getConfigName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-') + '.config.json';
5
+ export const preparePath = (rawPath) => {
6
+ if (path.isAbsolute(rawPath))
7
+ return rawPath;
8
+ return path.join(process.cwd(), rawPath);
9
+ };
10
+ export const readJsonFile = async (params) => {
11
+ if (!existsSync(params.path)) {
12
+ throw new Error(`File could not be found in ${params.path}`);
13
+ }
14
+ const jsonString = await fs.readFile(params.path, 'utf8');
15
+ let parsedValue;
16
+ try {
17
+ parsedValue = JSON.parse(jsonString);
18
+ }
19
+ catch {
20
+ throw new Error(`Invalid JSON format of file ${params.path}`);
21
+ }
22
+ return parsedValue;
23
+ };
@@ -0,0 +1,8 @@
1
+ export declare const createProgressPrinter: ({ action, done, start, }: {
2
+ action: string;
3
+ done?: string;
4
+ start: string;
5
+ }) => {
6
+ advance(key: string, num: number, message: string): void;
7
+ finish: () => void;
8
+ };
@@ -0,0 +1,27 @@
1
+ import * as p from '@clack/prompts';
2
+ export const createProgressPrinter = ({ action, done = 'completed', start, }) => {
3
+ const progressBars = {};
4
+ let lastKey = '';
5
+ const getProgress = (key) => {
6
+ if (key in progressBars) {
7
+ lastKey = key;
8
+ return progressBars[key];
9
+ }
10
+ if (lastKey in progressBars) {
11
+ const progressBar = progressBars[lastKey];
12
+ progressBar.stop(`${action} ${lastKey} ${done}`);
13
+ }
14
+ const progressBar = p.progress({ size: 100, style: 'block' });
15
+ progressBar.start(`${start} ${key}`);
16
+ progressBars[key] = progressBar;
17
+ return progressBar;
18
+ };
19
+ const finish = () => progressBars[lastKey].stop(`${action} ${lastKey} ${done}`);
20
+ return {
21
+ advance(key, num, message) {
22
+ const progress = getProgress(key);
23
+ progress.advance(num, message);
24
+ },
25
+ finish,
26
+ };
27
+ };