@speedkit/cli 3.28.0 → 3.29.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
+ # [3.29.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.28.0...v3.29.0) (2026-02-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * **content-encoding:** complete custom text encoding support in the CLI ([afeab5a](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/afeab5a7add0e967a7ddafa8079e769ad668fac8))
7
+ * **prewarm:** improve prewarming speed by using more concurrent workers ([40b57ce](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/40b57ce5aaca260a6aff6549e764a3c240afc2e3))
8
+
1
9
  # [3.28.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v3.27.0...v3.28.0) (2026-02-20)
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/3.28.0 linux-x64 node-v20.20.0
24
+ @speedkit/cli/3.29.0 linux-x64 node-v20.20.0
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -15,7 +15,7 @@ export declare class DocumentHandlerServer {
15
15
  private documentHandlerConfigCode;
16
16
  private database;
17
17
  constructor(customerConfig: CustomerConfig, files: FileListInterface, bundler: BundleService, cli: CliService, nodeModulesPath: string, verboseLevel?: boolean, entityManager?: EntityManager);
18
- transform(html: string, variation: string, url: string, headers: {
18
+ transform(contentType: string, html: string, variation: string, url: string, headers: {
19
19
  [name: string]: string;
20
20
  }): Promise<DocumentHandlerResponse>;
21
21
  private getTransformFunctionWrapper;
@@ -36,11 +36,13 @@ class DocumentHandlerServer {
36
36
  this.verboseLevel = verboseLevel;
37
37
  this.entityManager = entityManager;
38
38
  }
39
- async transform(html, variation, url, headers) {
39
+ async transform(contentType, html, variation, url, headers) {
40
40
  if (!this.documentHandlerCode) {
41
41
  await this.buildDocumentHandler();
42
42
  }
43
- const skInternalRequest = new request_1.Request(url, html, variation, headers);
43
+ const skInternalRequest = new request_1.Request(url, html, variation, headers, {
44
+ "content-type": contentType,
45
+ });
44
46
  const skInternalResponse = new document_handler_response_1.DocumentHandlerResponse();
45
47
  const vmContext = {
46
48
  db: await this.createDataBaseMock(),
@@ -6,13 +6,13 @@ class DocumentHandlerResponse {
6
6
  body = "";
7
7
  resolve;
8
8
  header(key, value) {
9
- this.set(key, value);
9
+ this.set(key.toLowerCase(), value);
10
10
  }
11
11
  setHeader(key, value) {
12
- this.set(key, value);
12
+ this.set(key.toLowerCase(), value);
13
13
  }
14
14
  set(key, value) {
15
- this.headers[key] = value;
15
+ this.headers[key.toLowerCase()] = value;
16
16
  }
17
17
  send(html) {
18
18
  this.body = html;
@@ -4,10 +4,14 @@ type query = {
4
4
  headers: string;
5
5
  };
6
6
  export declare class Request {
7
- private headers;
8
7
  query: query;
9
8
  body: string;
10
- constructor(url: string, body: string, variation: string, headers: {
9
+ headers: {
10
+ [name: string]: string;
11
+ };
12
+ constructor(url: string, body: string, variation: string, originalHeaders: {
13
+ [name: string]: string;
14
+ }, requestHeaders?: {
11
15
  [name: string]: string;
12
16
  });
13
17
  get(key: string): string | null;
@@ -2,16 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Request = void 0;
4
4
  class Request {
5
- headers;
6
5
  query;
7
6
  body;
8
- constructor(url, body, variation, headers) {
9
- this.headers = headers;
10
- this.query = { variation, headers: JSON.stringify(headers), url };
7
+ headers;
8
+ constructor(url, body, variation, originalHeaders, requestHeaders = {}) {
9
+ this.query = { variation, headers: JSON.stringify(originalHeaders), url };
11
10
  this.body = body;
11
+ this.headers = {};
12
+ for (const key of Object.keys(requestHeaders)) {
13
+ this.headers[key.toLowerCase()] = requestHeaders[key];
14
+ }
12
15
  }
13
16
  get(key) {
14
- return this.headers?.[key] || null;
17
+ return this.headers[key.toLowerCase()] || null;
15
18
  }
16
19
  header(key) {
17
20
  return this.get(key);
@@ -52,8 +52,8 @@ class BaqendResponse {
52
52
  value: String(this.contentLength),
53
53
  });
54
54
  }
55
- if (!headers?.find((o) => o.name === "Content-Type")) {
56
- headers.push({ name: "Content-Type", value: String(this.contentType) });
55
+ if (!headers?.find((o) => o.name.toLowerCase() === "content-type")) {
56
+ headers.push({ name: "content-type", value: String(this.contentType) });
57
57
  }
58
58
  headers.push({ name: "via", value: "sk-onboarding" }, { name: "Service-Worker-Allowed", value: "/" });
59
59
  const exposeHeaders = headers
@@ -26,13 +26,15 @@ class DiffAgainstCurrentPage {
26
26
  this.cli.startAction(`transform documents`);
27
27
  if (transform) {
28
28
  // todo check if the currentHTML is already transformed
29
- const currentDocumentHandlerResponse = await this.documentHandlerRuntime.transform(currentHtml, variant, currentUrl, {});
29
+ // TODO: we may need to forward the correct content type from the origin response
30
+ const contentType = "text/html; charset=utf-8";
31
+ const currentDocumentHandlerResponse = await this.documentHandlerRuntime.transform(contentType, currentHtml, variant, currentUrl, {});
30
32
  const currentResponse = currentDocumentHandlerResponse.body;
31
33
  if (Buffer.isBuffer(currentResponse)) {
32
34
  throw new TypeError("cant diff body as buffer");
33
35
  }
34
36
  currentHtml = currentResponse;
35
- const originDocumentHandlerResponse = await this.documentHandlerRuntime.transform(originHtml, variant, currentUrl, {});
37
+ const originDocumentHandlerResponse = await this.documentHandlerRuntime.transform(contentType, originHtml, variant, currentUrl, {});
36
38
  const originResponse = originDocumentHandlerResponse.body;
37
39
  if (Buffer.isBuffer(originResponse)) {
38
40
  throw new TypeError("cant diff body as buffer");
@@ -40,13 +40,17 @@ class RequestDiffService {
40
40
  const htmlWithoutParameter = await this.fetchHtml(urlWithoutParameter);
41
41
  const replaceRegEx = /<meta[^>]*name="baqend:asset_url"[^>]*>/;
42
42
  // <meta name="baqend:asset_url" content="https://www.kik.de/c/damen/damen-bekleidung-shirts-tops?page=7">
43
- const transformedHtmlWithParameterResponse = await this.documentHandlerRuntime.transform(htmlWithParameter, "", urlWithParameter, {});
43
+ const transformedHtmlWithParameterResponse = await this.documentHandlerRuntime.transform(
44
+ // TODO: ma use original response content type
45
+ "text/html; charset=utf-8", htmlWithParameter, "", urlWithParameter, {});
44
46
  if (Buffer.isBuffer(transformedHtmlWithParameterResponse.body)) {
45
47
  throw new TypeError("Can't diff body if it's a buffer");
46
48
  }
47
49
  let transformedHtmlWithParameter = transformedHtmlWithParameterResponse.body;
48
50
  transformedHtmlWithParameter = transformedHtmlWithParameter.replace(replaceRegEx, "");
49
- const transformedHtmlWithoutParameterResponse = await this.documentHandlerRuntime.transform(htmlWithoutParameter, "", urlWithParameter, {});
51
+ const transformedHtmlWithoutParameterResponse = await this.documentHandlerRuntime.transform(
52
+ // TODO: ma use original response content type
53
+ "text/html; charset=utf-8", htmlWithoutParameter, "", urlWithParameter, {});
50
54
  if (Buffer.isBuffer(transformedHtmlWithoutParameterResponse.body)) {
51
55
  throw new TypeError("Can't diff body if it's a buffer");
52
56
  }
@@ -6,9 +6,11 @@ const baqend_response_1 = require("../browser/baqend-response");
6
6
  const safe_1 = require("../../../helpers/safe");
7
7
  const onboarding_model_1 = require("../onboarding-model");
8
8
  const iconv_lite_1 = tslib_1.__importDefault(require("iconv-lite"));
9
- const encoding_japanese_1 = tslib_1.__importDefault(require("encoding-japanese"));
10
- const iconv_lite_2 = tslib_1.__importDefault(require("iconv-lite"));
11
9
  const unsupported_encoding_error_1 = require("../error/unsupported-encoding-error");
10
+ // for none encodable chars use a space, so the broken char is invisible.
11
+ // We are using the same trick in our node.js server
12
+ iconv_lite_1.default.defaultCharSingleByte = " ";
13
+ iconv_lite_1.default.defaultCharUnicode = " ";
12
14
  class VirtualOrestesApp {
13
15
  customerConfig;
14
16
  crawler;
@@ -35,7 +37,7 @@ class VirtualOrestesApp {
35
37
  const response = await this.crawler.fetchRemote(originUrl, variation);
36
38
  // todo check if we can handle more then htmlResponses
37
39
  // only handle html responses
38
- if (!response?.headers?.get("Content-Type")?.includes("html")) {
40
+ if (!response?.headers?.get("content-type")?.includes("html")) {
39
41
  return;
40
42
  }
41
43
  const hasNoStoreFlag = response?.headers
@@ -49,18 +51,14 @@ class VirtualOrestesApp {
49
51
  if (response.status > 300 && response.status < 400) {
50
52
  return this.returnRedirect(customHeaders, response, event);
51
53
  }
52
- const { responseContent, responseEncoding } = await this.getResponseText(response);
54
+ const { responseContent, textEncoding } = await this.getResponseText(response);
53
55
  if (!this.isHtmlContentHeader(response?.headers) ||
54
56
  !this.isValidHtmlContent(responseContent)) {
55
- const customResponse = new baqend_response_1.BaqendResponse(responseContent, `text/html; charset=${responseEncoding}`, customHeaders);
57
+ const customResponse = new baqend_response_1.BaqendResponse(responseContent, `text/html; charset=${textEncoding}`, customHeaders);
56
58
  this.cache.addEntry(event.request.url, customResponse);
57
59
  return customResponse;
58
60
  }
59
- const convertedResponseHeaders = this.convertResponseHeadersToOrestesFormat(response);
60
- const customResponse = await (0, safe_1.safe)(this.documentHandler.transform(responseContent, variation, originUrl, {
61
- ...convertedResponseHeaders,
62
- "content-type": `text/html;charset=${responseEncoding}`,
63
- }));
61
+ const customResponse = await (0, safe_1.safe)(this.documentHandler.transform(`text/html;charset=${textEncoding}`, responseContent, variation, originUrl, this.convertResponseHeadersToOrestesFormat(response)));
64
62
  if (customResponse.success === false) {
65
63
  return this.returnErrorResponse(customResponse, customHeaders);
66
64
  }
@@ -81,9 +79,11 @@ class VirtualOrestesApp {
81
79
  });
82
80
  customHeaders.push({
83
81
  name: "x-sk-detected-encoding",
84
- value: responseEncoding,
82
+ value: textEncoding,
85
83
  });
86
- const baqendResponse = new baqend_response_1.BaqendResponse(skResponse.body, response?.headers.get("Content-Type"), [...customHeaders, ...originHeaders]);
84
+ const baqendResponse = new baqend_response_1.BaqendResponse(Buffer.isBuffer(skResponse.body)
85
+ ? skResponse.body
86
+ : iconv_lite_1.default.encode(skResponse.body, textEncoding), skResponse.headers["content-type"] ?? `text/html;charset=${textEncoding}`, [...customHeaders, ...originHeaders]);
87
87
  this.cache.addEntry(event.request.url, baqendResponse);
88
88
  return baqendResponse;
89
89
  }
@@ -91,27 +91,26 @@ class VirtualOrestesApp {
91
91
  const buffer = Buffer.from(await response.arrayBuffer());
92
92
  const contentType = response?.headers.get("content-type") || "";
93
93
  const charsetRegex = /charset=([^\s"(),./:;<=>?@[\]]*)/i;
94
- const encodingFromHeader = contentType && charsetRegex.test(contentType)
94
+ const encodingFromHeader = (contentType && charsetRegex.test(contentType)
95
95
  ? charsetRegex.exec(contentType)[1]
96
- : null;
97
- const encodingFromBuffer = encoding_japanese_1.default.detect(buffer);
98
- const encodedBody = iconv_lite_2.default
99
- .decode(buffer, encodingFromHeader ?? "utf-8")
100
- .toString();
96
+ : null) ?? "utf-8";
97
+ if (!iconv_lite_1.default.encodingExists(encodingFromHeader)) {
98
+ throw new unsupported_encoding_error_1.UnsupportedEncodingError(`encoding is not supported by cli: "${encodingFromHeader}"`);
99
+ }
100
+ let encodedBody = iconv_lite_1.default.decode(buffer, encodingFromHeader);
101
101
  const metaSelector = onboarding_model_1.META_CONTENT_TYPE_REGEX;
102
102
  const encodingFromBodyMeta = metaSelector.test(encodedBody)
103
103
  ? metaSelector.exec(encodedBody)[1]
104
- : "";
105
- const detectedEncoding = encodingFromHeader ||
106
- encodingFromBodyMeta ||
107
- encodingFromBuffer ||
108
- "utf-8";
109
- if (!iconv_lite_2.default.encodingExists(detectedEncoding)) {
110
- throw new unsupported_encoding_error_1.UnsupportedEncodingError(`encoding is not supported by cli: "${detectedEncoding}"`);
104
+ : null;
105
+ if (encodingFromBodyMeta && encodingFromBodyMeta !== encodingFromHeader) {
106
+ if (!iconv_lite_1.default.encodingExists(encodingFromBodyMeta)) {
107
+ throw new unsupported_encoding_error_1.UnsupportedEncodingError(`encoding is not supported by cli: "${encodingFromBodyMeta}"`);
108
+ }
109
+ encodedBody = iconv_lite_1.default.decode(buffer, encodingFromBodyMeta);
111
110
  }
112
111
  return {
113
- responseContent: iconv_lite_1.default.decode(Buffer.from(buffer), detectedEncoding),
114
- responseEncoding: detectedEncoding,
112
+ responseContent: encodedBody,
113
+ textEncoding: encodingFromBodyMeta ?? encodingFromHeader,
115
114
  };
116
115
  }
117
116
  returnErrorResponse(customResponse, customHeaders) {
@@ -75,7 +75,7 @@ class PreWarmService {
75
75
  let batch = [];
76
76
  let completed = 0;
77
77
  let lastTime = Date.now();
78
- const workers = Array.from({ length: 10 }).map(async () => {
78
+ const workers = Array.from({ length: 100 }).map(async () => {
79
79
  for (;;) {
80
80
  const { done, value } = iterator.next();
81
81
  if (done) {
@@ -731,5 +731,5 @@
731
731
  ]
732
732
  }
733
733
  },
734
- "version": "3.28.0"
734
+ "version": "3.29.0"
735
735
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "3.28.0",
4
+ "version": "3.29.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"
@@ -67,6 +67,7 @@
67
67
  "dependencies": {
68
68
  "@aws-sdk/client-athena": "^3.529",
69
69
  "@inquirer/prompts": "^3.3.2",
70
+ "@nats-io/transport-node": "^3.3.0",
70
71
  "@oclif/core": "^3.23",
71
72
  "@oclif/plugin-autocomplete": "^3.0",
72
73
  "@oclif/plugin-help": "^6.2.19",
@@ -90,7 +91,7 @@
90
91
  "fs-extra": "^11.2.0",
91
92
  "handlebars": "^4.7.8",
92
93
  "htmlparser2": "8.0.2",
93
- "iconv-lite": "^0.6.3",
94
+ "iconv-lite": "^0.7.2",
94
95
  "json2csv": "^6.0.0-alpha.2",
95
96
  "node-fetch": "^2.7.0",
96
97
  "nypm": "^0.6.2",