@speedkit/cli 3.41.0 → 3.43.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,24 @@
1
+ # [3.43.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.42.0...v3.43.0) (2026-04-23)
2
+
3
+
4
+ ### Features
5
+
6
+ * **deploy-checks:** polyfill setTimeout and addEventListener in sandboxed config evaluation ([4dac3f0](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/4dac3f04fab2d2f60077a5a301aefef016d57b0f))
7
+ * **onboarding:** hide Chrome automation signals to avoid WAF blocks ([6065442](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/60654424e7a980fd4f8858bdb656ab482b0b9ef1))
8
+ * **onboarding:** serve empty service worker for ?empty=1 requests ([403ad50](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/403ad50bfe953868e63b9a2cd101503fdd1b0c56))
9
+
10
+ # [3.42.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.41.0...v3.42.0) (2026-04-23)
11
+
12
+
13
+ ### Features
14
+
15
+ * **customer-config:** add auto feature detection ([81125e8](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/81125e81fe54c8dca01bf7e03c63449d18caab11))
16
+ * **customer-config:** add extended auto detection for shop system + existence of install.js ([c321ba9](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/c321ba98f0a6f89ff90eda9bcab694f64ead5069))
17
+ * **customer-config:** add fetching for most important page types ([948381d](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/948381da8b25aa1f9ead8d7661216bac8ca9ad88))
18
+ * **customer-config:** improve prompts ([cc229d5](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/cc229d56bed19d5b2127cb766837fd660e156a9b))
19
+ * make deployChecks compatible to speed-kit-plugins ([0756959](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/07569595412e533824fda03b2980b7b844415f9f))
20
+ * **wizard:** templates ([a198dde](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a198dde127b1eee36321007159b92feecc4b64bf))
21
+
1
22
  # [3.41.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.40.0...v3.41.0) (2026-04-02)
2
23
 
3
24
 
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.41.0 linux-x64 node-v20.20.2
24
+ @speedkit/cli/3.43.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,110 @@
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: () => { },
48
+ clearTimeout: () => { },
49
+ addEventListener: () => { },
50
+ removeEventListener: () => { },
51
+ navigator: {
52
+ userAgent: "",
53
+ connection: {},
54
+ cookieEnabled: false,
55
+ languages: [],
56
+ online: true,
57
+ },
58
+ top: {},
59
+ };
60
+ window.top = window;
61
+ const document = {
62
+ cookie: "",
63
+ querySelector: () => { },
64
+ querySelectorAll: () => { },
65
+ createElement: () => { },
66
+ addEventListener: () => { },
67
+ removeEventListener: () => { },
68
+ head: {},
69
+ body: {},
70
+ };
71
+ document.head = document;
72
+ document.body = document;
73
+ return {
74
+ vmError: null,
75
+ ...window,
76
+ window,
77
+ document,
78
+ navigator: { userAgent: "" },
79
+ localStorage: {
80
+ getItem: () => { },
81
+ setItem: () => { },
82
+ },
83
+ JSON: JSON,
84
+ decodeURIComponent: decodeURIComponent,
85
+ URL: URL,
86
+ };
87
+ }
88
+ function safeFileContentWrapper(fileContent) {
89
+ return `
90
+ try{
91
+ ${fileContent}
92
+ } catch (error){
93
+ vmError = error
94
+ }
95
+ `;
96
+ }
97
+ async function bundle(content, app) {
98
+ const bundler = new bundler_1.BundleService();
99
+ return bundler.bundle({
100
+ basePaths: {
101
+ "speed-kit-config": "https://www.baqend.com/speed-kit-config/latest/",
102
+ },
103
+ entryPoints: { "config.js": content },
104
+ logLevel: "error",
105
+ minify: false,
106
+ define: {
107
+ APP: JSON.stringify(app),
108
+ },
109
+ });
110
+ }
@@ -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);
@@ -336,13 +336,12 @@ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
336
336
  stripQueryParams: [{
337
337
  params: [
338
338
  // Default:
339
+ /.*clid$/, // generic Click ID
339
340
  "AdSet", // Meta Ads (Facebook)
340
- "_autaclid", // Adition Click ID
341
341
  "_ga", // Google Analytics User ID (Universal Analytics)
342
342
  "_gl", // Google Analytics User ID (GA 4+)
343
343
  "_klaviyopid", // Klaviyo
344
344
  "_kx", // Klaviyo
345
- "_pmclid",
346
345
  "a8", // A8.net (affiliate marketing)
347
346
  "ad_id",
348
347
  "adid",
@@ -356,18 +355,15 @@ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
356
355
  "clickref",
357
356
  "cstrackid", // ChannelSight
358
357
  "cto_pld",
359
- "dclid", // another Click ID
360
358
  "dicbo",
361
359
  "ds_rl",
362
360
  "ef_id", // Adobe Advertising Cloud
363
361
  "epik",
364
362
  "fb_audience", // Facebook Ads
365
- "fbclid", // Facebook Click ID
366
363
  "gQT",
367
364
  "gb_clk", // Click tracking
368
365
  "gtm_debug", // Google Tag Manager
369
366
  "irclickid",
370
- "kgclid",
371
367
  "kk", // Kelkoo Shopping Search Engine Key
372
368
  "ldtag_cl", // LINE Ads
373
369
  "lt_r", // LINE Ads
@@ -378,16 +374,12 @@ import { ShopifyPlugin } from "speed-kit-config/ShopifyPlugin";
378
374
  "rdt_cid", // Reddit Ad Click ID
379
375
  "rtbhc", // RTB House
380
376
  "s_kwcid", // AMO ID, Adobe Advertising Cloud
381
- "soluteclid", // Solute Click ID
382
377
  "srclt", // LINE Ads
383
378
  "srsltid", // google search engine
384
- "syclid",
385
379
  "tblci", // Taboola Click ID
386
380
  "tduid", // Tradedoubler UID
387
381
  "trackingtoken",
388
- "ttclid", // TikTok Click ID
389
382
  "ved",
390
- "yclid", // Yandex Click ID
391
383
  "yj_r", // Yahoo! JAPAN Ads
392
384
  "zanpid", // AWIN (former Zanox Affiliate Network)
393
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}}
@@ -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;
@@ -10,5 +10,5 @@ export declare class CustomerServiceWorkerJs implements FetchRequestPausedEventH
10
10
  private customerConfig;
11
11
  get patterns(): Protocol.Fetch.RequestPattern[];
12
12
  constructor(customerConfig: CustomerConfig);
13
- handle(): Promise<Partial<Protocol.Fetch.FulfillRequestRequest>>;
13
+ handle(event: Protocol.Fetch.RequestPausedEvent): Promise<Partial<Protocol.Fetch.FulfillRequestRequest>>;
14
14
  }
@@ -22,7 +22,13 @@ class CustomerServiceWorkerJs {
22
22
  constructor(customerConfig) {
23
23
  this.customerConfig = customerConfig;
24
24
  }
25
- async handle() {
25
+ async handle(event) {
26
+ // When the SW is registered with `?empty=1`, the caller wants a no-op
27
+ // service worker on that scope — serve an empty script instead of the
28
+ // skonboarding wrapper that imports Speed Kit's sw.js.
29
+ if (new URL(event.request.url).searchParams.get("empty") === "1") {
30
+ return new baqend_response_1.BaqendResponse("// skonboarding empty sw", "text/javascript; charset=utf-8");
31
+ }
26
32
  const wrapperSwCode = `importScripts(\`https://${this.customerConfig.appDomain}/v1/speedkit/sw.js\${location.search}\`); // skonboarding`;
27
33
  return new baqend_response_1.BaqendResponse(wrapperSwCode, "text/javascript; charset=utf-8");
28
34
  }
@@ -9,8 +9,11 @@ const DEFAULT_BROWSER_ARGS = [
9
9
  "--enable-features=Translate,NetworkService",
10
10
  "--no-first-run",
11
11
  "--no-default-browser-check",
12
- "--disable-infobars",
13
- // "--enable-automation",
12
+ // Hides the `navigator.webdriver` fingerprint so customer CDN/WAF rules don't treat the session as a bot.
13
+ "--disable-blink-features=AutomationControlled",
14
+ // Signals a test environment so Chrome suppresses the "unsupported command-line flag" infobar
15
+ // triggered by the flag above. Value is arbitrary and only used as an identifier.
16
+ "--test-type=speed-kit-cli",
14
17
  ];
15
18
  exports.BROWSER_EVENTS = {
16
19
  DISCONNECTED: "disconnected",
@@ -91,6 +94,9 @@ class BrowserContext {
91
94
  headless: this.headless,
92
95
  executablePath: this.chromePath,
93
96
  args: this.browserArgs,
97
+ // Strip Puppeteer's default `--enable-automation` so Chrome doesn't show
98
+ // the "Chrome is being controlled by automated test software" infobar.
99
+ ignoreDefaultArgs: ["--enable-automation"],
94
100
  };
95
101
  if (this.userConfig?.chromeExtensionPaths) {
96
102
  if (this.isChromeBrowser()) {
@@ -751,5 +751,5 @@
751
751
  ]
752
752
  }
753
753
  },
754
- "version": "3.41.0"
754
+ "version": "3.43.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.41.0",
4
+ "version": "3.43.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",