@speedkit/cli 4.1.0 → 4.2.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,11 @@
1
+ # [4.2.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.1.0...v4.2.0) (2026-05-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * **onboarding:** local mode ([0a36f37](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/0a36f376d27f6bb369d38f073e5a5e5811cd1e11))
7
+ * **onboarding:** local mode ([e3e44ef](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/e3e44ef55bd7c6f71e776d978ce5cbd3fcf117e8))
8
+
1
9
  # [4.1.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.0.0...v4.1.0) (2026-05-06)
2
10
 
3
11
 
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/4.1.0 linux-x64 node-v22.22.2
24
+ @speedkit/cli/4.2.0 linux-x64 node-v22.22.2
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -0,0 +1 @@
1
+ export declare function javaRegexToJs(javaRegex: string): RegExp | undefined;
@@ -0,0 +1,21 @@
1
+ // Converts a Java-style regex (e.g. with leading `(?i)` inline flags) into a
2
+ // JS RegExp. Named groups `(?<name>...)` are already supported in JS.
3
+ export function javaRegexToJs(javaRegex) {
4
+ let pattern = javaRegex;
5
+ let flags = "";
6
+ const inlineFlags = pattern.match(/^\(\?([a-zA-Z]+)\)/);
7
+ if (inlineFlags) {
8
+ pattern = pattern.slice(inlineFlags[0].length);
9
+ for (const flag of inlineFlags[1]) {
10
+ if ("imsu".includes(flag) && !flags.includes(flag)) {
11
+ flags += flag;
12
+ }
13
+ }
14
+ }
15
+ try {
16
+ return new RegExp(pattern, flags);
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
@@ -8,6 +8,43 @@ export declare class Crawler {
8
8
  constructor(cli: CliService, agentConfig: Agent, speedKitServerConfig?: SpeedKitServerConfig);
9
9
  fetchRemote(originUrl: string, variation: string): Promise<Response>;
10
10
  private setDefaultUserAgent;
11
+ /**
12
+ * Mirrors how the Baqend asset server applies the `variations` block from
13
+ * the Speed Kit server config to outgoing origin fetches. The cookie /
14
+ * user-agent / etc. headers configured per variation must reach the origin
15
+ * so that the response matches what the variation is supposed to represent
16
+ * (e.g. a logged-in user, a specific store, a mobile device).
17
+ *
18
+ * Resolution order (matches Orestes server behavior):
19
+ * 1. The wildcard `"*"` variation (if any) — its headers always apply.
20
+ * 2. First-match-wins iteration over the remaining variations: for each
21
+ * entry in declaration order, try an exact key match against
22
+ * `variationParameter` first, then fall back to its Java-style
23
+ * `regex` (e.g. `(?i)desktop-fmarkt-variant-(?<storeId>.*)`). The
24
+ * first variation that matches by either means is applied — exact
25
+ * matches do NOT take priority over regex matches in earlier
26
+ * entries. When matched via regex, named capture groups are
27
+ * substituted into header values via `${name}` placeholders, so a
28
+ * cookie like `fmarktcookie=${storeId}` becomes
29
+ * `fmarktcookie=2879130`.
30
+ * 3. Origin-level basic auth fallback (only if no variation matched).
31
+ */
11
32
  private prepareOriginRequest;
33
+ /**
34
+ * Replaces `${name}` placeholders in each header value with the matching
35
+ * named capture group from a regex match. Used by `prepareOriginRequest`
36
+ * to materialize parameterized variation headers — e.g. given:
37
+ *
38
+ * headers = { cookie: "fmarktcookie=${storeId}" }
39
+ * groups = { storeId: "2879130" }
40
+ *
41
+ * the result is `{ cookie: "fmarktcookie=2879130" }`.
42
+ *
43
+ * Placeholders that don't match a captured group are left intact rather
44
+ * than replaced with `undefined`, so a misconfigured variation still
45
+ * produces a recognisable header value instead of a silent corruption.
46
+ * Non-string header values are passed through unchanged.
47
+ */
48
+ private substituteNamedGroups;
12
49
  private prepareAuthRequest;
13
50
  }
@@ -1,5 +1,6 @@
1
1
  import { USER_AGENT } from "../onboarding-model.js";
2
2
  import { safe } from "../../../helpers/safe.js";
3
+ import { javaRegexToJs } from "../../../helpers/java-regex.js";
3
4
  import { randomUUID } from "node:crypto";
4
5
  export class Crawler {
5
6
  cli;
@@ -41,30 +42,110 @@ export class Crawler {
41
42
  }
42
43
  return USER_AGENT.DESKTOP;
43
44
  }
45
+ /**
46
+ * Mirrors how the Baqend asset server applies the `variations` block from
47
+ * the Speed Kit server config to outgoing origin fetches. The cookie /
48
+ * user-agent / etc. headers configured per variation must reach the origin
49
+ * so that the response matches what the variation is supposed to represent
50
+ * (e.g. a logged-in user, a specific store, a mobile device).
51
+ *
52
+ * Resolution order (matches Orestes server behavior):
53
+ * 1. The wildcard `"*"` variation (if any) — its headers always apply.
54
+ * 2. First-match-wins iteration over the remaining variations: for each
55
+ * entry in declaration order, try an exact key match against
56
+ * `variationParameter` first, then fall back to its Java-style
57
+ * `regex` (e.g. `(?i)desktop-fmarkt-variant-(?<storeId>.*)`). The
58
+ * first variation that matches by either means is applied — exact
59
+ * matches do NOT take priority over regex matches in earlier
60
+ * entries. When matched via regex, named capture groups are
61
+ * substituted into header values via `${name}` placeholders, so a
62
+ * cookie like `fmarktcookie=${storeId}` becomes
63
+ * `fmarktcookie=2879130`.
64
+ * 3. Origin-level basic auth fallback (only if no variation matched).
65
+ */
44
66
  prepareOriginRequest(originUrl, variationParameter, init) {
45
67
  if (!this.speedKitServerConfig) {
46
68
  return init;
47
69
  }
48
- if (this.speedKitServerConfig.speedKit.variations?.["*"]) {
49
- const config = this.speedKitServerConfig.speedKit.variations["*"];
70
+ const variations = this.speedKitServerConfig.speedKit.variations;
71
+ // Wildcard variation headers apply to every request, regardless of which
72
+ // specific variation (if any) is requested.
73
+ if (variations?.["*"]) {
74
+ const config = variations["*"];
50
75
  init.headers = { ...init.headers, ...config.headers };
51
76
  }
52
77
  if (!variationParameter) {
53
78
  return init;
54
79
  }
55
- for (const variationKey in this.speedKitServerConfig.speedKit.variations) {
80
+ // First-match-wins over the variations in declaration order. Each entry
81
+ // can match by exact key or by its Java-style `regex` field; whichever
82
+ // entry matches first applies its headers and short-circuits the rest.
83
+ for (const variationKey in variations) {
84
+ if (variationKey === "*") {
85
+ continue;
86
+ }
87
+ const variationConfig = variations[variationKey];
56
88
  if (variationParameter === variationKey) {
57
- const variationConfig = this.speedKitServerConfig.speedKit.variations[variationKey];
58
89
  init.headers = { ...init.headers, ...variationConfig.headers };
59
90
  return init;
60
91
  }
92
+ if (!variationConfig.regex) {
93
+ continue;
94
+ }
95
+ const regex = javaRegexToJs(variationConfig.regex);
96
+ if (!regex) {
97
+ // Unsupported Java regex construct — skip rather than crash so later
98
+ // variations still get a chance to match.
99
+ continue;
100
+ }
101
+ const match = variationParameter.match(regex);
102
+ if (!match) {
103
+ continue;
104
+ }
105
+ init.headers = {
106
+ ...init.headers,
107
+ ...this.substituteNamedGroups(variationConfig.headers, match.groups),
108
+ };
109
+ return init;
61
110
  }
111
+ // No variation matched: fall back to origin basic auth if configured.
62
112
  const authentication = this.prepareAuthRequest(originUrl);
63
113
  if (authentication) {
64
114
  init.headers["Authorization"] = authentication;
65
115
  }
66
116
  return init;
67
117
  }
118
+ /**
119
+ * Replaces `${name}` placeholders in each header value with the matching
120
+ * named capture group from a regex match. Used by `prepareOriginRequest`
121
+ * to materialize parameterized variation headers — e.g. given:
122
+ *
123
+ * headers = { cookie: "fmarktcookie=${storeId}" }
124
+ * groups = { storeId: "2879130" }
125
+ *
126
+ * the result is `{ cookie: "fmarktcookie=2879130" }`.
127
+ *
128
+ * Placeholders that don't match a captured group are left intact rather
129
+ * than replaced with `undefined`, so a misconfigured variation still
130
+ * produces a recognisable header value instead of a silent corruption.
131
+ * Non-string header values are passed through unchanged.
132
+ */
133
+ substituteNamedGroups(headers, groups) {
134
+ if (!headers) {
135
+ return {};
136
+ }
137
+ if (!groups) {
138
+ return { ...headers };
139
+ }
140
+ const result = {};
141
+ for (const [key, value] of Object.entries(headers)) {
142
+ result[key] =
143
+ typeof value === "string"
144
+ ? value.replace(/\$\{(\w+)}/g, (match, name) => groups[name] ?? match)
145
+ : value;
146
+ }
147
+ return result;
148
+ }
68
149
  prepareAuthRequest(originUrl) {
69
150
  if (!this.speedKitServerConfig ||
70
151
  this.speedKitServerConfig.speedKit?.authentications?.length < 1) {
@@ -751,5 +751,5 @@
751
751
  ]
752
752
  }
753
753
  },
754
- "version": "4.1.0"
754
+ "version": "4.2.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": "4.1.0",
4
+ "version": "4.2.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"