@speedkit/cli 3.41.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 +12 -0
- package/README.md +1 -1
- package/dist/helpers/evaluate-speed-kit-config.d.ts +15 -0
- package/dist/helpers/evaluate-speed-kit-config.js +106 -0
- package/dist/helpers/site-analyzer.d.ts +12 -0
- package/dist/helpers/site-analyzer.js +145 -0
- package/dist/services/cli/cli-service-model.d.ts +7 -1
- package/dist/services/cli/cli-service.d.ts +7 -1
- package/dist/services/cli/cli-service.js +11 -1
- package/dist/services/customer-config/customer-config-service-model.d.ts +14 -2
- package/dist/services/customer-config/customer-config-service-model.js +14 -1
- package/dist/services/customer-config/customer-config-service.js +206 -35
- package/dist/services/customer-config/templates/config_SpeedKit.js.hbs +1 -9
- package/dist/services/customer-config/templates/config_customer.json.hbs +2 -10
- package/dist/services/deploy/checks/pre-deploy/device-detection-changed-check.d.ts +0 -3
- package/dist/services/deploy/checks/pre-deploy/device-detection-changed-check.js +4 -87
- package/dist/services/deploy/checks/pre-deploy/split-change-check.d.ts +0 -3
- package/dist/services/deploy/checks/pre-deploy/split-change-check.js +4 -87
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
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
|
+
|
|
1
13
|
# [3.41.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.40.0...v3.41.0) (2026-04-02)
|
|
2
14
|
|
|
3
15
|
|
package/README.md
CHANGED
|
@@ -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 = "
|
|
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"] = "
|
|
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
|
|
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(`[${
|
|
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,
|
|
140
|
-
this.appName = await this.cli.prompt(`[App
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
?
|
|
191
|
-
|
|
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
|
-
|
|
366
|
+
let convertSoftToHardNavigations;
|
|
197
367
|
if (hasSoftNavigations) {
|
|
198
|
-
convertSoftToHardNavigations = await this.cli.confirm("[
|
|
368
|
+
convertSoftToHardNavigations = await this.cli.confirm("[Soft Navigations] Convert soft navigations to hard navigations?", true);
|
|
369
|
+
this.cli.spacer();
|
|
199
370
|
}
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
|
6
|
-
"
|
|
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
|
|
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 } =
|
|
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
|
|
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 } =
|
|
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;
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speedkit/cli",
|
|
3
3
|
"description": "Speed Kit CLI",
|
|
4
|
-
"version": "3.
|
|
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": "^
|
|
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",
|