@contrail/cli 3.5.0 → 3.5.1-alpha.9da7ac5
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 +1 -1
- package/node_modules/@contrail/sdk/.github/workflows/publish-to-npm.yml +120 -0
- package/node_modules/@contrail/sdk/lib/auth/login.js +42 -3
- package/node_modules/@contrail/sdk/lib/core/request.d.ts +3 -0
- package/node_modules/@contrail/sdk/lib/core/request.js +100 -4
- package/node_modules/@contrail/sdk/lib/entities/entities.client.js +1 -1
- package/node_modules/@contrail/sdk/lib/entities/entity.resource.registry.js +1 -0
- package/node_modules/@contrail/sdk/lib/files/files.d.ts +13 -3
- package/node_modules/@contrail/sdk/lib/files/files.js +66 -25
- package/node_modules/@contrail/sdk/package.json +4 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
|
|
|
17
17
|
$ contrail COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ contrail (--version)
|
|
20
|
-
@contrail/cli/3.5.
|
|
20
|
+
@contrail/cli/3.5.1-alpha.9da7ac5 linux-x64 node-v24.14.1
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
paths-ignore:
|
|
7
|
+
- '.github/**'
|
|
8
|
+
pull_request:
|
|
9
|
+
paths-ignore:
|
|
10
|
+
- '.github/**'
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
check-version:
|
|
14
|
+
name: Verify version is available on npm
|
|
15
|
+
if: github.event_name == 'pull_request'
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Check version not already published
|
|
21
|
+
run: |
|
|
22
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
23
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
24
|
+
echo "Checking if ${PACKAGE_NAME}@${VERSION} exists on npm..."
|
|
25
|
+
if npm view "${PACKAGE_NAME}@${VERSION}" version 2>/dev/null; then
|
|
26
|
+
echo "::error::Version ${VERSION} is already published on npm. Bump the version in package.json before merging."
|
|
27
|
+
exit 1
|
|
28
|
+
else
|
|
29
|
+
echo "Version ${VERSION} is available."
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
check-dependencies:
|
|
33
|
+
name: Verify no private dependencies
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- name: Verify no private dependencies
|
|
39
|
+
run: |
|
|
40
|
+
node -e "
|
|
41
|
+
const deps = require('./package.json').dependencies || {};
|
|
42
|
+
const { execSync } = require('child_process');
|
|
43
|
+
let failed = false;
|
|
44
|
+
for (const pkg of Object.keys(deps)) {
|
|
45
|
+
try {
|
|
46
|
+
execSync('npm view ' + pkg, { stdio: 'pipe' });
|
|
47
|
+
} catch {
|
|
48
|
+
console.error('::error::Dependency ' + pkg + ' is not publicly accessible on npm');
|
|
49
|
+
failed = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (failed) process.exit(1);
|
|
53
|
+
console.log('All ' + Object.keys(deps).length + ' dependencies are public.');
|
|
54
|
+
"
|
|
55
|
+
|
|
56
|
+
publish-alpha:
|
|
57
|
+
name: Publish alpha
|
|
58
|
+
needs: check-dependencies
|
|
59
|
+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
permissions:
|
|
62
|
+
id-token: write
|
|
63
|
+
contents: read
|
|
64
|
+
outputs:
|
|
65
|
+
version: ${{ steps.publish.outputs.version }}
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- uses: actions/setup-node@v4
|
|
70
|
+
with:
|
|
71
|
+
node-version: '24'
|
|
72
|
+
registry-url: 'https://registry.npmjs.org'
|
|
73
|
+
|
|
74
|
+
- name: Install and build
|
|
75
|
+
run: npm ci && npm run build
|
|
76
|
+
|
|
77
|
+
- name: Publish alpha
|
|
78
|
+
id: publish
|
|
79
|
+
run: |
|
|
80
|
+
SHORT_SHA=$(echo ${{ github.event.pull_request.head.sha }} | cut -c1-7)
|
|
81
|
+
VERSION=$(node -p "require('./package.json').version")-alpha.${SHORT_SHA}
|
|
82
|
+
npm version $VERSION --no-git-tag-version
|
|
83
|
+
OUTPUT=$(npm publish --tag alpha 2>&1) && {
|
|
84
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
85
|
+
} || {
|
|
86
|
+
if echo "$OUTPUT" | grep -q "You cannot publish over the previously published versions"; then
|
|
87
|
+
echo "::warning::Alpha ${VERSION} already published, skipping."
|
|
88
|
+
else
|
|
89
|
+
echo "::error::Failed to publish alpha ${VERSION}"
|
|
90
|
+
echo "$OUTPUT"
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
publish-release:
|
|
96
|
+
name: Publish release
|
|
97
|
+
needs: check-dependencies
|
|
98
|
+
if: github.event_name == 'push'
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
permissions:
|
|
101
|
+
id-token: write
|
|
102
|
+
contents: read
|
|
103
|
+
outputs:
|
|
104
|
+
version: ${{ steps.publish.outputs.version }}
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
|
|
108
|
+
- uses: actions/setup-node@v4
|
|
109
|
+
with:
|
|
110
|
+
node-version: '24'
|
|
111
|
+
registry-url: 'https://registry.npmjs.org'
|
|
112
|
+
|
|
113
|
+
- name: Install and build
|
|
114
|
+
run: npm ci && npm run build
|
|
115
|
+
|
|
116
|
+
- name: Publish release
|
|
117
|
+
id: publish
|
|
118
|
+
run: |
|
|
119
|
+
npm publish
|
|
120
|
+
echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
|
@@ -93,8 +93,16 @@ function usernameAuthentication(email, password, orgSlug) {
|
|
|
93
93
|
cognitoUser.authenticateUser(authDetails, {
|
|
94
94
|
onSuccess: (result) => __awaiter(this, void 0, void 0, function* () {
|
|
95
95
|
const user = result;
|
|
96
|
-
const
|
|
97
|
-
|
|
96
|
+
const cognitoAccessToken = user.idToken.jwtToken;
|
|
97
|
+
try {
|
|
98
|
+
const apiTokens = yield exchangeCognitoTokensForApiTokens(cognitoAccessToken);
|
|
99
|
+
const config = yield setAuthContext(apiTokens.accessToken, orgSlug, apiTokens.refreshToken);
|
|
100
|
+
resolve(config);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('Failed to process Cognito token:', error);
|
|
104
|
+
reject(error);
|
|
105
|
+
}
|
|
98
106
|
}),
|
|
99
107
|
onFailure: (err) => {
|
|
100
108
|
reject(err);
|
|
@@ -103,11 +111,42 @@ function usernameAuthentication(email, password, orgSlug) {
|
|
|
103
111
|
});
|
|
104
112
|
});
|
|
105
113
|
}
|
|
106
|
-
function
|
|
114
|
+
function exchangeCognitoTokensForApiTokens(cognitoAccessToken) {
|
|
115
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
const apiGateway = CONFIGURATION.apiGateway;
|
|
117
|
+
const url = apiGateway + '/auth/cognito/';
|
|
118
|
+
const response = yield fetch(url, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: {
|
|
121
|
+
Accept: 'application/json',
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
accessToken: cognitoAccessToken,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(`Failed to authenticate with Cognito token: ${response.status}`);
|
|
130
|
+
}
|
|
131
|
+
const data = yield response.json();
|
|
132
|
+
if (!data.accessToken) {
|
|
133
|
+
throw new Error('Response from /auth/cognito/ missing required field: accessToken');
|
|
134
|
+
}
|
|
135
|
+
if (!data.refreshToken) {
|
|
136
|
+
throw new Error('Response from /auth/cognito/ missing required field: refreshToken');
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
accessToken: data.accessToken,
|
|
140
|
+
refreshToken: data.refreshToken,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function setAuthContext(bearerToken, orgSlug, apiRefreshToken) {
|
|
107
145
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
146
|
let apiGateway = CONFIGURATION.apiGateway;
|
|
109
147
|
auth_1.Auth.config = {
|
|
110
148
|
apiUserToken: bearerToken,
|
|
149
|
+
apiRefreshToken: apiRefreshToken,
|
|
111
150
|
apiGateway,
|
|
112
151
|
orgSlug,
|
|
113
152
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { InvocationContext } from '../config/config';
|
|
2
2
|
export declare const AUTH_TOKEN_REQUIRED = "Authentication token must be set to make a contrail call.";
|
|
3
|
+
export declare function _resetTokenRefreshPromise(): void;
|
|
3
4
|
export declare class Request {
|
|
4
5
|
private static MAX_RETRIES;
|
|
5
6
|
private static RETRY_DELAY;
|
|
6
7
|
static request(resource: any, params?: any, ctx?: InvocationContext): Promise<any>;
|
|
8
|
+
private static requestWithRetries;
|
|
9
|
+
private static isTokenExpiredError;
|
|
7
10
|
private static delay;
|
|
8
11
|
static logError(error: {}, resource: any, params?: {}): void;
|
|
9
12
|
static sanitizeParametersString(params: any): string;
|
|
@@ -12,19 +12,108 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.Request = exports.AUTH_TOKEN_REQUIRED = void 0;
|
|
15
|
+
exports.Request = exports._resetTokenRefreshPromise = exports.AUTH_TOKEN_REQUIRED = void 0;
|
|
16
16
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
17
17
|
const config_1 = require("../config/config");
|
|
18
|
+
const auth_1 = require("../auth/auth");
|
|
18
19
|
exports.AUTH_TOKEN_REQUIRED = 'Authentication token must be set to make a contrail call.';
|
|
19
20
|
class ApiError extends Error {
|
|
20
|
-
constructor(message, details) {
|
|
21
|
+
constructor(message, details, errorCode) {
|
|
21
22
|
super(message);
|
|
22
23
|
this.details = details;
|
|
24
|
+
this.errorCode = errorCode;
|
|
23
25
|
this.details = details;
|
|
26
|
+
this.errorCode = errorCode;
|
|
24
27
|
}
|
|
25
28
|
}
|
|
29
|
+
let tokenRefreshPromise = null;
|
|
30
|
+
function performTokenRefresh() {
|
|
31
|
+
var _a, _b, _c;
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const apiRefreshToken = (_a = auth_1.Auth.config) === null || _a === void 0 ? void 0 : _a.apiRefreshToken;
|
|
34
|
+
const apiGateway = (_b = auth_1.Auth.config) === null || _b === void 0 ? void 0 : _b.apiGateway;
|
|
35
|
+
if (!apiRefreshToken) {
|
|
36
|
+
throw new Error('No API refresh token available. Please login again.');
|
|
37
|
+
}
|
|
38
|
+
if (!apiGateway) {
|
|
39
|
+
throw new Error('API Gateway not configured. Ensure the application environment is properly configured.');
|
|
40
|
+
}
|
|
41
|
+
const url = apiGateway + '/auth/jwt_refresh/';
|
|
42
|
+
try {
|
|
43
|
+
const response = yield (0, node_fetch_1.default)(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: 'application/json',
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
refreshToken: apiRefreshToken,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const errorText = yield response.text();
|
|
55
|
+
throw new Error(`Token refresh failed with status ${response.status}: ${errorText}`);
|
|
56
|
+
}
|
|
57
|
+
const data = yield response.json();
|
|
58
|
+
if (!data.accessToken) {
|
|
59
|
+
throw new Error(`Token refresh response did not contain a new access token.`);
|
|
60
|
+
}
|
|
61
|
+
auth_1.Auth.config.apiUserToken = data.accessToken;
|
|
62
|
+
(0, config_1.setConfig)(auth_1.Auth.config);
|
|
63
|
+
console.log('Access token refreshed successfully');
|
|
64
|
+
return data.accessToken;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new Error(`Token refresh failed: ${(_c = error === null || error === void 0 ? void 0 : error.message) !== null && _c !== void 0 ? _c : String(error)}. Please login again with your credentials.`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function refreshAccessToken() {
|
|
72
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
+
if (tokenRefreshPromise) {
|
|
74
|
+
console.log('Token refresh already in progress, waiting for completion...');
|
|
75
|
+
return tokenRefreshPromise;
|
|
76
|
+
}
|
|
77
|
+
tokenRefreshPromise = performTokenRefresh();
|
|
78
|
+
try {
|
|
79
|
+
const newToken = yield tokenRefreshPromise;
|
|
80
|
+
return newToken;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
tokenRefreshPromise = null;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function _resetTokenRefreshPromise() {
|
|
88
|
+
tokenRefreshPromise = null;
|
|
89
|
+
}
|
|
90
|
+
exports._resetTokenRefreshPromise = _resetTokenRefreshPromise;
|
|
26
91
|
class Request {
|
|
27
92
|
static request(resource, params = { method: 'get' }, ctx) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
|
|
95
|
+
try {
|
|
96
|
+
return yield this.requestWithRetries(resource, params, ctx);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (this.isTokenExpiredError(error)) {
|
|
100
|
+
console.log(`[SDK Request: Token Expired] Attempting to refresh access token...`);
|
|
101
|
+
try {
|
|
102
|
+
yield refreshAccessToken();
|
|
103
|
+
params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
|
|
104
|
+
console.log(`[SDK Request: Token Refreshed] Retrying request...`);
|
|
105
|
+
return yield this.requestWithRetries(resource, params, ctx);
|
|
106
|
+
}
|
|
107
|
+
catch (refreshError) {
|
|
108
|
+
console.error(`[SDK Request: Token Refresh Failed] ${refreshError === null || refreshError === void 0 ? void 0 : refreshError.message}`, refreshError);
|
|
109
|
+
throw new ApiError('Access token refresh failed after TOKEN_EXPIRED error. Unable to proceed with request', refreshError);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
static requestWithRetries(resource, params, ctx) {
|
|
28
117
|
var _a;
|
|
29
118
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
119
|
const userCtx = ctx || (0, config_1.getGlobalContext)();
|
|
@@ -33,7 +122,6 @@ class Request {
|
|
|
33
122
|
const url = API_GATEWAY + resource;
|
|
34
123
|
const method = (params === null || params === void 0 ? void 0 : params.method) ? params.method.toUpperCase() : 'GET';
|
|
35
124
|
const fullResource = `${method} ${url.split('/')[3]}${resource}`;
|
|
36
|
-
params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
|
|
37
125
|
let res;
|
|
38
126
|
let attempt = 0;
|
|
39
127
|
while (attempt <= this.MAX_RETRIES) {
|
|
@@ -72,11 +160,16 @@ class Request {
|
|
|
72
160
|
else {
|
|
73
161
|
const error = yield res.text();
|
|
74
162
|
const errorJson = this.tryParseStringIntoObject(error);
|
|
163
|
+
const isTokenExpired = errorJson && typeof errorJson === 'object' && errorJson.errorCode === 'TOKEN_EXPIRED';
|
|
164
|
+
if (isTokenExpired) {
|
|
165
|
+
throw new ApiError('Token expired', errorJson, 'TOKEN_EXPIRED');
|
|
166
|
+
}
|
|
75
167
|
if (errorHandler) {
|
|
76
168
|
const retryCallback = () => __awaiter(this, void 0, void 0, function* () {
|
|
169
|
+
params.headers = Object.assign(Object.assign({}, (params.headers || {})), (yield Request.getHeaders()));
|
|
77
170
|
return yield (0, node_fetch_1.default)(url, params);
|
|
78
171
|
});
|
|
79
|
-
console.log(`[###### SDK Request: ERROR] code ${
|
|
172
|
+
console.log(`[###### SDK Request: ERROR] code ${res.status} resource: ${fullResource}`);
|
|
80
173
|
console.log(`[###### SDK Request: ERROR] params ${Request.sanitizeParametersString(params)}`);
|
|
81
174
|
const request = Object.assign(Object.assign({}, params), { url, method });
|
|
82
175
|
return yield errorHandler.handle(res, errorJson, retryCallback, request);
|
|
@@ -97,6 +190,9 @@ class Request {
|
|
|
97
190
|
}
|
|
98
191
|
});
|
|
99
192
|
}
|
|
193
|
+
static isTokenExpiredError(error) {
|
|
194
|
+
return error instanceof ApiError && error.errorCode === 'TOKEN_EXPIRED';
|
|
195
|
+
}
|
|
100
196
|
static delay(ms) {
|
|
101
197
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
198
|
}
|
|
@@ -131,7 +131,7 @@ class Entities {
|
|
|
131
131
|
resource += `skipHydrateRelationships=${options.skipHydrateRelationships}&`;
|
|
132
132
|
}
|
|
133
133
|
if (options.federatedIds) {
|
|
134
|
-
resource += `ids=${options.federatedIds.map((id) => id.replace(/,/g, '_COMMA_')).join(',')}&`;
|
|
134
|
+
resource += `ids=${options.federatedIds.map((id) => encodeURIComponent(id.replace(/,/g, '_COMMA_'))).join(',')}&`;
|
|
135
135
|
}
|
|
136
136
|
if (options.criteria) {
|
|
137
137
|
resource += 'criteria=' + JSURL.stringify(options.criteria);
|
|
@@ -55,3 +55,4 @@ EntityResourceRegistry.registerEntityResourceName('open-search', 'open-search');
|
|
|
55
55
|
EntityResourceRegistry.registerEntityResourceName('project-configurator', 'project-configurator');
|
|
56
56
|
EntityResourceRegistry.registerEntityResourceName('identity', 'identities');
|
|
57
57
|
EntityResourceRegistry.registerEntityResourceName('entity-policy', 'entity-policies');
|
|
58
|
+
EntityResourceRegistry.registerEntityResourceName('measurement-spec-entry', 'measurement-spec-entries');
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
export declare class Files {
|
|
2
3
|
private ctx;
|
|
3
4
|
constructor(ctx?: any);
|
|
4
5
|
createAndUploadFile(file: File, ownedByReference?: string, ttl?: number): Promise<any>;
|
|
5
|
-
createAndUploadFileFromBuffer(buffer:
|
|
6
|
-
uploadFile(uploadPost:
|
|
7
|
-
|
|
6
|
+
createAndUploadFileFromBuffer(buffer: Buffer, contentType: string, fileName: string, ownedByReference?: string, ttl?: number): Promise<any>;
|
|
7
|
+
uploadFile(uploadPost: {
|
|
8
|
+
url: string;
|
|
9
|
+
fields: Record<string, string>;
|
|
10
|
+
}, file: File): Promise<void>;
|
|
11
|
+
uploadFileAsStream(uploadPost: {
|
|
12
|
+
url: string;
|
|
13
|
+
fields: Record<string, string>;
|
|
14
|
+
}, buffer: Buffer | File, options?: {
|
|
15
|
+
contentType?: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
private withRetry;
|
|
8
18
|
private appendUploadHeaders;
|
|
9
19
|
}
|
|
@@ -17,6 +17,13 @@ const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
|
17
17
|
const entities_1 = require("../entities");
|
|
18
18
|
const FormData = require('form-data');
|
|
19
19
|
const file_type_helper_1 = require("./file-type-helper");
|
|
20
|
+
const RETRYABLE_HTTP_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
21
|
+
class NonRetryableError extends Error {
|
|
22
|
+
constructor(cause) {
|
|
23
|
+
super(cause.message);
|
|
24
|
+
this.cause = cause;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
class Files {
|
|
21
28
|
constructor(ctx) {
|
|
22
29
|
this.ctx = ctx;
|
|
@@ -69,36 +76,70 @@ class Files {
|
|
|
69
76
|
}
|
|
70
77
|
uploadFile(uploadPost, file) {
|
|
71
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const formData = new FormData();
|
|
81
|
+
formData.append('Content-Type', file.type || file_type_helper_1.FileTypeHelper.getContentType(file));
|
|
82
|
+
this.appendUploadHeaders(uploadPost, formData);
|
|
83
|
+
formData.append('file', file);
|
|
84
|
+
const response = yield (0, node_fetch_1.default)(uploadPost.url, { method: 'POST', body: formData });
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
const errorText = yield response.text();
|
|
87
|
+
const error = new Error(`File upload failed with status ${response.status}: ${errorText}`);
|
|
88
|
+
if (!RETRYABLE_HTTP_STATUSES.has(response.status))
|
|
89
|
+
throw new NonRetryableError(error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
yield response.text();
|
|
93
|
+
}), 'Files.uploadFile', () => console.error(`[Files.uploadFile: Max attempts reached] file: ${file.name}, size: ${file.size}`));
|
|
77
94
|
});
|
|
78
95
|
}
|
|
79
96
|
uploadFileAsStream(uploadPost, buffer, options) {
|
|
80
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
98
|
+
return this.withRetry(() => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const formData = new FormData();
|
|
100
|
+
if (options === null || options === void 0 ? void 0 : options.contentType) {
|
|
101
|
+
formData.append('Content-Type', options.contentType);
|
|
102
|
+
}
|
|
103
|
+
this.appendUploadHeaders(uploadPost, formData);
|
|
104
|
+
formData.append('file', buffer);
|
|
105
|
+
const contentLength = yield new Promise((resolve, reject) => {
|
|
106
|
+
formData.getLength((err, length) => (err ? reject(new NonRetryableError(err)) : resolve(length)));
|
|
107
|
+
});
|
|
108
|
+
const response = yield (0, node_fetch_1.default)(uploadPost.url, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: formData,
|
|
111
|
+
headers: { 'Content-Length': contentLength },
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const errorText = yield response.text();
|
|
115
|
+
const error = new Error(`File upload failed with status ${response.status}: ${errorText}`);
|
|
116
|
+
if (!RETRYABLE_HTTP_STATUSES.has(response.status))
|
|
117
|
+
throw new NonRetryableError(error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
yield response.text();
|
|
121
|
+
}), 'Files.uploadFileAsStream', () => console.error(`[Files.uploadFileAsStream: Max attempts reached] size: ${'size' in buffer ? buffer.size : buffer.length}`));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
withRetry(operation, context, onExhausted) {
|
|
125
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
const MAX_ATTEMPTS = 3;
|
|
127
|
+
const RETRY_DELAY_MS = 1000;
|
|
128
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
129
|
+
try {
|
|
130
|
+
return yield operation();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (error instanceof NonRetryableError)
|
|
134
|
+
throw error.cause;
|
|
135
|
+
console.error(`[Attempt ${attempt}][${context}: Error]`, error);
|
|
136
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
137
|
+
onExhausted();
|
|
138
|
+
throw error;
|
|
89
139
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
headers: {
|
|
94
|
-
'Content-Length': len,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
const text = yield response.text();
|
|
98
|
-
console.log('text: ', text);
|
|
99
|
-
res('ok');
|
|
100
|
-
}));
|
|
101
|
-
});
|
|
140
|
+
yield new Promise((resolve) => setTimeout(resolve, attempt * RETRY_DELAY_MS));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
102
143
|
});
|
|
103
144
|
}
|
|
104
145
|
appendUploadHeaders(uploadPost, formData) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/sdk",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"description": "Client library for interfacing with contrail services.",
|
|
5
5
|
"homepage": "https://archergreysorg.github.io/contrail-lib/",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
6
9
|
"main": "lib/index.js",
|
|
7
10
|
"types": "lib/index.d.ts",
|
|
8
11
|
"scripts": {
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.1-alpha.9da7ac5",
|
|
4
4
|
"description": "VibeIQ's Contrail Platform CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"contrail-cli": "./bin/run",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@contrail/entity-types": "^1.6.1",
|
|
34
34
|
"@contrail/loader": "^2.0.6",
|
|
35
35
|
"@contrail/org-config-migration": "^1.1.13",
|
|
36
|
-
"@contrail/sdk": "^1.5.
|
|
36
|
+
"@contrail/sdk": "^1.5.13",
|
|
37
37
|
"@contrail/types": "^3.1.5",
|
|
38
38
|
"@contrail/util": "^1.2.0",
|
|
39
39
|
"@oclif/core": "^2",
|