@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 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.0 linux-x64 node-v24.14.1
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 config = yield setAuthContext(user.idToken.jwtToken, orgSlug);
97
- resolve(config);
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 setAuthContext(bearerToken, orgSlug) {
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 ${error.statusCode} resource: ${fullResource}`);
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: any, contentType: string, fileName: string, ownedByReference?: string, ttl?: number): Promise<any>;
6
- uploadFile(uploadPost: any, file: File): Promise<void>;
7
- uploadFileAsStream(uploadPost: any, buffer: File, options?: any): Promise<unknown>;
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
- const formData = new FormData();
73
- formData.append('Content-Type', file.type || file_type_helper_1.FileTypeHelper.getContentType(file));
74
- this.appendUploadHeaders(uploadPost, formData);
75
- formData.append('file', file);
76
- yield (0, node_fetch_1.default)(uploadPost.url, { method: 'POST', body: formData });
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
- const formData = new FormData();
82
- formData.append('Content-Type', options.contentType);
83
- this.appendUploadHeaders(uploadPost, formData);
84
- formData.append('file', buffer);
85
- return new Promise((res, rej) => {
86
- formData.getLength((err, len) => __awaiter(this, void 0, void 0, function* () {
87
- if (err) {
88
- rej(err);
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
- const response = yield (0, node_fetch_1.default)(uploadPost.url, {
91
- method: 'POST',
92
- body: formData,
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.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": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.5.0",
2
+ "version": "3.5.1-alpha.9da7ac5",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "3.5.0",
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.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",