@contrail/cli 3.6.0-alpha-8-alpha.2c2e640 → 3.6.0-alpha-8-alpha.2ab4f80

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.6.0-alpha-8-alpha.2c2e640 linux-x64 node-v24.21.0
20
+ @contrail/cli/3.6.0-alpha-8-alpha.2ab4f80 linux-x64 node-v24.21.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getVersionFromPackageJson = exports.authUser = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const amazon_cognito_identity_js_1 = require("amazon-cognito-identity-js");
6
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
7
6
  const sdk_1 = require("@contrail/sdk");
8
7
  const config_1 = require("../helpers/config/config");
9
8
  const logger_1 = tslib_1.__importDefault(require("../logger"));
@@ -63,7 +62,7 @@ exports.authUser = authUser;
63
62
  async function getOrgSlug(config) {
64
63
  const orgSlug = config?.orgSlug || process.env.CONTRAIL_CLI_ORG_SLUG;
65
64
  if (!orgSlug) {
66
- const answers = await inquirer_1.default.prompt([
65
+ const answers = await (0, common_1.promptOnStderr)([
67
66
  {
68
67
  type: 'input',
69
68
  name: 'orgSlug',
@@ -106,7 +105,7 @@ async function authenticateWithUsernamePassword(config) {
106
105
  message: 'Password',
107
106
  });
108
107
  }
109
- const answers = await inquirer_1.default.prompt(questions);
108
+ const answers = await (0, common_1.promptOnStderr)(questions);
110
109
  const params = {
111
110
  email: process.env.CONTRAIL_CLI_EMAIL || answers.email,
112
111
  password: process.env.CONTRAIL_CLI_PASSWORD || answers.password,
@@ -161,7 +160,7 @@ async function refreshCurrentUserOrg() {
161
160
  const currentUser = await (0, sdk_1.getCurrentUser)().catch((e) => {
162
161
  console.error('error getting current user', e.message);
163
162
  if (isExpiredTokenError(e)) {
164
- console.log('try frefreshing');
163
+ console.error('access token expired; attempting refresh');
165
164
  return;
166
165
  }
167
166
  throw e;
@@ -176,7 +175,7 @@ async function refreshCurrentUserOrg() {
176
175
  apiGateway: config.apiGateway,
177
176
  orgSlug: config.orgSlug,
178
177
  };
179
- console.log('fetched new access token from refresh token', newVibeAccessToken);
178
+ (0, sdk_1.setConfig)(sdk_1.Auth.config);
180
179
  }
181
180
  catch (e) {
182
181
  throw Error(`Could not get refresh token: ${e}`);
@@ -215,9 +214,11 @@ async function requestRefreshedAccessToken(vibeRefreshToken, apiGateway) {
215
214
  },
216
215
  body: JSON.stringify({ refreshToken: vibeRefreshToken }),
217
216
  });
218
- return await response.json().then((data) => {
219
- return data.accessToken;
220
- });
217
+ const data = await response.json();
218
+ if (!response.ok || !data.accessToken) {
219
+ throw new Error(`jwt_refresh returned ${response.status} with no access token`);
220
+ }
221
+ return data.accessToken;
221
222
  }
222
223
  function getVersionFromPackageJson() {
223
224
  // Get the path to the package.json file
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseCommand = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const core_1 = require("@oclif/core");
6
+ const common_1 = require("./common");
6
7
  const logger_1 = tslib_1.__importDefault(require("./logger"));
7
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
8
  const config_1 = require("./helpers/config/config");
9
9
  const lodash_1 = require("lodash");
10
10
  const login_1 = require("./auth/login");
@@ -73,7 +73,7 @@ class BaseCommand extends core_1.Command {
73
73
  choices: ['yes', 'no'],
74
74
  default: 'no',
75
75
  });
76
- const { confirm } = await inquirer_1.default.prompt(questions);
76
+ const { confirm } = await (0, common_1.promptOnStderr)(questions);
77
77
  if (confirm === 'no') {
78
78
  logger_1.default.info('Please use `contrail config use` command to choose the configuration or `contrail config set --environment=prod` to set different environment.');
79
79
  this.exit(1);
@@ -35,7 +35,7 @@ class GetAPIKeyApp extends basecommand_1.BaseCommand {
35
35
  },
36
36
  ];
37
37
  const answers = await inquirer_1.default.prompt(questions);
38
- logger_1.default.info(`App API key: (app:${answers.appApiKey})`);
38
+ this.log(`App API key: (app:${answers.appApiKey})`);
39
39
  }
40
40
  else {
41
41
  const appFound = installedApps.find((x) => x.app.identifier === this.flags.appIdentifier);
@@ -43,7 +43,7 @@ class GetAPIKeyApp extends basecommand_1.BaseCommand {
43
43
  logger_1.default.info('You do not have such an app installed');
44
44
  return;
45
45
  }
46
- logger_1.default.info(`App API key: (app:${appFound.apiKey.apiKey})`);
46
+ this.log(`App API key: (app:${appFound.apiKey.apiKey})`);
47
47
  }
48
48
  }
49
49
  }
@@ -23,6 +23,7 @@ export default class CreateEntity extends BaseCommand {
23
23
  path: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
24
24
  };
25
25
  static strict: boolean;
26
+ init(): Promise<void>;
26
27
  run(): Promise<void>;
27
28
  }
28
29
  export type CreateSummary = {
@@ -218,8 +218,11 @@ class CreateEntity extends basecommand_1.BaseCommand {
218
218
  }),
219
219
  };
220
220
  static strict = false;
221
- async run() {
221
+ async init() {
222
222
  (0, common_1.routeConsoleLogToStderr)();
223
+ await super.init();
224
+ }
225
+ async run() {
223
226
  await (0, login_1.authUser)();
224
227
  const { flags, argv, args } = await this.parse(CreateEntity);
225
228
  const { count, dryRun: isDryRun, inputFile: inputFilename, outputFile: outputFilename, generateValues: shouldGenerateMissingValues, readableIds: shouldUseReadableIds, skipHydrateRelationships, suffix, apiVersion, } = flags;
@@ -371,8 +374,8 @@ function createEntityWithPopulatedValues(type, typeReferencesCache, objectRefere
371
374
  // Handle property specifications
372
375
  for (const [propertySlug, specifiedValue] of propertySpecificationBySlug.entries()) {
373
376
  const correspondingProperty = type.typeProperties.find((prop) => getPropertySlug(prop) === propertySlug);
374
- if (!specifiedValue) {
375
- throw new Error(`Value for property ${propertySlug} is required`);
377
+ if (specifiedValue === undefined) {
378
+ continue;
376
379
  }
377
380
  entity[propertySlug] = castValueToPropertyType(specifiedValue, correspondingProperty?.propertyType || types_1.PropertyType.String);
378
381
  }
@@ -521,6 +524,9 @@ function generateRandomDateString() {
521
524
  return new Date().toISOString();
522
525
  }
523
526
  function castValueToPropertyType(value, propertyType) {
527
+ if (Array.isArray(value) && propertyType !== types_1.PropertyType.MultiSelect) {
528
+ return value.map((element) => castValueToPropertyType(element, propertyType));
529
+ }
524
530
  switch (propertyType) {
525
531
  case types_1.PropertyType.Boolean:
526
532
  return value.toString().toLowerCase() === 'true';
@@ -553,7 +559,7 @@ async function createEntity(entityName, entity, routing = {}) {
553
559
  const skipHydrateRelationships = SHOULD_SKIP_HYDRATE_RELATIONSHIPS;
554
560
  const { suffix, apiVersion } = routing;
555
561
  if (entityName === 'assortment-item' && !suffix)
556
- return (0, entities_1.createAssortmentItem)(entity, { skipHydrateRelationships }); // special case
562
+ return (0, entities_1.createAssortmentItem)(entity, { skipHydrateRelationships, apiVersion }); // special case
557
563
  const response = await client.create({
558
564
  entityName,
559
565
  object: entity,
@@ -532,6 +532,10 @@ describe('castValueToPropertyType array coercion', () => {
532
532
  expect(consoleError).toHaveBeenCalledWith(expect.stringContaining('slug=option slug=family'));
533
533
  consoleError.mockRestore();
534
534
  });
535
+ it('should cast each element of a repeated-key array on scalar property types', () => {
536
+ expect((0, create_1.castValueToPropertyType)(['1', '2'], types_1.PropertyType.Number)).toEqual([1, 2]);
537
+ expect((0, create_1.castValueToPropertyType)(['true', 'false'], types_1.PropertyType.Boolean)).toEqual([true, false]);
538
+ });
535
539
  it('should keep commas literal on string properties without warning', () => {
536
540
  const consoleError = jest.spyOn(console, 'error').mockImplementation(() => undefined);
537
541
  expect((0, create_1.castValueToPropertyType)('my,name', types_1.PropertyType.String)).toBe('my,name');
@@ -15,5 +15,6 @@ export default class DeleteEntity extends BaseCommand {
15
15
  ids: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
16
16
  };
17
17
  static strict: boolean;
18
+ init(): Promise<void>;
18
19
  run(): Promise<void>;
19
20
  }
@@ -51,8 +51,11 @@ class DeleteEntity extends basecommand_1.BaseCommand {
51
51
  }),
52
52
  };
53
53
  static strict = false;
54
- async run() {
54
+ async init() {
55
55
  (0, common_1.routeConsoleLogToStderr)();
56
+ await super.init();
57
+ }
58
+ async run() {
56
59
  await (0, login_1.authUser)();
57
60
  const { args, flags, argv } = await this.parse(DeleteEntity);
58
61
  const { entityName } = args;
@@ -91,8 +94,8 @@ class DeleteEntity extends basecommand_1.BaseCommand {
91
94
  console.error('deleted entities:');
92
95
  }
93
96
  this.log(JSON.stringify(response, null, 2));
94
- if (response.idsErrored.length > 0) {
95
- console.error(`${response.idsErrored.length} of ${ids.length} deletes failed.`);
97
+ if (response.failed.length > 0) {
98
+ console.error(`${response.failed.length} of ${ids.length} deletes failed.`);
96
99
  process.exitCode = 1;
97
100
  }
98
101
  }
@@ -103,54 +106,32 @@ class DeleteEntity extends basecommand_1.BaseCommand {
103
106
  }
104
107
  exports.default = DeleteEntity;
105
108
  async function deleteEntityByIds(entityName, ids, routing = {}) {
106
- const idsDeleted = new Set();
107
- const idsNotFound = new Set();
108
- const idsErrored = new Set();
109
+ const deletedIds = [];
110
+ const notFoundIds = [];
111
+ const failed = [];
109
112
  const limit = (0, p_limit_1.default)(10);
110
113
  const deletePromises = ids.map((id) => limit(() => deleteEntityById(entityName, id, routing).then((entity) => {
111
114
  if (!entity) {
112
- idsNotFound.add(id);
113
- return null;
115
+ notFoundIds.push(id);
116
+ return;
114
117
  }
115
- idsDeleted.add(id);
116
- return entity;
118
+ deletedIds.push(id);
117
119
  }, (error) => {
118
- if (error.message.includes('not found')) {
119
- idsNotFound.add(id);
120
+ if (error.message?.includes('not found')) {
121
+ notFoundIds.push(id);
120
122
  }
121
123
  else {
122
- idsErrored.add(id);
124
+ failed.push({ id, error: (0, common_1.sdkErrorMessage)(error) });
123
125
  }
124
- return null;
125
126
  })));
126
127
  await Promise.all(deletePromises);
127
- return {
128
- idsDeleted: Array.from(idsDeleted),
129
- idsNotFound: Array.from(idsNotFound),
130
- idsErrored: Array.from(idsErrored),
131
- };
128
+ return { deletedIds, notFoundIds, failed };
132
129
  }
133
130
  async function deleteEntityById(entityName, id, routing = {}) {
134
131
  const client = new sdk_1.Entities();
135
132
  if (IS_DRY_RUN) {
136
- return client.get({ entityName, id, ignore404: true }).catch(throwIfNot404);
133
+ return client.get({ entityName, id, ignore404: true });
137
134
  }
138
135
  const { suffix, apiVersion } = routing;
139
- return client
140
- .delete({ entityName, id, ignore404: true, suffix, apiVersion })
141
- .catch(throwIfNot404);
142
- }
143
- function throwIfNot404(e) {
144
- const { statusCode } = parseSDKErrorMessage(e);
145
- if (statusCode === 404) {
146
- return null;
147
- }
148
- throw e;
149
- }
150
- function parseSDKErrorMessage(e) {
151
- const parsed = JSON.parse(e.details);
152
- return {
153
- statusCode: parsed.statusCode,
154
- message: parsed.message,
155
- };
136
+ return client.delete({ entityName, id, ignore404: true, suffix, apiVersion });
156
137
  }
@@ -20,5 +20,6 @@ export default class GetEntity extends BaseCommand {
20
20
  ids: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
21
21
  };
22
22
  static strict: boolean;
23
+ init(): Promise<void>;
23
24
  run(): Promise<void>;
24
25
  }
@@ -83,8 +83,11 @@ class GetEntity extends basecommand_1.BaseCommand {
83
83
  }),
84
84
  };
85
85
  static strict = false;
86
- async run() {
86
+ async init() {
87
87
  (0, common_1.routeConsoleLogToStderr)();
88
+ await super.init();
89
+ }
90
+ async run() {
88
91
  await (0, login_1.authUser)();
89
92
  const { args, flags, argv } = await this.parse(GetEntity);
90
93
  const { typePath } = args;
@@ -224,8 +227,9 @@ async function find(entityName, options = {}) {
224
227
  queryOptions.customParams = { propertySlugs: propertySlugs.join(',') };
225
228
  }
226
229
  const payload = id ? { id, ...queryOptions } : { criteria, ...queryOptions };
227
- // a suffix names a specific sub-route, so it always goes through the plain client
228
- if ((0, batchFetch_1.canBatchFetchEntity)(entityName) && !suffix) {
230
+ // a suffix names a specific sub-route and an id names one entity; both always go
231
+ // through the plain client (batch-fetch only understands criteria)
232
+ if ((0, batchFetch_1.canBatchFetchEntity)(entityName) && !suffix && !id) {
229
233
  return (0, batchFetch_1.batchFetch)(entityName, payload);
230
234
  }
231
235
  else {
@@ -16,6 +16,7 @@ export default class UpdateEntity extends BaseCommand {
16
16
  ids: import("@oclif/core/lib/interfaces/parser").Arg<string, Record<string, unknown>>;
17
17
  };
18
18
  static strict: boolean;
19
+ init(): Promise<void>;
19
20
  run(): Promise<void>;
20
21
  }
21
22
  export type UpdateSummary = {
@@ -57,8 +57,11 @@ class UpdateEntity extends basecommand_1.BaseCommand {
57
57
  }),
58
58
  };
59
59
  static strict = false;
60
- async run() {
60
+ async init() {
61
61
  (0, common_1.routeConsoleLogToStderr)();
62
+ await super.init();
63
+ }
64
+ async run() {
62
65
  await (0, login_1.authUser)();
63
66
  const { args, argv } = await this.parse(UpdateEntity);
64
67
  const { entityName } = args;
package/dist/common.d.ts CHANGED
@@ -4,3 +4,4 @@ export declare function delay(ms: number): Promise<NodeJS.Timeout>;
4
4
  export declare function removeSlash(string: string): string;
5
5
  export declare function routeConsoleLogToStderr(): void;
6
6
  export declare function sdkErrorMessage(reason: any): string;
7
+ export declare const promptOnStderr: any;
package/dist/common.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sdkErrorMessage = exports.routeConsoleLogToStderr = exports.removeSlash = exports.delay = exports.getSeparatorByPlatform = void 0;
3
+ exports.promptOnStderr = exports.sdkErrorMessage = exports.routeConsoleLogToStderr = exports.removeSlash = exports.delay = exports.getSeparatorByPlatform = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
4
6
  function getSeparatorByPlatform() {
5
7
  const isWin = process.platform === 'win32';
6
8
  const separator = isWin ? '\\' : '/';
@@ -28,3 +30,6 @@ function sdkErrorMessage(reason) {
28
30
  return reason?.message || reason?.details || String(reason);
29
31
  }
30
32
  exports.sdkErrorMessage = sdkErrorMessage;
33
+ // inquirer renders prompt UI to its output stream directly, bypassing the
34
+ // console.log redirect; stdout must stay data-only
35
+ exports.promptOnStderr = inquirer_1.default.createPromptModule({ output: process.stderr });
@@ -1,10 +1,12 @@
1
1
  import { Item } from '@contrail/entity-types';
2
+ import { API_VERSION } from '@contrail/sdk';
2
3
  export declare function throwIfInvalidAssortmentId(assortmentId: string): Promise<void>;
3
4
  export declare function createAssortmentItem(assortmentItem: {
4
5
  assortmentId: string;
5
6
  itemId: string;
6
7
  [key: string]: any;
7
- }, { skipHydrateRelationships }?: {
8
- skipHydrateRelationships: boolean;
8
+ }, options?: {
9
+ skipHydrateRelationships?: boolean;
10
+ apiVersion?: API_VERSION;
9
11
  }): Promise<any>;
10
12
  export declare function findItemsNotInAssortment(assortmentId: string, count?: number): Promise<Item[]>;
@@ -15,15 +15,17 @@ async function doesAssortmentExistById(id) {
15
15
  const assortment = await client.get({ entityName: 'assortment', id });
16
16
  return !!assortment;
17
17
  }
18
- async function createAssortmentItem(assortmentItem, { skipHydrateRelationships } = { skipHydrateRelationships: false }) {
18
+ async function createAssortmentItem(assortmentItem, options = {}) {
19
19
  const client = new sdk_1.Entities();
20
20
  const { assortmentId, itemId } = assortmentItem;
21
+ const { skipHydrateRelationships = false, apiVersion } = options;
21
22
  // add item to assortment
22
23
  await client.create({
23
24
  entityName: 'assortment',
24
25
  id: assortmentId,
25
26
  relation: 'items',
26
27
  object: { itemIds: [itemId] },
28
+ apiVersion,
27
29
  });
28
30
  // Update assortment item with properties
29
31
  const updated = await client.update({
@@ -31,6 +33,7 @@ async function createAssortmentItem(assortmentItem, { skipHydrateRelationships }
31
33
  id: `${assortmentId}:${itemId}`,
32
34
  object: assortmentItem,
33
35
  skipHydrateRelationships,
36
+ apiVersion,
34
37
  });
35
38
  return updated;
36
39
  }
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadData = exports.saveData = exports.parseFileAsJsonArray = exports.parseCSVData = exports.safeFileOverwrite = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
+ const common_1 = require("../common");
6
7
  const fs_1 = tslib_1.__importDefault(require("fs"));
7
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
8
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
9
9
  const logger_1 = tslib_1.__importDefault(require("../logger"));
10
10
  const sync_1 = require("csv-parse/sync");
@@ -15,7 +15,7 @@ async function safeFileOverwrite(fileLocation, overwrite) {
15
15
  logger_1.default.info(`overwriting ${fileLocation}`);
16
16
  return true;
17
17
  }
18
- const answers = await inquirer_1.default.prompt([
18
+ const answers = await (0, common_1.promptOnStderr)([
19
19
  {
20
20
  type: 'confirm',
21
21
  name: 'shouldOverwrite',
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.6.0-alpha-8-alpha.2c2e640",
2
+ "version": "3.6.0-alpha-8-alpha.2ab4f80",
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.6.0-alpha-8-alpha.2c2e640",
3
+ "version": "3.6.0-alpha-8-alpha.2ab4f80",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",