@unito/integration-cli 0.56.2 → 0.57.0

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.
@@ -6,7 +6,9 @@ export default class Activity extends BaseCommand<typeof Activity> {
6
6
  static flags: {
7
7
  number: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
8
  environment: import("@oclif/core/lib/interfaces").OptionFlag<GlobalConfiguration.Environment | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
+ follow: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
10
  };
10
11
  catch(error: Error): Promise<void>;
11
12
  run(): Promise<void>;
13
+ private wait;
12
14
  }
@@ -25,6 +25,13 @@ class Activity extends baseCommand_1.BaseCommand {
25
25
  options: Object.values(GlobalConfiguration.Environment),
26
26
  default: GlobalConfiguration.Environment.Production,
27
27
  })(),
28
+ follow: core_1.Flags.boolean({
29
+ char: 'f',
30
+ summary: 'follow the activity',
31
+ description: `Follow the activity of your integration. This will keep the command running and will show new events as they are created.
32
+
33
+ Usage: <%= config.bin %> <%= command.id %> --follow`,
34
+ }),
28
35
  };
29
36
  async catch(error) {
30
37
  /* istanbul ignore if */
@@ -71,14 +78,33 @@ class Activity extends baseCommand_1.BaseCommand {
71
78
  }
72
79
  core_1.ux.action.stop();
73
80
  core_1.ux.action.start('Retrieving activity');
74
- const events = await IntegrationsPlatform.getIntegrationEvents(integration.id);
75
- core_1.ux.action.stop();
76
- for (const event of events.slice(0, flags.number ?? 1000)) {
77
- core_1.ux.log();
78
- core_1.ux.log(chalk_1.default.bold(` ${event.date} - ${event.title}`));
79
- core_1.ux.log(` ${event.text}`);
80
- core_1.ux.log(chalk_1.default.dim(chalk_1.default.italic(` ${event.tags.join(', ')}`)));
81
+ let lastEventDate;
82
+ // eslint-disable-next-line no-constant-condition
83
+ while (true) {
84
+ const getEventsOptions = {
85
+ $from: lastEventDate,
86
+ limit: flags.number,
87
+ };
88
+ const events = (await IntegrationsPlatform.getIntegrationEvents(integration.id, getEventsOptions)).reverse();
89
+ for (const event of events) {
90
+ core_1.ux.log();
91
+ core_1.ux.log(chalk_1.default.bold(` ${event.date} - ${event.title}`));
92
+ core_1.ux.log(` ${event.text}`);
93
+ core_1.ux.log(chalk_1.default.dim(chalk_1.default.italic(` ${event.tags.join(', ')}`)));
94
+ }
95
+ if (process.env.NODE_ENV === 'test' || !flags.follow) {
96
+ break;
97
+ }
98
+ const mostRecentEvent = events.at(-1);
99
+ const mostRecentEventTimestamp = Date.parse(mostRecentEvent?.date ?? '');
100
+ lastEventDate = isNaN(mostRecentEventTimestamp) ? Date.now() : mostRecentEventTimestamp + 1;
101
+ // Wait 5 seconds before checking for new events.
102
+ await this.wait(5000);
81
103
  }
104
+ core_1.ux.action.stop();
105
+ }
106
+ async wait(ms) {
107
+ return new Promise(resolve => setTimeout(resolve, ms));
82
108
  }
83
109
  }
84
110
  exports.default = Activity;
@@ -54,7 +54,7 @@ class Dev extends baseCommand_1.BaseCommand {
54
54
  (0, integrations_1.validateIsIntegrationDirectory)();
55
55
  const { flags } = await this.parse(Dev);
56
56
  // Install NPM dependencies.
57
- core_1.ux.action.start('Installing NPM dependencies');
57
+ core_1.ux.action.start('Installing NPM dependencies', undefined, { stdout: true });
58
58
  child_process_1.default.execSync('npm install', {
59
59
  env: { ...process.env, NODE_ENV: 'development' },
60
60
  stdio: ['ignore', 'ignore', 'inherit'],
@@ -238,6 +238,7 @@ class Publish extends baseCommand_1.BaseCommand {
238
238
  core_1.ux.log('');
239
239
  core_1.ux.log(['Your integration', gradient.fruit(integrationConfiguration.name), 'is being published.'].join(' '));
240
240
  core_1.ux.log(chalk_1.default.yellowBright("Note that if the code of your integration did not change, it won't be republished."));
241
+ core_1.ux.log(chalk_1.default.blueBright(`You can follow the publishing of ${gradient.fruit(integrationConfiguration.name)} by running the command ${chalk_1.default.yellowBright('integration-cli activity --follow')}.`));
241
242
  }
242
243
  indent(paragraph, tabs = 1) {
243
244
  return paragraph
@@ -98,9 +98,6 @@ class Test extends baseCommand_1.BaseCommand {
98
98
  }
99
99
  credentialPayload = JSON.stringify(credentials);
100
100
  }
101
- if (!credentialPayload || credentialPayload === '{}') {
102
- throw new errors_1.MissingCredentialsError();
103
- }
104
101
  let secrets = {};
105
102
  // Decrypt secrets, if necessary.
106
103
  if (configuration.secrets) {
@@ -11,8 +11,6 @@ export declare class FailedToRetrieveAccessTokenError extends Error {
11
11
  }
12
12
  export declare class InvalidRequestContentTypeError extends Error {
13
13
  }
14
- export declare class MissingCredentialsError extends Error {
15
- }
16
14
  export declare class MissingAuth2AuthorizationError extends Error {
17
15
  }
18
16
  export declare class FileSizeExceeded extends Error {
@@ -22,8 +20,6 @@ export declare class MissingApiKey extends Error {
22
20
  export declare class AuthenticationFailed extends Error {
23
21
  }
24
22
  export declare class DecryptionAuthenticationError extends Error {
25
- encryptedEntityName: string;
26
- constructor(entityName: string);
27
23
  }
28
24
  export declare class EntryDecryptionError extends Error {
29
25
  key: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleError = exports.ConfigurationInvalid = exports.EntryDecryptionError = exports.DecryptionAuthenticationError = exports.AuthenticationFailed = exports.MissingApiKey = exports.FileSizeExceeded = exports.MissingAuth2AuthorizationError = exports.MissingCredentialsError = exports.InvalidRequestContentTypeError = exports.FailedToRetrieveAccessTokenError = exports.NoRefreshTokenError = exports.ConfigurationMalformed = exports.NoConfigurationFileError = exports.NoIntegrationFoundError = void 0;
3
+ exports.handleError = exports.ConfigurationInvalid = exports.EntryDecryptionError = exports.DecryptionAuthenticationError = exports.AuthenticationFailed = exports.MissingApiKey = exports.FileSizeExceeded = exports.MissingAuth2AuthorizationError = exports.InvalidRequestContentTypeError = exports.FailedToRetrieveAccessTokenError = exports.NoRefreshTokenError = exports.ConfigurationMalformed = exports.NoConfigurationFileError = exports.NoIntegrationFoundError = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const core_1 = require("@oclif/core");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
@@ -26,9 +26,6 @@ exports.FailedToRetrieveAccessTokenError = FailedToRetrieveAccessTokenError;
26
26
  class InvalidRequestContentTypeError extends Error {
27
27
  }
28
28
  exports.InvalidRequestContentTypeError = InvalidRequestContentTypeError;
29
- class MissingCredentialsError extends Error {
30
- }
31
- exports.MissingCredentialsError = MissingCredentialsError;
32
29
  class MissingAuth2AuthorizationError extends Error {
33
30
  }
34
31
  exports.MissingAuth2AuthorizationError = MissingAuth2AuthorizationError;
@@ -42,11 +39,6 @@ class AuthenticationFailed extends Error {
42
39
  }
43
40
  exports.AuthenticationFailed = AuthenticationFailed;
44
41
  class DecryptionAuthenticationError extends Error {
45
- encryptedEntityName;
46
- constructor(entityName) {
47
- super();
48
- this.encryptedEntityName = entityName;
49
- }
50
42
  }
51
43
  exports.DecryptionAuthenticationError = DecryptionAuthenticationError;
52
44
  class EntryDecryptionError extends Error {
@@ -118,35 +110,36 @@ function handleError(command, error) {
118
110
  command.logToStderr();
119
111
  command.logToStderr((0, json_colorizer_1.default)(JSON.stringify(error.data, null, 2)));
120
112
  }
121
- else if (error instanceof MissingCredentialsError) {
122
- command.logToStderr();
123
- command.logToStderr(chalk_1.default.redBright('The credentials are missing from your configuration file'));
124
- handled = true;
125
- }
126
113
  else if (error instanceof MissingApiKey) {
127
114
  command.logToStderr();
128
115
  command.logToStderr(chalk_1.default.redBright('Your API key is not set!'));
129
- command.logToStderr(`Make sure to run the ${chalk_1.default.yellowBright('login')} command first`);
116
+ command.logToStderr(`Make sure to run the ${chalk_1.default.yellowBright('login')} command first.`);
130
117
  handled = true;
131
118
  }
132
119
  else if (error instanceof AuthenticationFailed) {
133
120
  command.logToStderr();
134
121
  command.logToStderr(chalk_1.default.redBright('This command requires that you are logged to Unito!'));
135
- command.logToStderr(`Make sure to run the ${chalk_1.default.yellowBright('login')} command first`);
122
+ command.logToStderr(`Make sure to run the ${chalk_1.default.yellowBright('login')} command first.`);
136
123
  handled = true;
137
124
  }
138
125
  else if (error instanceof DecryptionAuthenticationError) {
139
126
  command.logToStderr();
140
- command.logToStderr(chalk_1.default.yellowBright(`Your configuration contains encrypted ${error.encryptedEntityName}.`));
141
- command.logToStderr('To use them locally, you must be authenticated to Unito.');
127
+ command.logToStderr(chalk_1.default.redBright('A secret cannot be decrypted!'));
128
+ command.logToStderr('Your configuration contains secrets (i.e. in top-level secrets, in test accounts, etc.).');
129
+ command.logToStderr('To decrypt those secrets locally, you must authenticate to Unito.');
130
+ command.logToStderr(`Make sure to run the ${chalk_1.default.yellowBright('login')} command first.`);
142
131
  handled = true;
143
132
  }
144
133
  else if (error instanceof EntryDecryptionError) {
145
134
  command.logToStderr();
146
- command.logToStderr(chalk_1.default.yellowBright(`Error decrypting entry:`));
147
- command.logToStderr(chalk_1.default.redBright(`${error.key}: ${error.value}`));
135
+ command.logToStderr(chalk_1.default.redBright('A secret cannot be decrypted!'));
136
+ command.logToStderr(`The secret ${chalk_1.default.yellowBright(error.key)} with the value ${chalk_1.default.yellowBright(error.value)} could not be decrypted.`);
137
+ command.logToStderr();
138
+ command.logToStderr(`To decrypt this secret locally, make sure:`);
148
139
  command.logToStderr();
149
- command.logToStderr(`Make sure they were encrypted by the same environement (Currently targeting: ${error.environment}).`);
140
+ command.logToStderr(`1. The integration was deployed once with the ${chalk_1.default.yellowBright('publish')} command.`);
141
+ command.logToStderr('2. You have access to the integration.');
142
+ command.logToStderr(`3. The secret was encrypted for the ${chalk_1.default.yellowBright(error.environment)} environment.`);
150
143
  handled = true;
151
144
  }
152
145
  else if (error instanceof MissingAuth2AuthorizationError) {
@@ -32,5 +32,9 @@ export declare function getIntegrations(): Promise<IntegrationSummary[]>;
32
32
  export declare function publishIntegration(archivePath: string): Promise<Integration>;
33
33
  export declare function createIntegration(configuration: Configuration): Promise<Integration>;
34
34
  export declare function inviteUserToIntegration(integrationId: number, email: string): Promise<Integration>;
35
- export declare function getIntegrationEvents(integrationId: number): Promise<IntegrationEvent[]>;
35
+ export declare function getIntegrationEvents(integrationId: number, options?: {
36
+ $from?: number;
37
+ search?: string;
38
+ limit?: number;
39
+ }): Promise<IntegrationEvent[]>;
36
40
  export declare function updateIntegration(integrationId: number, configuration: Configuration): Promise<Integration>;
@@ -27,7 +27,7 @@ function setApiKey(apiKey) {
27
27
  integrations_platform_client_1.default.defaults.headers = { Authorization: `Bearer ${apiKey}` };
28
28
  }
29
29
  else {
30
- integrations_platform_client_1.default.defaults.headers = undefined;
30
+ integrations_platform_client_1.default.defaults.headers = {};
31
31
  }
32
32
  }
33
33
  exports.setApiKey = setApiKey;
@@ -97,8 +97,8 @@ async function inviteUserToIntegration(integrationId, email) {
97
97
  return await integrations_platform_client_1.default.inviteUser(integrationId, { email });
98
98
  }
99
99
  exports.inviteUserToIntegration = inviteUserToIntegration;
100
- async function getIntegrationEvents(integrationId) {
101
- const { data: events } = await integrations_platform_client_1.default.getIntegrationEvents(integrationId);
100
+ async function getIntegrationEvents(integrationId, options = {}) {
101
+ const { data: events } = await integrations_platform_client_1.default.getIntegrationEvents(integrationId, options);
102
102
  return events;
103
103
  }
104
104
  exports.getIntegrationEvents = getIntegrationEvents;
@@ -7,6 +7,7 @@ const IntegrationsPlatform = tslib_1.__importStar(require("../../src/services/in
7
7
  const IntegrationResource = tslib_1.__importStar(require("../../src/resources/integrations"));
8
8
  const IntegrationConfiguration = tslib_1.__importStar(require("../../src/resources/configuration"));
9
9
  describe('activity', () => {
10
+ let getIntegrationEventsStub;
10
11
  beforeEach(() => {
11
12
  sinon_1.default.stub(IntegrationResource, 'validateIsIntegrationDirectory');
12
13
  sinon_1.default.stub(IntegrationsPlatform, 'setEnvironment');
@@ -14,7 +15,7 @@ describe('activity', () => {
14
15
  sinon_1.default
15
16
  .stub(IntegrationsPlatform, 'getIntegrationByName')
16
17
  .resolves({ name: 'integrationName', id: 'foo' });
17
- sinon_1.default.stub(IntegrationsPlatform, 'getIntegrationEvents').resolves([
18
+ getIntegrationEventsStub = sinon_1.default.stub(IntegrationsPlatform, 'getIntegrationEvents').resolves([
18
19
  { date: '1', text: 'This is a foo', title: 'foo', tags: ['a', 'b', 'c'] },
19
20
  { date: '2', text: 'This is a bar', title: 'bar', tags: ['x', 'y', 'z'] },
20
21
  ]);
@@ -35,7 +36,8 @@ describe('activity', () => {
35
36
  .command(['activity', '--number', '1'])
36
37
  .it('displays only specified number of activity entries', ctx => {
37
38
  (0, test_1.expect)(ctx.stdout).to.contains('foo');
38
- (0, test_1.expect)(ctx.stdout).to.not.contains('bar');
39
+ (0, test_1.expect)(ctx.stdout).to.contains('bar');
40
+ sinon_1.default.assert.calledWithExactly(getIntegrationEventsStub, 'foo', { $from: undefined, limit: 1 });
39
41
  });
40
42
  test_1.test
41
43
  .stdout()
@@ -61,4 +63,19 @@ describe('activity', () => {
61
63
  (0, test_1.expect)(error.message).to.equal('EEXIT: -1');
62
64
  })
63
65
  .it('handles unpublished integration');
66
+ test_1.test
67
+ .stdout()
68
+ .command(['activity', '--follow'])
69
+ .it('displays activity - follow', ctx => {
70
+ (0, test_1.expect)(ctx.stdout).to.contains('foo');
71
+ (0, test_1.expect)(ctx.stdout).to.contains('bar');
72
+ });
73
+ test_1.test
74
+ .stdout()
75
+ .command(['activity', '--follow', '--number', '1'])
76
+ .it('displays activity - follow', ctx => {
77
+ (0, test_1.expect)(ctx.stdout).to.contains('foo');
78
+ (0, test_1.expect)(ctx.stdout).to.contains('bar');
79
+ sinon_1.default.assert.calledWithExactly(getIntegrationEventsStub, 'foo', { $from: undefined, limit: 1 });
80
+ });
64
81
  });
@@ -63,17 +63,6 @@ describe('Test', () => {
63
63
  .command(['test'])
64
64
  .exit(-1)
65
65
  .it('handled exception');
66
- test_1.test
67
- .stdout()
68
- .stub(ConfigurationResource, 'getConfiguration', () => {
69
- return {
70
- name: 'a',
71
- baseUrl: 'b',
72
- };
73
- })
74
- .command(['test', '--test-account', 'compliance'])
75
- .exit(-1)
76
- .it('should throw an error if no test account is configured');
77
66
  test_1.test
78
67
  .stdout()
79
68
  .command(['test', '--verbose'])
@@ -78,16 +78,10 @@ describe('handleError', () => {
78
78
  const error = new errors_1.DecryptionAuthenticationError('secrets');
79
79
  const handled = (0, errors_1.handleError)(command, error);
80
80
  (0, chai_1.expect)(handled).to.be.true;
81
- (0, chai_1.expect)(stdErrStub.calledThrice).to.be.true;
82
- (0, chai_1.expect)(stdErrStub.secondCall.args[0]).to.equal(chalk_1.default.yellowBright('Your configuration contains encrypted secrets.'));
83
- (0, chai_1.expect)(stdErrStub.thirdCall.args[0]).to.equal('To use them locally, you must be authenticated to Unito.');
84
81
  });
85
82
  it('should handle EntryDecryptionError', () => {
86
83
  const error = new errors_1.EntryDecryptionError('key', 'value', 'staging');
87
84
  const handled = (0, errors_1.handleError)(command, error);
88
85
  (0, chai_1.expect)(handled).to.be.true;
89
- (0, chai_1.expect)(stdErrStub.called).to.be.true;
90
- (0, chai_1.expect)(stdErrStub.secondCall.args[0]).to.equal(chalk_1.default.yellowBright('Error decrypting entry:'));
91
- (0, chai_1.expect)(stdErrStub.thirdCall.args[0]).to.equal(chalk_1.default.redBright('key: value'));
92
86
  });
93
87
  });
@@ -56,7 +56,7 @@ describe('integrations platform', function () {
56
56
  });
57
57
  IntegrationsPlatform.setApiKey(undefined);
58
58
  (0, chai_1.expect)(IntegrationsPlatform.getApiKey()).to.equal(undefined);
59
- (0, chai_1.expect)(integrations_platform_client_1.default.defaults.headers).to.be.undefined;
59
+ (0, chai_1.expect)(integrations_platform_client_1.default.defaults.headers).to.be.deep.equal({});
60
60
  });
61
61
  it('getProfile', async function () {
62
62
  sinon.stub(integrations_platform_client_1.default, 'getProfile').resolves(user);
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.56.2",
2
+ "version": "0.57.0",
3
3
  "commands": {
4
4
  "activity": {
5
5
  "id": "activity",
@@ -32,6 +32,14 @@
32
32
  "production"
33
33
  ],
34
34
  "default": "production"
35
+ },
36
+ "follow": {
37
+ "name": "follow",
38
+ "type": "boolean",
39
+ "char": "f",
40
+ "summary": "follow the activity",
41
+ "description": "Follow the activity of your integration. This will keep the command running and will show new events as they are created.\n\n Usage: <%= config.bin %> <%= command.id %> --follow",
42
+ "allowNo": false
35
43
  }
36
44
  },
37
45
  "args": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-cli",
3
- "version": "0.56.2",
3
+ "version": "0.57.0",
4
4
  "description": "Integration CLI",
5
5
  "bin": {
6
6
  "integration-cli": "./bin/run"
@@ -38,8 +38,8 @@
38
38
  "@oclif/core": "2.x",
39
39
  "@typescript-eslint/eslint-plugin": "6.x",
40
40
  "@typescript-eslint/parser": "6.13.x",
41
- "@unito/integration-debugger": "0.22.10",
42
- "@unito/integrations-platform-client": "0.44.4",
41
+ "@unito/integration-debugger": "0.23.0",
42
+ "@unito/integrations-platform-client": "0.46.1",
43
43
  "ajv": "8.x",
44
44
  "ajv-formats": "2.x",
45
45
  "better-ajv-errors": "1.x",