@speedkit/cli 2.23.1 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # [2.24.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.23.2...v2.24.0) (2024-04-17)
2
+
3
+
4
+ ### Features
5
+
6
+ * **deploy:** add more detailed browserContext for vm ([4a32740](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/4a32740947c14b9c5d083ab022d3bcd6aa7d9113))
7
+ * **deploy:** catch error on splitTestIdCheck ([34844bd](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/34844bddf2ecc165ebc62e38ddca43be8c8763f2))
8
+ * **deploy:** implement splitChangeDetection ([ab0a56b](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/ab0a56bf43f8d5e20fd9d0a6669ae2a04180e232))
9
+
10
+ ## [2.23.2](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.23.1...v2.23.2) (2024-04-16)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **onboarding:** disable parameter dashboard temporarily ([c71bd4f](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/c71bd4faae3a38c7884d5bfcf20a48e7be2ccbe1))
16
+
1
17
  ## [2.23.1](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.23.0...v2.23.1) (2024-04-12)
2
18
 
3
19
 
package/README.md CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @speedkit/cli
21
21
  $ sk COMMAND
22
22
  running command...
23
23
  $ sk (--version)
24
- @speedkit/cli/2.23.1 linux-x64 node-v20.12.2
24
+ @speedkit/cli/2.24.0 linux-x64 node-v20.12.2
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -1,5 +1,6 @@
1
1
  import { table } from "@oclif/core/lib/cli-ux/styled/table";
2
2
  import { Options } from "cli-progress";
3
+ import chalk from "chalk";
3
4
  export declare class CliContext {
4
5
  readonly quiet: boolean;
5
6
  constructor(quiet?: boolean);
@@ -10,6 +11,7 @@ export declare enum CliActionStatus {
10
11
  SKIPPED = "skipped"
11
12
  }
12
13
  export interface CliServiceInterface {
14
+ style: chalk.Chalk;
13
15
  code(code: string, buffered?: boolean): void;
14
16
  codeStyle(code: string): string;
15
17
  comment(message: string, buffered?: boolean): void;
@@ -4,17 +4,7 @@ import { Options } from "cli-progress";
4
4
  import { CliActionStatus, CliServiceInterface } from "./cli-service-model";
5
5
  export declare class CliService implements CliServiceInterface {
6
6
  private readonly quiet;
7
- style: chalk.Chalk & chalk.ChalkFunction & {
8
- supportsColor: false | chalk.ColorSupport;
9
- Level: chalk.Level;
10
- Color: ("white" | "black" | "blue" | "yellow" | "green" | "red" | "magenta" | "cyan" | "gray" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright" | "grey") | ("bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright" | "bgGrey");
11
- ForegroundColor: "white" | "black" | "blue" | "yellow" | "green" | "red" | "magenta" | "cyan" | "gray" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright" | "grey";
12
- BackgroundColor: "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright" | "bgGrey";
13
- Modifiers: "bold" | "hidden" | "underline" | "dim" | "italic" | "strikethrough" | "visible" | "reset" | "inverse";
14
- stderr: chalk.Chalk & {
15
- supportsColor: false | chalk.ColorSupport;
16
- };
17
- };
7
+ style: chalk.Chalk;
18
8
  private buffer;
19
9
  private progressBar?;
20
10
  constructor(quiet?: boolean);
@@ -115,7 +115,9 @@ class CliService {
115
115
  this.progressBar.stop();
116
116
  }
117
117
  table(data, columns, options) {
118
+ this.spacer();
118
119
  core_1.ux.table(data, columns, options);
120
+ this.spacer();
119
121
  }
120
122
  updateProgress(current, payload) {
121
123
  if (this.quiet) {
@@ -1,6 +1,6 @@
1
1
  import { CliService } from "../../../cli";
2
2
  import ConfigApiService from "../../../config-api/config-api-service";
3
- import PostDeployCheckInterface from "../../interface/post-deploy-check-interface";
3
+ import { PostDeployCheckInterface } from "../../deploy-service-model";
4
4
  export default class RevalidatedModuleCheck implements PostDeployCheckInterface {
5
5
  private readonly configApi;
6
6
  private cli;
@@ -1,6 +1,6 @@
1
1
  import { CliService } from "../../../cli";
2
- import PostDeployCheckInterface from "../../interface/post-deploy-check-interface";
3
2
  import { FileListInterface } from "../../../integration-api/integration-api-model";
3
+ import { PostDeployCheckInterface } from "../../deploy-service-model";
4
4
  export default class SaveFilesCheck implements PostDeployCheckInterface {
5
5
  private fileList;
6
6
  private readonly cli;
@@ -0,0 +1,18 @@
1
+ import { FileListInterface } from "../../../integration-api/integration-api-model";
2
+ import { CliServiceInterface } from "../../../cli";
3
+ import ConfigApiService from "../../../config-api/config-api-service";
4
+ import CustomerConfig from "../../../integration-api/struct/customer-config";
5
+ export declare class SplitChangeCheck {
6
+ private cli;
7
+ private fileList;
8
+ private configApi;
9
+ private customerConfig;
10
+ private configName;
11
+ private isProduction;
12
+ constructor(cli: CliServiceInterface, fileList: FileListInterface, configApi: ConfigApiService, customerConfig: CustomerConfig, configName: string, isProduction: boolean);
13
+ check(): Promise<void>;
14
+ private checkSplitsForEachOrigin;
15
+ private evaluateLocalAndRemoteSpeedKit;
16
+ private getActiveInstallResource;
17
+ private buildBrowserMock;
18
+ }
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SplitChangeCheck = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const cli_1 = require("../../../cli");
6
+ const files_1 = require("../../../../models/files");
7
+ const vm = tslib_1.__importStar(require("node:vm"));
8
+ const safe_1 = require("../../../../helpers/safe");
9
+ class SplitChangeCheck {
10
+ cli;
11
+ fileList;
12
+ configApi;
13
+ customerConfig;
14
+ configName;
15
+ isProduction;
16
+ constructor(cli, fileList, configApi, customerConfig, configName, isProduction) {
17
+ this.cli = cli;
18
+ this.fileList = fileList;
19
+ this.configApi = configApi;
20
+ this.customerConfig = customerConfig;
21
+ this.configName = configName;
22
+ this.isProduction = isProduction;
23
+ }
24
+ async check() {
25
+ this.cli.startAction("check SplitTestId");
26
+ if (
27
+ // do not check for others then production
28
+ !this.isProduction ||
29
+ // check if we deploy a speedKitConfigFile
30
+ !this.fileList.hasFile(files_1.INTEGRATION_FILES.CONFIG.SPEED_KIT) ||
31
+ // check if we skipped the speedKitConfigFile
32
+ this.fileList
33
+ .getByName(files_1.INTEGRATION_FILES.CONFIG.SPEED_KIT)
34
+ .shouldSkipFile()) {
35
+ this.cli.endAction(cli_1.CliActionStatus.SKIPPED);
36
+ return;
37
+ }
38
+ const speedKitConfigFile = this.fileList.getByName(files_1.INTEGRATION_FILES.CONFIG.SPEED_KIT);
39
+ const activeInstallResource = await this.getActiveInstallResource();
40
+ const activeSpeedKitConfig = activeInstallResource[speedKitConfigFile.remoteKey];
41
+ const changesResponse = (0, safe_1.safe)(() => {
42
+ return this.checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig);
43
+ });
44
+ if (changesResponse.success === false) {
45
+ this.cli.endAction(cli_1.CliActionStatus.FAILED);
46
+ this.cli.writeError(`Unable to evaluate changes: ${changesResponse.error}`);
47
+ if (changesResponse.errorObj) {
48
+ console.error(changesResponse.errorObj);
49
+ }
50
+ return;
51
+ }
52
+ const changes = changesResponse.data;
53
+ this.cli.endAction(cli_1.CliActionStatus.COMPLETED);
54
+ if (changes.length === 0) {
55
+ return;
56
+ }
57
+ this.cli.table(changes, { origin: {}, type: {}, old: {}, new: {} });
58
+ const result = await this.cli.confirm("Split change detected. Do you want to continue?", false);
59
+ if (!result) {
60
+ this.cli.exit(0);
61
+ }
62
+ }
63
+ checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig) {
64
+ const changes = [];
65
+ for (const origin of this.customerConfig.origins) {
66
+ const { localSpeedKit, remoteSpeedKit } = this.evaluateLocalAndRemoteSpeedKit(origin, speedKitConfigFile, activeSpeedKitConfig);
67
+ // check for split change
68
+ if (localSpeedKit.split !== remoteSpeedKit.split) {
69
+ changes.push({
70
+ origin,
71
+ type: "split",
72
+ new: this.cli.style.green(localSpeedKit.split),
73
+ old: remoteSpeedKit.split,
74
+ });
75
+ }
76
+ // check for splitId change
77
+ if (localSpeedKit.splitTestId !== remoteSpeedKit.splitTestId) {
78
+ changes.push({
79
+ origin,
80
+ type: "splitTestId",
81
+ new: this.cli.style.green(localSpeedKit.splitTestId),
82
+ old: remoteSpeedKit.splitTestId,
83
+ });
84
+ }
85
+ // check for split change without splitId change
86
+ if (localSpeedKit.split !== remoteSpeedKit.split &&
87
+ localSpeedKit.splitTestId === remoteSpeedKit.splitTestId) {
88
+ changes.push({
89
+ origin,
90
+ type: this.cli.style.yellow("split changed\nwithout id change"),
91
+ new: `${localSpeedKit.split}\n${this.cli.style.red(localSpeedKit.splitTestId)}`,
92
+ old: `${remoteSpeedKit.split}\n${remoteSpeedKit.splitTestId}`,
93
+ });
94
+ }
95
+ // check for splitId change without split change
96
+ if (localSpeedKit.split === remoteSpeedKit.split &&
97
+ localSpeedKit.splitTestId !== remoteSpeedKit.splitTestId) {
98
+ changes.push({
99
+ origin,
100
+ type: this.cli.style.yellow("id changed\nwithout split change"),
101
+ new: `${this.cli.style.red(localSpeedKit.split)}\n${localSpeedKit.splitTestId}`,
102
+ old: `${remoteSpeedKit.split}\n${remoteSpeedKit.splitTestId}`,
103
+ });
104
+ }
105
+ // check for invalid split value
106
+ if (localSpeedKit.split > 1 || localSpeedKit.split < 0) {
107
+ changes.push({
108
+ origin,
109
+ type: this.cli.style.red("invalid"),
110
+ new: this.cli.style.red(localSpeedKit.split),
111
+ old: remoteSpeedKit.split,
112
+ });
113
+ }
114
+ }
115
+ return changes;
116
+ }
117
+ evaluateLocalAndRemoteSpeedKit(origin, speedKitConfigFile, activeSpeedKitConfig) {
118
+ const localBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
119
+ vm.runInNewContext(speedKitConfigFile.getContent(), localBrowserContext, {
120
+ filename: speedKitConfigFile.path,
121
+ displayErrors: true,
122
+ });
123
+ const remoteBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
124
+ vm.runInNewContext(activeSpeedKitConfig, remoteBrowserContext, {
125
+ displayErrors: true,
126
+ });
127
+ const localSpeedKit = localBrowserContext.window.speedKit;
128
+ const remoteSpeedKit = remoteBrowserContext.window.speedKit;
129
+ return { localSpeedKit, remoteSpeedKit };
130
+ }
131
+ async getActiveInstallResource() {
132
+ const activeInstallResourceResponse = await (0, safe_1.safe)(this.configApi.getActiveInstall(this.configName));
133
+ if (activeInstallResourceResponse.success !== true) {
134
+ this.cli.endAction(cli_1.CliActionStatus.FAILED);
135
+ this.cli.writeError("Could not load active installResource");
136
+ this.cli.exit(1);
137
+ return;
138
+ }
139
+ return activeInstallResourceResponse.data;
140
+ }
141
+ buildBrowserMock(origin, app) {
142
+ const originUrl = new URL(origin);
143
+ const window = {
144
+ location: originUrl,
145
+ APP: app,
146
+ speedKit: {
147
+ split: undefined,
148
+ splitTestId: undefined,
149
+ },
150
+ serviceWorkerUrl: "",
151
+ screen: { width: 0, height: 0 },
152
+ devicePixelRatio: 1,
153
+ top: {},
154
+ };
155
+ window.top = window;
156
+ const document = {
157
+ cookie: "",
158
+ querySelector: () => { },
159
+ querySelectorAll: () => { },
160
+ createElement: () => { },
161
+ addEventListener: () => { },
162
+ head: {},
163
+ body: {},
164
+ };
165
+ document.head = document;
166
+ document.body = document;
167
+ return {
168
+ ...window,
169
+ window,
170
+ document,
171
+ navigator: { userAgent: "" },
172
+ localStorage: { getItem: () => { }, setItem: () => { } },
173
+ JSON: JSON,
174
+ decodeURIComponent: decodeURIComponent,
175
+ };
176
+ }
177
+ }
178
+ exports.SplitChangeCheck = SplitChangeCheck;
@@ -4,6 +4,7 @@ export default class DeployContext {
4
4
  readonly slackToken?: string;
5
5
  readonly dryRun: boolean;
6
6
  readonly artifacts?: string[];
7
+ readonly isProduction: boolean;
7
8
  constructor(customerPath: string, configName?: string, slackToken?: string, artifacts?: string, dryRun?: boolean);
8
9
  hasArtifacts(): boolean;
9
10
  }
@@ -11,12 +11,14 @@ class DeployContext {
11
11
  slackToken;
12
12
  dryRun;
13
13
  artifacts;
14
+ isProduction;
14
15
  constructor(customerPath, configName = DEFAULT_CONFIG_NAME, slackToken, artifacts, dryRun = false) {
15
16
  this.customerPath = customerPath;
16
17
  this.configName = configName;
17
18
  this.slackToken = slackToken;
18
19
  this.dryRun = dryRun;
19
20
  this.artifacts = artifacts ? artifacts.split(ARTIFACTS_SEPARATOR) : [];
21
+ this.isProduction = configName.toLowerCase().includes("prod");
20
22
  }
21
23
  hasArtifacts() {
22
24
  return this.artifacts?.length > 0;
@@ -17,6 +17,7 @@ const deleted_file_check_1 = tslib_1.__importDefault(require("./handler/install-
17
17
  const module_handler_1 = tslib_1.__importDefault(require("./handler/module-handler"));
18
18
  const deployment_detection_modifier_1 = tslib_1.__importDefault(require("./handler/module-modifier/deployment-detection-modifier"));
19
19
  const server_config_handler_1 = tslib_1.__importDefault(require("./handler/server-config-handler"));
20
+ const split_change_check_1 = require("./checks/pre-deploy/split-change-check");
20
21
  class DeployServiceFactory {
21
22
  context;
22
23
  cliConfig;
@@ -48,7 +49,9 @@ class DeployServiceFactory {
48
49
  new deployment_detection_modifier_1.default(slackToken),
49
50
  ]),
50
51
  new server_config_handler_1.default(configApi, diffService, cliService),
51
- ], cliService, this.getPostDeployChecks(files, cliService, configApi, customer.app), this.context.dryRun);
52
+ ], cliService, [
53
+ new split_change_check_1.SplitChangeCheck(cliService, files, configApi, customer, configName, this.context.isProduction),
54
+ ], this.getPostDeployChecks(files, cliService, configApi, customer.app), this.context.dryRun);
52
55
  }
53
56
  getConfigApi(app) {
54
57
  const configApiContext = new config_api_context_1.default(app);
@@ -0,0 +1,12 @@
1
+ export interface PostDeployCheckInterface {
2
+ check(): Promise<void>;
3
+ }
4
+ export interface PreDeployCheckInterface {
5
+ check(): Promise<void>;
6
+ }
7
+ export type Change = {
8
+ origin: string;
9
+ type: string;
10
+ old: string;
11
+ new: string;
12
+ };
@@ -1,17 +1,18 @@
1
1
  import DeployHandlerInterface from "./interface/deploy-handler-interface";
2
2
  import { CliService } from "../cli";
3
- import PostDeployCheckInterface from "./interface/post-deploy-check-interface";
4
- import AbstractAsyncErrorHandlingService from "../error-handling/abstract/abstract-async-error-handling-service";
5
3
  import { FileListInterface } from "../integration-api/integration-api-model";
6
- export default class DeployService extends AbstractAsyncErrorHandlingService {
4
+ import { PostDeployCheckInterface, PreDeployCheckInterface } from "./deploy-service-model";
5
+ export default class DeployService {
7
6
  private readonly fileList;
8
7
  private readonly deployHandler;
9
8
  private readonly cli;
10
- private readonly dryRun;
9
+ private readonly preDeployChecks;
11
10
  private readonly postDeployChecks;
12
- constructor(fileList: FileListInterface, deployHandler: DeployHandlerInterface[], cli: CliService, postDeployChecks: PostDeployCheckInterface[], dryRun: boolean);
13
- _run(): Promise<void>;
11
+ private readonly dryRun;
12
+ constructor(fileList: FileListInterface, deployHandler: DeployHandlerInterface[], cli: CliService, preDeployChecks: PreDeployCheckInterface[], postDeployChecks: PostDeployCheckInterface[], dryRun: boolean);
13
+ run(): Promise<void>;
14
14
  private handleType;
15
15
  private uploadType;
16
+ private runPreDeployChecks;
16
17
  private runPostDeployChecks;
17
18
  }
@@ -1,22 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const abstract_async_error_handling_service_1 = tslib_1.__importDefault(require("../error-handling/abstract/abstract-async-error-handling-service"));
5
- class DeployService extends abstract_async_error_handling_service_1.default {
3
+ class DeployService {
6
4
  fileList;
7
5
  deployHandler;
8
6
  cli;
9
- dryRun = false;
7
+ preDeployChecks;
10
8
  postDeployChecks;
11
- constructor(fileList, deployHandler, cli, postDeployChecks, dryRun) {
12
- super();
9
+ dryRun;
10
+ constructor(fileList, deployHandler, cli, preDeployChecks, postDeployChecks, dryRun) {
13
11
  this.fileList = fileList;
14
12
  this.deployHandler = deployHandler;
15
13
  this.cli = cli;
14
+ this.preDeployChecks = preDeployChecks;
16
15
  this.postDeployChecks = postDeployChecks;
17
16
  this.dryRun = dryRun;
18
17
  }
19
- async _run() {
18
+ async run() {
20
19
  const types = this.fileList.getTypes();
21
20
  this.cli.spacer();
22
21
  this.cli.write(`App: "${this.fileList.getCustomerConfig().config.app}", Install: "${this.fileList.getCustomerConfig().config.name}"`);
@@ -25,6 +24,7 @@ class DeployService extends abstract_async_error_handling_service_1.default {
25
24
  for (const type of types) {
26
25
  await this.handleType(type);
27
26
  }
27
+ await this.runPreDeployChecks();
28
28
  if (this.dryRun) {
29
29
  this.cli.comment("dryRunFlag -d is set, skip upload");
30
30
  }
@@ -51,6 +51,11 @@ class DeployService extends abstract_async_error_handling_service_1.default {
51
51
  }
52
52
  }
53
53
  }
54
+ async runPreDeployChecks() {
55
+ for (const preDeploy of this.preDeployChecks) {
56
+ await preDeploy.check();
57
+ }
58
+ }
54
59
  async runPostDeployChecks() {
55
60
  for (let iterator = 0; iterator < this.postDeployChecks.length; iterator++) {
56
61
  const check = this.postDeployChecks[iterator];
@@ -109,7 +109,12 @@ class OnboardingServiceFactory {
109
109
  const athenaClient = await this.getAthenaClient();
110
110
  if (athenaClient) {
111
111
  handlers.push(new dashboard_request_1.DashboardRequest(new dashboard_1.Dashboard(customerConfig, parameterQueryBuilder, athenaClient, requestDiffService, cache)));
112
- browserContext.startParameterDashboard = true;
112
+ /**
113
+ * todo check error `Cannot read properties of undefined (reading 'mainFrame')`
114
+ * until this is fixed, we need to deaktivate the parameter dashboard
115
+ * See ticket: https://www.notion.so/baqend/d3749c5796d647f5892aa5ebfa2fd75f?v=306744f97b924ce29f59a2a3779a045c&p=728aae13a9a94c10807f368143b7dabf&pm=s
116
+ **/
117
+ // browserContext.startParameterDashboard = true;
113
118
  }
114
119
  return new fetch_event_handler_1.FetchEventHandler(handlers, customerConfig, cli, configApi);
115
120
  }
@@ -660,5 +660,5 @@
660
660
  ]
661
661
  }
662
662
  },
663
- "version": "2.23.1"
663
+ "version": "2.24.0"
664
664
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "2.23.1",
4
+ "version": "2.24.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"
@@ -1,3 +0,0 @@
1
- export default interface PostDeployCheckInterface {
2
- check(): Promise<void>;
3
- }