@super-protocol/sp-cli 0.0.2 → 0.0.4
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 +217 -263
- package/bin/dev.js +1 -1
- package/dist/commands/account/get-sppi.d.ts +8 -0
- package/dist/commands/account/get-sppi.js +23 -0
- package/dist/commands/account/info.d.ts +22 -0
- package/dist/commands/account/info.js +46 -0
- package/dist/commands/auth/login.d.ts +6 -11
- package/dist/commands/auth/login.js +28 -76
- package/dist/commands/base.d.ts +2 -4
- package/dist/commands/base.js +8 -10
- package/dist/commands/config/add.js +1 -12
- package/dist/commands/config/base.d.ts +5 -3
- package/dist/commands/config/base.js +30 -14
- package/dist/commands/config/create.js +1 -11
- package/dist/commands/config/list.js +20 -19
- package/dist/commands/config/use.js +5 -3
- package/dist/commands/files/download.d.ts +15 -0
- package/dist/commands/files/download.js +55 -0
- package/dist/commands/files/upload.d.ts +18 -0
- package/dist/commands/files/upload.js +79 -0
- package/dist/commands/storage/base.d.ts +13 -0
- package/dist/commands/storage/base.js +125 -0
- package/dist/commands/storage/create.d.ts +11 -0
- package/dist/commands/storage/create.js +53 -0
- package/dist/commands/storage/select.d.ts +9 -0
- package/dist/commands/storage/select.js +40 -0
- package/dist/commands/storage/show.d.ts +17 -0
- package/dist/commands/storage/show.js +36 -0
- package/dist/commands/storage/update.d.ts +14 -0
- package/dist/commands/storage/update.js +187 -0
- package/dist/commands/workflows/extend-lease.d.ts +17 -0
- package/dist/commands/workflows/extend-lease.js +94 -0
- package/dist/config/config-file.schema.d.ts +4 -4
- package/dist/config/config-file.schema.js +1 -1
- package/dist/config/config.schema.d.ts +25 -15
- package/dist/config/config.schema.js +7 -10
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/hooks/finally/shutdown-blockchain.d.ts +3 -0
- package/dist/hooks/finally/shutdown-blockchain.js +8 -0
- package/dist/lib/container.d.ts +7 -7
- package/dist/lib/container.js +48 -26
- package/dist/managers/account-manager.d.ts +4 -1
- package/dist/managers/account-manager.js +29 -30
- package/dist/managers/config-file-manager.d.ts +2 -2
- package/dist/managers/config-file-manager.js +29 -12
- package/dist/middlewares/auth-middleware.js +5 -1
- package/dist/services/auth.service.d.ts +24 -0
- package/dist/services/auth.service.js +93 -0
- package/dist/services/storage.service.d.ts +73 -0
- package/dist/services/storage.service.js +351 -0
- package/dist/utils/helper.d.ts +6 -0
- package/dist/utils/helper.js +23 -0
- package/dist/utils/progress.d.ts +8 -0
- package/dist/utils/progress.js +35 -0
- package/oclif.manifest.json +432 -156
- package/package.json +19 -9
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ProviderClient } from '@super-protocol/provider-client';
|
|
2
|
+
import pino from 'pino';
|
|
3
|
+
import { AccountManager } from '../managers/account-manager.js';
|
|
4
|
+
import { ConfigManager } from '../managers/config-manager.js';
|
|
5
|
+
export declare class AuthError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthService {
|
|
8
|
+
private readonly accountManager;
|
|
9
|
+
private readonly configManager;
|
|
10
|
+
private readonly providerClient;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
constructor(accountManager: AccountManager, configManager: ConfigManager, providerClient: ProviderClient, logger: pino.BaseLogger);
|
|
13
|
+
auth(): Promise<void>;
|
|
14
|
+
getTokens(nonce: string): Promise<void>;
|
|
15
|
+
getUserNonce(address: string): Promise<{
|
|
16
|
+
error: {
|
|
17
|
+
message: string;
|
|
18
|
+
statusCode: number;
|
|
19
|
+
} | undefined;
|
|
20
|
+
user: {
|
|
21
|
+
nonce?: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export class AuthError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export class AuthService {
|
|
4
|
+
accountManager;
|
|
5
|
+
configManager;
|
|
6
|
+
providerClient;
|
|
7
|
+
logger;
|
|
8
|
+
constructor(accountManager, configManager, providerClient, logger) {
|
|
9
|
+
this.accountManager = accountManager;
|
|
10
|
+
this.configManager = configManager;
|
|
11
|
+
this.providerClient = providerClient;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
async auth() {
|
|
15
|
+
const address = this.accountManager.getAddress();
|
|
16
|
+
const { error: nonceError, user } = await this.getUserNonce(address);
|
|
17
|
+
if (nonceError && nonceError.statusCode === 404) {
|
|
18
|
+
this.logger.debug('Nonce error call sign up');
|
|
19
|
+
const { data: nonceResponse, error: signUpError } = await this.providerClient.POST('/api/auth/sign-up', {
|
|
20
|
+
body: {
|
|
21
|
+
address,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (signUpError && signUpError.statusCode === 409) {
|
|
25
|
+
this.logger.debug({ signUpError }, 'Error signing up');
|
|
26
|
+
const { error: nonceError, user } = await this.getUserNonce(address);
|
|
27
|
+
this.logger.debug({ nonceError, user }, 'Requesting nonce again');
|
|
28
|
+
if (user && user.nonce) {
|
|
29
|
+
await this.getTokens(user.nonce);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new AuthError('User exists but nonce is unavailable. Please try again or contact support.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (signUpError) {
|
|
36
|
+
this.logger.error({ signUpError }, 'Sign-up failed');
|
|
37
|
+
throw new AuthError('Sign-up failed. Please try again later.');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const nonce = nonceResponse?.nonce;
|
|
41
|
+
if (!nonce) {
|
|
42
|
+
this.logger.error({ response: nonceResponse }, 'Unexpected sign-up response');
|
|
43
|
+
throw new AuthError('Provider did not return a nonce.');
|
|
44
|
+
}
|
|
45
|
+
await this.getTokens(nonce);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (user && user.nonce) {
|
|
49
|
+
this.logger.debug({ user }, 'Requesting for existed user');
|
|
50
|
+
await this.getTokens(user.nonce);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
throw new AuthError('Unable to retrieve authentication nonce. Please try again later or contact support if the issue persists.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async getTokens(nonce) {
|
|
57
|
+
const signature = await this.accountManager.createSign(nonce);
|
|
58
|
+
const { data: tokenResponse, error, } = await this.providerClient.POST('/api/auth/token', {
|
|
59
|
+
body: {
|
|
60
|
+
address: this.accountManager.getAddress(),
|
|
61
|
+
provider: 'sp-cli',
|
|
62
|
+
signature,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (error) {
|
|
66
|
+
this.logger.error({ err: error }, 'Token exchange failed');
|
|
67
|
+
throw new AuthError(error.message);
|
|
68
|
+
}
|
|
69
|
+
const { accessToken } = tokenResponse || {};
|
|
70
|
+
if (!accessToken) {
|
|
71
|
+
this.logger.error({ response: tokenResponse }, 'Unexpected token response');
|
|
72
|
+
throw new AuthError('Provider returned an unexpected token response.');
|
|
73
|
+
}
|
|
74
|
+
await this.configManager.setCredentials({ accessKey: accessToken });
|
|
75
|
+
}
|
|
76
|
+
async getUserNonce(address) {
|
|
77
|
+
try {
|
|
78
|
+
const { data, error } = await this.providerClient.GET('/api/users/nonce/{address}', {
|
|
79
|
+
params: {
|
|
80
|
+
path: {
|
|
81
|
+
address,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
this.logger.debug({ data, error }, 'Getting user nonce');
|
|
86
|
+
return { error, user: data };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.logger.error({ err: error }, 'Error request nonce');
|
|
90
|
+
throw new AuthError('Connection failure, please try again later');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EncryptionWithIV, Resource, RuntimeInputInfo } from '@super-protocol/dto-js';
|
|
2
|
+
import { AddStorageDto, ProviderClient, StorageResponseDto, UpdateStorageDto } from '@super-protocol/provider-client';
|
|
3
|
+
import pino from 'pino';
|
|
4
|
+
export declare class StorageError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export declare class StoragesUndefinedError extends StorageError {
|
|
7
|
+
}
|
|
8
|
+
export declare class StorageCreateError extends StorageError {
|
|
9
|
+
}
|
|
10
|
+
export declare class StorageUpdateError extends StorageError {
|
|
11
|
+
}
|
|
12
|
+
export declare class StorageGetError extends StorageError {
|
|
13
|
+
}
|
|
14
|
+
type IUploadParams = {
|
|
15
|
+
localPath: string;
|
|
16
|
+
maximumConcurrent?: number;
|
|
17
|
+
metadataPath?: string;
|
|
18
|
+
outputPath?: string;
|
|
19
|
+
remotePath?: string;
|
|
20
|
+
sync?: boolean;
|
|
21
|
+
withEncryption: boolean;
|
|
22
|
+
};
|
|
23
|
+
type IDownloadParams = {
|
|
24
|
+
downloadPath: string;
|
|
25
|
+
maximumConcurrent?: number;
|
|
26
|
+
resourcePath: string;
|
|
27
|
+
};
|
|
28
|
+
export type ResourceFile = Partial<Pick<RuntimeInputInfo, 'args' | 'hardwareContext' | 'hash' | 'signatureKeyHash'>> & {
|
|
29
|
+
encryption?: EncryptionWithIV;
|
|
30
|
+
resource: Resource;
|
|
31
|
+
};
|
|
32
|
+
type ProgressCb = (arg: {
|
|
33
|
+
current: number;
|
|
34
|
+
key: string;
|
|
35
|
+
total: number;
|
|
36
|
+
}) => void;
|
|
37
|
+
export declare const generateExternalId: () => string;
|
|
38
|
+
export declare class StorageService {
|
|
39
|
+
private readonly providerClient;
|
|
40
|
+
private readonly logger;
|
|
41
|
+
constructor(providerClient: ProviderClient, logger: pino.BaseLogger);
|
|
42
|
+
createStorage(storage: AddStorageDto): Promise<StorageResponseDto>;
|
|
43
|
+
download(params: IDownloadParams, progressCb: ProgressCb): Promise<void>;
|
|
44
|
+
getCentralizedStorage(): Promise<StorageResponseDto>;
|
|
45
|
+
getCurrentStorage(): Promise<StorageResponseDto>;
|
|
46
|
+
getLabel(storage: StorageResponseDto): string;
|
|
47
|
+
hasStorage(): Promise<boolean>;
|
|
48
|
+
initCentralizedStorage(): Promise<{
|
|
49
|
+
bucket: string;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
id: string;
|
|
52
|
+
isCentralized: boolean;
|
|
53
|
+
prefix: string;
|
|
54
|
+
s3Credentials?: import("@super-protocol/provider-client").components["schemas"]["S3CredentialsResponseDto"];
|
|
55
|
+
storageType: import("@super-protocol/provider-client").components["schemas"]["StorageType"];
|
|
56
|
+
storjCredentials?: import("@super-protocol/provider-client").components["schemas"]["StorJCredentialsResponseDto"];
|
|
57
|
+
updatedAt: string;
|
|
58
|
+
userId: string;
|
|
59
|
+
}>;
|
|
60
|
+
requestStorages(): Promise<StorageResponseDto[]>;
|
|
61
|
+
saveStorage(selectedStorage: string): Promise<{
|
|
62
|
+
activeStorageId: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
decentralizedStorageId?: string;
|
|
65
|
+
id: string;
|
|
66
|
+
updatedAt: string;
|
|
67
|
+
userId: string;
|
|
68
|
+
} | undefined>;
|
|
69
|
+
updateStorage(id: string, storage: UpdateStorageDto): Promise<StorageResponseDto>;
|
|
70
|
+
upload(params: IUploadParams, progressCb: ProgressCb): Promise<void>;
|
|
71
|
+
private getStorageCredentials;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,351 @@
|
|
|
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 { StorageResourceSchema, 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 } from 'node:path';
|
|
12
|
+
import { Value } from 'typebox/value';
|
|
13
|
+
import { Retryable } from 'typescript-retry-decorator';
|
|
14
|
+
import { S3_ENDPOINT } from '../constants.js';
|
|
15
|
+
import { ProviderClientError } from '../errors.js';
|
|
16
|
+
import { joinPaths, preparePath, readJsonFile } from '../utils/helper.js';
|
|
17
|
+
export class StorageError extends Error {
|
|
18
|
+
}
|
|
19
|
+
export class StoragesUndefinedError extends StorageError {
|
|
20
|
+
}
|
|
21
|
+
export class StorageCreateError extends StorageError {
|
|
22
|
+
}
|
|
23
|
+
export class StorageUpdateError extends StorageError {
|
|
24
|
+
}
|
|
25
|
+
export class StorageGetError extends StorageError {
|
|
26
|
+
}
|
|
27
|
+
export const generateExternalId = () => Array.from({ length: 16 }).map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
28
|
+
export class StorageService {
|
|
29
|
+
providerClient;
|
|
30
|
+
logger;
|
|
31
|
+
constructor(providerClient, logger) {
|
|
32
|
+
this.providerClient = providerClient;
|
|
33
|
+
this.logger = logger;
|
|
34
|
+
}
|
|
35
|
+
async createStorage(storage) {
|
|
36
|
+
this.logger.info({ storageType: storage.storageType }, 'Creating storage');
|
|
37
|
+
try {
|
|
38
|
+
const { data, error } = await this.providerClient.POST('/api/storages', {
|
|
39
|
+
body: storage,
|
|
40
|
+
});
|
|
41
|
+
if (error && error.message) {
|
|
42
|
+
this.logger.error({ err: error }, 'Failed to create storage');
|
|
43
|
+
throw new StorageCreateError(error.message);
|
|
44
|
+
}
|
|
45
|
+
if (!data) {
|
|
46
|
+
this.logger.error('Provider returned empty storage response');
|
|
47
|
+
throw new StorageCreateError('Incorrect response');
|
|
48
|
+
}
|
|
49
|
+
this.logger.info({ storageId: data.id }, 'Storage created successfully');
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.logger.error({ err: error }, 'Storage creation failed');
|
|
54
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
55
|
+
throw error_;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async download(params, progressCb) {
|
|
59
|
+
const resourceFile = await readJsonFile({ path: params.resourcePath });
|
|
60
|
+
if (!Value.Check(StorageResourceSchema, resourceFile)) {
|
|
61
|
+
throw new StorageError('Invalid resource file format, please verify resource.json against the schema.');
|
|
62
|
+
}
|
|
63
|
+
const { resource } = resourceFile;
|
|
64
|
+
if (resource.type !== ResourceType.StorageProvider) {
|
|
65
|
+
throw new StorageError(`Resource type ${resource.type} is not supported, use StorageProvider type for this command`);
|
|
66
|
+
}
|
|
67
|
+
let localPath = preparePath(params.downloadPath);
|
|
68
|
+
if (resource.filepath) {
|
|
69
|
+
const sanitizedFilepath = resource.filepath.replaceAll('..', '').replace(/^\/+/, '');
|
|
70
|
+
localPath = joinPaths(localPath, sanitizedFilepath);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
this.logger.info('Starting download');
|
|
74
|
+
const downloadResult = await download(resource, localPath, {
|
|
75
|
+
encryption: resourceFile.encryption,
|
|
76
|
+
progressCallback: progressCb,
|
|
77
|
+
retry: {
|
|
78
|
+
initialDelayMs: 0,
|
|
79
|
+
maxRetries: 2,
|
|
80
|
+
},
|
|
81
|
+
threads: params.maximumConcurrent,
|
|
82
|
+
});
|
|
83
|
+
this.logger.info(downloadResult, 'Downloaded');
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.logger.error({ err: error }, 'Download failed with error');
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async getCentralizedStorage() {
|
|
91
|
+
try {
|
|
92
|
+
const storages = await this.requestStorages();
|
|
93
|
+
const centralized = storages.find(storage => storage.isCentralized);
|
|
94
|
+
if (!centralized) {
|
|
95
|
+
const storage = await this.initCentralizedStorage();
|
|
96
|
+
return storage;
|
|
97
|
+
}
|
|
98
|
+
return centralized;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error({ err: error }, 'Error getting centralized storage');
|
|
102
|
+
if (error instanceof StoragesUndefinedError) {
|
|
103
|
+
const storage = await this.initCentralizedStorage();
|
|
104
|
+
return storage;
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async getCurrentStorage() {
|
|
110
|
+
try {
|
|
111
|
+
const { data, error } = await this.providerClient.GET('/api/user-settings');
|
|
112
|
+
if (error) {
|
|
113
|
+
throw new StorageGetError('Error getting storage');
|
|
114
|
+
}
|
|
115
|
+
const { activeStorageId } = data || {};
|
|
116
|
+
const storages = await this.requestStorages();
|
|
117
|
+
const storage = storages.find(storage => storage.id === activeStorageId);
|
|
118
|
+
if (!storage) {
|
|
119
|
+
throw new StorageError('Selected storage not found please select storage first');
|
|
120
|
+
}
|
|
121
|
+
return storage;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.logger.error({ err: error }, 'Failed to get current storage');
|
|
125
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
126
|
+
throw error_;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
getLabel(storage) {
|
|
130
|
+
return `${storage.isCentralized ? 'Super cloud' : `${storage.storageType} - ${storage.bucket}/${storage.prefix}`} `;
|
|
131
|
+
}
|
|
132
|
+
async hasStorage() {
|
|
133
|
+
try {
|
|
134
|
+
const { data, error } = await this.providerClient.GET('/api/user-settings');
|
|
135
|
+
if (error && error.statusCode === 404) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (error) {
|
|
139
|
+
this.logger.error({ err: error }, 'Error checking storage');
|
|
140
|
+
throw new StorageError(`Error request storage ${error}`);
|
|
141
|
+
}
|
|
142
|
+
if (!data) {
|
|
143
|
+
throw new StorageError('Error request storage');
|
|
144
|
+
}
|
|
145
|
+
return Boolean(data.activeStorageId);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
this.logger.error({ err: error }, 'Storage initialization failed');
|
|
149
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
150
|
+
throw error_;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async initCentralizedStorage() {
|
|
154
|
+
this.logger.info('Requesting storage initialization');
|
|
155
|
+
try {
|
|
156
|
+
const result = await this.providerClient.POST('/api/storages/centralized');
|
|
157
|
+
if (result.error) {
|
|
158
|
+
this.logger.error({ err: result.error }, 'Failed to initialize storage');
|
|
159
|
+
throw new StorageCreateError(result.error.message);
|
|
160
|
+
}
|
|
161
|
+
if (!result.data) {
|
|
162
|
+
this.logger.error('Provider returned empty storage initialization response');
|
|
163
|
+
throw new StorageCreateError('Incorrect response');
|
|
164
|
+
}
|
|
165
|
+
this.logger.info('Storage initialized successfully');
|
|
166
|
+
return result.data;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.logger.error({ err: error }, 'Storage initialization failed');
|
|
170
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
171
|
+
throw error_;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async requestStorages() {
|
|
175
|
+
let data;
|
|
176
|
+
this.logger.info('Requesting available storages');
|
|
177
|
+
try {
|
|
178
|
+
const result = await this.providerClient.GET('/api/storages');
|
|
179
|
+
data = result.data;
|
|
180
|
+
if (result.error) {
|
|
181
|
+
this.logger.error({ err: result.error }, 'Failed to fetch storages');
|
|
182
|
+
throw new StorageError(result.error.message);
|
|
183
|
+
}
|
|
184
|
+
if (data === undefined) {
|
|
185
|
+
this.logger.warn('Storages list response is wrong');
|
|
186
|
+
throw new StoragesUndefinedError('Storages is empty please create or import first one');
|
|
187
|
+
}
|
|
188
|
+
this.logger.debug({ count: data.length }, 'Received storages list');
|
|
189
|
+
return data;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger.error({ err: error }, 'Failed to request storages');
|
|
193
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
194
|
+
throw error_;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async saveStorage(selectedStorage) {
|
|
198
|
+
this.logger.info({ selectedStorage }, 'Saving selected storage');
|
|
199
|
+
try {
|
|
200
|
+
let { data, error } = await this.providerClient.PATCH('/api/user-settings', {
|
|
201
|
+
body: {
|
|
202
|
+
activeStorageId: selectedStorage,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
if (error && error.statusCode === 404) {
|
|
206
|
+
const result = await this.providerClient.POST('/api/user-settings', {
|
|
207
|
+
body: {
|
|
208
|
+
activeStorageId: selectedStorage,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
error = result.error;
|
|
212
|
+
data = result.data;
|
|
213
|
+
}
|
|
214
|
+
if (error) {
|
|
215
|
+
throw new StorageError(`User settings request error ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
this.logger.info({ selectedStorage: data?.activeStorageId }, 'Selected storage saved successfully');
|
|
218
|
+
return data;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
this.logger.error({ err: error }, 'Failed to save selected storage');
|
|
222
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
223
|
+
throw error_;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async updateStorage(id, storage) {
|
|
227
|
+
this.logger.info({ storageId: id }, 'Updating storage');
|
|
228
|
+
try {
|
|
229
|
+
const { data, error } = await this.providerClient.PATCH('/api/storages/{id}', {
|
|
230
|
+
body: storage,
|
|
231
|
+
params: { path: { id } },
|
|
232
|
+
});
|
|
233
|
+
if (error) {
|
|
234
|
+
this.logger.error({ err: error, storageId: id }, 'Failed to update storage');
|
|
235
|
+
throw new StorageUpdateError(error.message);
|
|
236
|
+
}
|
|
237
|
+
if (!data) {
|
|
238
|
+
this.logger.error({ storageId: id }, 'Provider returned empty storage update response');
|
|
239
|
+
throw new StorageUpdateError('Incorrect response');
|
|
240
|
+
}
|
|
241
|
+
this.logger.info({ storageId: data.id }, 'Storage updated successfully');
|
|
242
|
+
return data;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.logger.error({ err: error, storageId: id }, 'Failed to update storage');
|
|
246
|
+
const error_ = error instanceof StorageError ? error : new ProviderClientError('Request failed please try again later');
|
|
247
|
+
throw error_;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async upload(params, progressCb) {
|
|
251
|
+
const storage = await this.getCurrentStorage();
|
|
252
|
+
const remotePath = params.remotePath ? params.remotePath.replaceAll('..', '').replace(/^\/+/, '') : generateExternalId();
|
|
253
|
+
const resourceFilePath = preparePath(params.outputPath ?? 'resource.json');
|
|
254
|
+
try {
|
|
255
|
+
await fs.stat(resourceFilePath);
|
|
256
|
+
throw new Error('Output file for resource file already exists');
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
let metadata = {};
|
|
264
|
+
if (params.metadataPath) {
|
|
265
|
+
metadata = await readJsonFile({ path: preparePath(params.metadataPath) });
|
|
266
|
+
}
|
|
267
|
+
const { readCredentials, writeCredentials } = this.getStorageCredentials(storage, remotePath);
|
|
268
|
+
const info = await fs.stat(params.localPath);
|
|
269
|
+
let { localPath } = params;
|
|
270
|
+
let sourcePath;
|
|
271
|
+
if (info.isFile()) {
|
|
272
|
+
sourcePath = basename(localPath);
|
|
273
|
+
localPath = dirname(localPath);
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
this.logger.info('Starting upload');
|
|
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
|
+
retry: {
|
|
286
|
+
initialDelayMs: 0,
|
|
287
|
+
maxRetries: 2,
|
|
288
|
+
},
|
|
289
|
+
sourcePath,
|
|
290
|
+
sync: params.sync,
|
|
291
|
+
threads: params.maximumConcurrent,
|
|
292
|
+
});
|
|
293
|
+
uploadResult.resource.storageType = storage.storageType;
|
|
294
|
+
uploadResult.resource.credentials = readCredentials;
|
|
295
|
+
const result = {
|
|
296
|
+
...metadata,
|
|
297
|
+
...uploadResult,
|
|
298
|
+
};
|
|
299
|
+
await fs.writeFile(resourceFilePath, JSON.stringify(result, null, 2));
|
|
300
|
+
this.logger.info(`Resource file was created in ${resourceFilePath}`);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.logger.error({ err: error }, 'Something went wrong with upload');
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getStorageCredentials(storage, remotePath) {
|
|
308
|
+
if (storage.storageType === StorageType.StorJ) {
|
|
309
|
+
const { readAccessToken, writeAccessToken } = storage.storjCredentials || {};
|
|
310
|
+
if (!writeAccessToken || !readAccessToken) {
|
|
311
|
+
throw new StorageError('StorJ credentials are missing or incomplete');
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
readCredentials: {
|
|
315
|
+
bucket: storage.bucket,
|
|
316
|
+
prefix: joinPaths(storage.prefix, remotePath),
|
|
317
|
+
token: readAccessToken,
|
|
318
|
+
}, writeCredentials: {
|
|
319
|
+
bucket: storage.bucket,
|
|
320
|
+
prefix: joinPaths(storage.prefix, remotePath),
|
|
321
|
+
token: writeAccessToken,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (storage.storageType === StorageType.S3) {
|
|
326
|
+
const { readAccessKeyId, readSecretAccessKey, writeAccessKeyId, writeSecretAccessKey } = storage.s3Credentials || {};
|
|
327
|
+
if (!writeAccessKeyId || !writeSecretAccessKey || !readAccessKeyId || !readSecretAccessKey) {
|
|
328
|
+
throw new StorageError('S3 credentials are missing or incomplete');
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
readCredentials: {
|
|
332
|
+
accessKeyId: readAccessKeyId,
|
|
333
|
+
bucket: storage.bucket,
|
|
334
|
+
endpoint: S3_ENDPOINT,
|
|
335
|
+
prefix: joinPaths(storage.prefix, remotePath),
|
|
336
|
+
secretKey: readSecretAccessKey,
|
|
337
|
+
}, writeCredentials: {
|
|
338
|
+
accessKeyId: writeAccessKeyId,
|
|
339
|
+
bucket: storage.bucket,
|
|
340
|
+
endpoint: S3_ENDPOINT,
|
|
341
|
+
prefix: joinPaths(storage.prefix, remotePath),
|
|
342
|
+
secretKey: writeSecretAccessKey,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
throw new StorageError(`Unknown storage type: ${storage.storageType}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
__decorate([
|
|
350
|
+
Retryable({ maxAttempts: 3, value: [StoragesUndefinedError, StorageCreateError, ProviderClientError] })
|
|
351
|
+
], StorageService.prototype, "getCentralizedStorage", null);
|
package/dist/utils/helper.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export declare const getConfigName: (name: string) => string;
|
|
2
|
+
export declare const preparePath: (rawPath: string) => string;
|
|
3
|
+
export declare const joinPaths: (...args: string[]) => string;
|
|
4
|
+
export type ReadJsonFileParams = {
|
|
5
|
+
path: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const readJsonFile: <T>(params: ReadJsonFileParams) => Promise<T>;
|
package/dist/utils/helper.js
CHANGED
|
@@ -1 +1,24 @@
|
|
|
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 joinPaths = (...args) => path.join(...args).replaceAll('\\', '/');
|
|
11
|
+
export const readJsonFile = async (params) => {
|
|
12
|
+
if (!existsSync(params.path)) {
|
|
13
|
+
throw new Error(`File could not be found in ${params.path}`);
|
|
14
|
+
}
|
|
15
|
+
const jsonString = await fs.readFile(params.path, 'utf8');
|
|
16
|
+
let parsedValue;
|
|
17
|
+
try {
|
|
18
|
+
parsedValue = JSON.parse(jsonString);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error(`Invalid JSON format of file ${params.path}`);
|
|
22
|
+
}
|
|
23
|
+
return parsedValue;
|
|
24
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
export const createProgressPrinter = ({ action, done = 'completed', start, }) => {
|
|
3
|
+
const progressBars = {};
|
|
4
|
+
let lastKey = '';
|
|
5
|
+
let lastProgress = 0;
|
|
6
|
+
const getProgress = (key) => {
|
|
7
|
+
if (key in progressBars) {
|
|
8
|
+
lastKey = key;
|
|
9
|
+
return progressBars[key];
|
|
10
|
+
}
|
|
11
|
+
if (lastKey in progressBars) {
|
|
12
|
+
const progressBar = progressBars[lastKey];
|
|
13
|
+
lastProgress = 0;
|
|
14
|
+
progressBar.stop(`${action} ${lastKey} ${done}`);
|
|
15
|
+
}
|
|
16
|
+
const progressBar = p.progress({ size: 100, style: 'block' });
|
|
17
|
+
progressBar.start(`${start} ${key}`);
|
|
18
|
+
progressBars[key] = progressBar;
|
|
19
|
+
return progressBar;
|
|
20
|
+
};
|
|
21
|
+
const finish = () => {
|
|
22
|
+
if (lastKey && progressBars[lastKey]) {
|
|
23
|
+
lastProgress = 0;
|
|
24
|
+
progressBars[lastKey].stop(`${action} ${lastKey} ${done}`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
advance(key, num, message) {
|
|
29
|
+
const progress = getProgress(key);
|
|
30
|
+
progress.advance(num - lastProgress, message);
|
|
31
|
+
lastProgress = num;
|
|
32
|
+
},
|
|
33
|
+
finish,
|
|
34
|
+
};
|
|
35
|
+
};
|