From 1b998bffa48b6e05038d2cca5c8eba280394634e Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Mon, 28 Aug 2023 15:43:39 +0200 Subject: [PATCH 01/26] feat: add support for retry strategies (#830) --- packages/cli/src/constructs/check-group.ts | 8 +++ packages/cli/src/constructs/check.ts | 8 +++ packages/cli/src/constructs/index.ts | 1 + packages/cli/src/constructs/retry-strategy.ts | 67 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 packages/cli/src/constructs/retry-strategy.ts diff --git a/packages/cli/src/constructs/check-group.ts b/packages/cli/src/constructs/check-group.ts index 88e1d294..f3a1d629 100644 --- a/packages/cli/src/constructs/check-group.ts +++ b/packages/cli/src/constructs/check-group.ts @@ -14,6 +14,7 @@ import { ApiCheckDefaultConfig } from './api-check' import { pathToPosix } from '../services/util' import type { Region } from '..' import type { Frequency } from './frequency' +import type { RetryStrategy } from './retry-strategy' const defaultApiCheckDefaults: ApiCheckDefaultConfig = { headers: [], @@ -93,6 +94,10 @@ export interface CheckGroupProps { */ localTearDownScript?: string apiCheckDefaults?: ApiCheckDefaultConfig + /** + * Sets a retry policy for the group. Use RetryStrategyBuilder to create a retry policy. + */ + retryStrategy?: RetryStrategy } /** @@ -119,6 +124,7 @@ export class CheckGroup extends Construct { localTearDownScript?: string apiCheckDefaults: ApiCheckDefaultConfig browserChecks?: BrowserCheckConfig + retryStrategy?: RetryStrategy static readonly __checklyType = 'check-group' @@ -156,6 +162,7 @@ export class CheckGroup extends Construct { this.alertChannels = props.alertChannels ?? [] this.localSetupScript = props.localSetupScript this.localTearDownScript = props.localTearDownScript + this.retryStrategy = props.retryStrategy // `browserChecks` is not a CheckGroup resource property. Not present in synthesize() this.browserChecks = props.browserChecks const fileAbsolutePath = Session.checkFileAbsolutePath! @@ -247,6 +254,7 @@ export class CheckGroup extends Construct { localTearDownScript: this.localTearDownScript, apiCheckDefaults: this.apiCheckDefaults, environmentVariables: this.environmentVariables, + retryStrategy: this.retryStrategy, } } } diff --git a/packages/cli/src/constructs/check.ts b/packages/cli/src/constructs/check.ts index 689f3032..990d589b 100644 --- a/packages/cli/src/constructs/check.ts +++ b/packages/cli/src/constructs/check.ts @@ -10,6 +10,7 @@ import type { Region } from '..' import type { CheckGroup } from './check-group' import { PrivateLocation } from './private-location' import { PrivateLocationCheckAssignment } from './private-location-check-assignment' +import { RetryStrategy } from './retry-strategy' export interface CheckProps { /** @@ -80,6 +81,10 @@ export interface CheckProps { * Determines if the check is available only when 'test' runs (not included when 'deploy' is executed). */ testOnly?: boolean + /** + * Sets a retry policy for the check. Use RetryStrategyBuilder to create a retry policy. + */ + retryStrategy?: RetryStrategy } // This is an abstract class. It shouldn't be used directly. @@ -99,6 +104,7 @@ export abstract class Check extends Construct { groupId?: Ref alertChannels?: Array testOnly?: boolean + retryStrategy?: RetryStrategy __checkFilePath?: string // internal variable to filter by check file name from the CLI static readonly __checklyType = 'check' @@ -134,6 +140,7 @@ export abstract class Check extends Construct { // alertSettings, useGlobalAlertSettings, groupId, groupOrder this.testOnly = props.testOnly ?? false + this.retryStrategy = props.retryStrategy this.__checkFilePath = Session.checkFilePath } @@ -209,6 +216,7 @@ export abstract class Check extends Construct { frequencyOffset: this.frequencyOffset, groupId: this.groupId, environmentVariables: this.environmentVariables, + retryStrategy: this.retryStrategy, } } } diff --git a/packages/cli/src/constructs/index.ts b/packages/cli/src/constructs/index.ts index cbeb9097..e310ae81 100644 --- a/packages/cli/src/constructs/index.ts +++ b/packages/cli/src/constructs/index.ts @@ -22,3 +22,4 @@ export * from './private-location-group-assignment' export * from './check' export * from './dashboard' export * from './phone-call-alert-channel' +export * from './retry-strategy' diff --git a/packages/cli/src/constructs/retry-strategy.ts b/packages/cli/src/constructs/retry-strategy.ts new file mode 100644 index 00000000..0e0785f2 --- /dev/null +++ b/packages/cli/src/constructs/retry-strategy.ts @@ -0,0 +1,67 @@ +export type RetryStrategyType = 'LINEAR' | 'EXPONENTIAL' | 'FIXED' + +export interface RetryStrategy { + type: RetryStrategyType, + /** + * The number of seconds to wait before the first retry attempt. + */ + baseBackoffSeconds?: number, + /** + * The maximum number of attempts to retry the check. Value must be between 1 and 10. + */ + maxAttempts?: number, + /** + * The total amount of time to continue retrying the check (maximum 600 seconds). + */ + maxDurationSeconds?: number, + /** + * Whether retries should be run in the same region as the initial check run. + */ + sameRegion?: boolean, +} + +export type RetryStrategyOptions = Pick + +export class RetryStrategyBuilder { + private static readonly DEFAULT_BASE_BACKOFF_SECONDS = 60 + private static readonly DEFAULT_MAX_ATTEMPTS = 2 + private static readonly DEFAULT_MAX_DURATION_SECONDS = 60 * 10 + private static readonly DEFAULT_SAME_REGION = false + + /** + * Each retry is run with the same backoff between attempts. + */ + static fixedStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('FIXED', options) + } + + /** + * The delay between retries increases linearly + * + * The delay between retries is calculated using `baseBackoffSeconds * attempt`. + * For example, retries will be run with a backoff of 10s, 20s, 30s, and so on. + */ + static linearStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('LINEAR', options) + } + + /** + * The delay between retries increases exponentially + * + * The delay between retries is calculated using `baseBackoffSeconds ^ attempt`. + * For example, retries will be run with a backoff of 10s, 100s, 1000s, and so on. + */ + static exponentialStrategy (options: RetryStrategyOptions): RetryStrategy { + return RetryStrategyBuilder.retryStrategy('EXPONENTIAL', options) + } + + private static retryStrategy (type: RetryStrategyType, options: RetryStrategyOptions): RetryStrategy { + return { + type, + baseBackoffSeconds: options.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS, + maxAttempts: options.maxAttempts ?? RetryStrategyBuilder.DEFAULT_MAX_ATTEMPTS, + maxDurationSeconds: options.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS, + sameRegion: options.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION, + } + } +} From de980a75909e647e535e5cec3e948d56c85c1046 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Mon, 28 Aug 2023 16:17:40 +0200 Subject: [PATCH 02/26] feat: add retryStrategy to checkly config defaults (#832) --- packages/cli/src/services/checkly-config-loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/services/checkly-config-loader.ts b/packages/cli/src/services/checkly-config-loader.ts index 9ef0ea3d..eafc2cd0 100644 --- a/packages/cli/src/services/checkly-config-loader.ts +++ b/packages/cli/src/services/checkly-config-loader.ts @@ -9,7 +9,7 @@ import { ReporterType } from '../reporters/reporter' export type CheckConfigDefaults = Pick + | 'alertChannels' | 'privateLocations' | 'retryStrategy'> export type ChecklyConfig = { /** From 094c452b79c995dfc1e1f572648f13051ed31e09 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Wed, 30 Aug 2023 09:12:52 +0100 Subject: [PATCH 03/26] fix(heartbeats): addSubscriptions to alert channels [sc-17522] (#833) --- packages/cli/src/constructs/heartbeat-check.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/constructs/heartbeat-check.ts b/packages/cli/src/constructs/heartbeat-check.ts index 0571030c..accc894e 100644 --- a/packages/cli/src/constructs/heartbeat-check.ts +++ b/packages/cli/src/constructs/heartbeat-check.ts @@ -80,6 +80,7 @@ export class HeartbeatCheck extends Check { } Session.registerConstruct(this) + this.addSubscriptions() } synthesize (): any | null { From 7dba16a8451fc05da0495fde0c176dda49842757 Mon Sep 17 00:00:00 2001 From: Michelle Liebheit <54396648+miliberlin@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:38:38 +0200 Subject: [PATCH 04/26] feat: adjust default grace and period for heartbeat checks [sc-17531] (#834) --- .../advanced-project-js/src/__checks__/heartbeat.check.js | 6 +++--- examples/advanced-project/src/__checks__/heartbeat.check.ts | 6 +++--- .../boilerplate-project-js/__checks__/heartbeat.check.js | 6 +++--- examples/boilerplate-project/__checks__/heartbeat.check.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/advanced-project-js/src/__checks__/heartbeat.check.js b/examples/advanced-project-js/src/__checks__/heartbeat.check.js index 0977ef38..1df1638b 100644 --- a/examples/advanced-project-js/src/__checks__/heartbeat.check.js +++ b/examples/advanced-project-js/src/__checks__/heartbeat.check.js @@ -7,9 +7,9 @@ const { HeartbeatCheck } = require('checkly/constructs') /* new HeartbeatCheck('heartbeat-check-1', { name: 'Send weekly newsletter job', - period: 30, - periodUnit: 'minutes', - grace: 10, + period: 1, + periodUnit: 'hours', + grace: 30, graceUnit: 'minutes', }) */ diff --git a/examples/advanced-project/src/__checks__/heartbeat.check.ts b/examples/advanced-project/src/__checks__/heartbeat.check.ts index dddda52c..f8cf7a0a 100644 --- a/examples/advanced-project/src/__checks__/heartbeat.check.ts +++ b/examples/advanced-project/src/__checks__/heartbeat.check.ts @@ -7,9 +7,9 @@ import { HeartbeatCheck } from 'checkly/constructs' /* new HeartbeatCheck('heartbeat-check-1', { name: 'Send weekly newsletter job', - period: 30, - periodUnit: 'minutes', - grace: 10, + period: 1, + periodUnit: 'hours', + grace: 30, graceUnit: 'minutes', }) */ diff --git a/examples/boilerplate-project-js/__checks__/heartbeat.check.js b/examples/boilerplate-project-js/__checks__/heartbeat.check.js index 0977ef38..1df1638b 100644 --- a/examples/boilerplate-project-js/__checks__/heartbeat.check.js +++ b/examples/boilerplate-project-js/__checks__/heartbeat.check.js @@ -7,9 +7,9 @@ const { HeartbeatCheck } = require('checkly/constructs') /* new HeartbeatCheck('heartbeat-check-1', { name: 'Send weekly newsletter job', - period: 30, - periodUnit: 'minutes', - grace: 10, + period: 1, + periodUnit: 'hours', + grace: 30, graceUnit: 'minutes', }) */ diff --git a/examples/boilerplate-project/__checks__/heartbeat.check.ts b/examples/boilerplate-project/__checks__/heartbeat.check.ts index dddda52c..f8cf7a0a 100644 --- a/examples/boilerplate-project/__checks__/heartbeat.check.ts +++ b/examples/boilerplate-project/__checks__/heartbeat.check.ts @@ -7,9 +7,9 @@ import { HeartbeatCheck } from 'checkly/constructs' /* new HeartbeatCheck('heartbeat-check-1', { name: 'Send weekly newsletter job', - period: 30, - periodUnit: 'minutes', - grace: 10, + period: 1, + periodUnit: 'hours', + grace: 30, graceUnit: 'minutes', }) */ From 18ac2b0b9350d99c961ad365fdd8dd88eb05c9c8 Mon Sep 17 00:00:00 2001 From: Tim Nolet Date: Thu, 31 Aug 2023 12:40:22 +0200 Subject: [PATCH 05/26] chore: adds CI name as header to API (#835) --- packages/cli/src/rest/api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/src/rest/api.ts b/packages/cli/src/rest/api.ts index 7e49eb52..8bcd0f30 100644 --- a/packages/cli/src/rest/api.ts +++ b/packages/cli/src/rest/api.ts @@ -1,4 +1,5 @@ import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios' +import { name as CIname } from 'ci-info' import config from '../services/config' import { assignProxy } from '../services/util' import Accounts from './accounts' @@ -56,6 +57,8 @@ export function requestInterceptor (config: InternalAxiosRequestConfig) { config.headers['x-checkly-account'] = accountId } + config.headers['x-checkly-ci-name'] = CIname + return config } From b2d542a87c52d61b2d2f01bd8a4877f76b4f4b8a Mon Sep 17 00:00:00 2001 From: Stefan Judis Date: Thu, 31 Aug 2023 12:51:16 +0200 Subject: [PATCH 06/26] chore: adjust checkly.config repoUrl in boilerplate projects (#821) Co-authored-by: Tim Nolet --- examples/advanced-project-js/checkly.config.js | 4 ++-- examples/advanced-project/checkly.config.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/advanced-project-js/checkly.config.js b/examples/advanced-project-js/checkly.config.js index a89877a2..c9fb6ba6 100644 --- a/examples/advanced-project-js/checkly.config.js +++ b/examples/advanced-project-js/checkly.config.js @@ -10,8 +10,8 @@ const config = defineConfig({ * See https://www.checklyhq.com/docs/cli/constructs/ to learn more about logical IDs. */ logicalId: 'advanced-example-project', - /* An optional URL to your Git repo */ - repoUrl: 'https://github.com/checkly/checkly-cli', + /* An optional URL to your Git repo to be shown in your test sessions and resource activity log */ + /* repoUrl: 'https://github.com/checkly/checkly-cli', */ /* Sets default values for Checks */ checks: { /* A default for how often your Check should run in minutes */ diff --git a/examples/advanced-project/checkly.config.ts b/examples/advanced-project/checkly.config.ts index eda6c322..91d09598 100644 --- a/examples/advanced-project/checkly.config.ts +++ b/examples/advanced-project/checkly.config.ts @@ -10,8 +10,8 @@ const config = defineConfig({ * See https://www.checklyhq.com/docs/cli/constructs/ to learn more about logical IDs. */ logicalId: 'advanced-example-project', - /* An optional URL to your Git repo */ - repoUrl: 'https://github.com/checkly/checkly-cli', + /* An optional URL to your Git repo to be shown in your test sessions and resource activity log */ + /* repoUrl: 'https://github.com/checkly/checkly-cli', */ /* Sets default values for Checks */ checks: { /* A default for how often your Check should run in minutes */ From f655963e605d80eec24528d7a696604781d9eb02 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Thu, 31 Aug 2023 14:33:32 +0200 Subject: [PATCH 07/26] feat: deprecate doubleCheck property (#836) --- packages/cli/src/constructs/check-group.ts | 1 + packages/cli/src/constructs/check.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cli/src/constructs/check-group.ts b/packages/cli/src/constructs/check-group.ts index f3a1d629..7c868ccf 100644 --- a/packages/cli/src/constructs/check-group.ts +++ b/packages/cli/src/constructs/check-group.ts @@ -49,6 +49,7 @@ export interface CheckGroupProps { /** * Setting this to "true" will trigger a retry when a check fails from the failing region and another, * randomly selected region before marking the check as failed. + * @deprecated Use {@link CheckGroupProps.retryStrategy} instead. */ doubleCheck?: boolean /** diff --git a/packages/cli/src/constructs/check.ts b/packages/cli/src/constructs/check.ts index 990d589b..8dec2f2a 100644 --- a/packages/cli/src/constructs/check.ts +++ b/packages/cli/src/constructs/check.ts @@ -28,6 +28,7 @@ export interface CheckProps { /** * Setting this to "true" will trigger a retry when a check fails from the failing region and another, * randomly selected region before marking the check as failed. + * @deprecated Use {@link CheckProps.retryStrategy} instead. */ doubleCheck?: boolean /** From f2efdf44c227db6431eec947cac03b23319abf9f Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Thu, 31 Aug 2023 16:31:54 +0200 Subject: [PATCH 08/26] feat: add retryStrategy to the advanced examples (#837) --- examples/advanced-project-js/checkly.config.js | 3 +++ .../src/__checks__/website-group.check.js | 9 +++++++-- examples/advanced-project/checkly.config.ts | 3 +++ .../src/__checks__/website-group.check.ts | 7 ++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/examples/advanced-project-js/checkly.config.js b/examples/advanced-project-js/checkly.config.js index c9fb6ba6..922d6475 100644 --- a/examples/advanced-project-js/checkly.config.js +++ b/examples/advanced-project-js/checkly.config.js @@ -1,4 +1,5 @@ const { defineConfig } = require('checkly'); +const { RetryStrategyBuilder } = require('checkly/constructs'); /** * See https://www.checklyhq.com/docs/cli/project-structure/ @@ -24,6 +25,8 @@ const config = defineConfig({ * See https://www.checklyhq.com/docs/cli/npm-packages/ */ runtimeId: '2023.02', + /* Failed check runs will be retried before triggering alerts */ + retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxAttempts: 4, sameRegion: true }), /* A glob pattern that matches the Checks inside your repo, see https://www.checklyhq.com/docs/cli/using-check-test-match/ */ checkMatch: '**/__checks__/**/*.check.js', browserChecks: { diff --git a/examples/advanced-project-js/src/__checks__/website-group.check.js b/examples/advanced-project-js/src/__checks__/website-group.check.js index e41838d7..09800b50 100644 --- a/examples/advanced-project-js/src/__checks__/website-group.check.js +++ b/examples/advanced-project-js/src/__checks__/website-group.check.js @@ -1,4 +1,4 @@ -const { CheckGroup } = require('checkly/constructs'); +const { CheckGroup, RetryStrategyBuilder } = require('checkly/constructs'); const { smsChannel, emailChannel } = require('../alert-channels'); const alertChannels = [smsChannel, emailChannel]; /* @@ -24,6 +24,11 @@ const websiteGroup = new CheckGroup('website-check-group-1', { apiCheckDefaults: {}, concurrency: 100, alertChannels, + /* + * Failed check runs in this group will be retried before triggering alerts. + * The wait time between retries will increase linearly: 30 seconds, 60 seconds, and then 90 seconds between the retries. + */ + retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxAttempts: 3, sameRegion: false }), }); -module.exports = websiteGroup; +module.exports = { websiteGroup }; diff --git a/examples/advanced-project/checkly.config.ts b/examples/advanced-project/checkly.config.ts index 91d09598..77799bfe 100644 --- a/examples/advanced-project/checkly.config.ts +++ b/examples/advanced-project/checkly.config.ts @@ -1,4 +1,5 @@ import { defineConfig } from 'checkly' +import { RetryStrategyBuilder } from 'checkly/constructs' /** * See https://www.checklyhq.com/docs/cli/project-structure/ @@ -24,6 +25,8 @@ const config = defineConfig({ * See https://www.checklyhq.com/docs/cli/npm-packages/ */ runtimeId: '2023.02', + /* Failed check runs will be retried before triggering alerts */ + retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxAttempts: 4, sameRegion: true }), /* A glob pattern that matches the Checks inside your repo, see https://www.checklyhq.com/docs/cli/using-check-test-match/ */ checkMatch: '**/__checks__/**/*.check.ts', browserChecks: { diff --git a/examples/advanced-project/src/__checks__/website-group.check.ts b/examples/advanced-project/src/__checks__/website-group.check.ts index 1d8c0b35..3ddf799d 100644 --- a/examples/advanced-project/src/__checks__/website-group.check.ts +++ b/examples/advanced-project/src/__checks__/website-group.check.ts @@ -1,4 +1,4 @@ -import { CheckGroup } from 'checkly/constructs' +import { CheckGroup, RetryStrategyBuilder } from 'checkly/constructs' import { smsChannel, emailChannel } from '../alert-channels' const alertChannels = [smsChannel, emailChannel] /* @@ -24,4 +24,9 @@ export const websiteGroup = new CheckGroup('website-check-group-1', { apiCheckDefaults: {}, concurrency: 100, alertChannels, + /* + * Failed check runs in this group will be retried before triggering alerts. + * The wait time between retries will increase linearly: 30 seconds, 60 seconds, and then 90 seconds between the retries. + */ + retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxAttempts: 3, sameRegion: false }), }) From b24d7c38d48fcc0bd7f419785ddc64cea567b984 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:45:46 +0000 Subject: [PATCH 09/26] chore(deps): Bump ci-info from 3.7.1 to 3.8.0 (#811) Bumps [ci-info](https://github.com/watson/ci-info) from 3.7.1 to 3.8.0. - [Release notes](https://github.com/watson/ci-info/releases) - [Changelog](https://github.com/watson/ci-info/blob/master/CHANGELOG.md) - [Commits](https://github.com/watson/ci-info/compare/v3.7.1...v3.8.0) --- updated-dependencies: - dependency-name: ci-info dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 13 ++++++++----- packages/cli/package.json | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index fad3fa2d..ebd7aac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5413,14 +5413,15 @@ } }, "node_modules/ci-info": { - "version": "3.7.1", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], - "license": "MIT", "engines": { "node": ">=8" } @@ -16377,7 +16378,7 @@ "async-mqtt": "2.6.3", "axios": "1.4.0", "chalk": "4.1.2", - "ci-info": "3.7.1", + "ci-info": "3.8.0", "conf": "10.2.0", "dotenv": "16.3.1", "git-repo-info": "2.1.1", @@ -20497,7 +20498,7 @@ "async-mqtt": "2.6.3", "axios": "1.4.0", "chalk": "4.1.2", - "ci-info": "3.7.1", + "ci-info": "3.8.0", "conf": "10.2.0", "config": "3.3.8", "cross-env": "7.0.3", @@ -20593,7 +20594,9 @@ "version": "2.0.0" }, "ci-info": { - "version": "3.7.1" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" }, "cjs-module-lexer": { "version": "1.2.3", diff --git a/packages/cli/package.json b/packages/cli/package.json index b827aa2d..16b7e2e3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -79,7 +79,7 @@ "async-mqtt": "2.6.3", "axios": "1.4.0", "chalk": "4.1.2", - "ci-info": "3.7.1", + "ci-info": "3.8.0", "conf": "10.2.0", "dotenv": "16.3.1", "git-repo-info": "2.1.1", From bd750aec2ef8394231478dd88f854d8387530f4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 10:17:24 +0200 Subject: [PATCH 10/26] chore(deps-dev): Bump eslint from 8.41.0 to 8.48.0 (#831) Bumps [eslint](https://github.com/eslint/eslint) from 8.41.0 to 8.48.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.41.0...v8.48.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 267 +++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 172 insertions(+), 97 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebd7aac0..1005e474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,20 @@ "@commitlint/config-conventional": "17.6.5", "@typescript-eslint/eslint-plugin": "5.59.8", "@typescript-eslint/parser": "5.61.0", - "eslint": "8.41.0", + "eslint": "8.48.0", "lint-staged": "13.2.3", "simple-git-hooks": "2.8.1" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "dev": true, @@ -1269,21 +1278,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1299,9 +1310,10 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -1314,8 +1326,9 @@ }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -1324,9 +1337,10 @@ } }, "node_modules/@eslint/js": { - "version": "8.41.0", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1337,9 +1351,10 @@ "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -1349,11 +1364,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -1366,6 +1376,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "license": "ISC", @@ -4444,8 +4460,9 @@ }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6278,8 +6295,9 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -6736,26 +6754,27 @@ } }, "node_modules/eslint": { - "version": "8.41.0", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6765,7 +6784,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -6775,9 +6793,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -7298,9 +7315,10 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -7313,9 +7331,10 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.1", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -7325,8 +7344,9 @@ }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -7433,11 +7453,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -7448,6 +7469,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/espree/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "3.4.1", "dev": true, @@ -7622,8 +7655,9 @@ }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -10902,8 +10936,9 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-schema-typed": { "version": "7.0.3", @@ -11067,8 +11102,9 @@ }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -13152,16 +13188,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, - "license": "MIT", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -13687,8 +13724,9 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -15147,8 +15185,9 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -15622,14 +15661,6 @@ "node": ">=8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "license": "MIT" @@ -16877,6 +16908,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@ampproject/remapping": { "version": "2.2.0", "dev": true, @@ -17695,16 +17732,20 @@ } }, "@eslint-community/regexpp": { - "version": "4.5.1", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", "dev": true }, "@eslint/eslintrc": { - "version": "2.0.3", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -17714,7 +17755,9 @@ }, "dependencies": { "globals": { - "version": "13.20.0", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -17722,12 +17765,16 @@ }, "type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } }, "@eslint/js": { - "version": "8.41.0", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true }, "@gar/promisify": { @@ -17735,24 +17782,26 @@ "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.8", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.5" - }, - "dependencies": { - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true - } } }, "@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@isaacs/cliui": { "version": "8.0.2", "requires": { @@ -19840,6 +19889,8 @@ }, "ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -21351,6 +21402,8 @@ }, "deep-is": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "deepmerge": { @@ -21653,25 +21706,27 @@ "dev": true }, "eslint": { - "version": "8.41.0", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -21681,7 +21736,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -21691,9 +21745,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { @@ -21728,7 +21781,9 @@ "dev": true }, "eslint-scope": { - "version": "7.2.0", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -21736,11 +21791,15 @@ } }, "eslint-visitor-keys": { - "version": "3.4.1", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "estraverse": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, "find-up": { @@ -22055,14 +22114,22 @@ "dev": true }, "espree": { - "version": "9.5.2", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, "dependencies": { + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, "eslint-visitor-keys": { "version": "3.4.1", "dev": true @@ -22178,6 +22245,8 @@ }, "fast-levenshtein": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastest-levenshtein": { @@ -24374,6 +24443,8 @@ }, "json-schema-traverse": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-schema-typed": { @@ -24474,6 +24545,8 @@ }, "levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { "prelude-ls": "^1.2.1", @@ -25846,15 +25919,17 @@ } }, "optionator": { - "version": "0.9.1", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "os-tmpdir": { @@ -26168,6 +26243,8 @@ }, "prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prettier": { @@ -27049,6 +27126,8 @@ }, "type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { "prelude-ls": "^1.2.1" @@ -27372,10 +27451,6 @@ "string-width": "^4.0.0" } }, - "word-wrap": { - "version": "1.2.3", - "dev": true - }, "wordwrap": { "version": "1.0.0" }, diff --git a/package.json b/package.json index 88c4f4ee..0b4285c6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@commitlint/config-conventional": "17.6.5", "@typescript-eslint/eslint-plugin": "5.59.8", "@typescript-eslint/parser": "5.61.0", - "eslint": "8.41.0", + "eslint": "8.48.0", "lint-staged": "13.2.3", "simple-git-hooks": "2.8.1" }, From a1b77dbcc8283897b045c81ee8632968f9d68783 Mon Sep 17 00:00:00 2001 From: Umut Uzgur Date: Fri, 1 Sep 2023 11:01:25 +0100 Subject: [PATCH 11/26] refactor: rename maxAttempts to maxRetries [gh-0] (#838) * refactor: rename maxAttempts to maxRetries [gh-0] * refactor: rename DEFAULT_MAX_ATTEMPTS to DEFAULT_MAX_RETRIES [gh-0] --- examples/advanced-project-js/checkly.config.js | 2 +- .../src/__checks__/website-group.check.js | 2 +- examples/advanced-project/checkly.config.ts | 2 +- .../src/__checks__/website-group.check.ts | 2 +- packages/cli/src/constructs/retry-strategy.ts | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/advanced-project-js/checkly.config.js b/examples/advanced-project-js/checkly.config.js index 922d6475..77c49fda 100644 --- a/examples/advanced-project-js/checkly.config.js +++ b/examples/advanced-project-js/checkly.config.js @@ -26,7 +26,7 @@ const config = defineConfig({ */ runtimeId: '2023.02', /* Failed check runs will be retried before triggering alerts */ - retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxAttempts: 4, sameRegion: true }), + retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxRetries: 4, sameRegion: true }), /* A glob pattern that matches the Checks inside your repo, see https://www.checklyhq.com/docs/cli/using-check-test-match/ */ checkMatch: '**/__checks__/**/*.check.js', browserChecks: { diff --git a/examples/advanced-project-js/src/__checks__/website-group.check.js b/examples/advanced-project-js/src/__checks__/website-group.check.js index 09800b50..7f49ebe0 100644 --- a/examples/advanced-project-js/src/__checks__/website-group.check.js +++ b/examples/advanced-project-js/src/__checks__/website-group.check.js @@ -28,7 +28,7 @@ const websiteGroup = new CheckGroup('website-check-group-1', { * Failed check runs in this group will be retried before triggering alerts. * The wait time between retries will increase linearly: 30 seconds, 60 seconds, and then 90 seconds between the retries. */ - retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxAttempts: 3, sameRegion: false }), + retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxRetries: 3, sameRegion: false }), }); module.exports = { websiteGroup }; diff --git a/examples/advanced-project/checkly.config.ts b/examples/advanced-project/checkly.config.ts index 77799bfe..8035dcb6 100644 --- a/examples/advanced-project/checkly.config.ts +++ b/examples/advanced-project/checkly.config.ts @@ -26,7 +26,7 @@ const config = defineConfig({ */ runtimeId: '2023.02', /* Failed check runs will be retried before triggering alerts */ - retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxAttempts: 4, sameRegion: true }), + retryStrategy: RetryStrategyBuilder.fixedStrategy({ baseBackoffSeconds: 60, maxRetries: 4, sameRegion: true }), /* A glob pattern that matches the Checks inside your repo, see https://www.checklyhq.com/docs/cli/using-check-test-match/ */ checkMatch: '**/__checks__/**/*.check.ts', browserChecks: { diff --git a/examples/advanced-project/src/__checks__/website-group.check.ts b/examples/advanced-project/src/__checks__/website-group.check.ts index 3ddf799d..22b193c5 100644 --- a/examples/advanced-project/src/__checks__/website-group.check.ts +++ b/examples/advanced-project/src/__checks__/website-group.check.ts @@ -28,5 +28,5 @@ export const websiteGroup = new CheckGroup('website-check-group-1', { * Failed check runs in this group will be retried before triggering alerts. * The wait time between retries will increase linearly: 30 seconds, 60 seconds, and then 90 seconds between the retries. */ - retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxAttempts: 3, sameRegion: false }), + retryStrategy: RetryStrategyBuilder.linearStrategy({ baseBackoffSeconds: 30, maxRetries: 3, sameRegion: false }), }) diff --git a/packages/cli/src/constructs/retry-strategy.ts b/packages/cli/src/constructs/retry-strategy.ts index 0e0785f2..7d772e19 100644 --- a/packages/cli/src/constructs/retry-strategy.ts +++ b/packages/cli/src/constructs/retry-strategy.ts @@ -9,7 +9,7 @@ export interface RetryStrategy { /** * The maximum number of attempts to retry the check. Value must be between 1 and 10. */ - maxAttempts?: number, + maxRetries?: number, /** * The total amount of time to continue retrying the check (maximum 600 seconds). */ @@ -20,11 +20,11 @@ export interface RetryStrategy { sameRegion?: boolean, } -export type RetryStrategyOptions = Pick +export type RetryStrategyOptions = Pick export class RetryStrategyBuilder { private static readonly DEFAULT_BASE_BACKOFF_SECONDS = 60 - private static readonly DEFAULT_MAX_ATTEMPTS = 2 + private static readonly DEFAULT_MAX_RETRIES = 2 private static readonly DEFAULT_MAX_DURATION_SECONDS = 60 * 10 private static readonly DEFAULT_SAME_REGION = false @@ -59,7 +59,7 @@ export class RetryStrategyBuilder { return { type, baseBackoffSeconds: options.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS, - maxAttempts: options.maxAttempts ?? RetryStrategyBuilder.DEFAULT_MAX_ATTEMPTS, + maxRetries: options.maxRetries ?? RetryStrategyBuilder.DEFAULT_MAX_RETRIES, maxDurationSeconds: options.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS, sameRegion: options.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION, } From 25696221caab165af91dc0a07fc32a711abd07c7 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Fri, 1 Sep 2023 12:31:12 +0200 Subject: [PATCH 12/26] feat: allow no options for RetryStrategyBuilder (#839) --- packages/cli/src/constructs/retry-strategy.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/constructs/retry-strategy.ts b/packages/cli/src/constructs/retry-strategy.ts index 7d772e19..a7accdde 100644 --- a/packages/cli/src/constructs/retry-strategy.ts +++ b/packages/cli/src/constructs/retry-strategy.ts @@ -31,7 +31,7 @@ export class RetryStrategyBuilder { /** * Each retry is run with the same backoff between attempts. */ - static fixedStrategy (options: RetryStrategyOptions): RetryStrategy { + static fixedStrategy (options?: RetryStrategyOptions): RetryStrategy { return RetryStrategyBuilder.retryStrategy('FIXED', options) } @@ -41,7 +41,7 @@ export class RetryStrategyBuilder { * The delay between retries is calculated using `baseBackoffSeconds * attempt`. * For example, retries will be run with a backoff of 10s, 20s, 30s, and so on. */ - static linearStrategy (options: RetryStrategyOptions): RetryStrategy { + static linearStrategy (options?: RetryStrategyOptions): RetryStrategy { return RetryStrategyBuilder.retryStrategy('LINEAR', options) } @@ -51,17 +51,17 @@ export class RetryStrategyBuilder { * The delay between retries is calculated using `baseBackoffSeconds ^ attempt`. * For example, retries will be run with a backoff of 10s, 100s, 1000s, and so on. */ - static exponentialStrategy (options: RetryStrategyOptions): RetryStrategy { + static exponentialStrategy (options?: RetryStrategyOptions): RetryStrategy { return RetryStrategyBuilder.retryStrategy('EXPONENTIAL', options) } - private static retryStrategy (type: RetryStrategyType, options: RetryStrategyOptions): RetryStrategy { + private static retryStrategy (type: RetryStrategyType, options?: RetryStrategyOptions): RetryStrategy { return { type, - baseBackoffSeconds: options.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS, - maxRetries: options.maxRetries ?? RetryStrategyBuilder.DEFAULT_MAX_RETRIES, - maxDurationSeconds: options.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS, - sameRegion: options.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION, + baseBackoffSeconds: options?.baseBackoffSeconds ?? RetryStrategyBuilder.DEFAULT_BASE_BACKOFF_SECONDS, + maxRetries: options?.maxRetries ?? RetryStrategyBuilder.DEFAULT_MAX_RETRIES, + maxDurationSeconds: options?.maxDurationSeconds ?? RetryStrategyBuilder.DEFAULT_MAX_DURATION_SECONDS, + sameRegion: options?.sameRegion ?? RetryStrategyBuilder.DEFAULT_SAME_REGION, } } } From 47a3dfa7bc8bc795d0c22e1ec9f1c78962927043 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Fri, 1 Sep 2023 13:17:55 +0200 Subject: [PATCH 13/26] feat: update sameRegion default to true (#840) --- packages/cli/src/constructs/retry-strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/constructs/retry-strategy.ts b/packages/cli/src/constructs/retry-strategy.ts index a7accdde..731e29f2 100644 --- a/packages/cli/src/constructs/retry-strategy.ts +++ b/packages/cli/src/constructs/retry-strategy.ts @@ -26,7 +26,7 @@ export class RetryStrategyBuilder { private static readonly DEFAULT_BASE_BACKOFF_SECONDS = 60 private static readonly DEFAULT_MAX_RETRIES = 2 private static readonly DEFAULT_MAX_DURATION_SECONDS = 60 * 10 - private static readonly DEFAULT_SAME_REGION = false + private static readonly DEFAULT_SAME_REGION = true /** * Each retry is run with the same backoff between attempts. From 340c6516075716d7347e6d360e42107b4276380b Mon Sep 17 00:00:00 2001 From: Manel Fourati Date: Tue, 5 Sep 2023 13:18:34 +0200 Subject: [PATCH 14/26] fix(frequency): fix typo to handle `EVERY_3H` frequency [sc-00] (#846) --- packages/cli/src/constructs/frequency.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/constructs/frequency.ts b/packages/cli/src/constructs/frequency.ts index 903c867d..3b233fac 100644 --- a/packages/cli/src/constructs/frequency.ts +++ b/packages/cli/src/constructs/frequency.ts @@ -12,7 +12,7 @@ export class Frequency { static EVERY_1H = new Frequency(1 * 60) static EVERY_2H = new Frequency(2 * 60) - static EVERY_3M = new Frequency(3 * 60) + static EVERY_3H = new Frequency(3 * 60) static EVERY_6H = new Frequency(6 * 60) static EVERY_12H = new Frequency(12 * 60) static EVERY_24H = new Frequency(24 * 60) From cba8bd86b2c9377c48f0a69f6a8ce7422392f60b Mon Sep 17 00:00:00 2001 From: Umut Uzgur Date: Fri, 22 Sep 2023 15:39:43 +0300 Subject: [PATCH 15/26] chore: update module to node16 [gh-0] (#854) --- packages/cli/e2e/__tests__/check-parse-error.spec.ts | 2 +- packages/cli/e2e/__tests__/deploy.spec.ts | 2 +- packages/cli/e2e/__tests__/destroy.spec.ts | 2 +- packages/cli/e2e/__tests__/env/env.add.spec.ts | 2 +- packages/cli/e2e/__tests__/env/env.ls.spec.ts | 2 +- packages/cli/e2e/__tests__/env/env.pull.spec.ts | 2 +- packages/cli/e2e/__tests__/env/env.rm.spec.ts | 2 +- packages/cli/e2e/__tests__/env/env.upate.spec.ts | 2 +- packages/cli/e2e/__tests__/login.spec.ts | 2 +- packages/cli/e2e/__tests__/switch.spec.ts | 2 +- packages/cli/e2e/__tests__/test.spec.ts | 2 +- packages/cli/e2e/__tests__/trigger.spec.ts | 2 +- packages/cli/e2e/__tests__/whoami.spec.ts | 2 +- packages/cli/src/commands/baseCommand.ts | 2 +- packages/cli/src/commands/deploy.ts | 4 ++-- packages/cli/src/commands/destroy.ts | 2 +- packages/cli/src/commands/env/pull.ts | 2 +- packages/cli/src/commands/env/rm.ts | 2 +- packages/cli/src/commands/login.ts | 6 +++--- packages/cli/src/commands/logout.ts | 2 +- packages/cli/src/commands/switch.ts | 2 +- packages/cli/src/commands/test.ts | 2 +- packages/cli/src/reporters/abstract-list.ts | 4 ++-- packages/cli/src/reporters/ci.ts | 2 +- packages/cli/src/reporters/dot.ts | 2 +- packages/cli/src/reporters/list.ts | 4 ++-- packages/cli/src/reporters/util.ts | 4 ++-- packages/cli/src/services/util.ts | 4 ++-- packages/cli/tsconfig.json | 3 ++- 29 files changed, 37 insertions(+), 36 deletions(-) diff --git a/packages/cli/e2e/__tests__/check-parse-error.spec.ts b/packages/cli/e2e/__tests__/check-parse-error.spec.ts index 1557ddb8..c9cf5946 100644 --- a/packages/cli/e2e/__tests__/check-parse-error.spec.ts +++ b/packages/cli/e2e/__tests__/check-parse-error.spec.ts @@ -1,5 +1,5 @@ import * as path from 'path' -import * as config from 'config' +import config from 'config' import { runChecklyCli } from '../run-checkly' describe('check parse error', () => { diff --git a/packages/cli/e2e/__tests__/deploy.spec.ts b/packages/cli/e2e/__tests__/deploy.spec.ts index 6481b5b9..c2ec4cef 100644 --- a/packages/cli/e2e/__tests__/deploy.spec.ts +++ b/packages/cli/e2e/__tests__/deploy.spec.ts @@ -1,5 +1,5 @@ import * as path from 'path' -import * as config from 'config' +import config from 'config' import { v4 as uuidv4 } from 'uuid' import axios from 'axios' import { runChecklyCli } from '../run-checkly' diff --git a/packages/cli/e2e/__tests__/destroy.spec.ts b/packages/cli/e2e/__tests__/destroy.spec.ts index d73e2552..8679f3d6 100644 --- a/packages/cli/e2e/__tests__/destroy.spec.ts +++ b/packages/cli/e2e/__tests__/destroy.spec.ts @@ -1,5 +1,5 @@ import * as path from 'path' -import * as config from 'config' +import config from 'config' import { v4 as uuidv4 } from 'uuid' import { runChecklyCli } from '../run-checkly' diff --git a/packages/cli/e2e/__tests__/env/env.add.spec.ts b/packages/cli/e2e/__tests__/env/env.add.spec.ts index eb2166eb..320c5bc6 100644 --- a/packages/cli/e2e/__tests__/env/env.add.spec.ts +++ b/packages/cli/e2e/__tests__/env/env.add.spec.ts @@ -1,6 +1,6 @@ // create test for checkly env add import * as path from 'path' -import * as config from 'config' +import config from 'config' import { nanoid } from 'nanoid' import { runChecklyCli } from '../../run-checkly' diff --git a/packages/cli/e2e/__tests__/env/env.ls.spec.ts b/packages/cli/e2e/__tests__/env/env.ls.spec.ts index f2e925e8..c1741dfa 100644 --- a/packages/cli/e2e/__tests__/env/env.ls.spec.ts +++ b/packages/cli/e2e/__tests__/env/env.ls.spec.ts @@ -1,6 +1,6 @@ // create test for checkly env ls import * as path from 'path' -import * as config from 'config' +import config from 'config' import { nanoid } from 'nanoid' import { runChecklyCli } from '../../run-checkly' diff --git a/packages/cli/e2e/__tests__/env/env.pull.spec.ts b/packages/cli/e2e/__tests__/env/env.pull.spec.ts index 0b78534f..20525c6c 100644 --- a/packages/cli/e2e/__tests__/env/env.pull.spec.ts +++ b/packages/cli/e2e/__tests__/env/env.pull.spec.ts @@ -1,6 +1,6 @@ // create test for checkly env pull import * as path from 'path' -import * as config from 'config' +import config from 'config' import { nanoid } from 'nanoid' import * as fs from 'fs' diff --git a/packages/cli/e2e/__tests__/env/env.rm.spec.ts b/packages/cli/e2e/__tests__/env/env.rm.spec.ts index 8558a0c3..3ac245cf 100644 --- a/packages/cli/e2e/__tests__/env/env.rm.spec.ts +++ b/packages/cli/e2e/__tests__/env/env.rm.spec.ts @@ -1,6 +1,6 @@ // create test for checkly env add import * as path from 'path' -import * as config from 'config' +import config from 'config' import { nanoid } from 'nanoid' import { runChecklyCli } from '../../run-checkly' diff --git a/packages/cli/e2e/__tests__/env/env.upate.spec.ts b/packages/cli/e2e/__tests__/env/env.upate.spec.ts index d605ebb0..c0f3da38 100644 --- a/packages/cli/e2e/__tests__/env/env.upate.spec.ts +++ b/packages/cli/e2e/__tests__/env/env.upate.spec.ts @@ -1,6 +1,6 @@ // create test for checkly env update import * as path from 'path' -import * as config from 'config' +import config from 'config' import { nanoid } from 'nanoid' import { runChecklyCli } from '../../run-checkly' diff --git a/packages/cli/e2e/__tests__/login.spec.ts b/packages/cli/e2e/__tests__/login.spec.ts index 8fd994a3..4734b1d5 100644 --- a/packages/cli/e2e/__tests__/login.spec.ts +++ b/packages/cli/e2e/__tests__/login.spec.ts @@ -1,4 +1,4 @@ -import * as config from 'config' +import config from 'config' import { runChecklyCli } from '../run-checkly' describe('login', () => { diff --git a/packages/cli/e2e/__tests__/switch.spec.ts b/packages/cli/e2e/__tests__/switch.spec.ts index 3654fb76..953435d7 100644 --- a/packages/cli/e2e/__tests__/switch.spec.ts +++ b/packages/cli/e2e/__tests__/switch.spec.ts @@ -1,5 +1,5 @@ import { runChecklyCli } from '../run-checkly' -import * as config from 'config' +import config from 'config' describe('switch', () => { it('should switch between user accounts', async () => { diff --git a/packages/cli/e2e/__tests__/test.spec.ts b/packages/cli/e2e/__tests__/test.spec.ts index c0ca63ef..00123a8a 100644 --- a/packages/cli/e2e/__tests__/test.spec.ts +++ b/packages/cli/e2e/__tests__/test.spec.ts @@ -1,6 +1,6 @@ import * as path from 'path' import * as uuid from 'uuid' -import * as config from 'config' +import config from 'config' import * as fs from 'fs' import { runChecklyCli } from '../run-checkly' diff --git a/packages/cli/e2e/__tests__/trigger.spec.ts b/packages/cli/e2e/__tests__/trigger.spec.ts index d433096f..780755d4 100644 --- a/packages/cli/e2e/__tests__/trigger.spec.ts +++ b/packages/cli/e2e/__tests__/trigger.spec.ts @@ -1,6 +1,6 @@ import * as path from 'path' import * as uuid from 'uuid' -import * as config from 'config' +import config from 'config' import { runChecklyCli } from '../run-checkly' diff --git a/packages/cli/e2e/__tests__/whoami.spec.ts b/packages/cli/e2e/__tests__/whoami.spec.ts index 9385cfda..1cdf69d8 100644 --- a/packages/cli/e2e/__tests__/whoami.spec.ts +++ b/packages/cli/e2e/__tests__/whoami.spec.ts @@ -1,5 +1,5 @@ import { runChecklyCli } from '../run-checkly' -import * as config from 'config' +import config from 'config' describe('whomai', () => { it('should give correct user', async () => { diff --git a/packages/cli/src/commands/baseCommand.ts b/packages/cli/src/commands/baseCommand.ts index 14eeef38..3289f58d 100644 --- a/packages/cli/src/commands/baseCommand.ts +++ b/packages/cli/src/commands/baseCommand.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import * as prompts from 'prompts' +import prompts from 'prompts' import { Command } from '@oclif/core' import { api } from '../rest/api' diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index 66ea3a66..1096120d 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -1,6 +1,6 @@ import * as api from '../rest/api' import config from '../services/config' -import * as prompts from 'prompts' +import prompts from 'prompts' import { Flags, ux } from '@oclif/core' import { AuthCommand } from './authCommand' import { parseProject } from '../services/project-parser' @@ -12,7 +12,7 @@ import { MaintenanceWindow, PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, Project, ProjectData, } from '../constructs' -import * as chalk from 'chalk' +import chalk from 'chalk' import { splitConfigFilePath, getGitInformation } from '../services/util' import commonMessages from '../messages/common-messages' import { ProjectDeployResponse } from '../rest/projects' diff --git a/packages/cli/src/commands/destroy.ts b/packages/cli/src/commands/destroy.ts index 7df90441..97e76e82 100644 --- a/packages/cli/src/commands/destroy.ts +++ b/packages/cli/src/commands/destroy.ts @@ -2,7 +2,7 @@ import { Flags } from '@oclif/core' import * as api from '../rest/api' import { loadChecklyConfig } from '../services/checkly-config-loader' import { AuthCommand } from './authCommand' -import * as prompts from 'prompts' +import prompts from 'prompts' import config from '../services/config' import { splitConfigFilePath } from '../services/util' import commonMessages from '../messages/common-messages' diff --git a/packages/cli/src/commands/env/pull.ts b/packages/cli/src/commands/env/pull.ts index bfb5cfa5..9249dffd 100644 --- a/packages/cli/src/commands/env/pull.ts +++ b/packages/cli/src/commands/env/pull.ts @@ -1,4 +1,4 @@ -import * as prompts from 'prompts' +import prompts from 'prompts' import * as path from 'path' import * as api from '../../rest/api' import { Flags, Args } from '@oclif/core' diff --git a/packages/cli/src/commands/env/rm.ts b/packages/cli/src/commands/env/rm.ts index 7dc1aa92..a5456786 100644 --- a/packages/cli/src/commands/env/rm.ts +++ b/packages/cli/src/commands/env/rm.ts @@ -1,4 +1,4 @@ -import * as prompts from 'prompts' +import prompts from 'prompts' import * as api from '../../rest/api' import { Flags, Args } from '@oclif/core' import { AuthCommand } from '../authCommand' diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index 2b22755e..b423703d 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -1,7 +1,7 @@ -import * as open from 'open' -import * as chalk from 'chalk' +import open from 'open' +import chalk from 'chalk' import { BaseCommand } from './baseCommand' -import * as prompts from 'prompts' +import prompts from 'prompts' import config from '../services/config' import * as api from '../rest/api' import type { Account } from '../rest/accounts' diff --git a/packages/cli/src/commands/logout.ts b/packages/cli/src/commands/logout.ts index 0686f52f..4d760e31 100644 --- a/packages/cli/src/commands/logout.ts +++ b/packages/cli/src/commands/logout.ts @@ -1,4 +1,4 @@ -import * as prompts from 'prompts' +import prompts from 'prompts' import { Flags } from '@oclif/core' import config from '../services/config' import { BaseCommand } from './baseCommand' diff --git a/packages/cli/src/commands/switch.ts b/packages/cli/src/commands/switch.ts index 6ab8df45..b17e4bb4 100644 --- a/packages/cli/src/commands/switch.ts +++ b/packages/cli/src/commands/switch.ts @@ -1,4 +1,4 @@ -import * as chalk from 'chalk' +import chalk from 'chalk' import { Flags } from '@oclif/core' import config from '../services/config' import * as api from '../rest/api' diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index a481a2b1..56266294 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -1,5 +1,5 @@ import { Flags, Args, ux } from '@oclif/core' -import * as indentString from 'indent-string' +import indentString from 'indent-string' import { isCI } from 'ci-info' import * as api from '../rest/api' import config from '../services/config' diff --git a/packages/cli/src/reporters/abstract-list.ts b/packages/cli/src/reporters/abstract-list.ts index a41e1c22..bca22d98 100644 --- a/packages/cli/src/reporters/abstract-list.ts +++ b/packages/cli/src/reporters/abstract-list.ts @@ -1,5 +1,5 @@ -import * as chalk from 'chalk' -import * as indentString from 'indent-string' +import chalk from 'chalk' +import indentString from 'indent-string' import { Reporter } from './reporter' import { CheckStatus, formatCheckTitle, getTestSessionUrl, printLn } from './util' diff --git a/packages/cli/src/reporters/ci.ts b/packages/cli/src/reporters/ci.ts index 46e81fbe..a0b34064 100644 --- a/packages/cli/src/reporters/ci.ts +++ b/packages/cli/src/reporters/ci.ts @@ -1,4 +1,4 @@ -import * as indentString from 'indent-string' +import indentString from 'indent-string' import AbstractListReporter from './abstract-list' import { formatCheckTitle, formatCheckResult, CheckStatus, printLn } from './util' diff --git a/packages/cli/src/reporters/dot.ts b/packages/cli/src/reporters/dot.ts index ce116dff..72b25a7e 100644 --- a/packages/cli/src/reporters/dot.ts +++ b/packages/cli/src/reporters/dot.ts @@ -1,4 +1,4 @@ -import * as chalk from 'chalk' +import chalk from 'chalk' import AbstractListReporter from './abstract-list' import { CheckRunId } from '../services/abstract-check-runner' import { print, printLn } from './util' diff --git a/packages/cli/src/reporters/list.ts b/packages/cli/src/reporters/list.ts index ada01a1e..a4e8eb3b 100644 --- a/packages/cli/src/reporters/list.ts +++ b/packages/cli/src/reporters/list.ts @@ -1,5 +1,5 @@ -import * as indentString from 'indent-string' -import * as chalk from 'chalk' +import indentString from 'indent-string' +import chalk from 'chalk' import AbstractListReporter from './abstract-list' import { CheckRunId } from '../services/abstract-check-runner' diff --git a/packages/cli/src/reporters/util.ts b/packages/cli/src/reporters/util.ts index 3666521e..f57780d5 100644 --- a/packages/cli/src/reporters/util.ts +++ b/packages/cli/src/reporters/util.ts @@ -1,5 +1,5 @@ -import * as chalk from 'chalk' -import * as indentString from 'indent-string' +import chalk from 'chalk' +import indentString from 'indent-string' import { DateTime } from 'luxon' import * as logSymbols from 'log-symbols' diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index 669bded7..d1f3813a 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -3,7 +3,7 @@ import * as path from 'path' import * as fs from 'fs/promises' import * as fsSync from 'fs' import { Service } from 'ts-node' -import * as gitRepoInfo from 'git-repo-info' +import gitRepoInfo from 'git-repo-info' import { parse } from 'dotenv' // @ts-ignore import { getProxyForUrl } from 'proxy-from-env' @@ -66,7 +66,7 @@ export async function loadTsFile (filepath: string): Promise { try { const tsCompiler = await getTsCompiler() tsCompiler.enabled(true) - let { default: exported } = await import(filepath) + let { default: exported } = await require(filepath) if (exported instanceof Function) { exported = await exported() } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 3bb342bc..56a143b3 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -3,7 +3,8 @@ "incremental": true, "declaration": true, "importHelpers": false, - "module": "commonjs", + "module": "node16", + "esModuleInterop": true, "outDir": "dist", "rootDirs": ["src", "e2e"], "strict": true, From 741d64033cf611828e480f1dc4a55267ded20f72 Mon Sep 17 00:00:00 2001 From: Umut Uzgur Date: Mon, 25 Sep 2023 12:06:37 +0300 Subject: [PATCH 16/26] chore: upgrade typescript eslint parser to support typescript 5 [gh-848] (#857) --- package-lock.json | 117 ++++++++++++++++++++++++++------------ packages/cli/package.json | 2 +- 2 files changed, 82 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1005e474..b50654e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4181,10 +4181,11 @@ "license": "ISC" }, "node_modules/@typescript-eslint/types": { - "version": "5.46.1", - "license": "MIT", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.2.tgz", + "integrity": "sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -4192,19 +4193,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.46.1", - "license": "BSD-2-Clause", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz", + "integrity": "sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==", "dependencies": { - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/visitor-keys": "5.46.1", + "@typescript-eslint/types": "6.7.2", + "@typescript-eslint/visitor-keys": "6.7.2", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -4218,7 +4220,8 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { "yallist": "^4.0.0" }, @@ -4227,8 +4230,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "license": "ISC", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -4241,7 +4245,8 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { "version": "4.0.0", - "license": "ISC" + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@typescript-eslint/utils": { "version": "5.59.7", @@ -4364,14 +4369,15 @@ "license": "ISC" }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.46.1", - "license": "MIT", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz", + "integrity": "sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==", "dependencies": { - "@typescript-eslint/types": "5.46.1", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.7.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -4379,10 +4385,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "license": "Apache-2.0", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/abbrev": { @@ -15009,6 +15019,17 @@ "node": ">=8" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-jest": { "version": "29.0.3", "dev": true, @@ -15150,6 +15171,7 @@ }, "node_modules/tsutils": { "version": "3.21.0", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.8.1" @@ -15163,6 +15185,7 @@ }, "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", + "dev": true, "license": "0BSD" }, "node_modules/tunnel": { @@ -16403,7 +16426,7 @@ "@oclif/plugin-not-found": "2.3.23", "@oclif/plugin-plugins": "2.3.0", "@oclif/plugin-warn-if-update-available": "2.0.24", - "@typescript-eslint/typescript-estree": "5.46.1", + "@typescript-eslint/typescript-estree": "6.7.2", "acorn": "8.8.1", "acorn-walk": "8.2.0", "async-mqtt": "2.6.3", @@ -19737,34 +19760,44 @@ } }, "@typescript-eslint/types": { - "version": "5.46.1" + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.2.tgz", + "integrity": "sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg==" }, "@typescript-eslint/typescript-estree": { - "version": "5.46.1", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz", + "integrity": "sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ==", "requires": { - "@typescript-eslint/types": "5.46.1", - "@typescript-eslint/visitor-keys": "5.46.1", + "@typescript-eslint/types": "6.7.2", + "@typescript-eslint/visitor-keys": "6.7.2", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "dependencies": { "lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" } }, "semver": { - "version": "7.3.8", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { "lru-cache": "^6.0.0" } }, "yallist": { - "version": "4.0.0" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -19832,14 +19865,18 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.46.1", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz", + "integrity": "sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ==", "requires": { - "@typescript-eslint/types": "5.46.1", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.7.2", + "eslint-visitor-keys": "^3.4.1" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.3.0" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" } } }, @@ -20543,7 +20580,7 @@ "@types/tunnel": "0.0.3", "@types/uuid": "9.0.1", "@types/ws": "8.5.5", - "@typescript-eslint/typescript-estree": "5.46.1", + "@typescript-eslint/typescript-estree": "6.7.2", "acorn": "8.8.1", "acorn-walk": "8.2.0", "async-mqtt": "2.6.3", @@ -27028,6 +27065,12 @@ "version": "3.0.1", "dev": true }, + "ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "requires": {} + }, "ts-jest": { "version": "29.0.3", "dev": true, @@ -27104,12 +27147,14 @@ }, "tsutils": { "version": "3.21.0", + "dev": true, "requires": { "tslib": "^1.8.1" }, "dependencies": { "tslib": { - "version": "1.14.1" + "version": "1.14.1", + "dev": true } } }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 16b7e2e3..3ada0bec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -73,7 +73,7 @@ "@oclif/plugin-not-found": "2.3.23", "@oclif/plugin-plugins": "2.3.0", "@oclif/plugin-warn-if-update-available": "2.0.24", - "@typescript-eslint/typescript-estree": "5.46.1", + "@typescript-eslint/typescript-estree": "6.7.2", "acorn": "8.8.1", "acorn-walk": "8.2.0", "async-mqtt": "2.6.3", From 88f8b12af48045386d00aaef810b775d32946bd0 Mon Sep 17 00:00:00 2001 From: Nico Domino Date: Wed, 18 Oct 2023 10:33:49 +0200 Subject: [PATCH 17/26] feat: add multistep construct and support for running test session [sc-17814] (#868) --- .../src/__checks__/multi-step-spacex.check.js | 21 +++ .../src/__checks__/spacex-requests.spec.js | 31 ++++ .../src/__checks__/multi-step-spacex.check.ts | 21 +++ .../src/__checks__/spacex-requests.spec.ts | 31 ++++ packages/cli/src/constants.ts | 1 + .../__tests__/multi-step-check.spec.ts | 39 +++++ packages/cli/src/constructs/check-group.ts | 15 ++ packages/cli/src/constructs/index.ts | 1 + .../cli/src/constructs/multi-step-check.ts | 133 ++++++++++++++++++ packages/cli/src/constructs/project.ts | 1 + packages/cli/src/rest/runtimes.ts | 1 + 11 files changed, 295 insertions(+) create mode 100644 examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js create mode 100644 examples/advanced-project-js/src/__checks__/spacex-requests.spec.js create mode 100644 examples/advanced-project/src/__checks__/multi-step-spacex.check.ts create mode 100644 examples/advanced-project/src/__checks__/spacex-requests.spec.ts create mode 100644 packages/cli/src/constructs/__tests__/multi-step-check.spec.ts create mode 100644 packages/cli/src/constructs/multi-step-check.ts diff --git a/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js b/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js new file mode 100644 index 00000000..472c13ed --- /dev/null +++ b/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js @@ -0,0 +1,21 @@ +import * as path from 'path' +import { MultiStepCheck } from 'checkly/constructs' +import { smsChannel, emailChannel } from '../alert-channels' + +const alertChannels = [smsChannel, emailChannel] + +/* +* In this example, we bundle all basic checks needed to check the Checkly homepage. We explicitly define the Browser +* check here, instead of using a default based on a .spec.js file. This allows us to override the check configuration. +* We can also add more checks into one file, in this case to cover a specific API call needed to hydrate the homepage. +*/ + +// We can define multiple checks in a single *.check.js file. +new MultiStepCheck('spacex-multistep-check', { + name: 'SpaceX MS', + runtimeId: '2023.09', + alertChannels, + code: { + entrypoint: path.join(__dirname, 'spacex-requests.spec.js') + }, +}) diff --git a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js new file mode 100644 index 00000000..15eae9ac --- /dev/null +++ b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test' + +const baseUrl = 'https://api.spacexdata.com/v3' + +test('space-x dragon capsules', async ({ request }) => { + /** + * Get all SpaceX Dragon Capsules + */ + const response = await test.step('get all capsules', async () => { + return request.get(`${baseUrl}/dragons`) + }) + + expect(response).toBeOK() + + const data = await response.json() + expect(data.length).toBeGreaterThan(0) + + const [first] = data + + /** + * Get a single Dragon Capsule + */ + const getSingleResponse = await test.step('get single dragon capsule', async () => { + return request.get(`${baseUrl}/dragons/${first.id}`) + }) + + expect(getSingleResponse).toBeOK() + + const dragonCapsule = await getSingleResponse.json() + expect(dragonCapsule.name).toEqual(first.name) +}) diff --git a/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts b/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts new file mode 100644 index 00000000..b280cf44 --- /dev/null +++ b/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts @@ -0,0 +1,21 @@ +import * as path from 'path' +import { MultiStepCheck } from 'checkly/constructs' +import { smsChannel, emailChannel } from '../alert-channels' + +const alertChannels = [smsChannel, emailChannel] + +/* +* In this example, we bundle all basic checks needed to check the Checkly homepage. We explicitly define the Browser +* check here, instead of using a default based on a .spec.js file. This allows us to override the check configuration. +* We can also add more checks into one file, in this case to cover a specific API call needed to hydrate the homepage. +*/ + +// We can define multiple checks in a single *.check.ts file. +new MultiStepCheck('spacex-multistep-check', { + name: 'SpaceX MS', + runtimeId: '2023.09', + alertChannels, + code: { + entrypoint: path.join(__dirname, 'spacex-requests.spec.ts') + }, +}) diff --git a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts new file mode 100644 index 00000000..15eae9ac --- /dev/null +++ b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test' + +const baseUrl = 'https://api.spacexdata.com/v3' + +test('space-x dragon capsules', async ({ request }) => { + /** + * Get all SpaceX Dragon Capsules + */ + const response = await test.step('get all capsules', async () => { + return request.get(`${baseUrl}/dragons`) + }) + + expect(response).toBeOK() + + const data = await response.json() + expect(data.length).toBeGreaterThan(0) + + const [first] = data + + /** + * Get a single Dragon Capsule + */ + const getSingleResponse = await test.step('get single dragon capsule', async () => { + return request.get(`${baseUrl}/dragons/${first.id}`) + }) + + expect(getSingleResponse).toBeOK() + + const dragonCapsule = await getSingleResponse.json() + expect(dragonCapsule.name).toEqual(first.name) +}) diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index cd8e9bcb..327bf843 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -2,6 +2,7 @@ const CheckTypes = { API: 'API', BROWSER: 'BROWSER', HEARTBEAT: 'HEARTBEAT', + MULTI_STEP: 'MULTI_STEP', } export default CheckTypes diff --git a/packages/cli/src/constructs/__tests__/multi-step-check.spec.ts b/packages/cli/src/constructs/__tests__/multi-step-check.spec.ts new file mode 100644 index 00000000..e24513d9 --- /dev/null +++ b/packages/cli/src/constructs/__tests__/multi-step-check.spec.ts @@ -0,0 +1,39 @@ +import { Project, Session } from '../project' +import { MultiStepCheck } from '../multi-step-check' + +const runtimesWithMultiStepSupport = { + 2023.09: { name: '2023.09', multiStepSupport: true, default: false, stage: 'CURRENT', description: 'Main updates are Playwright 1.28.0, Node.js 16.x and Typescript support. We are also dropping support for Puppeteer', dependencies: { '@playwright/test': '1.28.0', '@opentelemetry/api': '1.0.4', '@opentelemetry/sdk-trace-base': '1.0.1', '@faker-js/faker': '5.5.3', aws4: '1.11.0', axios: '0.27.2', btoa: '1.2.1', chai: '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', expect: '29.3.1', 'form-data': '4.0.0', jsonwebtoken: '8.5.1', lodash: '4.17.21', mocha: '10.1.0', moment: '2.29.2', node: '16.x', otpauth: '9.0.2', playwright: '1.28.0', typescript: '4.8.4', uuid: '9.0.0' } }, +} + +describe('MultistepCheck', () => { + it('should report multistep as check type', () => { + Session.project = new Project('project-id', { + name: 'Test Project', + repoUrl: 'https://github.com/checkly/checkly-cli', + }) + Session.availableRuntimes = runtimesWithMultiStepSupport + const check = new MultiStepCheck('main-check', { + name: 'Main Check', + runtimeId: '2023.09', + code: { content: '' }, + }) + expect(check.synthesize()).toMatchObject({ checkType: 'MULTI_STEP' }) + }) + it('should throw if runtime does not support multi step check type', () => { + Session.project = new Project('project-id', { + name: 'Test Project', + repoUrl: 'https://github.com/checkly/checkly-cli', + }) + Session.availableRuntimes = { + ...runtimesWithMultiStepSupport, + 2023.09: { + ...runtimesWithMultiStepSupport['2023.09'], + multiStepSupport: false, + }, + } + expect(() => new MultiStepCheck('main-check', { + name: 'Main Check', + code: { content: '' }, + })).toThrowError('This runtime does not support multi step checks.') + }) +}) diff --git a/packages/cli/src/constructs/check-group.ts b/packages/cli/src/constructs/check-group.ts index 7c868ccf..0b8b1026 100644 --- a/packages/cli/src/constructs/check-group.ts +++ b/packages/cli/src/constructs/check-group.ts @@ -33,6 +33,13 @@ type BrowserCheckConfig = CheckConfigDefaults & { testMatch: string, } +type MultiStepCheckConfig = CheckConfigDefaults & { + /** + * Glob pattern to include multiple files, i.e. all `.spec.ts` files + */ + testMatch: string, +} + export interface CheckGroupProps { /** * The name of the check group. @@ -82,6 +89,7 @@ export interface CheckGroupProps { */ alertChannels?: Array browserChecks?: BrowserCheckConfig, + multiStepChecks?: MultiStepCheckConfig, /** * A valid piece of Node.js code to run in the setup phase of an API check in this group. * @deprecated use the "ApiCheck.setupScript" property instead and use common JS/TS code @@ -125,6 +133,7 @@ export class CheckGroup extends Construct { localTearDownScript?: string apiCheckDefaults: ApiCheckDefaultConfig browserChecks?: BrowserCheckConfig + multiStepChecks?: MultiStepCheckConfig retryStrategy?: RetryStrategy static readonly __checklyType = 'check-group' @@ -238,6 +247,12 @@ export class CheckGroup extends Construct { } } + public getMultiStepCheckDefaults (): CheckConfigDefaults { + return { + frequency: this.browserChecks?.frequency, + } + } + synthesize () { return { name: this.name, diff --git a/packages/cli/src/constructs/index.ts b/packages/cli/src/constructs/index.ts index e310ae81..9e667d22 100644 --- a/packages/cli/src/constructs/index.ts +++ b/packages/cli/src/constructs/index.ts @@ -23,3 +23,4 @@ export * from './check' export * from './dashboard' export * from './phone-call-alert-channel' export * from './retry-strategy' +export * from './multi-step-check' diff --git a/packages/cli/src/constructs/multi-step-check.ts b/packages/cli/src/constructs/multi-step-check.ts new file mode 100644 index 00000000..ec7a4193 --- /dev/null +++ b/packages/cli/src/constructs/multi-step-check.ts @@ -0,0 +1,133 @@ +import * as path from 'path' +import { Check, CheckProps } from './check' +import { Session } from './project' +import { Parser } from '../services/check-parser/parser' +import { CheckConfigDefaults } from '../services/checkly-config-loader' +import { pathToPosix } from '../services/util' +import { Content, Entrypoint } from './construct' +import CheckTypes from '../constants' +import { CheckDependency } from './browser-check' + +export interface MultiStepCheckProps extends CheckProps { + /** + * A valid piece of Node.js javascript code describing a multi-step interaction + * with the Puppeteer or Playwright frameworks. + */ + code: Content|Entrypoint +} + +/** + * Creates a multi-step Check + * + * @remarks + * + * This class make use of the multi-step checks endpoints. + */ +export class MultiStepCheck extends Check { + script: string + scriptPath?: string + dependencies?: Array + + /** + * Constructs the multi-step instance + * + * @param logicalId unique project-scoped resource name identification + * @param props check configuration properties + * {@link https://checklyhq.com/docs/cli/constructs/#multistepcheck Read more in the docs} + */ + constructor (logicalId: string, props: MultiStepCheckProps) { + if (props.group) { + MultiStepCheck.applyDefaultMultiStepCheckGroupConfig(props, props.group.getMultiStepCheckDefaults()) + } + MultiStepCheck.applyDefaultMultiStepCheckConfig(props) + super(logicalId, props) + + if (!Session.availableRuntimes[this.runtimeId!]?.multiStepSupport) { + throw new Error('This runtime does not support multi step checks.') + } + if ('content' in props.code) { + const script = props.code.content + this.script = script + } else if ('entrypoint' in props.code) { + const entrypoint = props.code.entrypoint + let absoluteEntrypoint = null + if (path.isAbsolute(entrypoint)) { + absoluteEntrypoint = entrypoint + } else { + if (!Session.checkFileAbsolutePath) { + throw new Error('You cannot use relative paths without the checkFileAbsolutePath in session') + } + absoluteEntrypoint = path.join(path.dirname(Session.checkFileAbsolutePath), entrypoint) + } + // runtimeId will always be set by check or multi-step check defaults so it is safe to use ! operator + const bundle = MultiStepCheck.bundle(absoluteEntrypoint, this.runtimeId!) + if (!bundle.script) { + throw new Error(`Multi-Step check "${logicalId}" is not allowed to be empty`) + } + this.script = bundle.script + this.scriptPath = bundle.scriptPath + this.dependencies = bundle.dependencies + } else { + throw new Error('Unrecognized type for the "code" property. The "code" property should be a string of JS/TS code.') + } + Session.registerConstruct(this) + this.addSubscriptions() + this.addPrivateLocationCheckAssignments() + } + + private static applyDefaultMultiStepCheckGroupConfig (props: CheckConfigDefaults, groupProps: CheckConfigDefaults) { + let configKey: keyof CheckConfigDefaults + for (configKey in groupProps) { + const newVal: any = props[configKey] ?? groupProps[configKey] + props[configKey] = newVal + } + } + + private static applyDefaultMultiStepCheckConfig (props: CheckConfigDefaults) { + if (!Session.multiStepCheckDefaults) { + return + } + let configKey: keyof CheckConfigDefaults + for (configKey in Session.multiStepCheckDefaults) { + const newVal: any = props[configKey] ?? Session.multiStepCheckDefaults[configKey] + props[configKey] = newVal + } + } + + static bundle (entry: string, runtimeId: string) { + const runtime = Session.availableRuntimes[runtimeId] + if (!runtime) { + throw new Error(`${runtimeId} is not supported`) + } + const parser = new Parser(Object.keys(runtime.dependencies)) + const parsed = parser.parse(entry) + // Maybe we can get the parsed deps with the content immediately + + const deps: CheckDependency[] = [] + for (const { filePath, content } of parsed.dependencies) { + deps.push({ + path: pathToPosix(path.relative(Session.basePath!, filePath)), + content, + }) + } + return { + script: parsed.entrypoint.content, + scriptPath: pathToPosix(path.relative(Session.basePath!, parsed.entrypoint.filePath)), + dependencies: deps, + } + } + + getSourceFile () { + return this.__checkFilePath ?? this.scriptPath + } + + synthesize () { + return { + ...super.synthesize(), + checkType: CheckTypes.MULTI_STEP, + script: this.script, + scriptPath: this.scriptPath, + dependencies: this.dependencies, + } + } +} diff --git a/packages/cli/src/constructs/project.ts b/packages/cli/src/constructs/project.ts index 0fd067f6..1d472771 100644 --- a/packages/cli/src/constructs/project.ts +++ b/packages/cli/src/constructs/project.ts @@ -138,6 +138,7 @@ export class Session { static basePath?: string static checkDefaults?: CheckConfigDefaults static browserCheckDefaults?: CheckConfigDefaults + static multiStepCheckDefaults?: CheckConfigDefaults static checkFilePath?: string static checkFileAbsolutePath?: string static availableRuntimes: Record diff --git a/packages/cli/src/rest/runtimes.ts b/packages/cli/src/rest/runtimes.ts index ad4e0a1c..8330f41e 100644 --- a/packages/cli/src/rest/runtimes.ts +++ b/packages/cli/src/rest/runtimes.ts @@ -6,6 +6,7 @@ export interface Runtime { runtimeEndOfLife?: string description?: string dependencies: Record + multiStepSupport?: boolean } class Runtimes { From 5a348d8014c6eebcb1d935a4ecef1c6462fb9d8e Mon Sep 17 00:00:00 2001 From: Nico Domino Date: Wed, 18 Oct 2023 13:51:25 +0200 Subject: [PATCH 18/26] fix: remove multistep examples until GA release [sc-00] (#869) --- .../src/__checks__/multi-step-spacex.check.js | 21 ------------- .../src/__checks__/spacex-requests.spec.js | 31 ------------------- .../src/__checks__/multi-step-spacex.check.ts | 21 ------------- .../src/__checks__/spacex-requests.spec.ts | 31 ------------------- 4 files changed, 104 deletions(-) delete mode 100644 examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js delete mode 100644 examples/advanced-project-js/src/__checks__/spacex-requests.spec.js delete mode 100644 examples/advanced-project/src/__checks__/multi-step-spacex.check.ts delete mode 100644 examples/advanced-project/src/__checks__/spacex-requests.spec.ts diff --git a/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js b/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js deleted file mode 100644 index 472c13ed..00000000 --- a/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js +++ /dev/null @@ -1,21 +0,0 @@ -import * as path from 'path' -import { MultiStepCheck } from 'checkly/constructs' -import { smsChannel, emailChannel } from '../alert-channels' - -const alertChannels = [smsChannel, emailChannel] - -/* -* In this example, we bundle all basic checks needed to check the Checkly homepage. We explicitly define the Browser -* check here, instead of using a default based on a .spec.js file. This allows us to override the check configuration. -* We can also add more checks into one file, in this case to cover a specific API call needed to hydrate the homepage. -*/ - -// We can define multiple checks in a single *.check.js file. -new MultiStepCheck('spacex-multistep-check', { - name: 'SpaceX MS', - runtimeId: '2023.09', - alertChannels, - code: { - entrypoint: path.join(__dirname, 'spacex-requests.spec.js') - }, -}) diff --git a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js deleted file mode 100644 index 15eae9ac..00000000 --- a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { test, expect } from '@playwright/test' - -const baseUrl = 'https://api.spacexdata.com/v3' - -test('space-x dragon capsules', async ({ request }) => { - /** - * Get all SpaceX Dragon Capsules - */ - const response = await test.step('get all capsules', async () => { - return request.get(`${baseUrl}/dragons`) - }) - - expect(response).toBeOK() - - const data = await response.json() - expect(data.length).toBeGreaterThan(0) - - const [first] = data - - /** - * Get a single Dragon Capsule - */ - const getSingleResponse = await test.step('get single dragon capsule', async () => { - return request.get(`${baseUrl}/dragons/${first.id}`) - }) - - expect(getSingleResponse).toBeOK() - - const dragonCapsule = await getSingleResponse.json() - expect(dragonCapsule.name).toEqual(first.name) -}) diff --git a/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts b/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts deleted file mode 100644 index b280cf44..00000000 --- a/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as path from 'path' -import { MultiStepCheck } from 'checkly/constructs' -import { smsChannel, emailChannel } from '../alert-channels' - -const alertChannels = [smsChannel, emailChannel] - -/* -* In this example, we bundle all basic checks needed to check the Checkly homepage. We explicitly define the Browser -* check here, instead of using a default based on a .spec.js file. This allows us to override the check configuration. -* We can also add more checks into one file, in this case to cover a specific API call needed to hydrate the homepage. -*/ - -// We can define multiple checks in a single *.check.ts file. -new MultiStepCheck('spacex-multistep-check', { - name: 'SpaceX MS', - runtimeId: '2023.09', - alertChannels, - code: { - entrypoint: path.join(__dirname, 'spacex-requests.spec.ts') - }, -}) diff --git a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts deleted file mode 100644 index 15eae9ac..00000000 --- a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { test, expect } from '@playwright/test' - -const baseUrl = 'https://api.spacexdata.com/v3' - -test('space-x dragon capsules', async ({ request }) => { - /** - * Get all SpaceX Dragon Capsules - */ - const response = await test.step('get all capsules', async () => { - return request.get(`${baseUrl}/dragons`) - }) - - expect(response).toBeOK() - - const data = await response.json() - expect(data.length).toBeGreaterThan(0) - - const [first] = data - - /** - * Get a single Dragon Capsule - */ - const getSingleResponse = await test.step('get single dragon capsule', async () => { - return request.get(`${baseUrl}/dragons/${first.id}`) - }) - - expect(getSingleResponse).toBeOK() - - const dragonCapsule = await getSingleResponse.json() - expect(dragonCapsule.name).toEqual(first.name) -}) From 93370ef4d1ec21a359e447f93dd917335241c821 Mon Sep 17 00:00:00 2001 From: Nico Domino Date: Tue, 31 Oct 2023 15:46:42 +0100 Subject: [PATCH 19/26] fix: rm commitlint issue ref setting [sc-00] (#875) --- commitlint.config.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/commitlint.config.js b/commitlint.config.js index 5094bc7d..08879233 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -2,11 +2,5 @@ module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'header-max-length': [2, 'always', 100], - 'references-empty': [1, 'never'], - }, - parserPreset: { - parserOpts: { - issuePrefixes: ['gh', 'sc'], - }, }, } From 843497c02d422d7467989f29442af381f0314638 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Wed, 1 Nov 2023 13:27:35 +0000 Subject: [PATCH 20/26] feat: multistep fix + add examples back [sc-00] (#872) * Revert "fix: remove multistep examples until GA release [sc-00] (#869)" This reverts commit 5a348d8014c6eebcb1d935a4ecef1c6462fb9d8e. * feat: filter out multi-step checks [sc-00] * feat: ignore files from multistep as well [sc-00] * feat: revert line [sc-00] * feat: remove unused import [sc-00] * feat: updates after comments [sc-00] * Update examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js Co-authored-by: Chris Lample * Update examples/advanced-project/src/__checks__/multi-step-spacex.check.ts Co-authored-by: Chris Lample --------- Co-authored-by: Chris Lample --- examples/advanced-project-js/README.md | 28 ----------------- .../src/__checks__/multi-step-spacex.check.js | 21 +++++++++++++ .../src/__checks__/spacex-requests.spec.js | 31 +++++++++++++++++++ examples/advanced-project/README.md | 28 ----------------- .../src/__checks__/multi-step-spacex.check.ts | 21 +++++++++++++ .../src/__checks__/spacex-requests.spec.ts | 31 +++++++++++++++++++ packages/cli/src/services/project-parser.ts | 4 +-- 7 files changed, 106 insertions(+), 58 deletions(-) create mode 100644 examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js create mode 100644 examples/advanced-project-js/src/__checks__/spacex-requests.spec.js create mode 100644 examples/advanced-project/src/__checks__/multi-step-spacex.check.ts create mode 100644 examples/advanced-project/src/__checks__/spacex-requests.spec.ts diff --git a/examples/advanced-project-js/README.md b/examples/advanced-project-js/README.md index 8b561e5f..955b9a12 100644 --- a/examples/advanced-project-js/README.md +++ b/examples/advanced-project-js/README.md @@ -15,34 +15,6 @@ npm create checkly -- --template advanced-project This project mimics a typical app where you organize code with top-level defaults and per page, service or component checks. -``` -. -├── .github -│   └── workflow.yml -├── README.md -├── checkly.config.js -├── package.json -└── src - ├── __checks__ - │   ├── api.check.js - │   ├── home.check.js - │   ├── homepage.spec.js - │   ├── login.spec.js - │   ├── utils - │   │   ├── auth-client.js - │   │   └── setup.js - │   └── website-group.check.js - ├── alert-channels.js - ├── defaults.js - └── services - ├── api - │   └── __checks__ - │   └── api.check.js - └── top-sellers - └── __checks__ - └── top-sellers.spec.js -``` - - Running `npx checkly test` will look for `.check.js` files and `.spec.js` in `__checks__` directories and execute them in a dry run. - Running `npx check deploy` will deploy your checks to Checkly, attach alert channels, and run them on a 10m schedule in the diff --git a/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js b/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js new file mode 100644 index 00000000..ea17fb62 --- /dev/null +++ b/examples/advanced-project-js/src/__checks__/multi-step-spacex.check.js @@ -0,0 +1,21 @@ +import * as path from 'path' +import { MultiStepCheck } from 'checkly/constructs' +import { smsChannel, emailChannel } from '../alert-channels' + +const alertChannels = [smsChannel, emailChannel] + +/* +* In this example, we utilize the SpaceX public API to construct a series of chained requests, with the goal of confirming +* that the capsules retrieved from the main endpoint match those obtained from the individual capsule endpoint. +* Read more in our documentation https://www.checklyhq.com/docs/multistep-checks/ +*/ + +// We can define multiple checks in a single *.check.js file. +new MultiStepCheck('spacex-multistep-check', { + name: 'SpaceX MS', + runtimeId: '2023.09', + alertChannels, + code: { + entrypoint: path.join(__dirname, 'spacex-requests.spec.js') + }, +}) diff --git a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js new file mode 100644 index 00000000..15eae9ac --- /dev/null +++ b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test' + +const baseUrl = 'https://api.spacexdata.com/v3' + +test('space-x dragon capsules', async ({ request }) => { + /** + * Get all SpaceX Dragon Capsules + */ + const response = await test.step('get all capsules', async () => { + return request.get(`${baseUrl}/dragons`) + }) + + expect(response).toBeOK() + + const data = await response.json() + expect(data.length).toBeGreaterThan(0) + + const [first] = data + + /** + * Get a single Dragon Capsule + */ + const getSingleResponse = await test.step('get single dragon capsule', async () => { + return request.get(`${baseUrl}/dragons/${first.id}`) + }) + + expect(getSingleResponse).toBeOK() + + const dragonCapsule = await getSingleResponse.json() + expect(dragonCapsule.name).toEqual(first.name) +}) diff --git a/examples/advanced-project/README.md b/examples/advanced-project/README.md index 12ae0876..01a2f244 100644 --- a/examples/advanced-project/README.md +++ b/examples/advanced-project/README.md @@ -15,34 +15,6 @@ npm create checkly -- --template advanced-project This project mimics a typical app where you organize code with top-level defaults and per page, service or component checks. -``` -. -├── .github -│   └── workflow.yml -├── README.md -├── checkly.config.ts -├── package.json -└── src - ├── __checks__ - │   ├── api.check.ts - │   ├── home.check.ts - │   ├── homepage.spec.ts - │   ├── login.spec.ts - │   ├── utils - │   │   ├── auth-client.ts - │   │   └── setup.ts - │   └── website-group.check.ts - ├── alert-channels.ts - ├── defaults.ts - └── services - ├── api - │   └── __checks__ - │   └── api.check.ts - └── top-sellers - └── __checks__ - └── top-sellers.spec.ts -``` - - Running `npx checkly test` will look for `.check.ts` files and `.spec.ts` in `__checks__` directories and execute them in a dry run. - Running `npx check deploy` will deploy your checks to Checkly, attach alert channels, and run them on a 10m schedule in the diff --git a/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts b/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts new file mode 100644 index 00000000..908460c3 --- /dev/null +++ b/examples/advanced-project/src/__checks__/multi-step-spacex.check.ts @@ -0,0 +1,21 @@ +import * as path from 'path' +import { MultiStepCheck } from 'checkly/constructs' +import { smsChannel, emailChannel } from '../alert-channels' + +const alertChannels = [smsChannel, emailChannel] + +/* +* In this example, we utilize the SpaceX public API to construct a series of chained requests, with the goal of confirming +* that the capsules retrieved from the main endpoint match those obtained from the individual capsule endpoint. +* Read more in our documentation https://www.checklyhq.com/docs/multistep-checks/ +*/ + +// We can define multiple checks in a single *.check.ts file. +new MultiStepCheck('spacex-multistep-check', { + name: 'SpaceX MS', + runtimeId: '2023.09', + alertChannels, + code: { + entrypoint: path.join(__dirname, 'spacex-requests.spec.ts') + }, +}) diff --git a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts new file mode 100644 index 00000000..15eae9ac --- /dev/null +++ b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '@playwright/test' + +const baseUrl = 'https://api.spacexdata.com/v3' + +test('space-x dragon capsules', async ({ request }) => { + /** + * Get all SpaceX Dragon Capsules + */ + const response = await test.step('get all capsules', async () => { + return request.get(`${baseUrl}/dragons`) + }) + + expect(response).toBeOK() + + const data = await response.json() + expect(data.length).toBeGreaterThan(0) + + const [first] = data + + /** + * Get a single Dragon Capsule + */ + const getSingleResponse = await test.step('get single dragon capsule', async () => { + return request.get(`${baseUrl}/dragons/${first.id}`) + }) + + expect(getSingleResponse).toBeOK() + + const dragonCapsule = await getSingleResponse.json() + expect(dragonCapsule.name).toEqual(first.name) +}) diff --git a/packages/cli/src/services/project-parser.ts b/packages/cli/src/services/project-parser.ts index ab781b6e..65e9ea73 100644 --- a/packages/cli/src/services/project-parser.ts +++ b/packages/cli/src/services/project-parser.ts @@ -3,7 +3,7 @@ import * as path from 'path' import { loadJsFile, loadTsFile, pathToPosix } from './util' import { Check, BrowserCheck, CheckGroup, Project, Session, - PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, + PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, MultiStepCheck, } from '../constructs' import { Ref } from '../constructs/ref' import { CheckConfigDefaults } from './checkly-config-loader' @@ -104,7 +104,7 @@ async function loadAllBrowserChecks ( const checkFiles = await findFilesWithPattern(directory, browserCheckFilePattern, ignorePattern) const preexistingCheckFiles = new Set() Object.values(project.data.check).forEach((check) => { - if (check instanceof BrowserCheck && check.scriptPath) { + if ((check instanceof BrowserCheck || check instanceof MultiStepCheck) && check.scriptPath) { preexistingCheckFiles.add(check.scriptPath) } }) From 4b8e5d3b792a980911b254cdfca62621c07c88f1 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Thu, 2 Nov 2023 10:38:11 +0000 Subject: [PATCH 21/26] feat(multistep): update template best practice [sc-00] (#877) --- .../src/__checks__/spacex-requests.spec.js | 28 +++++++++---------- .../src/__checks__/spacex-requests.spec.ts | 28 +++++++++---------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js index 15eae9ac..f329fcc9 100644 --- a/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js +++ b/examples/advanced-project-js/src/__checks__/spacex-requests.spec.js @@ -6,26 +6,24 @@ test('space-x dragon capsules', async ({ request }) => { /** * Get all SpaceX Dragon Capsules */ - const response = await test.step('get all capsules', async () => { - return request.get(`${baseUrl}/dragons`) - }) - - expect(response).toBeOK() + const [first] = await test.step('get all capsules', async () => { + const response = await request.get(`${baseUrl}/dragons`) + expect(response).toBeOK() - const data = await response.json() - expect(data.length).toBeGreaterThan(0) + const data = await response.json() + expect(data.length).toBeGreaterThan(0) - const [first] = data + return data + }) /** * Get a single Dragon Capsule */ - const getSingleResponse = await test.step('get single dragon capsule', async () => { - return request.get(`${baseUrl}/dragons/${first.id}`) - }) - - expect(getSingleResponse).toBeOK() + await test.step('get single dragon capsule', async () => { + const response = await request.get(`${baseUrl}/dragons/${first.id}`) + expect(response).toBeOK() - const dragonCapsule = await getSingleResponse.json() - expect(dragonCapsule.name).toEqual(first.name) + const dragonCapsule = await response.json() + expect(dragonCapsule.name).toEqual(first.name) + }) }) diff --git a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts index 15eae9ac..f329fcc9 100644 --- a/examples/advanced-project/src/__checks__/spacex-requests.spec.ts +++ b/examples/advanced-project/src/__checks__/spacex-requests.spec.ts @@ -6,26 +6,24 @@ test('space-x dragon capsules', async ({ request }) => { /** * Get all SpaceX Dragon Capsules */ - const response = await test.step('get all capsules', async () => { - return request.get(`${baseUrl}/dragons`) - }) - - expect(response).toBeOK() + const [first] = await test.step('get all capsules', async () => { + const response = await request.get(`${baseUrl}/dragons`) + expect(response).toBeOK() - const data = await response.json() - expect(data.length).toBeGreaterThan(0) + const data = await response.json() + expect(data.length).toBeGreaterThan(0) - const [first] = data + return data + }) /** * Get a single Dragon Capsule */ - const getSingleResponse = await test.step('get single dragon capsule', async () => { - return request.get(`${baseUrl}/dragons/${first.id}`) - }) - - expect(getSingleResponse).toBeOK() + await test.step('get single dragon capsule', async () => { + const response = await request.get(`${baseUrl}/dragons/${first.id}`) + expect(response).toBeOK() - const dragonCapsule = await getSingleResponse.json() - expect(dragonCapsule.name).toEqual(first.name) + const dragonCapsule = await response.json() + expect(dragonCapsule.name).toEqual(first.name) + }) }) From 711ddd6917257d10a59710e9b15f348d65d6ff25 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Mon, 6 Nov 2023 10:33:17 +0100 Subject: [PATCH 22/26] feat: add alpha support for PWT snapshot testing (#876) --- packages/cli/src/commands/deploy.ts | 28 +++++++- packages/cli/src/commands/test.ts | 11 ++++ .../__tests__/browser-check.spec.ts | 2 +- packages/cli/src/constructs/browser-check.ts | 9 +++ packages/cli/src/rest/api.ts | 2 + packages/cli/src/rest/checkly-storage.ts | 23 +++++++ .../cli/src/services/abstract-check-runner.ts | 28 ++++---- packages/cli/src/services/snapshot-service.ts | 64 +++++++++++++++++++ packages/cli/src/services/test-runner.ts | 16 ++++- packages/cli/src/services/util.ts | 29 +++++++++ 10 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 packages/cli/src/rest/checkly-storage.ts create mode 100644 packages/cli/src/services/snapshot-service.ts diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index 1096120d..d7083a38 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -10,12 +10,13 @@ import type { Runtime } from '../rest/runtimes' import { Check, AlertChannelSubscription, AlertChannel, CheckGroup, Dashboard, MaintenanceWindow, PrivateLocation, PrivateLocationCheckAssignment, PrivateLocationGroupAssignment, - Project, ProjectData, + Project, ProjectData, BrowserCheck, } from '../constructs' import chalk from 'chalk' import { splitConfigFilePath, getGitInformation } from '../services/util' import commonMessages from '../messages/common-messages' import { ProjectDeployResponse } from '../rest/projects' +import { uploadSnapshots } from '../services/snapshot-service' // eslint-disable-next-line no-restricted-syntax enum ResourceDeployStatus { @@ -54,12 +55,26 @@ export default class Deploy extends AuthCommand { char: 'c', description: commonMessages.configFile, }), + 'update-snapshots': Flags.boolean({ + char: 'u', + description: 'Update any snapshots using the actual result of this test run.', + default: false, + // Mark --update-snapshots as hidden until we're ready for GA + hidden: true, + }), } async run (): Promise { ux.action.start('Parsing your project', undefined, { stdout: true }) const { flags } = await this.parse(Deploy) - const { force, preview, 'schedule-on-deploy': scheduleOnDeploy, output, config: configFilename } = flags + const { + force, + preview, + 'schedule-on-deploy': scheduleOnDeploy, + output, + config: configFilename, + 'update-snapshots': updateSnapshots, + } = flags const { configDirectory, configFilenames } = splitConfigFilePath(configFilename) const { config: checklyConfig, @@ -85,6 +100,15 @@ export default class Deploy extends AuthCommand { const repoInfo = getGitInformation(project.repoUrl) ux.action.stop() + if (!preview && updateSnapshots) { + for (const check of Object.values(project.data.check)) { + if (!(check instanceof BrowserCheck)) { + continue + } + check.snapshots = await uploadSnapshots(check.rawSnapshots) + } + } + const projectPayload = project.synthesize(false) if (!projectPayload.resources.length) { if (preview) { diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 56266294..3e2d35a9 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -94,6 +94,13 @@ export default class Test extends AuthCommand { char: 'n', description: 'A name to use when storing results in Checkly with --record.', }), + 'update-snapshots': Flags.boolean({ + char: 'u', + description: 'Update any snapshots using the actual result of this test run.', + default: false, + // Mark --update-snapshots as hidden until we're ready for GA + hidden: true, + }), } static args = { @@ -125,6 +132,7 @@ export default class Test extends AuthCommand { config: configFilename, record: shouldRecord, 'test-session-name': testSessionName, + 'update-snapshots': updateSnapshots, } = flags const filePatterns = argv as string[] @@ -218,6 +226,8 @@ export default class Test extends AuthCommand { shouldRecord, repoInfo, ciInfo.environment, + updateSnapshots, + configDirectory, ) runner.on(Events.RUN_STARTED, @@ -237,6 +247,7 @@ export default class Test extends AuthCommand { if (result.hasFailures) { process.exitCode = 1 } + reporters.forEach(r => r.onCheckEnd(checkRunId, { logicalId: check.logicalId, sourceFile: check.getSourceFile(), diff --git a/packages/cli/src/constructs/__tests__/browser-check.spec.ts b/packages/cli/src/constructs/__tests__/browser-check.spec.ts index 57a0870c..dc782333 100644 --- a/packages/cli/src/constructs/__tests__/browser-check.spec.ts +++ b/packages/cli/src/constructs/__tests__/browser-check.spec.ts @@ -15,7 +15,7 @@ describe('BrowserCheck', () => { const bundle = BrowserCheck.bundle(getFilePath('entrypoint.js'), '2022.10') delete Session.basePath - expect(bundle).toEqual({ + expect(bundle).toMatchObject({ script: fs.readFileSync(getFilePath('entrypoint.js')).toString(), scriptPath: 'fixtures/browser-check/entrypoint.js', dependencies: [ diff --git a/packages/cli/src/constructs/browser-check.ts b/packages/cli/src/constructs/browser-check.ts index 46871206..8469d211 100644 --- a/packages/cli/src/constructs/browser-check.ts +++ b/packages/cli/src/constructs/browser-check.ts @@ -5,6 +5,7 @@ import { Parser } from '../services/check-parser/parser' import { CheckConfigDefaults } from '../services/checkly-config-loader' import { pathToPosix } from '../services/util' import { Content, Entrypoint } from './construct' +import { detectSnapshots, Snapshot } from '../services/snapshot-service' export interface CheckDependency { path: string @@ -37,6 +38,11 @@ export class BrowserCheck extends Check { dependencies?: Array sslCheckDomain?: string + // For snapshots, we first store `rawSnapshots` with the path to the file. + // The `snapshots` field is set later (with a `key`) after these are uploaded to storage. + rawSnapshots?: Array<{ absolutePath: string, path: string }> + snapshots?: Array + /** * Constructs the Browser Check instance * @@ -73,6 +79,7 @@ export class BrowserCheck extends Check { this.script = bundle.script this.scriptPath = bundle.scriptPath this.dependencies = bundle.dependencies + this.rawSnapshots = bundle.snapshots } else { throw new Error('Unrecognized type for the "code" property. The "code" property should be a string of JS/TS code.') } @@ -120,6 +127,7 @@ export class BrowserCheck extends Check { script: parsed.entrypoint.content, scriptPath: pathToPosix(path.relative(Session.basePath!, parsed.entrypoint.filePath)), dependencies: deps, + snapshots: detectSnapshots(Session.basePath!, parsed.entrypoint.filePath), } } @@ -135,6 +143,7 @@ export class BrowserCheck extends Check { scriptPath: this.scriptPath, dependencies: this.dependencies, sslCheckDomain: this.sslCheckDomain || null, // empty string is converted to null + snapshots: this.snapshots, } } } diff --git a/packages/cli/src/rest/api.ts b/packages/cli/src/rest/api.ts index 8bcd0f30..3f6cd398 100644 --- a/packages/cli/src/rest/api.ts +++ b/packages/cli/src/rest/api.ts @@ -12,6 +12,7 @@ import Locations from './locations' import TestSessions from './test-sessions' import EnvironmentVariables from './environment-variables' import HeartbeatChecks from './heartbeat-checks' +import ChecklyStorage from './checkly-storage' export function getDefaults () { const apiKey = config.getApiKey() @@ -98,3 +99,4 @@ export const privateLocations = new PrivateLocations(api) export const testSessions = new TestSessions(api) export const environmentVariables = new EnvironmentVariables(api) export const heartbeatCheck = new HeartbeatChecks(api) +export const checklyStorage = new ChecklyStorage(api) diff --git a/packages/cli/src/rest/checkly-storage.ts b/packages/cli/src/rest/checkly-storage.ts new file mode 100644 index 00000000..114ab3d7 --- /dev/null +++ b/packages/cli/src/rest/checkly-storage.ts @@ -0,0 +1,23 @@ +import type { AxiosInstance } from 'axios' +import type { Readable } from 'node:stream' + +class ChecklyStorage { + api: AxiosInstance + constructor (api: AxiosInstance) { + this.api = api + } + + upload (stream: Readable) { + return this.api.post<{ key: string }>( + '/next/checkly-storage/upload', + stream, + { headers: { 'Content-Type': 'application/octet-stream' } }, + ) + } + + download (key: string) { + return this.api.post('/next/checkly-storage/download', { key }, { responseType: 'stream' }) + } +} + +export default ChecklyStorage diff --git a/packages/cli/src/services/abstract-check-runner.ts b/packages/cli/src/services/abstract-check-runner.ts index 3d3568cd..24777548 100644 --- a/packages/cli/src/services/abstract-check-runner.ts +++ b/packages/cli/src/services/abstract-check-runner.ts @@ -141,20 +141,8 @@ export default abstract class AbstractCheckRunner extends EventEmitter { } else if (subtopic === 'run-end') { this.disableTimeout(checkRunId) const { result } = message - const { - region, - logPath, - checkRunDataPath, - } = result.assets - if (logPath && (this.verbose || result.hasFailures)) { - result.logs = await assets.getLogs(region, logPath) - } - if (checkRunDataPath && (this.verbose || result.hasFailures)) { - result.checkRunData = await assets.getCheckRunData(region, checkRunDataPath) - } - + await this.processCheckResult(result) const links = testResultId && result.hasFailures && await this.getShortLinks(testResultId) - this.emit(Events.CHECK_SUCCESSFUL, checkRunId, check, result, links) this.emit(Events.CHECK_FINISHED, check) } else if (subtopic === 'error') { @@ -164,6 +152,20 @@ export default abstract class AbstractCheckRunner extends EventEmitter { } } + async processCheckResult (result: any) { + const { + region, + logPath, + checkRunDataPath, + } = result.assets + if (logPath && (this.verbose || result.hasFailures)) { + result.logs = await assets.getLogs(region, logPath) + } + if (checkRunDataPath && (this.verbose || result.hasFailures)) { + result.checkRunData = await assets.getCheckRunData(region, checkRunDataPath) + } + } + private allChecksFinished (): Promise { let finishedCheckCount = 0 const numChecks = this.checks.size diff --git a/packages/cli/src/services/snapshot-service.ts b/packages/cli/src/services/snapshot-service.ts new file mode 100644 index 00000000..d4ba238c --- /dev/null +++ b/packages/cli/src/services/snapshot-service.ts @@ -0,0 +1,64 @@ +import * as fsAsync from 'node:fs/promises' +import * as fs from 'node:fs' +import * as path from 'node:path' +import * as stream from 'node:stream/promises' + +import { checklyStorage } from '../rest/api' +import { findFilesRecursively, pathToPosix } from './util' + +export interface Snapshot { + key: string, + path: string, +} + +export async function pullSnapshots (basePath: string, snapshots?: Snapshot[] | null) { + if (!snapshots?.length) { + return + } + + try { + for (const snapshot of snapshots) { + const fullPath = path.resolve(basePath, snapshot.path) + if (!fullPath.startsWith(basePath)) { + // The snapshot file should always be within the project, but we validate this just in case. + throw new Error(`Detected invalid snapshot file ${fullPath}`) + } + await fsAsync.mkdir(path.dirname(fullPath), { recursive: true }) + const fileStream = fs.createWriteStream(fullPath) + const { data: contentStream } = await checklyStorage.download(snapshot.key) + contentStream.pipe(fileStream) + await stream.finished(contentStream) + } + } catch (err: any) { + throw new Error(`Error downloading snapshots: ${err.message}`) + } +} + +export function detectSnapshots (projectBasePath: string, scriptFilePath: string) { + // By default, PWT will store snapshots in the `script.spec.js-snapshots` directory. + // Other paths can be configured, though, and we should add support for those as well. + // https://playwright.dev/docs/api/class-testconfig#test-config-snapshot-path-template + const snapshotFiles = findFilesRecursively(`${scriptFilePath}-snapshots`) + return snapshotFiles.map(absolutePath => ({ + absolutePath, + path: pathToPosix(path.relative(projectBasePath, absolutePath)), + })) +} + +export async function uploadSnapshots (rawSnapshots?: Array<{ absolutePath: string, path: string }>) { + if (!rawSnapshots?.length) { + return [] + } + + try { + const snapshots: Array = [] + for (const rawSnapshot of rawSnapshots) { + const snapshotStream = fs.createReadStream(rawSnapshot.absolutePath) + const { data: { key } } = await checklyStorage.upload(snapshotStream) + snapshots.push({ key, path: rawSnapshot.path }) + } + return snapshots + } catch (err: any) { + throw new Error(`Error uploading snapshots: ${err.message}`) + } +} diff --git a/packages/cli/src/services/test-runner.ts b/packages/cli/src/services/test-runner.ts index 0dfd83a7..2e81627b 100644 --- a/packages/cli/src/services/test-runner.ts +++ b/packages/cli/src/services/test-runner.ts @@ -3,6 +3,7 @@ import AbstractCheckRunner, { RunLocation, CheckRunId } from './abstract-check-r import { GitInformation } from './util' import { Check } from '../constructs/check' import { Project } from '../constructs' +import { pullSnapshots } from '../services/snapshot-service' import * as uuid from 'uuid' @@ -13,6 +14,8 @@ export default class TestRunner extends AbstractCheckRunner { shouldRecord: boolean repoInfo: GitInformation | null environment: string | null + updateSnapshots: boolean + baseDirectory: string constructor ( accountId: string, project: Project, @@ -23,6 +26,8 @@ export default class TestRunner extends AbstractCheckRunner { shouldRecord: boolean, repoInfo: GitInformation | null, environment: string | null, + updateSnapshots: boolean, + baseDirectory: string, ) { super(accountId, timeout, verbose) this.project = project @@ -31,6 +36,8 @@ export default class TestRunner extends AbstractCheckRunner { this.shouldRecord = shouldRecord this.repoInfo = repoInfo this.environment = environment + this.updateSnapshots = updateSnapshots + this.baseDirectory = baseDirectory } async scheduleChecks ( @@ -46,7 +53,7 @@ export default class TestRunner extends AbstractCheckRunner { ...check.synthesize(), group: check.groupId ? this.project.data['check-group'][check.groupId.ref].synthesize() : undefined, groupId: undefined, - sourceInfo: { checkRunSuiteId, checkRunId }, + sourceInfo: { checkRunSuiteId, checkRunId, updateSnapshots: this.updateSnapshots }, logicalId: check.logicalId, filePath: check.getSourceFile(), })) @@ -71,4 +78,11 @@ export default class TestRunner extends AbstractCheckRunner { throw new Error(err.response?.data?.message ?? err.message) } } + + async processCheckResult (result: any) { + await super.processCheckResult(result) + if (this.updateSnapshots) { + await pullSnapshots(this.baseDirectory, result.assets?.snapshots) + } + } } diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index d1f3813a..2b1fc094 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -43,6 +43,35 @@ export async function walkDirectory ( } } +export function findFilesRecursively (directory: string, ignoredPaths: Array = []) { + if (!fsSync.statSync(directory, { throwIfNoEntry: false })?.isDirectory()) { + return [] + } + + const files = [] + const directoriesToVisit = [directory] + const ignoredPathsSet = new Set(ignoredPaths) + while (directoriesToVisit.length > 0) { + const currentDirectory = directoriesToVisit.shift()! + const contents = fsSync.readdirSync(currentDirectory, { withFileTypes: true }) + for (const content of contents) { + if (content.isSymbolicLink()) { + continue + } + const fullPath = path.resolve(currentDirectory, content.name) + if (ignoredPathsSet.has(fullPath)) { + continue + } + if (content.isDirectory()) { + directoriesToVisit.push(fullPath) + } else { + files.push(fullPath) + } + } + } + return files +} + export async function loadJsFile (filepath: string): Promise { try { // There is a Node opened issue related with a segmentation fault using ES6 modules From 8ff1565116f5277cfc52ec68d954e73790e6b7d7 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Tue, 7 Nov 2023 10:04:32 +0100 Subject: [PATCH 23/26] refactor: remove unused method (#881) --- packages/cli/src/services/util.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index 2b1fc094..ded7e1b7 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -25,24 +25,6 @@ export interface CiInformation { environment: string | null } -// TODO: Remove this in favor of glob? It's unused. -export async function walkDirectory ( - directory: string, - ignoreDirectories: Set, - callback: (filepath: string) => Promise, -): Promise { - const files = await fs.readdir(directory) - for (const file of files.sort()) { - const filepath = path.join(directory, file) - const stats = await fs.stat(filepath) - if (stats.isDirectory() && !ignoreDirectories.has(file)) { - await walkDirectory(filepath, ignoreDirectories, callback) - } else { - await callback(filepath) - } - } -} - export function findFilesRecursively (directory: string, ignoredPaths: Array = []) { if (!fsSync.statSync(directory, { throwIfNoEntry: false })?.isDirectory()) { return [] From dc46e6c25daadfe3dcfff32b0480f6082065f6dd Mon Sep 17 00:00:00 2001 From: Umut Uzgur Date: Tue, 7 Nov 2023 17:05:02 +0100 Subject: [PATCH 24/26] feat: enable the default snapshot support [gh-0] (#883) --- packages/cli/src/commands/deploy.ts | 10 +--------- packages/cli/src/commands/test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index d7083a38..4d1eb9ca 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -55,13 +55,6 @@ export default class Deploy extends AuthCommand { char: 'c', description: commonMessages.configFile, }), - 'update-snapshots': Flags.boolean({ - char: 'u', - description: 'Update any snapshots using the actual result of this test run.', - default: false, - // Mark --update-snapshots as hidden until we're ready for GA - hidden: true, - }), } async run (): Promise { @@ -73,7 +66,6 @@ export default class Deploy extends AuthCommand { 'schedule-on-deploy': scheduleOnDeploy, output, config: configFilename, - 'update-snapshots': updateSnapshots, } = flags const { configDirectory, configFilenames } = splitConfigFilePath(configFilename) const { @@ -100,7 +92,7 @@ export default class Deploy extends AuthCommand { const repoInfo = getGitInformation(project.repoUrl) ux.action.stop() - if (!preview && updateSnapshots) { + if (!preview) { for (const check of Object.values(project.data.check)) { if (!(check instanceof BrowserCheck)) { continue diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 3e2d35a9..36f323ee 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -23,6 +23,7 @@ import { createReporters, ReporterType } from '../reporters/reporter' import commonMessages from '../messages/common-messages' import { TestResultsShortLinks } from '../rest/test-sessions' import { printLn, formatCheckTitle, CheckStatus } from '../reporters/util' +import { uploadSnapshots } from '../services/snapshot-service' const DEFAULT_REGION = 'eu-central-1' @@ -98,8 +99,6 @@ export default class Test extends AuthCommand { char: 'u', description: 'Update any snapshots using the actual result of this test run.', default: false, - // Mark --update-snapshots as hidden until we're ready for GA - hidden: true, }), } @@ -200,6 +199,13 @@ export default class Test extends AuthCommand { return check }) + for (const check of checks) { + if (!(check instanceof BrowserCheck)) { + continue + } + check.snapshots = await uploadSnapshots(check.rawSnapshots) + } + ux.action.stop() if (!checks.length) { From 6d5a909f68029d396d4b520db5ff08f206595781 Mon Sep 17 00:00:00 2001 From: Chris Lample Date: Wed, 8 Nov 2023 08:31:58 +0100 Subject: [PATCH 25/26] test: add e2e snapshot tests (#885) --- packages/cli/e2e/__tests__/deploy.spec.ts | 12 +++++++ .../.gitignore | 1 + .../checkly.config.ts | 14 ++++++++ .../snapshot-test.spec.ts | 8 +++++ .../snapshot-project/checkly.config.ts | 14 ++++++++ .../snapshot-project/snapshot-test.spec.ts | 10 ++++++ .../Danube-Snapshot-Test-1-chromium-linux.png | Bin 0 -> 124084 bytes packages/cli/e2e/__tests__/test.spec.ts | 33 ++++++++++++++++++ packages/cli/package.json | 4 +++ 9 files changed, 96 insertions(+) create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/.gitignore create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/checkly.config.ts create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/snapshot-test.spec.ts create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project/checkly.config.ts create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts create mode 100644 packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts-snapshots/Danube-Snapshot-Test-1-chromium-linux.png diff --git a/packages/cli/e2e/__tests__/deploy.spec.ts b/packages/cli/e2e/__tests__/deploy.spec.ts index c2ec4cef..8db6ed57 100644 --- a/packages/cli/e2e/__tests__/deploy.spec.ts +++ b/packages/cli/e2e/__tests__/deploy.spec.ts @@ -244,4 +244,16 @@ Update and Unchanged: expect(result.stderr).toContain('Failed to deploy your project. Unable to find constructs to deploy.') expect(result.status).toBe(1) }) + + it('Should deploy a project with snapshots', async () => { + const result = await runChecklyCli({ + args: ['deploy', '--force'], + apiKey: config.get('apiKey'), + accountId: config.get('accountId'), + directory: path.join(__dirname, 'fixtures', 'snapshot-project'), + env: { PROJECT_LOGICAL_ID: projectLogicalId }, + }) + expect(result.status).toBe(0) + // TODO: Add assertions that the snapshots are successfully uploaded. + }) }) diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/.gitignore b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/.gitignore new file mode 100644 index 00000000..ed56a91c --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/.gitignore @@ -0,0 +1 @@ +*-snapshots diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/checkly.config.ts b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/checkly.config.ts new file mode 100644 index 00000000..9b69c8ae --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/checkly.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + projectName: 'Snapshot Project (Missing Snapshots)', + logicalId: process.env.PROJECT_LOGICAL_ID!, + checks: { + browserChecks: { testMatch: '**/*.spec.ts' }, + }, + cli: { + runLocation: 'us-east-1', + }, +}) + +export default config diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/snapshot-test.spec.ts b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/snapshot-test.spec.ts new file mode 100644 index 00000000..ccfb2999 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/snapshot-project-missing-snapshots/snapshot-test.spec.ts @@ -0,0 +1,8 @@ +import { expect, test } from '@playwright/test' + +test.use({ actionTimeout: 10000 }) + +test('Danube Snapshot Test', async ({ page }) => { + await page.goto('https://danube-web.shop') + await expect(page).toHaveScreenshot({ maxDiffPixels: 10000 }) +}) diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project/checkly.config.ts b/packages/cli/e2e/__tests__/fixtures/snapshot-project/checkly.config.ts new file mode 100644 index 00000000..bb3a7869 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/snapshot-project/checkly.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + projectName: 'Snapshot Project', + logicalId: process.env.PROJECT_LOGICAL_ID!, + checks: { + browserChecks: { testMatch: '**/*.spec.ts' }, + }, + cli: { + runLocation: 'us-east-1', + }, +}) + +export default config diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts b/packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts new file mode 100644 index 00000000..db918f51 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts @@ -0,0 +1,10 @@ +/* eslint-disable no-console */ +import { expect, test } from '@playwright/test' + +test.use({ actionTimeout: 10000 }) + +test('Danube Snapshot Test', async ({ page }) => { + await page.goto('https://danube-web.shop') + await expect(page).toHaveScreenshot({ maxDiffPixels: 10000 }) + console.log(process.env.SECRET_ENV) +}) diff --git a/packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts-snapshots/Danube-Snapshot-Test-1-chromium-linux.png b/packages/cli/e2e/__tests__/fixtures/snapshot-project/snapshot-test.spec.ts-snapshots/Danube-Snapshot-Test-1-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..0199a3d0416ca86df8254495a5975b0a74aa513b GIT binary patch literal 124084 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%0Ah<5xq6h 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

~_HS^=%qH?f7mg0FDC zeo}QNrX#r-6HeDJDd<42N$WvmA90O`e8*cEDs{ULLP`$#daAyV7#94vrTvRMt$A}0 z7bFHNb9%?cDoXXBkFU9AH|8sN)(e5^YM4meF~zl}1YkdgY+Vwe0aeibf(ed?ux>t& zERdWe=ykTZxC?xdOFf2vyJwgef<~Nt`^c|JAw%-N*S}OLOaw}fJ>Gu!c$G<^v(Sv!Np7B!)>&~3}Jwpm%RTh&jAhB6m+_Uf86%y<`=ngl|SFen@ z@O}sAoGu_>O_o1=IcB%TeMAZiM`(?J1)B1a&U4rw zJ)%edO(tvrt$3N6nmCZ5UFh|&0JQ`z_cc~a3(4l?k{V2Q;HT>YPFU?K#UEDJm)DqRP zX3`2E6or!t3g`3x0^>*hM%F3SavfuL_huY?6QF2^vxfU4fMSG@2f2O~u4!!Y)X#G& zl~dC(zm-+X;8)oi%l}wr^Q;6MknrzBSAXe%0W244I=Vzq*w<%Zle-G~Pv}hUSXf%w zFHLka%hM@cC83+}^=6{Nl+TmZ`xVpNVBi1?0H{9YD8wu1AzCrBEIs%96gO)?vuc;){rhe!s zdU&>Uoe>TW;ZufeS26tWub3-O(_8h}0>&@<1YLT9MRYJ%MFp12M2)1M&KGvKEgHbY zkLiz_d&5|LTk$ElD2sMdG76jPQI%~b-~d5EP(EmFKt(jMMz109RTCk5JQGPH(KP|F zcWfp|4y%Ooe;F*}m97vH=F+9+U}*^aj5>3D7-YP!(^q7`a`z3hsD9gN9C})=Tf@KR zf3yDNmQt=1r@p3~f-?C95Y{d9lxxG7Ut4jS@7up%0IAO+3KP^4E;6l|pNkBOvS zaxs0g&Jg=qXEn<*P4h(FSFm@yRCk{kaP>aT+AIG1ZaRavBvsYb!)0Qe16nmbJ*R39 zC^y9J=4OGegVDT=nn9_O|A3kN`x+p#kAM<@>(&URCxULEbH8B#ki`tppp2@TnuDHW zZe(QCPR$@vqQ6>z@!BSWN>-~A2F;uKPOhnCK_DI_P-V^PZ~e~h#}I$USPU8vdkmiW$7xz7NW4O#6CuUkIrjiTyaQjx22ynL>S$VIuZ z+05+alZA&Monrd_jDW*_a6pcfLK;M-z#_nQWN4hxBUo~+5)``hkE%3gsJM}^Jp%SK zsR-MUUR?)h<^Y|N`fjKN^fD|lJr$x+Hj=OGFssA^cA@S|FEXlsvT_I?Q0`jpwZLjo z`vlO(5VU{!6x3bL0jEQr@om=O0Ef&UtKokbBox>w4V%L_9_(M|#P(^unmqs`ttA;J z;o<^sg^awzZt3mWz~gc-$$H`Bd??zOIP(Bm_h&KVyLUp%%d6dy3C@>#M=UU67k#V< zkxxhG3CNlNPbgEOP8D#U1eT)XJSR*yl70bp9_ZN=MkSYu17v>4u*Bze)CjDc1sq7C zUj50-!eBrjWMTpwkI7t(YNc3&LR~56k0@5-{_9&7kfJ6x>y#)nl`h3EQ@~}1en|*F zK5LoG?(lh8Em1ewcUXKt(@yvY4MCX?OkuQWUTy==8IL!YadYYGzWCZ;!2dqMPU3?@ zcZhvm*c(TG+C12a;?X!}x|FwR!{%_vgy`zJad?q-;(BBSqBqDPGV-YLuY}Ko+=6#m zwQ!ao3&eA^C6?CkBP8nJq*qqT^P93S*; zKsNuO$9W=8@vIh7P`oy%oau`{3aLjI+#_*)+k+c!ap`qLYbf2Yir!Nh|MoJH7eJ$A ziP+Uy-|wyQB^+tKD;OZ}vu$rWMWbVKi|D#JQ}G3Gprl&=y#&K1(;+x<@qNGIZo&mg z9jcz-iK$zcf?sPh07XQ@IT~WlL_Q)7;4FcX-|&lao|M9-_*bCk@{-?sQSzU^unfZ< zBYj7v3|%IT?Qf%?uzCF^a3Vdu7U>_DA`)m=2LFQ3RQ5?035i*?*k6|(;v&(1@RwSA zcfYew*<27wvJsCHti$-iCUC#~jD zJnlEWFSsl1n>3Q&{?Elqp_-5%ko#E0rJzIq1Qg@%aFa%rzK4-K|GY}j|9dKm@flm& zj>_GS*PN5mRSf{odt#X-f0#O2r0`wROih;U=ls|dBy;)M@jafav@ zCYX%Z1LR6T(st7M;DOfQMMMto-N5!uQl>*C8U$AJx;gYUhQ|=jKe8wdB(5E1_F^Tb zk&#i>hn`6soSwa8;ev+{6~Wu?PCSSBPx6EN5SEHJ7`WE&i)jo1NjYYqtp%5vTW7}p z`{dQU*79)0s;ftJM=paby_GvNowY;~dO*8nX=|A*oFxpmF;;>)vQDEtmx{Id46;tE zj>dQ1N%X#aX1sH|0?G+09Z5j7UQ6TH6QZQ1tbO$E0b{?vaD76e5C@_7?e3Nf>xZX6 z$S8iXCTxPz04gMnl-J=T<9Rw81Uk*}oI%|lNHvSagtljE)LZ+UDb*QaXa%_k0q0W& zRBhMwu0f~e>FJ~}F|7GuYn(whi0D6&jKfNxWqgQ7u{%EZg(lHl+v4 zijM-Z&m4h92z1&2DKhb|WBttajYt-|7I(Z*wI4bJtD@NJ$LL>&Zw`ewKuIbQ8eUr* zJ-1g_8D=x_2Nm|$f2Sn``cerCf^vy{?*%E#OidpUdW%67K|9Yf+h0EN+prFh)Ie1M zbmO4$Sj=1!P*Q=sy|?#G8LzOgaG@s38Ys5R8FS5q>bh{=a)Zn;b3M_2E=~q69u^u3 zv9x5!P@T&VFbNRSjWus-q!qt}DzYx-?~OE{4Bp=7fs#I!83J_+!Q=A;oj?Nv>RPQM zE9w7kWozEmY!#3FwVC#Cezg=&x130%E9g+2jE17cOb^IoQEFw9vhwq*R6212wOpEP z^{~p+JOHOaH$kOP=WC}DBpD=ID30uQ>52?e^E#_n{Sf&)`M^pf?oI@SS9*s#@7@hZ zMaKuEb=#g}39eK;2@7K`FFcYU1(er6^%{LEYLa1js*u7tKokM_Or}#m2X0rtqB}}t z0WtfeI9RLwx3?eD=d^aWB5--F8St?CS3qVLRu=a)C@FIuZ{KQHNF?%{;$iBNa3>(KUfQNy`aCCJ}kzi*BGp1%bPWVNBA7i0Y`T*ezmx?6K+5NOiLwN0njC^ zWI6e>IjwO8@m?_uNE(mV#{m`9!OZr)6{rXSqDQfI>kP<*!v+qpOPxR>UA=n?hmUWq zQ1tO}w;)}68$cJZq+oR@IOyG4+|*|rFzI^#h81Cxuv?HX|G57-4YqZ+=NBK#z+h6C z;lT33LKD!SgAN?6oE;8eqjmFM_pR)^-`|_D-j7`PSu79r2XzLZ``gZxJ5Jf8cx7M# zF4KX^_I3nycys5pe-MSlDp2Lh7bv44Az@NTCFueSbMOAsl@z3r3dyIBtCi5c`p9&5 z;xTLYwD%7Rnv!+vG~dI2gxYJH4_1K9zbjw|tbow8haO-ymkti)#u_y=;;Ha)_&}7o zF`28#W7xfcy`g_wK{ZG|;C2;P=C9$eV)DM4stsl(LSJ$7;6q;L>i)Ynw3Z<-STWlC>LjK8m5KukE26tR+0T|#`Z zcJq4#cK`H{Xw&;IXox@Om8d{76hU@|@h|8esPC$bZ`1D9hR*-y)Y=DGM>unSr+F?r zKL<<07D6V$Bgq(yEEv6|4+S-Q|D0&qZB}~B&8ZXsJ7?^lmr7V#4WxHB2Qgzp)Kr#9 z#t->kCq=N{n`x-!KW=V?P#WYy^8a7X*k=Ff`ahQl<=5~KBt->&HaHGXQm9_=AH%(3 ze`QbqvQnQ3=YgeN`R|G<*gGO`VC7E^D_hwz@c(>-H7D?#D}nKk?g!lW`j4kBeG6jl zY7zjkCv{5x>+iGwM=jN#JNc)u>i_-&`vepMGTO)As;WVwXx~YAN%EUWk|QnT?rxA= zeu;@$jYAMtUG;Lg!tc@wMEM76w5_n8V-U_s0&}R zlE8sy2Ak%FryXYUuav$e^WOsKznUUBSgJF34y50+gRR()t1SZf^tFg+JQmHV{a17) z$uS{w0|Snn>{e_nk8H>*Q$U|;oWFtWx+ZK@lAw4%_EFvZfNo{>tXxGasI|R&vvB(2 z(-po?!x7ipFV~=bjKNJ3^TE+c)%e#}FLjtBTwNiq%9!1h2p-@(Vj>GPA0Pc!uvNtV zcNIz6AJI~DmmoHsoVXm)nxHgoH>U(#olu_t|+D1$$bQtOcU$NY}SA5;s6v%c-(j-j=fk0UCDXDC5t#+J+WATvBOe?i0LS%}c7 z8#4b?DQYOb#xg<$(?{1(?YY*S)G$ZNhs8(utX?UuwLdGI5L!rhMl4pIMp{fXPc+xd zP(H=yS?rkK#{OVz{;f03&=!|1Fe#QjEssPp+VjN*5tA+)rCu!OeJY zWajP>Z@+WG^qwKh<*cVy3-|v98Inr*2xN1`Q{)pAAxrem(-hD)y)ea4}+Z+?k7<1&u<$@y#jD> zS6b<1w9IGUNu;eFlDuqhyY5;f1milC)7?1j@W%5%pi3P0$*KfK!*@yx&tT`@4|7Zs zm9k#}&p;CJ=n>@L?y2v4mltxpcaT&aonidV@zIH3DY(yV?d|U)JZ|CMM&shGx@020 z#W)g?7NHF6ugR>P$@gpZ^Yi;K9G?+hOx3rC>R(EWP8h_!IcmWI;T-SFGJTI{tf?){ z@SXEURK!TxM-$$ap1fbBr-39ZN1;Cu%ZB1Tz>^w-74B&8#rl&puOjGCvo-mDL33Ck zYD#@6I+LN*Nt;A%slU`Fie2fyZ*wedF>w@?k+`f*`j4AVRVSHL?k#TLA#0eKJyccu znQO&8#ZvH9_EKQp;30qRlcae$T{Gi>5CUrBi4>xJUKhQ?fWy(E)!>3l97bb8idi~} zm`r&}hCvI_u&dXX<<3Z>%tN!syUs+<9k$S$+6CN0!3hZoubJqW&tK3!9~-FHh2P<{ z<`CX9_U<#DcD^UYrIlGN9%nFcvOmTn?Do~^60a{8MahE6V)Fi69hC2V9RH3wGjCB@ zxG|<7?c%(y;l^+w`|{W8(DBL3#`9%G4fszrQ}UHXU27V5PY4JkEJbi`I>nbSV*DZ5 zR1x>?BT=XoS$`>|T(LeQV-A%Eqv z-i*0W!oD->YFTwRFAE3f(q<$0QLjR^_KmnMp3lpfXcZ6T>WWn9HBNLf6ft z>ni6;r(oTG8*5A0^b@(Pk)43pS!IPa^k|+Za81j1(-TvCyL2ixaa3?EQ zJAEf6hPgfk&OEJPhtNL%^&y}aUX`_=vxu8RWP8KNq{L;9R6WW}dt|^BEbvA7`iD0) z<%4;%828?GS#eCM;!hruMQ(-KO)J-)5`Dv_YAY;%KTo*_roQutF<(nbvPxrZsa$QQ z4vN7!J$!L-Ri-=m0V0ID;m3ctT$fZ@rck~EeTvh@xgVq)8wVz~zcp)cXjGbF$%fQ{ zn^3Hm*D*C0@`F|m{xvv+j)~@)=xXEK54kK71FV6VCvW-^xI$YHY5W2M4aOVN`ZOv& zmthx`tshWv{4fnKm<|jKTnnF$FqM$QV9L{~JR7zVuoZt4u~m*BPaN=ZJUevz(#k$} zpbuFfV;`?#Uw>$eHon0!g4Lp)N?Sj%W2%}2tmpZbtf%Y7VE5ee*hh7-l?voPwT(gpBXHR zvXG{H3dFVvLkP~9SNDItY^2hder?!#R!^e>r2yB|`?aRw5*U!Ks;~A@p$y>#6Vo@l zX?vT4HugF913@KW6eLcy`*%moUr?0ci*=SI70ssvH9B*L%NK0)NEM#j-dmWb6{C{v zij|R5XLmb$l7@8lE=Ri9YMtjA0Y2x0RtWC$SDoY{BtEcDnV%Ib^|fys<>rA>6M=w7 zCo#3Q08+9SJ9b>?LFKxpo`C1SzDB6J+n|bE%j7z95!BOr3R5k&Rrk(_jl%z2F3HW% z=8w|$@S+$fIW0nPY;f^XC(bfO#si})j0*h*0skYgO(-xlHmtY)QHi`ywIytwzJyBN zBL>sw@zW;SCNdq>B6vCp^iZ4Mcp zWZy^G_ai?nE+z1*SJ`-%UoJ@A>4vBY$uB&wBS*70}@k3_(gqtckaXuyoAK4FhQtyFI|sjaQ;=%n2w2cDtc zMB*dUr$nt)j+j+E`-RIRRHdN3F53pUPpCHp=bVHA1T$*6ELE1LiG5qwyzvzKmKpLOJT60nNr zaIjE90ZJL47BgkRK$7`LI45DyCdWZ9x8eG&ZbmVD@FBD#{EV2`9q%r>Ik5h#Rqak- z%4eRm`zOh=m&4-mr%0^(+-+dStHXpuu!1&sojV)SGNNXVBh9quN>SChYrJCo$4Ix~ z<3pUU6P$w>Y!06TW5GXePiqM*47SBw<^|15y_ukqHh!2Pv>nm`9-!jlXz=rLfJD}`%zWW4!_V>Mj#;FgDTna- zuad-J-8kIEQ9t^+sE(DZjj@3rVJY19jqzjb98gc+ZaQ>?UKo7j9sMQG7>t(4);R~D zfzk+^-Rsu_8rFsYQVv@)V;8I)bRT123)?{-#@p7uYeeB&`r=I#r%DZ$PtFvW^m;tJG2GVm!0&c(5I99ACSVNP4&U zI4Oq30T=Kv0#d?pU!{_cp*v(%eQJTO8P>o>SnaNHKP#AexoURRqL4q|fMiBXTl2%j z$DQv5ilXUK5&}g7e$oYwOxdk60B*c?_`ArcD2BkHTs!ZG=R)hjN+usv8~7U3aD6Su zY-{)9(&SDilU_Yy^M@hWO3j$-(j7`Kfw}Gfylf~pTM_JFKIi&?Z}<6`?5dUy!Y6yZ zcXxXeZ4T#`__*(ZMaJiGN@OB4Gn#Aig9ars*|yLm);lIF?y9P-Wqlw}sEYLN>FB}g z(WAVHrxWlDP6UjY)k-=ZxzK&8M1=LCiMG&k_a{%D3k3xh{2UoFSj?pb_=$dG`rCIu zvNwvS@Pgb88BRiluc^BGoZN=C3P9D7Op1Dr_&R%;?3BUkL;Y+FBBk-Em%JKNuREuh z+`nxbt{*jXoMlS0!V?aNY2Xen0}yIbLTzEv;B6A9wDM zRHLj9kN}|r$9QPRhip?dAG+Pxw8)-%8=<{J%%e;2S(&uTeqgedS6C&^z``GJn1q)G z3;%)#W{C58jh>I&wE?45?{hN%yr5&lNvh+JXZ}PLrpN8<^hjk~F3PA>z0ack>V5Pi zo-EtLRI1FpytICg#01c0Ox1u!6_qcq;}oR#7$8dKGpjRX4lCB|YEgqBHQ4rBypf%Q zn#IQBx8jITB0Gb|R*#30(m0h;GBVzG>wK}Y()c{Ris!Lqu%qnqNF|EV%VAjL~Tn;`VXvQ4@&CCaTXLh>e-0*?)f}d%L`}1fU_|t}079;N^gA3PzIRieWas z2S6mHv0QY{v6A)Xl^LJH;ZfT$)|)RV3}!Pgdcup}$_aR3;W^~$RF#`XuUvot;e7#_ zj;;>7?J#O(FRRfA%W8DMxW#O1rX`%4`E&ppEuPFsbD(Cc+xp36URy^82o;&b&$60c z^?uIIHj2IFdZBx#O|0a40=?%E@kPZa(*5KFQ8dGIxZS_h=!y+kBYLgI z<*Qw2e7$Xg4(?8_pkOwF-$VTR zGkv7!-WPA{+bszY5>lz%-bMxgWXM&@X_|RYl3{1R!`(O6O0hYYq90iia{%_?_YB4I zo`8*cg04B|S3$yw_r7||0vUn9^_zPhl4j3s7A92s`o)V`SoN=iwI)i|8A?h@PBwmq zmS%j@zk=-vk@&_XK@>)t7HJoko1FPGY*6e;ss8g}6eEJu>S^Gb#{I*!0#?m^9&Lk< z;d>3|8x!A-mCToJhJvmnf64Bi#SJ-Md%HA%MHKk!m{KX1En)dM;zH$J7rqP}C5TGX zd%F0NI_wDGvSmwXKpg8W?9wqlNJNdoY|K&#KbEU#4$1JJJ~+8FF`sn~|A_d4@2leX zi3TV}QQcfhO5s2-53ks9Vb#1$hC*ugC>CFiyli76LQqnD)ACT+(-H9?-Cy5~+B;*Y z)Jk!ZpS!YgaKx?`)%gbc!6g@_aVLK&&Q|LE+;~PL!945D=0O;g_!t|zvtC$AhH@pG z?t=g*&}6e6{$7ioC~Bc6HuY3USk2#m-25@&A+B`&ti;dkLK2XaUEDlYdM8LmAd>P4 zOW{0FN3h|ZT%my>O1AkLh=rG1}6&F@!`>hsgeYFO0`hexF|@jqj3@m3FB)SM33{!=&OC| zG^RK=$w=b2BK=bR@c?uoB}aM@jGNDe4SYT)Iio~ef_xk zPjz2rj*q1z;JUK1U_7y9t+l`_*2*QdwCSgGlsD-9?RWFADP4Wo)TYXkPvSZ-;G7HM zlaOw`s`tOpaYeZu13+Nz^^D|?T9Bpo4JbfZs-ji$c zf!IWBbTs^(px`n#h6o(>v1#oygEPYUnNj5}A9Vnl0dK<3-+FL*jJ?W&@(wW>a3R63 z!Y=%s;Xyb8xExGOJTd8Og!@f6Q7g@1u1gjjvT5>^*~>TE+TwJw zgGjIG1X{a1W_djdjORT_P-1;>g)#R04eJNq*Q39NhDSvzl7Q~g<=|8xGW#YaQS&<6 z&aXycqoebKsAHA*4DHgsdr;YG(M_G(c7om~(Zf?&4jx8h+Y-yhstl)C8UDY?~&MqkN`RXQ5^wzj6bo4~bdwbqAo;spbQ;@8# z4Z5Qptn(s;=0`C&c2r(nokwV#%OmFZ#avEHKg*x$>#s%6`1yS7*`*4D*&}_?iS+2C z{P5M$9+4&IeDAlL0PJ9{sw4ngzNxQeuYtns9F(-52em}Cd(F>3(!h%)OYsT<=nL=B zjK>Q+TB&$nWe`4zln6dij*c^MDnq7y2){(V`s4!X9(hdC&nKHUdg(v43ob6tL;^LLECU4)&f&ane?4NnxlO@w_BbgcZp!}ZVV(QKof_YU5E{R7w!Y@kw4PN#8}j#D?*g4+(bSF zV1zC`(AGwfhkKSLOCwKJA414;+P0mLs~})&%aS!y2+^vc#p!eq{Iql_y2T$`EN*~B zWkkj;65rs6DqWt-X2+wuNhZHVzz!UhQbRy4yfm=RZQ`uEPb68TNivS2YAvZM+$)#qv z{QQmgW^Zepja0uqD~+tFi?7Xps7>(v*#Izq^Wnn8hCfWz(ScC`B^;M_C-RzgZq)tG zyQhh~4n(4*E-jQ3cKCU+<0Y@aGCr8v<~~?;(81@876Cggk$xU>03bzO!$W0hiSX5dj@ zu+YR~uG8rz7j*)vTc3RvboYZf+pGbtjLu*`Hjary5N?8?uBETdH!(pKPCD%)89=8E zw@DkU!(Uz^7F6vXDt}W8#qAKfKWg`5ee^O9#6|;?DC6?8_ODrnwPNV8JdiCk%$BJk z3Q1zt=gpX&{QOi#X2k6hLnFToq>Ci`RB94o*s=WvEeYPM8pPG2UpzsLn^Z%JIOTKR zhub6TMV1;3A?HLqOXu%@2_BtJpTl?@0jPby>=a@FZ8{Pq_V_9e-tz_f_5{yEl1^In z@-Lt3IrJ%MNf2DOI5;eacbVkY=$5Zn@n)Pa?h%;L!oz!&rG#Y%Cj_9L#&BD)k!1BP zcONVL*m;AaE8q&6YGh34Zf$)8p+27U4chF!qvgtp(~okNYTKJPYX;-fA`VP0; z_C9NB8AYUf4vn@wkXCq#hR;f1n6-r~+E{8fNwaJ6 z+g)p$W=nzi1qQGi17n2hRnfSd&OE0H0VuB^mHb8RE_bC;Kc=_t#FO?>p{Q?&85_nk zVH(`L6%kR6#d@j5%q;T-)2h)X31mA)GIMN7^{uafSxy5S8=#(J0X@cKIb$mx@SV+V z?d6BX$24)wGS8LiYW3DnVObGCcO9R9US_c={Nm$tAE0lfe!M6^46wm{h& zz0G`N+m9pns?`Tl!q&^;?cL;bGXn_%GOBS6={44e%CKo-UVnz;=1c`?sY$VMsc2+uY`uL;Qs?Tz9R9}zc(-++Z?*!0Oj=1v_q=(z z?&Om&K+!5Z=jW!Is&Nq}681^!%&AUZQzy68sN_OE@jUKW2(H|j0gB%rB!!rKO5=E) zt(gIDT^)a{WRW@bgenV!CCB-+{gXdz$$#xqFS)J5Aq6I602lxW-=-YZGo>Yi^;7T< zef6w>mMJD)Xkfg-^qv<;K8$3Esi}9MiQ3G@*I9^52%Xm(W{o|i)dOh?N~M{747|7_ z>Y0F;!Ap-#P>$eHMIpg`Kz!Rpcz(mHZ_FKZAydflOp4uixAH2| zW;jSRUd(1LY`y4WV>?J<5k5LD2J+J)93^UYpCQ?>)6`<~#Y#g!;s`8FWkCVUxe>y0 zaOZlbxa^!Ry*3pa@HDV2*uq*Gkh+3Iy}N zzlg=&K@DL4^kp?1eo#|x@mZa2J;|%n>0lxZggz5>wvkUO|NZSnVG>oyLzq$J00-(-BlCVs)-tMdhAMSIWtE*HC~&S=tAd}`afBb(0v@4R*=zsLENaP7VCLH z`=rnTq-UGjx-g_?odE?D0@%~xEU{^>-f5huOMGq{#z|WfF`K|0<(R#zR`()1}IRV$yqkapk&HUqF@czBw1e(>H&^D+t;1s`$3 z*Dpr45fQ&yZe1&&S!lh7qqf=O{S=8%jp;_3`kOfFvLS#5t~FyyLDp!eo9guOsy^9% zWziyzf+Zj#3#SK!dEGxwDCu=vqzt!8i;L6X+3&0#yk`Z^*U=@NG+-9{GtVZ@wo;?e zmT^F<+7Ealz8 zvhkp)Q7uw7J@|E6W|j5z4jGtTKiL%Rw}2#-CJs# z4%C}3HD;fdnG9a6(^Vg@T#7wsQ|QbTc`KRF*pkP(pS`Rc0Z;+b0@%81MHBBtjBo(4 z@xx>&7_e*z3}(%uPah&KE+&EbST3kU_xfZ9ykY^(>MxKi0@QE^?~CB*4IEIK(3vjN zm7}*eg2>!tGjpPx2N3mY7%t>O-jS?J$cCGp5hk zW($btOPE*@T`%TB-L zpaCKtTNZwu+3~&t?~pyzElnNJGdQ|P=N%wKv5CxYT|Q(60M{yC)o7*EI=(v0km&lp&$Lm9z#_%R)syF#X+>~x8*xh zms?kj?&N(cUqK67Co*zwPQs@Ln>HY6n&zdRGC_VJB zQ8E`9nJJukkyMCACg+Xk)jOJ~6)WoWW-iYl^>~On$1GedSF^!)0ZuF4`!S025aNh8&rem9-kYu8NgrJ*g zZ+KJZIDlDq3;k;Q;;UL1;i8CulhqkWe_T})Ae15v!2n@Is7hmr^_$(mwfdi+(AxFy>a(#nY<>virR`Wc^F)=+4Ww#K@f`s2+s(#Y)P&fL@*6u`f{ zPbyR~F^*P^83p;X<5CrfsXCJv5bcO*+K69@#|i z{ir{F`A8l3J4*SchaSM!;{X#jI@%x)0lJ3p#xYuLkXOBbB43_rQ=6Y#d<-LoHGiRg z+YXn>-IGPhOqQoqhOV1~Ejlt&0g_e#8ZWp(c>wMPY0|;C$~i!M`Kr^2mF|FNXLsl( z0Pu5c)h7A|YH43!m7!Pi5~za8U@ZTxLyJK4ps;%dU0l2OqHBh-H!?J8ss#9OLKvl< zE`<#i3wikzo9peXk`k(Yo0c<>WQFyaloLYN-fhVNAPEETotGDpL7u_Nt!OvJHs(ny z79j8%c9~`(1Niw>H;3KF355TrwX=?@I{MoD6_pMZkdRPOI;6W1q@=sMyBnpXkq!ar zPU)6T>5fYy-Cc98`g`Y{HSe0g#^rJ;;Qhv_y`Sf^&ynt)dEMg#K#U3|=7Y1t_mYx5 zWz7>qnJM}2wU*#wfx);DGs#Io7|>fKd%glfh9io1U%m`}rC2{+OLFQtGTE3*iL;X5 zOu&6%yEH>%>Ba@05Q5ZpE!#CrlqLJ=lPAv|w`(kjFy4&~c9lu}BCw z7g-)OT6K@e1~o2i-M84%L%eik3P12KxOnw1e|`QarlyANZpJJ6j0PVLoRy(-w7CiL z!;j?#FD45Dp*fJ^`8tG_r6@}_4*AxpZW!QiC`r&6;#UByIto36`_EamCgn_^P7tU- zo$cLxiH<9K54S4zC~n`%tJ`l6MHcmKe9ub@lOSyy{`rye2Q%oTlsCn5HR8MY3SR<4 zJHUKd(zt*LG~wBijw}n1(|!VUOO(hhvNtq`56nB?jdB#pv5}+2-fGOUY;@<*>AaKW zfFcSw;<4DEr={tDyhqB$a~Z1TsNTl&7@i&&ah>R!l7NX29e~~w#ldHbU%X(lgpY7c zkQkUWz|bB(u?ZobK}eABkBub-^QZGE>8`ym@gN5DF&ijU<_`FH1QIsqCg)If+8pCF zID7N?_zB6ateUWrd$O&n&4EC#3DF`-+mSLx9F=AZ3{z9FljBVWBj9vyZoN^noaIGG zh%1|)5$TX&` zsvE;wuCf<+T2U?EwbL^)YR?xb!1(XhXOIXDJ-GXg;0!Dv&581~{H>QJ?L}SIBFDNyDs6VgtzeRnL;4%e*0>$V$+R%Dmz2a3Q$_W2G z?UQG@$;?bX)UMSd#4(*#$hKBJKc3fi3l1SH^(`eOegf)(+x3$d*I6-9#bJt(r{3~Z zHKV}~euI!h?VhIvbRQRBQT_s>pRtDZ(%u0C{G>@vyE*sqCcuUoNmC%Xa-YJI<9I%gf)L3sN`d+leHw?q-_E$P5FHIU1y+QPc%sZUWn&Puu-| zgYH;Gz_HZ%$K#-yjr}>*?Ona^VJzEh7&JRNJRu~gL1;y+1#~VXZ;Q=mH$QZqLd>de z1OZ6$-BqX3{Fa4F%N00wNa#p0z2=pa*cTS-y03Xe!04a_g2+;GH3lpOCue)qm5-fe z>%RCK4+J>CxncykRsZ}0y&Q2G^c7vbz_7qq46@_gyjjPgFZJU=WaM z$IVeC#&G|4k_C;8$8JIty2X+G;`7@gTyK+@o@g+gLiSv_bXI;f0)aVQy4&S^i6rbS zW~4vvVC8a&3Sva7*%tUC4v(=0XS_gONHT8`U{L+)4Y5uOJg7V46w@4?{_r)o^>`lK z^Xfer!Q04=R>~j;r;yty(I5PYNzrv+JgFYU>u|g$=!%-qQmMx|B&`$|%kFq4n_acL zBTv|spT3Cv`9P16`A+sSqUQB0QkK~75z;T46x4F=dL~W}PsOX@N*&P@9xqlVIUTz0 z`$&*x#QJEIBezI>;`cUPjA(z>WK@M>{n`DpNq)j4w?H&iPghT_QMw*_xEMaJ=5laS zd!ilvGHlH1W@N@NB)8^CIUlLGS3)%40)D|z!!p+trW(G{R7T=et_&b%;|={F0fzi) zn+R{Gq*`|n8YeDq1qP5h;<2F5T0t1<(<#}NE&bpq2_IGuoE9}^c-0vv5y+H>x06JYgm3!I1ld~R`nd{ znAW@N8UPM>KONZtv*stQ7D&LF%MqrKWuZu}6bbb|%aXgNhsA1OQp~*YR*M~IeeHD= z8x(O0>oh_M&)(D1Gw5&b1fJlZaVkDHWaCtbTPM4__1I=M2izNwhZzd3CarqyR1Fy3 zQp7ZRw`gC5C-m+Fwq@D1M7#6>ZtXI=0STH)TnBeBJc;F{|+&=$h*E)YKHsAWHr>qM0(drzE8^noNJ9e_4(-Y|tAgmc^E zYqED3FzWR-tf>RGO+o^-k)t}z%)LXWU{4VC$pBIrZqTuSsmH32>@~oeDO%3Gzt|Y; zHn8I$_<911dq#NX^RRq+GHWHZr?c z{{_wT4RB$zlH5UQZ#S@RD%H+i?w3fgd!s*;J=SQg@NUT@d3gQT+pt}-(@(b~dceQA zPLcuKMQbQ!jTsba?qv&NLa-R(_3 zdXXir1~TP#kUC9!73wKNX_eIyjof%1|Gds1wp6k_xbDuRs{o>0rdXY?G%gaDwF1F9 z0QfTjMk6u`U;pZGIuOa0TMjd!qMcmiZCHa10VFyu6g>k&35I-Vo5R+s@q#yJiwGYV z^KnFFsnKrO-MfMEi!x|(F! zK_V;q!Pq&{gVMNYi^et48P+YGFX= z0zEn`xt1Jexm}PVIk+cyTu3>0e+dli0IXPK2QnrhX4_Xe96$vLONnsKGhrdKhoL); zu#e{Xdj$23=PM?l&7L0}Pd@@N6?a;}_ zC*0N143MFmY|Z)ePMCKv#7yv-xLmFiVsDXRq-*9YKXoihmTI=#P1vgYyg2kup8-%? z*ejDicZUaaM2buuK{^O_Q(yzcKCH$cumTJqU?sVtw>?q^OX%yk9+TNQ75O_P_VxYS zE`S1&iuRp9lH8uu1z6n4c5DN~PLAQP8d~UwZPL>R3M9T@g*&jDHI${gp!ab-oIQCD zXjnE;GgoPcG$?2WDwe%WpbR~NhtDV}5y`U6QeiQAtw0(5SQ{W))XKF9G+x9lBXc2T zK5q2JHxhLiDJ>IgKl0YJy8weFc4#gcf(o2-fE|fWxDtKW9LQHUlMev-lDP^?FaS*7 z8Ev{IIubz`@L-lJE^koj%T-cfG&Hy$hrMbZ(4FmN?^*ZiV>Z9M+68rh!}-^76Kl?+ zwY~pg0qQtrcA(`7yam5zSb`D+O zKw8811BxN2h={G!e!+TTUg`mM-EJ*~2DCx#gx_^GzWN;eh{?f&-Y zOUck_w+pNsI`xj9#c&{!i((3W4j4sbMofjtREv9pRUi!sUccGTj(!G=o|$U%_Fs)6 z7Y_YQTjREiJwI^f&YUGW%HJ~1ZPme`r^ni@%!_8A&9Xdc5P%RR7XAv;#=dmrr=C;_ zmzQ5c;8SkSJl&45Tveo6byy&N6>6UbZVDtr!jh7r^78inM^RIgUD6ZKWQ+u{R!%M=!$9NtkqHE|4uT?4{GWb}&oT%|hk_Q*R@#Fn`9d*ySi5|k+OCHZ|xgpk(Dmq-^ z-(3^nJN^~{S?zB|TVz1si>hLiPSxlUho>}YL$lx7n~y8ww|as!6FS!THgzwq7dZc? zbP`;>2CmmvmEPaA0o(cEdaP8C7bJUv+R`6_Osar{_{yWM)C?-KRekLT!YJ$Gy|%)= zu$PE%5N^)OY~xpEp9KYKmp#b_Ae~w-cr!5`x;Y4FXoLcW?s#OIkWiJ`@Ny2yI9Hht z;2TiW0~9>5%wY@Xz16QLn3oJP7Oum=^=n->I60v3^@@ul+@-M41+b0pL>JZnHo5uU{H}2=rjY+R76TEA~Ch^;_OS= zrKBW8x!yRD=}J#^&I+27XGb$OK8`z*g94KPKgT4oJKqp^m1rFdT#ANLM-AHy3`pc^ zw57O2i5RjCD28s2Lz?nm!%hFP4ux)Xjnj0IP#j~c1%^ewl3u3Ioc24svO*^08kj9T4_LbhgIuLPCo_C zNRu9~y@ez=`S$l%Hd6C|dOuiLEK5r0{ z$O0x1Y?|F#*kL9kCkOi%%tK@T)mI(QVfh5605)vD)Mt3d%%ZBWCx;7YOjmL>UfuI+=R?!lx08%#8?yWtj(FTTuu?qe$3LZbu+qG}o-IaO z$BWpF9pXBa(*Jf1a4{3i|MRk+4~R+skUsx0wm_W+uf$)Ehutuw&mFj7QOnR;@x9u_ zIDjjxBndR2S5vX(j(jJ1no5#o0@4FEmi;sv7fZ!@|ex~hQPutygSAM z>E9zBK+oF3|CoaAP%!y-ugW|;;&=hmHKs8G^o|q`%n<62y4M(hz7zli56GOy1r|Vr zHcIATYxYPv@VYa@GCYb`t96pM#%xRb{`*J&eFk5yFTkCXuL1Qw*))dAdZ|}35AL8f<^_|LbW&r0)Y9!R zhMk-qOr&u|!=)bZQ-9;|2P+jI5g3vj_w>&F-%F^KG7RY1UaY#n0&#l@CLnJJy%*85G?jQj zg#xNRI${leK&BeZc#oCLm*?&zaq)g1-?fP(I^cD8_;}e~-;w-ZUXI=23{9tmM$0Dm zi=i=TgvF85*^uO_2u3%S@7bi5Z~yg?B{#A<2nuLc7vVg4$N>NqM%`GXLCFxZ-{%!0 zTKWbrr3q?l9gtV|tCXXAgV7Hknk3pM?7&`O@px8gKo96)?4W$DyPKSJP^EXShlIV( zj+s-j-gbWp6UdM-zyX-!??*dH^KQhYyM|K$1N#9=r`{-fN{OATn#YB+oC9sRj$mtP z(q*sO<>|f+keX^Xpdh=`6z8WxS*O;(;G11Xn*y2--~b^oWCWCvRv1ga@Duo1+qWk{ zFdQx^ZX^p%0v47??2G@|I92M*0V))Z0a6#WMpvY_yg}U3J}n;4l+O!P>rUXjgZLbg z0gst*2NVnCjWu3w4b%gl75RLY2m&MLDY5&~tVY z0~v--NhE2-h)(R1-M%$6`_wG3xA|TV+}nuN&ArX7FB8a%R!{f2OD3g02tKhD)VyCf zb_HMdy(j%^Q?KTN>{@qYj;BWpEWnY}qv<*pSV44gZ(s_s()|Bch}CNxgMY^x&#+iQ zyuW_p0iyj{cOD9P3^Ir-5BGVQ&yPrWa(O z@x3xbF&B?(>E^JOX`m?5CJ5Q{>)WA{27 zpu|f*qc(uTs@yv}{=&&7Ul9+?0$q2if>Eyd&;3AIm@Hs^lr;Av`qnB;R8zrwfjj|* zTa;y-$$W)oU6;azRZwtn+vFqyajKJw>xYZsNra1w;ec@B#ty;Ah?S$O^OVVLA95=& zPi{lqG)pkj8*V29mCiwj76>z)onxQJ_4Yr+)Cy(u_2q>2H#E<=~jI0rgsA<74Cs zg^1&nv*nK4yb$$9m+%LT9B|gFV~^1Zc*TK!#emmve)jb&&mqdSaOMVw4%z_NZX9Km zc$sWp$;2-Ci~XbH4by_ILkzb#%Q+ z5lMevXz+neS*dXN&Z)!8`&9iynKy)zjrfjD!W=?~Rxf&w!>j~vs@ zYz;8BsaW@PBcw1yKrJ08#MCu4ZwHGFm)oVk>LERxh(x0J+u0FwHD;VFfuEovN4eZF zDOzzBl=I@zC#anhEfYWau)D?1YW*c81szl_iDqA3T*(v~^Mms&irUN3IHf7EL4ivb zXb|9Q6fZOcXJ^9$fB+al$?prQOZ85x1A*dZFN*Wt1 zyM5Z^rKzd;TbM|IHuz^>fnhfB6VH=VXtP(i;|Ya+Ia@ZWd1=7{*hP%d% z(<{)c&I4&%K(A`WXajNySb^+LLMVweKoh`b!GVhK zGeRCop?0IZdLh5^w`G7a_f-)$Uf+q%C_923zP??(0Ps7km*9w+uj{kZvFpGr_@zH7TY)QtMHSU1fHl&osrLyL&<@q+}Oe+nUQZ50^R0-+%PpE=A!X2EwZdb0VvK~pQb z`krU_FNGoFb?^2Kj9k*B1+eH|y}_qoB>`HNGQHKac~wjhyE z10jrzg%-V5LKsN-dE2?B9+QQiFnziPGsS1uQTL%VDl=dcIbkWKfZxAy&5>~;vH{@V ze#RYuHd{Upa&rG@2)Y(YzieIs0u&CvSb4_ zE=T5SR`@rU-$TavNWuN9bKIeQ%eMpL3Y5b<@o{&puvqZcOrAaHN-R=dXUzg^8i;Zk z^p;FPK?%8nFY>*|t5>f?mm!ZIJ^D^_&Zjny1L4j>uv(DsY-!0GGVx7Rn$0t?M5)mOI2~c_x`9BT7dr`o6{SNzYjF*vq z{G9+uO5*mey?RGni9GKh_S7kiHm&~x$PwZ`0O1zSRs|#%XxO={foM)vozEM%D7gwn z41nJ2p4hF}W&WmfTfc+_#l8}W9=1y)x5^I)e%YMFf%)M(Owu9@E9M@7pyodh%}k zymW2a;EZN;At_iGHD(g(vi~_+a&C5z1ai zP$K#Qaina7cU&9}*!Ah=#vMQphv>g)aYR2fjt7h-jj8F0HjXF%`3v(2ek}#lI>&7B zK15=$Uw7v{u+oD%zCaUu~7ftrd69)(d;mQ=Uc9Mff`CFW**@CT@dLUUZ=?FX)F)MWeQ2Rj^A zm8zngPTqGq1>NL3pK3D+`S*%f@ko%6wnNg%R}CCqc5@@i1tKX0r+DfFTn^T<8`~$Z z!ikOrgC|Tf=8?6MwJM5n-5kjh-!5MY)DmWUIInJ1Y9RTk_;hg0FD=)XCUsBy8o}yt zX9eRhATT3v^GQ!tvfIVM%5F$dGiFh6pCr4Z9VIP=L8qL!i~d$7{2EEC04}R8A}wtq zsbzvOnr*Qwyt2n?q6EagR@CV?^ZGaa63dBNd8Q(Vq^hHv0yA7j^C#zFFKkA`a}*0| zvpm?$SS;6*X;P2YnyeB|hi*}~CYeyeamA^WG(HaYFQ2}ZT3Xqiesf(JQJcm6Xs^yoe8iW@le8%6#M54;1bp9TFe>IYt(^$&&K@`}b|4g31Wj0#3DaC$){N1$?Qk zqCN9@s6}#|t677;CJC!wr@GPXDb}LaK4h-!(qhIBUa8TNA%@=ON)C0;3qgvTBPJ*a zd7|n1Y5h4d9AA#zO5i5O&_o~lQ;qp_6fUco(kf!I-qAP@2`$cW6`SjzvtJv`xY>L)3dRvMVUEe4EA>1z{H<} z&;5p8wbxh_Ppc)Il^f4}>*B9qTSxIxstJ@*fZcEl(0>=Y;k{kpq0!J{1_Re^JBt5Zkp54Afa0gmCnnrOKIbK zkOoao$DM^ie+-_qhV;)tK~$)FlTkTFl5L_zO%(V3>1$CGEi?fV(@)Y5^@HSZ69x>= z_U6UCM-?qm?jSL;n!6ss;|av&q)}=%7(Vc50vW?>>Yn#^w?fVyVTv)r#aoZ-oc7WA zz{Y%@O0has{*WG-x#ygm8b8p2yecd7zj)ef3lMLIhU$fbhCa64Q1 zYkM*_1Q&a_5U<;7$1`G%-T*5)qw4^(1a_C#cF!Bd7P8<%vPcaFy!`UcLk#w-BnvgG zBI52E!sLwPES(7|RR_Lit~4~Qn((OJb6_h`IxkgLJDc3CG}Lby%=kU}ahl5cfX#(8 zVI#Ikw}&zOup`!?(B%}Eddg}Ju>U=Y(7aLEVK;ry85}~4*Oj!*%eS4bbGclbD^FR~ z*qo@@TpvS|>iN^D%Ts&yx?uXYNBHXGcjxk&S^mu0>T3IfRamJ*o0uWDy;dcQRtce@ z9s6K&oqyaq@2{`{s~;AllwdNsao=;7yB$S*<@==dmoXFmvuDpfP~nFT6DY5EWUxn^ zEYpKieaBpF77;HmE}UhNh0|pzOO>fJv$G?=eGBQ(jVD zziD0542#e8JUD*bEp+E}W@$CEbFeFT(9yG_cFev)6-QHzD}3rB>jpY4cqal=0qN zqOSgW`R+utB=)38f7PPwouf7US@mr^$w{*Su`J!uM1zLrQ$|C&8_cY124RdB?cX*r zz7MlOe-}Iv01fXmvVH37?1|xBE6^o#i^}&tJmS>&TZpeKp>PRtSdP)ImlvDBg50#= zR!8wkpnG^lLzr@r=~Wf9yKIaT>3p`$52ad*kxt0tUz;Zx)6%3h&vMY6PPnqILbLgsL|KR1Z01Mc~Fjs++x*5-x zik6-wSCaqJ9qP_(6gFMIne?6M@$+WkfNtxmn>A)|gauoo{bv)f8IMJh7O%L+>{I+X z|Myag=;+zm+XO*@ZPvXi-?-dmlArTSQ z9hB$GlvR8CRlmd=1K0Jp|I^c@A9{l^mNS@DzFg#Vcy^?J)IJPBeK8jundo=rl9R!Z#EOds+qV0s)%b>e zT>|@j%E;@kW>~}w;xWZ04$W)7AKR{M1BZ~l_fj3mH$Xu=Ie*JkgXX!QnJbUZDW?vb z09d6J6@}$%!gfCMzrhL7I~aOZQ>*Yx6%jeHb)oT<(6yI@^B4T69^Y*%#dv#PcjZz> z|K){D{Lay8t^0x_egZDs418iQ(WxO-<3{-Ko?@YGsaGitX%X2 zYq4vH-{W#6I#6NMhD?e;(kAdLFIN?$pyP8~4uV-6Z+N)UnnmG#y)6=5u{vz_Yv~(J z_WMChS|x`CT4hP_%I-en*kqQ}`xha}=0p~zgF0JnVm$iSh6ZgG6s=2Q2?GQ2Ab~mW z1^d8F>o#+FA>6Xz>L>Tt0WO|<#0lr!@`??ow8U2Tk$hfSDJjgMtH*dDsb_CyS&B@p zIlqmW`lIi(`PeQV z-`q#o9l_cU{6P?p3PFD5YmKN_P{|)|Y#n`5a{cd{U!ai>_`pv&4w_a*#>EMK{N)Fa z_IcWk=p`#_z}%cU-XpBPpE9=iB-k5#p7A_L+iRWgHc2U+T0f5JGe%DTtY(NM8m_$qhx(U!>T%WMaAbcLEOeAJ7k=k^$RZl?ou!6 z?_JPwb0=33K{0m@x$*Eei)gKw@MWkB$fM;{O$(hg|31#rls^k zypN07Jr7&jHXq^PRj=&sYE*`%lh@SOa|$gOGb#4HK!sN?z zKV7d5()_S=yCIz%Yn{~g>I4a#>ojCqiHi!K?v7zywWwK`ySaGyiXzVvOX~=n#~hWK z3Xo~)Ar1Hket+J(ah5(r8a&NBITaPjT7UHkl1g9_Y4e1|YY(TVoAm`-=dwSD1C3JC zqKX;4H8zSAsZ2J|hj`7g@%*^o#Z{1W&&P1-y?LdREQ96OP>BAhmQ06|e#vxrly2wU zJv%ExXmQk^hXEHG7VBYzfU8cSd3kxI*GU*S9f_Uzr?Ae!BG|pv7@^LOKcP_Q-tmJ6 z4}|PfdiPeb!Ey5H#Y2a7zofZlcqD{`9)Z)Ooc(y(6XqUCtZ6#C#ZP-!vOi7E0m6`V z+X~XZ*BA9)A-?!y$fLU2iR+iJ2p{p4sy~68ijQqA*c(yf#yzQ=UEyC3E?Qk$;=j)Q zMR3U;`8va&Wv@#HOT5y8TG1%7oM^Sd1pN>xqkvm#@afUhT|G8;%4im%9Fy&)04_Nx zaEjG~52B*4<{Yqp;j{k2;sUyh%=l4}hQ^=?Y#^kJB|RAs*^>OSK00K!nmrW1ZC?BE z&l?{$W&*KGgd!7u2777ewXogG%?`Z}A3pGbFj1d}hbJZa^Vi&HWN?T?US8fCGBUj~ zWofFlOstSMw6w(&4S$;NNxH+=zx_2YHy5#p5*q`@BlH{u12yCnSlHO?_6vg~2vJ$w zICPpq^bI~gY)Dj8RN|?350ATRE&ui6Vb`hC*Dr5Hq|$2I5xqNP**(Ov@(D(F9IlJB zE1ylUeCd$lmL<9F)Vk~a+P-pQaT~O5273J$(u~P%q{AB`h@D?p#uc7-5YemcVKzLZ zQ;rZ#UIHm8sel1DO1BSopB5-cLeE~X$GVa&dAikD zFP)jK`<40$ibOiQjVgM6FQmsT&r;x>;yIw_CE?0AJ+l58w&%@>NHLtId+WNgNCD*w*u; k*MfO zv}bYLoDC-`3@u8)lV?Z$|6Q%q1CvF02K7*44_ka?LWr>k*S&VM!Q$M|bjB?fLK@4> z8XzD@kHK|BO_=do`8LN7D+braWsL*XUssVv&3eFtw@DlCsxX+^^Ge3Ofy>!rm(;Ld zt@YmH;5i5@3l*tIlOQig?;CD!fAzmz$Wa=4~wHUUPKac+4zLwc)&J`U>ZT5STK0Q8>&MMqvzKA*Eo zovaXt?}#yp(mVATmadWzG&u!$sHgA*ZLDO`RyJo#?iDUyq7$KJX6!4@z^j@Lpy;4z zdD;06E%K#!?(l<+wgU<#{L0!8?UW~kSM6rr^kRd@^9F&i!I0@%0?vIu zh=46kpv9e@;kV9_db}z6Z7=9XuFupSIGbI*_J=Y?4c?t%pU-ngj%e+LeA%@};jVWw zsT!pAEdtLu><-$!(fYR$BwwC#50CR(QR;5t*%g%c8V6Gt<9&url}sO)f0_Ih3*IuN zBV6#beR-OZ58n*2=O82z3Cr&Hc(D8WdW_>@jr~-N2VDNV*&mbr;E@ar z;^<>Eie1h7N=WCJqrE&Th>C*!8GAN1=UyE(Z7|;1%sR}_2ZV^I1^<7HjX^jSBc?cNu@aG+z>=qFtwR5{pQXd*&74` zZ<5&tK)`Ah0oKIfNRvuc4u>t@baLheIV?XQ&8usa+)Bhuq~N62c0i|-8pI!SU5~r~ zVcbNW { expect(result.stdout).toContain(secretEnv) expect(result.status).toBe(0) }) + + it('Should run snapshot tests', async () => { + const secretEnv = uuid.v4() + const result = await runChecklyCli({ + args: ['test', '-e', `SECRET_ENV=${secretEnv}`, '--verbose'], + apiKey: config.get('apiKey'), + accountId: config.get('accountId'), + directory: path.join(__dirname, 'fixtures', 'snapshot-project'), + env: { PROJECT_LOGICAL_ID: `snapshot-project-${uuid.v4()}` }, + }) + expect(result.stdout).toContain(secretEnv) + expect(result.status).toBe(0) + }) + + it('Should create snapshots when using --update-snapshots', async () => { + const snapshotDir = path.join(__dirname, 'fixtures', 'snapshot-project-missing-snapshots', 'snapshot-test.spec.ts-snapshots') + try { + const result = await runChecklyCli({ + args: ['test', '--update-snapshots'], + apiKey: config.get('apiKey'), + accountId: config.get('accountId'), + directory: path.join(__dirname, 'fixtures', 'snapshot-project-missing-snapshots'), + env: { PROJECT_LOGICAL_ID: `snapshot-project-${uuid.v4()}` }, + }) + expect(result.status).toBe(0) + expect(fs.readdirSync(snapshotDir)).toEqual([ + 'Danube-Snapshot-Test-1-chromium-linux.png', + ]) + } finally { + // Clean up the snapshots for future runs + fs.rmSync(snapshotDir, { recursive: true }) + } + }) }) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3ada0bec..cc1c9663 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -131,6 +131,10 @@ "testMatch": [ "/e2e/__tests__/**/*.spec.ts" ], + "testPathIgnorePatterns": [ + "/node_modules/", + "/e2e/__tests__/fixtures/" + ], "preset": "ts-jest", "testEnvironment": "node" } From 8a18ddcf4bdb65ad7d6334b9b7c019ec181ad1fc Mon Sep 17 00:00:00 2001 From: ferrandiaz Date: Wed, 22 Nov 2023 16:52:25 +0100 Subject: [PATCH 26/26] feat: add ability to filter by group tag (#893) --- packages/cli/src/commands/test.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index 36f323ee..bff0c871 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -16,7 +16,7 @@ import { loadChecklyConfig } from '../services/checkly-config-loader' import { filterByFileNamePattern, filterByCheckNamePattern, filterByTags } from '../services/test-filters' import type { Runtime } from '../rest/runtimes' import { AuthCommand } from './authCommand' -import { BrowserCheck, Check, HeartbeatCheck, Session } from '../constructs' +import { BrowserCheck, Check, HeartbeatCheck, Project, Session } from '../constructs' import type { Region } from '..' import { splitConfigFilePath, getGitInformation, getCiInformation, getEnvs } from '../services/util' import { createReporters, ReporterType } from '../reporters/reporter' @@ -181,7 +181,13 @@ export default class Test extends AuthCommand { return filterByCheckNamePattern(grep, check.name) }) .filter(([, check]) => { - return filterByTags(targetTags?.map((tags: string) => tags.split(',')) ?? [], check.tags) + const tags = check.tags ?? [] + const checkGroup = this.getCheckGroup(project, check) + if (checkGroup) { + const checkGroupTags = checkGroup.tags ?? [] + tags.concat(checkGroupTags) + } + return filterByTags(targetTags?.map((tags: string) => tags.split(',')) ?? [], tags) }) .map(([key, check]) => { check.logicalId = key @@ -346,4 +352,12 @@ export default class Test extends AuthCommand { } } } + + private getCheckGroup (project: Project, check: Check) { + if (!check.groupId) { + return + } + const ref = check.groupId.ref.toString() + return project.data['check-group'][ref] + } }