@speedkit/cli 3.40.0 → 3.42.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,22 @@
1
+ # [3.42.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.41.0...v3.42.0) (2026-04-23)
2
+
3
+
4
+ ### Features
5
+
6
+ * **customer-config:** add auto feature detection ([81125e8](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/81125e81fe54c8dca01bf7e03c63449d18caab11))
7
+ * **customer-config:** add extended auto detection for shop system + existence of install.js ([c321ba9](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/c321ba98f0a6f89ff90eda9bcab694f64ead5069))
8
+ * **customer-config:** add fetching for most important page types ([948381d](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/948381da8b25aa1f9ead8d7661216bac8ca9ad88))
9
+ * **customer-config:** improve prompts ([cc229d5](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/cc229d56bed19d5b2127cb766837fd660e156a9b))
10
+ * make deployChecks compatible to speed-kit-plugins ([0756959](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/07569595412e533824fda03b2980b7b844415f9f))
11
+ * **wizard:** templates ([a198dde](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a198dde127b1eee36321007159b92feecc4b64bf))
12
+
13
+ # [3.41.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.40.0...v3.41.0) (2026-04-02)
14
+
15
+
16
+ ### Features
17
+
18
+ * **customer-config:** add shopify config plugin and remove custom a2c tracking event ([d96b6cc](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/d96b6cc76685cdc4881f89f5c5de4ac5e1a9e2e5))
19
+
1
20
  # [3.40.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.39.0...v3.40.0) (2026-04-01)
2
21
 
3
22
 
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/3.40.0 linux-x64 node-v20.20.2
24
+ @speedkit/cli/3.42.0 linux-x64 node-v20.20.2
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -0,0 +1,15 @@
1
+ import { InstallResourceFile } from "../services/integration-api";
2
+ export declare function evaluateLocalAndRemoteSpeedKit(app: string, origin: string, speedKitConfigFile: InstallResourceFile, activeSpeedKitConfig: string): Promise<{
3
+ localSpeedKit: {
4
+ split: any;
5
+ splitTestId: any;
6
+ detectDevice: () => void;
7
+ customVariation: any[];
8
+ };
9
+ remoteSpeedKit: {
10
+ split: any;
11
+ splitTestId: any;
12
+ detectDevice: () => void;
13
+ customVariation: any[];
14
+ };
15
+ }>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateLocalAndRemoteSpeedKit = evaluateLocalAndRemoteSpeedKit;
4
+ const tslib_1 = require("tslib");
5
+ const evaluate_sk_config_error_1 = require("../services/deploy/error/evaluate-sk-config-error");
6
+ const vm = tslib_1.__importStar(require("node:vm"));
7
+ const bundler_1 = require("../services/bundler");
8
+ async function evaluateLocalAndRemoteSpeedKit(app, origin, speedKitConfigFile, activeSpeedKitConfig) {
9
+ const localBrowserContext = buildBrowserMock(origin, app);
10
+ const localFileContent = safeFileContentWrapper(await bundle(speedKitConfigFile.getContent(), app));
11
+ vm.runInNewContext(localFileContent, localBrowserContext, {
12
+ filename: speedKitConfigFile.path,
13
+ displayErrors: false,
14
+ breakOnSigint: true,
15
+ });
16
+ const remoteBrowserContext = buildBrowserMock(origin, app);
17
+ const remoteFileContent = safeFileContentWrapper(await bundle(activeSpeedKitConfig, app));
18
+ vm.runInNewContext(remoteFileContent, remoteBrowserContext, {
19
+ displayErrors: false,
20
+ breakOnSigint: true,
21
+ });
22
+ if (localBrowserContext.vmError !== null) {
23
+ throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[config_Speedkit.js] ${localBrowserContext.vmError?.message}`);
24
+ }
25
+ if (remoteBrowserContext.vmError !== null) {
26
+ throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[remote_config] ${JSON.stringify(remoteBrowserContext.vmError?.message)}`);
27
+ }
28
+ const localSpeedKit = localBrowserContext.window.speedKit;
29
+ const remoteSpeedKit = remoteBrowserContext.window.speedKit;
30
+ return { localSpeedKit, remoteSpeedKit };
31
+ }
32
+ function buildBrowserMock(origin, app) {
33
+ const originUrl = new URL(origin);
34
+ const window = {
35
+ location: originUrl,
36
+ APP: app,
37
+ speedKit: {
38
+ split: undefined,
39
+ splitTestId: undefined,
40
+ detectDevice: () => { },
41
+ customVariation: [],
42
+ },
43
+ SpeedKit: {},
44
+ serviceWorkerUrl: "",
45
+ screen: { width: 0, height: 0 },
46
+ devicePixelRatio: 1,
47
+ setTimeout: (callBack, time) => setTimeout(callBack, time),
48
+ navigator: {
49
+ userAgent: "",
50
+ connection: {},
51
+ cookieEnabled: false,
52
+ languages: [],
53
+ online: true,
54
+ },
55
+ top: {},
56
+ };
57
+ window.top = window;
58
+ const document = {
59
+ cookie: "",
60
+ querySelector: () => { },
61
+ querySelectorAll: () => { },
62
+ createElement: () => { },
63
+ addEventListener: () => { },
64
+ head: {},
65
+ body: {},
66
+ };
67
+ document.head = document;
68
+ document.body = document;
69
+ return {
70
+ vmError: null,
71
+ ...window,
72
+ window,
73
+ document,
74
+ navigator: { userAgent: "" },
75
+ localStorage: {
76
+ getItem: () => { },
77
+ setItem: () => { },
78
+ },
79
+ JSON: JSON,
80
+ decodeURIComponent: decodeURIComponent,
81
+ URL: URL,
82
+ };
83
+ }
84
+ function safeFileContentWrapper(fileContent) {
85
+ return `
86
+ try{
87
+ ${fileContent}
88
+ } catch (error){
89
+ vmError = error
90
+ }
91
+ `;
92
+ }
93
+ async function bundle(content, app) {
94
+ const bundler = new bundler_1.BundleService();
95
+ return bundler.bundle({
96
+ basePaths: {
97
+ "speed-kit-config": "https://www.baqend.com/speed-kit-config/latest/",
98
+ },
99
+ entryPoints: { "config.js": content },
100
+ logLevel: "error",
101
+ minify: false,
102
+ define: {
103
+ APP: JSON.stringify(app),
104
+ },
105
+ });
106
+ }
@@ -0,0 +1,12 @@
1
+ export type DetectedShopSystem = "shopify" | "shopware" | "salesforce" | "oxid" | "plentymarkets" | "magento" | "woocommerce" | "bigcommerce" | "prestashop" | "jtl" | "commercetools" | "spryker" | "shoptet" | "ecforce" | "futureshop" | null;
2
+ export interface SiteAnalysisResult {
3
+ useGATracking: boolean;
4
+ withGoogleOptimize: boolean;
5
+ activateCfRocketLoaderPlugin: boolean;
6
+ includeServiceWorker: boolean;
7
+ removeLazyLoading: boolean;
8
+ hasSoftNavigations: boolean;
9
+ shopSystem: DetectedShopSystem;
10
+ hasInstallJs: boolean;
11
+ }
12
+ export declare function analyzeSite(host: string, log?: (message: string) => void): Promise<SiteAnalysisResult | null>;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeSite = analyzeSite;
4
+ const tslib_1 = require("tslib");
5
+ const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
6
+ const safe_js_1 = require("./safe.js");
7
+ const EMPTY_FLAGS = {
8
+ useGATracking: false,
9
+ withGoogleOptimize: false,
10
+ activateCfRocketLoaderPlugin: false,
11
+ includeServiceWorker: false,
12
+ removeLazyLoading: false,
13
+ hasSoftNavigations: false,
14
+ };
15
+ function detectFeatures(html, headers) {
16
+ return {
17
+ useGATracking: /dataLayer|gtm\.js|googletagmanager\.com|gtag\/js/.test(html),
18
+ withGoogleOptimize: /googleoptimize\.com|optimize\.google\.com/.test(html),
19
+ activateCfRocketLoaderPlugin: /data-cfasync|rocket-loader/.test(html) || "cf-ray" in headers,
20
+ includeServiceWorker: /navigator\.serviceWorker\.register|serviceWorker\.register/.test(html),
21
+ removeLazyLoading: /loading=["']lazy["']|data-src=|class=["'][^"']*lazyload|lazysizes/.test(html),
22
+ hasSoftNavigations: /__NEXT_DATA__|__NUXT__|vue-router|react-router|history\.pushState/.test(html),
23
+ };
24
+ }
25
+ function mergeFeatures(results) {
26
+ const keys = Object.keys(EMPTY_FLAGS);
27
+ const merged = { ...EMPTY_FLAGS };
28
+ for (const key of keys) {
29
+ merged[key] = results.some((r) => r[key]);
30
+ }
31
+ return merged;
32
+ }
33
+ function detectShopSystem(html) {
34
+ if (/Shopify\.theme|myshopify\.com|cdn\.shopify\.com/.test(html))
35
+ return "shopify";
36
+ if (/shopware|sw-buy-box|sw-product/.test(html))
37
+ return "shopware";
38
+ if (/demandware|dw\.ac|sfcc|SalesforceCommerceCloud/.test(html))
39
+ return "salesforce";
40
+ if (/oxid-esales|oxid\.php/.test(html))
41
+ return "oxid";
42
+ if (/plentymarkets|plenty-/.test(html))
43
+ return "plentymarkets";
44
+ if (/Magento|mage-error|magento\.com|\/static\/version/.test(html))
45
+ return "magento";
46
+ if (/woocommerce|wc-block|wp-content.*woo/.test(html))
47
+ return "woocommerce";
48
+ if (/BigCommerce|bigcommerce\.com|stencil-/.test(html))
49
+ return "bigcommerce";
50
+ if (/PrestaShop|prestashop|modules\/ps_/.test(html))
51
+ return "prestashop";
52
+ if (/jtl-shop|JTL-Shop|jtl-/.test(html))
53
+ return "jtl";
54
+ if (/commercetools/.test(html))
55
+ return "commercetools";
56
+ if (/spryker|Spryker/.test(html))
57
+ return "spryker";
58
+ if (/shoptet/.test(html))
59
+ return "shoptet";
60
+ if (/ecforce/.test(html))
61
+ return "ecforce";
62
+ if (/futureshop/.test(html))
63
+ return "futureshop";
64
+ return null;
65
+ }
66
+ function detectInstallJs(html) {
67
+ return /app\.baqend\.com\/v1\/speedkit\/install\.js/.test(html);
68
+ }
69
+ // Heuristics to find PLP/PDP links from page HTML
70
+ const PLP_PATTERN = /\/collections\/[^\s"#'?]+|\/category\/[^\s"#'?]+|\/kategorie\/[^\s"#'?]+|\/c\/[^\s"#'?]+/i;
71
+ const PDP_PATTERN = /\/products\/[^\s"#'?]+|\/product\/[^\s"#'?]+|\/produkt\/[^\s"#'?]+|\/p\/[^\s"#'?]+|\/dp\/[^\s"#'?]+/i;
72
+ function findUrl(html, host, pattern) {
73
+ const match = html.match(pattern);
74
+ if (!match)
75
+ return null;
76
+ const path = match[0];
77
+ // Skip paths that look like assets
78
+ if (/\.\w{2,4}$/.test(path))
79
+ return null;
80
+ return `https://${host}${path}`;
81
+ }
82
+ async function fetchPage(url, signal) {
83
+ const result = await (0, safe_js_1.safe)((0, node_fetch_1.default)(url, { signal: signal }));
84
+ if (!result.success)
85
+ return null;
86
+ const response = result.data;
87
+ if (response.status !== 200)
88
+ return null;
89
+ const html = await response.text();
90
+ const headers = Object.fromEntries([...response.headers.entries()].map(([k, v]) => [k.toLowerCase(), v]));
91
+ return { html, headers };
92
+ }
93
+ async function fetchAndAnalyze(host, log) {
94
+ const controller = new AbortController();
95
+ const timeout = setTimeout(() => controller.abort(), 10_000);
96
+ try {
97
+ const featureResults = [];
98
+ // 1. Fetch homepage (required)
99
+ log(`Fetching homepage: https://${host}`);
100
+ const homePage = await fetchPage(`https://${host}`, controller.signal);
101
+ if (!homePage)
102
+ return null;
103
+ featureResults.push(detectFeatures(homePage.html, homePage.headers));
104
+ // 2. Detect shop system from homepage
105
+ const shopSystem = detectShopSystem(homePage.html);
106
+ // 3. Check install.js presence in homepage HTML
107
+ const hasInstallJs = detectInstallJs(homePage.html);
108
+ // 4. Find PLP + PDP links from homepage
109
+ const plpUrl = findUrl(homePage.html, host, PLP_PATTERN);
110
+ let pdpUrl = findUrl(homePage.html, host, PDP_PATTERN);
111
+ // 5. Fetch PLP (best-effort)
112
+ let plpPage = null;
113
+ if (plpUrl) {
114
+ log(`Fetching PLP: ${plpUrl}`);
115
+ plpPage = await fetchPage(plpUrl, controller.signal);
116
+ if (plpPage) {
117
+ featureResults.push(detectFeatures(plpPage.html, plpPage.headers));
118
+ // 6. If homepage didn't have a PDP link, try extracting one from PLP
119
+ if (!pdpUrl) {
120
+ pdpUrl = findUrl(plpPage.html, host, PDP_PATTERN);
121
+ }
122
+ }
123
+ }
124
+ // 7. Fetch PDP (best-effort)
125
+ if (pdpUrl) {
126
+ log(`Fetching PDP: ${pdpUrl}`);
127
+ const pdpPage = await fetchPage(pdpUrl, controller.signal);
128
+ if (pdpPage) {
129
+ featureResults.push(detectFeatures(pdpPage.html, pdpPage.headers));
130
+ }
131
+ }
132
+ return {
133
+ ...mergeFeatures(featureResults),
134
+ shopSystem,
135
+ hasInstallJs,
136
+ };
137
+ }
138
+ finally {
139
+ clearTimeout(timeout);
140
+ }
141
+ }
142
+ async function analyzeSite(host, log = console.log) {
143
+ const result = await (0, safe_js_1.safe)(fetchAndAnalyze(host, log));
144
+ return result.success ? result.data : null;
145
+ }
@@ -24,7 +24,13 @@ export interface CliServiceInterface {
24
24
  defaultAnswer?: any;
25
25
  validator?: any;
26
26
  }): Promise<string>;
27
- select(message: string, choices: any): Promise<string>;
27
+ select(message: string, choices: any, defaultValue?: string): Promise<string>;
28
+ multiSelect<T>(message: string, choices: {
29
+ name: string;
30
+ value: T;
31
+ checked?: boolean;
32
+ description?: string;
33
+ }[]): Promise<T[]>;
28
34
  spacer(): void;
29
35
  startAction(message: string): void;
30
36
  startProgress(total: number, start?: number, payload?: object, config?: Options): void;
@@ -19,7 +19,13 @@ export declare class CliService implements CliServiceInterface {
19
19
  defaultAnswer?: any;
20
20
  validator?: any;
21
21
  }): Promise<string>;
22
- select(message: string, choices: any): Promise<string>;
22
+ select(message: string, choices: any, defaultValue?: string): Promise<string>;
23
+ multiSelect<T>(message: string, choices: {
24
+ name: string;
25
+ value: T;
26
+ checked?: boolean;
27
+ description?: string;
28
+ }[]): Promise<T[]>;
23
29
  spacer(): void;
24
30
  startAction(message: string): void;
25
31
  startProgress(total: number, start?: number, payload?: object, config?: Options): void;
@@ -89,8 +89,18 @@ class CliService {
89
89
  },
90
90
  });
91
91
  }
92
- async select(message, choices) {
92
+ async select(message, choices, defaultValue) {
93
93
  return (0, prompts_1.select)({
94
+ choices,
95
+ default: defaultValue,
96
+ message: this.getFormattedMessage(message),
97
+ });
98
+ }
99
+ async multiSelect(message, choices) {
100
+ if (this.quiet) {
101
+ return choices.filter((c) => c.checked).map((c) => c.value);
102
+ }
103
+ return (0, prompts_1.checkbox)({
94
104
  choices,
95
105
  message: this.getFormattedMessage(message),
96
106
  });
@@ -4,12 +4,22 @@ export declare enum DirectoryOverwriteOption {
4
4
  OVERWRITE_COMPLETELY = "Empty and create new"
5
5
  }
6
6
  export declare enum ShopSystem {
7
- OTHER = "None of the below-mentioned",
7
+ OTHER = "- unknown -",
8
8
  SHOPIFY = "Shopify",
9
9
  SHOPWARE = "Shopware",
10
10
  SALESFORCE = "Salesforce Commerce Cloud",
11
11
  OXID = "OXID eShop",
12
- PLENTYMARKETS = "Plentymarkets"
12
+ PLENTYMARKETS = "Plentymarkets",
13
+ MAGENTO = "Magento",
14
+ WOOCOMMERCE = "WooCommerce",
15
+ PRESTASHOP = "PrestaShop",
16
+ JTL = "JTL-Shop",
17
+ COMMERCETOOLS = "commercetools",
18
+ SPRYKER = "Spryker",
19
+ SHOPTET = "Shoptet",
20
+ BIGCOMMERCE = "BigCommerce",
21
+ ECFORCE = "ecforce",
22
+ FUTURESHOP = "futureshop"
13
23
  }
14
24
  export declare enum EnvironmentType {
15
25
  PRODUCTION = "production",
@@ -40,9 +50,11 @@ export interface CustomerConfigSettings {
40
50
  convertSoftToHardNavigations?: boolean;
41
51
  isPOV?: boolean;
42
52
  useDisabledSitesApproach?: boolean;
53
+ shopSystemName?: string;
43
54
  isShopify?: boolean;
44
55
  isShopware?: boolean;
45
56
  isSalesforce?: boolean;
46
57
  isOxid?: boolean;
47
58
  isPlentymarkets?: boolean;
59
+ addDowntimeDetection?: boolean;
48
60
  }
@@ -9,12 +9,25 @@ var DirectoryOverwriteOption;
9
9
  })(DirectoryOverwriteOption || (exports.DirectoryOverwriteOption = DirectoryOverwriteOption = {}));
10
10
  var ShopSystem;
11
11
  (function (ShopSystem) {
12
- ShopSystem["OTHER"] = "None of the below-mentioned";
12
+ ShopSystem["OTHER"] = "- unknown -";
13
+ // DACH / Europe
13
14
  ShopSystem["SHOPIFY"] = "Shopify";
14
15
  ShopSystem["SHOPWARE"] = "Shopware";
15
16
  ShopSystem["SALESFORCE"] = "Salesforce Commerce Cloud";
16
17
  ShopSystem["OXID"] = "OXID eShop";
17
18
  ShopSystem["PLENTYMARKETS"] = "Plentymarkets";
19
+ ShopSystem["MAGENTO"] = "Magento";
20
+ ShopSystem["WOOCOMMERCE"] = "WooCommerce";
21
+ ShopSystem["PRESTASHOP"] = "PrestaShop";
22
+ ShopSystem["JTL"] = "JTL-Shop";
23
+ ShopSystem["COMMERCETOOLS"] = "commercetools";
24
+ ShopSystem["SPRYKER"] = "Spryker";
25
+ ShopSystem["SHOPTET"] = "Shoptet";
26
+ // US
27
+ ShopSystem["BIGCOMMERCE"] = "BigCommerce";
28
+ // Japan
29
+ ShopSystem["ECFORCE"] = "ecforce";
30
+ ShopSystem["FUTURESHOP"] = "futureshop";
18
31
  })(ShopSystem || (exports.ShopSystem = ShopSystem = {}));
19
32
  var EnvironmentType;
20
33
  (function (EnvironmentType) {
@@ -6,6 +6,7 @@ const fs = tslib_1.__importStar(require("node:fs"));
6
6
  const path = tslib_1.__importStar(require("node:path"));
7
7
  const Handlebars = tslib_1.__importStar(require("handlebars"));
8
8
  const cli_1 = require("../cli");
9
+ const site_analyzer_js_1 = require("../../helpers/site-analyzer.js");
9
10
  const customer_config_service_model_1 = require("./customer-config-service-model");
10
11
  class CustomerConfigService {
11
12
  context;
@@ -118,10 +119,11 @@ class CustomerConfigService {
118
119
  this.cli.spacer();
119
120
  }
120
121
  async getEnvironmentConfig(environmentType, defaultHost) {
121
- const host = await this.cli.prompt(`[${environmentType.toUpperCase()}] Enter ${environmentType.toUpperCase()} host (without https://):`, {
122
+ const label = environmentType.charAt(0).toUpperCase() + environmentType.slice(1);
123
+ const host = await this.cli.prompt(`[${label}] Enter ${label.toLowerCase()} host (without https://):`, {
122
124
  defaultAnswer: defaultHost,
123
125
  });
124
- const forceInstall = !(await this.cli.confirm(`[${environmentType.toUpperCase()}] Is the install.js already present on ${environmentType.toUpperCase()}?`, false));
126
+ const forceInstall = !(await this.cli.confirm(`[${label}] Is the install.js already present?`, false));
125
127
  return {
126
128
  host,
127
129
  forceInstall,
@@ -136,15 +138,70 @@ class CustomerConfigService {
136
138
  return true;
137
139
  }
138
140
  async getConfigSettings() {
139
- let { production, staging, includeServiceWorker, activateImageOptimisation, removeLazyLoading, addSSR, addSSRInnerShadowDomSupport, addDeviceDetection, useGATracking, useScrapingBee, withGoogleOptimize, activateCfRocketLoaderPlugin, hasSoftNavigations, convertSoftToHardNavigations, useDisabledSitesApproach, isShopify, isShopware, isSalesforce, isOxid, isPlentymarkets, isPOV, } = {};
140
- this.appName = await this.cli.prompt(`[App Name] Enter desired SK app name:`, {
141
+ let { production, staging, includeServiceWorker, isShopify, isShopware, isSalesforce, isOxid, isPlentymarkets, } = {};
142
+ this.appName = await this.cli.prompt(`[App] Enter desired Speed Kit app name:`, {
141
143
  validator: (input) => /^[\da-z]+(?:-[\da-z]+)*$/.test(input) ? "" : "No valid kebab-case",
142
144
  defaultAnswer: this.appName,
143
145
  });
144
- const selectedShopSystem = await this.cli.select("[Shop System] Which shop system is in use?", Object.values(customer_config_service_model_1.ShopSystem).map((option) => ({
145
- name: option,
146
- value: option,
147
- })));
146
+ this.cli.spacer();
147
+ // 1. Production host (needed for site analysis)
148
+ const productionHost = await this.cli.prompt(`[Production] Enter production host (without https://):`, { defaultAnswer: `www.${this.appName}.de` });
149
+ this.cli.spacer();
150
+ // 2. Site analysis (informs shop system, install.js, and feature defaults)
151
+ let siteAnalysis = null;
152
+ const shouldAnalyze = await this.cli.confirm("[Analysis] Analyze production site to auto-detect settings?", true);
153
+ if (shouldAnalyze) {
154
+ this.cli.startAction(`Analyzing https://${productionHost}`);
155
+ siteAnalysis = await (0, site_analyzer_js_1.analyzeSite)(productionHost, (message) => this.cli.comment(message));
156
+ if (siteAnalysis) {
157
+ this.cli.endAction(cli_1.CliActionStatus.COMPLETED);
158
+ }
159
+ else {
160
+ this.cli.endAction(cli_1.CliActionStatus.SKIPPED);
161
+ this.cli.comment("Could not analyze site — using defaults.");
162
+ }
163
+ }
164
+ this.cli.spacer();
165
+ // 3. Shop system
166
+ const detectedShopMap = {
167
+ shopify: customer_config_service_model_1.ShopSystem.SHOPIFY,
168
+ shopware: customer_config_service_model_1.ShopSystem.SHOPWARE,
169
+ salesforce: customer_config_service_model_1.ShopSystem.SALESFORCE,
170
+ oxid: customer_config_service_model_1.ShopSystem.OXID,
171
+ plentymarkets: customer_config_service_model_1.ShopSystem.PLENTYMARKETS,
172
+ magento: customer_config_service_model_1.ShopSystem.MAGENTO,
173
+ woocommerce: customer_config_service_model_1.ShopSystem.WOOCOMMERCE,
174
+ bigcommerce: customer_config_service_model_1.ShopSystem.BIGCOMMERCE,
175
+ prestashop: customer_config_service_model_1.ShopSystem.PRESTASHOP,
176
+ jtl: customer_config_service_model_1.ShopSystem.JTL,
177
+ commercetools: customer_config_service_model_1.ShopSystem.COMMERCETOOLS,
178
+ spryker: customer_config_service_model_1.ShopSystem.SPRYKER,
179
+ shoptet: customer_config_service_model_1.ShopSystem.SHOPTET,
180
+ ecforce: customer_config_service_model_1.ShopSystem.ECFORCE,
181
+ futureshop: customer_config_service_model_1.ShopSystem.FUTURESHOP,
182
+ };
183
+ const detectedShop = siteAnalysis?.shopSystem
184
+ ? detectedShopMap[siteAnalysis.shopSystem]
185
+ : undefined;
186
+ let selectedShopSystem;
187
+ if (detectedShop) {
188
+ selectedShopSystem = detectedShop;
189
+ this.cli.comment(`[Shop System] Detected: ${detectedShop}`);
190
+ }
191
+ else {
192
+ if (siteAnalysis) {
193
+ this.cli.comment("[Shop System] Could not detect shop system from site.");
194
+ }
195
+ selectedShopSystem = await this.cli.select("[Shop System] Which shop system is in use?", [
196
+ customer_config_service_model_1.ShopSystem.SHOPIFY,
197
+ customer_config_service_model_1.ShopSystem.SHOPWARE,
198
+ customer_config_service_model_1.ShopSystem.SALESFORCE,
199
+ customer_config_service_model_1.ShopSystem.OXID,
200
+ customer_config_service_model_1.ShopSystem.PLENTYMARKETS,
201
+ customer_config_service_model_1.ShopSystem.OTHER,
202
+ ].map((option) => ({ name: option, value: option })));
203
+ }
204
+ this.cli.spacer();
148
205
  switch (selectedShopSystem) {
149
206
  case customer_config_service_model_1.ShopSystem.SHOPIFY: {
150
207
  isShopify = true;
@@ -164,42 +221,155 @@ class CustomerConfigService {
164
221
  }
165
222
  case customer_config_service_model_1.ShopSystem.PLENTYMARKETS: {
166
223
  isPlentymarkets = true;
224
+ break;
167
225
  }
168
226
  }
169
- production = await this.getEnvironmentConfig(customer_config_service_model_1.EnvironmentType.PRODUCTION, `www.${this.appName}.de`);
227
+ // 4. Production install.js
228
+ const installJsDetected = siteAnalysis?.hasInstallJs ?? false;
229
+ const forceInstall = !(await this.cli.confirm(`[Production] Is the install.js already present?${installJsDetected ? " (detected)" : ""}`, installJsDetected));
230
+ this.cli.spacer();
231
+ production = { host: productionHost, forceInstall };
232
+ // 5. Staging
170
233
  if (!isShopify) {
171
- const withStaging = await this.cli.confirm(`[${customer_config_service_model_1.EnvironmentType.STAGING.toUpperCase()}] Should a ${customer_config_service_model_1.EnvironmentType.STAGING.toUpperCase()} config also be added?`, false);
234
+ const withStaging = await this.cli.confirm("[Staging] Should a staging config also be added?", false);
172
235
  if (withStaging) {
173
- staging = await this.getEnvironmentConfig(customer_config_service_model_1.EnvironmentType.STAGING, production.host.replace("www", "staging"));
236
+ staging = await this.getEnvironmentConfig(customer_config_service_model_1.EnvironmentType.STAGING, productionHost.replace("www", "staging"));
174
237
  }
238
+ this.cli.spacer();
175
239
  }
176
- if (await this.cli.confirm("[Service Worker] Is there a Service Worker which needs to be included?", false)) {
177
- includeServiceWorker = await this.cli.prompt("[Service Worker] Enter path of Service Worker to be included:", { defaultAnswer: "/sw.js" });
240
+ const resolve = (feature, fallback) => {
241
+ if (!siteAnalysis)
242
+ return { label: "", checked: fallback };
243
+ if (siteAnalysis[feature])
244
+ return { label: " (detected)", checked: true };
245
+ return { label: " (not detected)", checked: fallback };
246
+ };
247
+ const lazyLoading = resolve("removeLazyLoading", true);
248
+ const softNav = resolve("hasSoftNavigations", false);
249
+ const serviceWorker = resolve("includeServiceWorker", false);
250
+ const gaTracking = resolve("useGATracking", true);
251
+ const googleOptimize = resolve("withGoogleOptimize", false);
252
+ const cfRocketLoader = resolve("activateCfRocketLoaderPlugin", false);
253
+ // Detectable features — sorted detected-first when analysis is available
254
+ const detectableChoices = [
255
+ {
256
+ name: `Lazy-Loading Removal / Preloading${lazyLoading.label}`,
257
+ value: "removeLazyLoading",
258
+ checked: lazyLoading.checked,
259
+ description: "Remove lazy-loading attributes and preload critical resources",
260
+ },
261
+ {
262
+ name: `Soft Navigation Tracking${softNav.label}`,
263
+ value: "hasSoftNavigations",
264
+ checked: softNav.checked,
265
+ description: "Track SPA route changes (Next.js, Nuxt, Vue/React Router)",
266
+ },
267
+ {
268
+ name: `Include existing Service Worker${serviceWorker.label}`,
269
+ value: "includeServiceWorker",
270
+ checked: serviceWorker.checked,
271
+ description: "Merge customer's existing Service Worker with Speed Kit's",
272
+ },
273
+ ...(isShopify
274
+ ? []
275
+ : [
276
+ {
277
+ name: `GA DataLayer Tracking${gaTracking.label}`,
278
+ value: "useGATracking",
279
+ checked: gaTracking.checked,
280
+ description: "Forward dataLayer events through Speed Kit",
281
+ },
282
+ ]),
283
+ {
284
+ name: `Google Optimize Support${googleOptimize.label}`,
285
+ value: "withGoogleOptimize",
286
+ checked: googleOptimize.checked,
287
+ description: "Ensure compatibility with Google Optimize experiments",
288
+ },
289
+ {
290
+ name: `Cloudflare Rocket Loader Plugin${cfRocketLoader.label}`,
291
+ value: "activateCfRocketLoaderPlugin",
292
+ checked: cfRocketLoader.checked,
293
+ description: "Handle Cloudflare Rocket Loader script deferral",
294
+ },
295
+ ];
296
+ if (siteAnalysis) {
297
+ const detectionRank = (name) => {
298
+ if (name.includes("(detected)"))
299
+ return 0;
300
+ if (name.includes("(not detected)"))
301
+ return 1;
302
+ return 2;
303
+ };
304
+ detectableChoices.sort((a, b) => detectionRank(a.name) - detectionRank(b.name));
178
305
  }
179
- useDisabledSitesApproach = await this.cli.confirm("[Disabled Sites] Use disabled sites approach as the given URL structure makes it hard to identify specific page types?", false);
180
- activateImageOptimisation = await this.cli.confirm("[IO] Should image optimisation be activated?", false);
181
- removeLazyLoading = await this.cli.confirm("[Lazy Loading] Does the shop require us to resolve lazy-loaded content?");
182
- if (await this.cli.confirm("[Lambda SSR] Add server-side-rendering? This requires a set-up Lambda function to work.", false)) {
183
- addSSR = true;
184
- if (await this.cli.confirm("[Lambda SSR] Does the customer utilize Shadow DOMs? If so additional handling for this will be added.", false)) {
185
- addSSRInnerShadowDomSupport = true;
186
- }
306
+ // Static features not auto-detectable
307
+ const staticChoices = [
308
+ {
309
+ name: "Disabled Sites approach",
310
+ value: "useDisabledSitesApproach",
311
+ description: "Use when URL structure makes page type identification hard",
312
+ },
313
+ {
314
+ name: "Image Optimisation",
315
+ value: "activateImageOptimisation",
316
+ description: "Optimize images via Speed Kit (skip if site already uses an image CDN with WebP)",
317
+ },
318
+ { name: "Server-Side Rendering (SSR)", value: "addSSR" },
319
+ {
320
+ name: "Device Detection",
321
+ value: "addDeviceDetection",
322
+ description: "Auto-enabled with SSR",
323
+ },
324
+ {
325
+ name: "Enhanced PoV Tracking",
326
+ value: "isPOV",
327
+ description: "Extended analytics for Proof of Value engagements",
328
+ },
329
+ {
330
+ name: "ScrapingBee",
331
+ value: "useScrapingBee",
332
+ description: "Use for geo-location-based responses",
333
+ },
334
+ { name: "Downtime Detection", value: "addDowntimeDetection" },
335
+ ];
336
+ const featureChoices = [
337
+ ...detectableChoices,
338
+ ...staticChoices,
339
+ ];
340
+ const selectedFeatures = await this.cli.multiSelect("[Features] Select features to enable:", featureChoices);
341
+ this.cli.spacer();
342
+ const has = (feature) => selectedFeatures.includes(feature);
343
+ const useDisabledSitesApproach = has("useDisabledSitesApproach");
344
+ const activateImageOptimisation = has("activateImageOptimisation");
345
+ const removeLazyLoading = has("removeLazyLoading");
346
+ const addSSR = has("addSSR");
347
+ // SSR always implies device detection
348
+ const addDeviceDetection = addSSR || has("addDeviceDetection");
349
+ const hasSoftNavigations = has("hasSoftNavigations");
350
+ const useGATracking = has("useGATracking");
351
+ const isPOV = has("isPOV");
352
+ const withGoogleOptimize = has("withGoogleOptimize");
353
+ const activateCfRocketLoaderPlugin = has("activateCfRocketLoaderPlugin");
354
+ const useScrapingBee = has("useScrapingBee");
355
+ const addDowntimeDetection = has("addDowntimeDetection");
356
+ // Follow-up questions for features that need additional input
357
+ if (has("includeServiceWorker")) {
358
+ includeServiceWorker = await this.cli.prompt("[Service Worker] Enter path of Service Worker to be included:", { defaultAnswer: "/sw.js" });
359
+ this.cli.spacer();
187
360
  }
188
- // ask for variation handling only if adding Lambda SSR is not needed, as it will come with device detection anyway
189
- addDeviceDetection = addSSR
190
- ? true
191
- : await this.cli.confirm("[Device Detection] Do mobile and desktop versions of the website differ in their HTML structure, which would require us to add variations?", false);
192
- useScrapingBee = await this.cli.confirm("[ScrapingBee] Do customer responses differ geo-location-based which would require ScrapingBee API to fetch documents?", false);
193
- if (!isShopify) {
194
- useGATracking = await this.cli.confirm("[Tracking] Add tracking based on Google Analytics' DataLayer?");
361
+ let addSSRInnerShadowDomSupport;
362
+ if (addSSR) {
363
+ addSSRInnerShadowDomSupport = await this.cli.confirm("[SSR] Activate handling for customer-utilized Shadow DOMs?", false);
364
+ this.cli.spacer();
195
365
  }
196
- hasSoftNavigations = await this.cli.confirm("[Tracking] Add tracking for Soft Navigations?", false);
366
+ let convertSoftToHardNavigations;
197
367
  if (hasSoftNavigations) {
198
- convertSoftToHardNavigations = await this.cli.confirm("[Soft2Hard Navigations] Should soft navigations be converted to hard navigations?", false);
368
+ convertSoftToHardNavigations = await this.cli.confirm("[Soft Navigations] Convert soft navigations to hard navigations?", true);
369
+ this.cli.spacer();
199
370
  }
200
- isPOV = await this.cli.confirm("[Tracking] Is a POV (proof of value) anticipated, so that additional tracking is needed?", false);
201
- withGoogleOptimize = await this.cli.confirm("[Services] Is Google Optimize used?", false);
202
- activateCfRocketLoaderPlugin = await this.cli.confirm("[Services] Is Cloudflare's Rocket Loader used, for which we need to activate our rocket loader plugin?", false);
371
+ // Resolve shop system display name for template
372
+ const shopSystemName = selectedShopSystem === customer_config_service_model_1.ShopSystem.OTHER ? undefined : selectedShopSystem;
203
373
  return {
204
374
  appName: this.appName,
205
375
  production,
@@ -217,12 +387,14 @@ class CustomerConfigService {
217
387
  hasSoftNavigations,
218
388
  convertSoftToHardNavigations,
219
389
  useDisabledSitesApproach,
390
+ shopSystemName,
220
391
  isShopify,
221
392
  isShopware,
222
393
  isSalesforce,
223
394
  isOxid,
224
395
  isPlentymarkets,
225
396
  isPOV,
397
+ addDowntimeDetection,
226
398
  };
227
399
  }
228
400
  async logWarnings(configSettings) {
@@ -238,8 +410,7 @@ class CustomerConfigService {
238
410
  async generateConfig() {
239
411
  this.cli.write("\nLet's generate a config...\n");
240
412
  const configSettings = await this.getConfigSettings();
241
- const addDowntimeDetection = await this.cli.confirm("[Downtime Detection] Add Downtime Detection?", false);
242
- if (addDowntimeDetection) {
413
+ if (configSettings.addDowntimeDetection) {
243
414
  this.templates.push("downtimeDetection.js.hbs");
244
415
  }
245
416
  await this.compileHandlebarsFiles(configSettings);
@@ -32,7 +32,16 @@
32
32
  /\/backend/i,
33
33
  {{/inline}}
34
34
 
35
+ {{#if isShopify}}
36
+ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
37
+
38
+ (function() {
39
+ SpeedKit.configPlugins = SpeedKit.configPlugins || [];
40
+ SpeedKit.configPlugins.push(new ShopifyPlugin());
41
+
42
+ {{else}}
35
43
  (function() {
44
+ {{/if}}
36
45
  var ENABLED_HOSTS_CONFIGS = [
37
46
  // Production:
38
47
  {
@@ -327,13 +336,12 @@
327
336
  stripQueryParams: [{
328
337
  params: [
329
338
  // Default:
339
+ /.*clid$/, // generic Click ID
330
340
  "AdSet", // Meta Ads (Facebook)
331
- "_autaclid", // Adition Click ID
332
341
  "_ga", // Google Analytics User ID (Universal Analytics)
333
342
  "_gl", // Google Analytics User ID (GA 4+)
334
343
  "_klaviyopid", // Klaviyo
335
344
  "_kx", // Klaviyo
336
- "_pmclid",
337
345
  "a8", // A8.net (affiliate marketing)
338
346
  "ad_id",
339
347
  "adid",
@@ -347,18 +355,15 @@
347
355
  "clickref",
348
356
  "cstrackid", // ChannelSight
349
357
  "cto_pld",
350
- "dclid", // another Click ID
351
358
  "dicbo",
352
359
  "ds_rl",
353
360
  "ef_id", // Adobe Advertising Cloud
354
361
  "epik",
355
362
  "fb_audience", // Facebook Ads
356
- "fbclid", // Facebook Click ID
357
363
  "gQT",
358
364
  "gb_clk", // Click tracking
359
365
  "gtm_debug", // Google Tag Manager
360
366
  "irclickid",
361
- "kgclid",
362
367
  "kk", // Kelkoo Shopping Search Engine Key
363
368
  "ldtag_cl", // LINE Ads
364
369
  "lt_r", // LINE Ads
@@ -369,16 +374,12 @@
369
374
  "rdt_cid", // Reddit Ad Click ID
370
375
  "rtbhc", // RTB House
371
376
  "s_kwcid", // AMO ID, Adobe Advertising Cloud
372
- "soluteclid", // Solute Click ID
373
377
  "srclt", // LINE Ads
374
378
  "srsltid", // google search engine
375
- "syclid",
376
379
  "tblci", // Taboola Click ID
377
380
  "tduid", // Tradedoubler UID
378
381
  "trackingtoken",
379
- "ttclid", // TikTok Click ID
380
382
  "ved",
381
- "yclid", // Yandex Click ID
382
383
  "yj_r", // Yahoo! JAPAN Ads
383
384
  "zanpid", // AWIN (former Zanox Affiliate Network)
384
385
  /^(mb|nb|nx|ny)$/,
@@ -2,16 +2,8 @@
2
2
  "production": {
3
3
  "_meta": {
4
4
  "shopSystem":
5
- {{#if isShopify}}
6
- "Shopify"
7
- {{else if isShopware}}
8
- "Shopware"
9
- {{else if isSalesforce}}
10
- "Salesforce Commerce Cloud"
11
- {{else if isOxid}}
12
- "OXID eShop"
13
- {{else if isPlentymarkets}}
14
- "Plentymarkets"
5
+ {{#if shopSystemName}}
6
+ "{{shopSystemName}}"
15
7
  {{else}}
16
8
  "TODO: enter name of system in use"
17
9
  {{/if}}
@@ -237,30 +237,11 @@ import { PredictivePreloading } from 'predictive-preloading/predictivePreloading
237
237
  // },
238
238
  // },
239
239
  {{else}}
240
+
240
241
  {{#if isShopify}}
241
- {
242
- key: "ecAddToCart",
243
- // TODO depending on the used theme different events are in use variant:added, product-ajax:added, product:added
244
- // TODO the correct event can easily spotted by search for the initiator of the /cart/add request after doing a addToCart
245
- on: (callback) => document.addEventListener("product-ajax:added", callback),
246
- append: function (event) {
247
- // TODO depending on the used event the product is exposed under a different property
248
- var product = event.detail.product;
249
-
250
- if (product) {
251
- return {
252
- id: product.product_id,
253
- price: (product.discounted_price ?? product.price) / 100, // price is in cents
254
- currencyCode: window.Shopify?.currency?.active || defaultCurrency,
255
- quantity: product.quantity,
256
- };
257
- }
258
- },
259
- },
260
-
242
+ // No custom ecAddToCart tracking event needed as it is already handled by the speed-kit-config/ShopifyPlugin
261
243
  // No custom ecConversion tracking event as it is already handled by the Speed Kit Extension for Shopify
262
244
  {{else}}
263
-
264
245
  {
265
246
  key: 'ecAddToCart',
266
247
  // TODO: set proper 'on' event listener
@@ -11,8 +11,5 @@ export declare class DeviceDetectionChangedCheck {
11
11
  constructor(cli: CliServiceInterface, fileList: FileListInterface, configApi: ConfigApiService, customerConfig: CustomerConfig, configName: string, isProduction: boolean);
12
12
  check(): Promise<void>;
13
13
  private checkForChangedDeviceDetection;
14
- private evaluateLocalAndRemoteSpeedKit;
15
14
  private getActiveInstallResource;
16
- private buildBrowserMock;
17
- safeFileContentWrapper(fileContent: string): string;
18
15
  }
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DeviceDetectionChangedCheck = void 0;
4
- const tslib_1 = require("tslib");
5
4
  const cli_1 = require("../../../cli");
6
5
  const files_1 = require("../../../../models/files");
7
- const vm = tslib_1.__importStar(require("node:vm"));
8
6
  const safe_1 = require("../../../../helpers/safe");
9
- const evaluate_sk_config_error_1 = require("../../error/evaluate-sk-config-error");
7
+ const evaluate_speed_kit_config_1 = require("../../../../helpers/evaluate-speed-kit-config");
10
8
  class DeviceDetectionChangedCheck {
11
9
  cli;
12
10
  fileList;
@@ -41,9 +39,7 @@ class DeviceDetectionChangedCheck {
41
39
  this.cli.startAction("Checking for customDeviceDetection");
42
40
  const speedKitConfigFile = this.fileList.getByName(files_1.INTEGRATION_FILES.CONFIG.SPEED_KIT);
43
41
  const activeSpeedKitConfig = activeInstallResource[speedKitConfigFile.remoteKey];
44
- const changesResponse = (0, safe_1.safe)(() => {
45
- return this.checkForChangedDeviceDetection(speedKitConfigFile, activeSpeedKitConfig);
46
- });
42
+ const changesResponse = await (0, safe_1.safe)(this.checkForChangedDeviceDetection(speedKitConfigFile, activeSpeedKitConfig));
47
43
  if (changesResponse.success === false) {
48
44
  this.cli.endAction(cli_1.CliActionStatus.FAILED);
49
45
  this.cli.writeError(`Unable to evaluate changes: ${changesResponse.error}`);
@@ -62,9 +58,9 @@ class DeviceDetectionChangedCheck {
62
58
  this.cli.exit(0);
63
59
  }
64
60
  }
65
- checkForChangedDeviceDetection(speedKitConfigFile, activeSpeedKitConfig) {
61
+ async checkForChangedDeviceDetection(speedKitConfigFile, activeSpeedKitConfig) {
66
62
  const changes = [];
67
- const { localSpeedKit, remoteSpeedKit } = this.evaluateLocalAndRemoteSpeedKit(this.customerConfig.origins[0], speedKitConfigFile, activeSpeedKitConfig);
63
+ const { localSpeedKit, remoteSpeedKit } = await (0, evaluate_speed_kit_config_1.evaluateLocalAndRemoteSpeedKit)(this.customerConfig.app, this.customerConfig.origins[0], speedKitConfigFile, activeSpeedKitConfig);
68
64
  if ((localSpeedKit.detectDevice && !remoteSpeedKit.detectDevice) ||
69
65
  (!localSpeedKit.detectDevice && remoteSpeedKit.detectDevice)) {
70
66
  changes.push("detectDevice");
@@ -96,28 +92,6 @@ class DeviceDetectionChangedCheck {
96
92
  }
97
93
  return changes;
98
94
  }
99
- evaluateLocalAndRemoteSpeedKit(origin, speedKitConfigFile, activeSpeedKitConfig) {
100
- const localBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
101
- vm.runInNewContext(this.safeFileContentWrapper(speedKitConfigFile.getContent()), localBrowserContext, {
102
- filename: speedKitConfigFile.path,
103
- displayErrors: false,
104
- breakOnSigint: true,
105
- });
106
- const remoteBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
107
- vm.runInNewContext(this.safeFileContentWrapper(activeSpeedKitConfig), remoteBrowserContext, {
108
- displayErrors: false,
109
- breakOnSigint: true,
110
- });
111
- if (localBrowserContext.vmError !== null) {
112
- throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[config_Speedkit.js] ${localBrowserContext.vmError?.message}`);
113
- }
114
- if (remoteBrowserContext.vmError !== null) {
115
- throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[remote_config] ${JSON.stringify(remoteBrowserContext.vmError?.message)}`);
116
- }
117
- const localSpeedKit = localBrowserContext.window.speedKit;
118
- const remoteSpeedKit = remoteBrowserContext.window.speedKit;
119
- return { localSpeedKit, remoteSpeedKit };
120
- }
121
95
  async getActiveInstallResource() {
122
96
  const activeInstallResourceResponse = await (0, safe_1.safe)(this.configApi.getActiveInstall(this.configName));
123
97
  if (activeInstallResourceResponse.success !== true) {
@@ -128,62 +102,5 @@ class DeviceDetectionChangedCheck {
128
102
  }
129
103
  return activeInstallResourceResponse.data;
130
104
  }
131
- buildBrowserMock(origin, app) {
132
- const originUrl = new URL(origin);
133
- const window = {
134
- location: originUrl,
135
- APP: app,
136
- speedKit: {
137
- split: undefined,
138
- splitTestId: undefined,
139
- },
140
- serviceWorkerUrl: "",
141
- screen: { width: 0, height: 0 },
142
- devicePixelRatio: 1,
143
- navigator: {
144
- userAgent: "",
145
- connection: {},
146
- cookieEnabled: false,
147
- languages: [],
148
- online: true,
149
- },
150
- top: {},
151
- };
152
- window.top = window;
153
- const document = {
154
- cookie: "",
155
- querySelector: () => { },
156
- querySelectorAll: () => { },
157
- createElement: () => { },
158
- addEventListener: () => { },
159
- head: {},
160
- body: {},
161
- };
162
- document.head = document;
163
- document.body = document;
164
- return {
165
- vmError: null,
166
- ...window,
167
- window,
168
- document,
169
- navigator: { userAgent: "" },
170
- localStorage: {
171
- getItem: () => { },
172
- setItem: () => { },
173
- },
174
- JSON: JSON,
175
- decodeURIComponent: decodeURIComponent,
176
- URL: URL,
177
- };
178
- }
179
- safeFileContentWrapper(fileContent) {
180
- return `
181
- try{
182
- ${fileContent}
183
- } catch (error){
184
- vmError = error
185
- }
186
- `;
187
- }
188
105
  }
189
106
  exports.DeviceDetectionChangedCheck = DeviceDetectionChangedCheck;
@@ -11,8 +11,5 @@ export declare class SplitChangeCheck {
11
11
  constructor(cli: CliServiceInterface, fileList: FileListInterface, configApi: ConfigApiService, customerConfig: CustomerConfig, configName: string, isProduction: boolean);
12
12
  check(): Promise<void>;
13
13
  private checkSplitsForEachOrigin;
14
- private evaluateLocalAndRemoteSpeedKit;
15
14
  private getActiveInstallResource;
16
- private buildBrowserMock;
17
- safeFileContentWrapper(fileContent: string): string;
18
15
  }
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SplitChangeCheck = void 0;
4
- const tslib_1 = require("tslib");
5
4
  const cli_1 = require("../../../cli");
6
5
  const files_1 = require("../../../../models/files");
7
- const vm = tslib_1.__importStar(require("node:vm"));
8
6
  const safe_1 = require("../../../../helpers/safe");
9
- const evaluate_sk_config_error_1 = require("../../error/evaluate-sk-config-error");
7
+ const evaluate_speed_kit_config_1 = require("../../../../helpers/evaluate-speed-kit-config");
10
8
  class SplitChangeCheck {
11
9
  cli;
12
10
  fileList;
@@ -41,9 +39,7 @@ class SplitChangeCheck {
41
39
  this.cli.startAction("Checking Split Test ID");
42
40
  const speedKitConfigFile = this.fileList.getByName(files_1.INTEGRATION_FILES.CONFIG.SPEED_KIT);
43
41
  const activeSpeedKitConfig = activeInstallResource[speedKitConfigFile.remoteKey];
44
- const changesResponse = (0, safe_1.safe)(() => {
45
- return this.checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig);
46
- });
42
+ const changesResponse = await (0, safe_1.safe)(this.checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig));
47
43
  if (changesResponse.success === false) {
48
44
  this.cli.endAction(cli_1.CliActionStatus.FAILED);
49
45
  this.cli.writeError(`Unable to evaluate changes: ${changesResponse.error}`);
@@ -60,10 +56,10 @@ class SplitChangeCheck {
60
56
  this.cli.exit(0);
61
57
  }
62
58
  }
63
- checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig) {
59
+ async checkSplitsForEachOrigin(speedKitConfigFile, activeSpeedKitConfig) {
64
60
  const changes = [];
65
61
  for (const origin of this.customerConfig.origins) {
66
- const { localSpeedKit, remoteSpeedKit } = this.evaluateLocalAndRemoteSpeedKit(origin, speedKitConfigFile, activeSpeedKitConfig);
62
+ const { localSpeedKit, remoteSpeedKit } = await (0, evaluate_speed_kit_config_1.evaluateLocalAndRemoteSpeedKit)(this.customerConfig.app, origin, speedKitConfigFile, activeSpeedKitConfig);
67
63
  // check for split change
68
64
  if (localSpeedKit.split !== remoteSpeedKit.split) {
69
65
  changes.push({
@@ -114,28 +110,6 @@ class SplitChangeCheck {
114
110
  }
115
111
  return changes;
116
112
  }
117
- evaluateLocalAndRemoteSpeedKit(origin, speedKitConfigFile, activeSpeedKitConfig) {
118
- const localBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
119
- vm.runInNewContext(this.safeFileContentWrapper(speedKitConfigFile.getContent()), localBrowserContext, {
120
- filename: speedKitConfigFile.path,
121
- displayErrors: false,
122
- breakOnSigint: true,
123
- });
124
- const remoteBrowserContext = this.buildBrowserMock(origin, this.customerConfig.app);
125
- vm.runInNewContext(this.safeFileContentWrapper(activeSpeedKitConfig), remoteBrowserContext, {
126
- displayErrors: false,
127
- breakOnSigint: true,
128
- });
129
- if (localBrowserContext.vmError !== null) {
130
- throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[config_Speedkit.js] ${localBrowserContext.vmError?.message}`);
131
- }
132
- if (remoteBrowserContext.vmError !== null) {
133
- throw new evaluate_sk_config_error_1.EvaluateSkConfigError(`[remote_config] ${JSON.stringify(remoteBrowserContext.vmError?.message)}`);
134
- }
135
- const localSpeedKit = localBrowserContext.window.speedKit;
136
- const remoteSpeedKit = remoteBrowserContext.window.speedKit;
137
- return { localSpeedKit, remoteSpeedKit };
138
- }
139
113
  async getActiveInstallResource() {
140
114
  const activeInstallResourceResponse = await (0, safe_1.safe)(this.configApi.getActiveInstall(this.configName));
141
115
  if (activeInstallResourceResponse.success !== true) {
@@ -146,62 +120,5 @@ class SplitChangeCheck {
146
120
  }
147
121
  return activeInstallResourceResponse.data;
148
122
  }
149
- buildBrowserMock(origin, app) {
150
- const originUrl = new URL(origin);
151
- const window = {
152
- location: originUrl,
153
- APP: app,
154
- speedKit: {
155
- split: undefined,
156
- splitTestId: undefined,
157
- },
158
- serviceWorkerUrl: "",
159
- screen: { width: 0, height: 0 },
160
- devicePixelRatio: 1,
161
- navigator: {
162
- userAgent: "",
163
- connection: {},
164
- cookieEnabled: false,
165
- languages: [],
166
- online: true,
167
- },
168
- top: {},
169
- };
170
- window.top = window;
171
- const document = {
172
- cookie: "",
173
- querySelector: () => { },
174
- querySelectorAll: () => { },
175
- createElement: () => { },
176
- addEventListener: () => { },
177
- head: {},
178
- body: {},
179
- };
180
- document.head = document;
181
- document.body = document;
182
- return {
183
- vmError: null,
184
- ...window,
185
- window,
186
- document,
187
- navigator: { userAgent: "" },
188
- localStorage: {
189
- getItem: () => { },
190
- setItem: () => { },
191
- },
192
- JSON: JSON,
193
- decodeURIComponent: decodeURIComponent,
194
- URL: URL,
195
- };
196
- }
197
- safeFileContentWrapper(fileContent) {
198
- return `
199
- try{
200
- ${fileContent}
201
- } catch (error){
202
- vmError = error
203
- }
204
- `;
205
- }
206
123
  }
207
124
  exports.SplitChangeCheck = SplitChangeCheck;
@@ -751,5 +751,5 @@
751
751
  ]
752
752
  }
753
753
  },
754
- "version": "3.40.0"
754
+ "version": "3.42.0"
755
755
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "3.40.0",
4
+ "version": "3.42.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"
@@ -66,7 +66,7 @@
66
66
  "type": "commonjs",
67
67
  "dependencies": {
68
68
  "@aws-sdk/client-athena": "^3.529",
69
- "@inquirer/prompts": "^3.3.2",
69
+ "@inquirer/prompts": "^5.5.0",
70
70
  "@nats-io/transport-node": "^3.3.1",
71
71
  "@oclif/core": "^3.23",
72
72
  "@oclif/plugin-autocomplete": "^3.0",