@speedkit/cli 4.23.0 → 4.23.2
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 +16 -0
- package/README.md +1 -1
- package/dist/hooks/init/dns-result-order.d.ts +12 -0
- package/dist/hooks/init/dns-result-order.js +14 -0
- package/dist/hooks/init/dns-result-order.spec.d.ts +1 -0
- package/dist/hooks/init/dns-result-order.spec.js +15 -0
- package/dist/services/document-handler-runtime/context/document-handler-runtime-context.d.ts +1 -0
- package/dist/services/document-handler-runtime/context/document-handler-runtime-context.js +4 -0
- package/dist/services/document-handler-runtime/document-handler-abort.d.ts +58 -0
- package/dist/services/document-handler-runtime/document-handler-abort.js +66 -0
- package/dist/services/document-handler-runtime/document-handler-abort.spec.d.ts +1 -0
- package/dist/services/document-handler-runtime/document-handler-abort.spec.js +84 -0
- package/dist/services/document-handler-runtime/document-handler-server.js +2 -1
- package/dist/services/document-handler-runtime/server/request.d.ts +1 -0
- package/dist/services/document-handler-runtime/server/request.js +7 -1
- package/dist/services/document-handler-runtime/templates/execute-document-handler.js +2 -0
- package/dist/services/document-handler-runtime/templates/orestes-mock.js +2 -0
- package/dist/services/document-handler-runtime/templates/test.js +6 -1
- package/dist/services/onboarding/error/document-handler-transform-error.d.ts +6 -0
- package/dist/services/onboarding/error/document-handler-transform-error.js +8 -0
- package/dist/services/onboarding/virtual-orestes-app/index.d.ts +8 -0
- package/dist/services/onboarding/virtual-orestes-app/index.js +21 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [4.23.2](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.23.1...v4.23.2) (2026-08-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **onboarding:** answer a Document Handler abort with 400, not 500 ([c4473ee](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/c4473eee65011c260caddbd073098cb21e652ae8))
|
|
7
|
+
* **onboarding:** send the abort as errorcode;desc=303, not an invented cause ([0e3beb0](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/0e3beb034ff5a47a4acffcb86191fc9a8f5683ea))
|
|
8
|
+
|
|
9
|
+
## [4.23.1](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.23.0...v4.23.1) (2026-08-21)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **cli:** resolve IPv4 addresses before IPv6 ([057fa59](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/057fa59bb06f3d49af5b8631681e7850a062d585))
|
|
15
|
+
* **onboarding:** allow one hour Shopify staleness for local runs ([4e31124](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/4e31124aa3c217f698ef3bb48a51c83fb5395ca4))
|
|
16
|
+
|
|
1
17
|
# [4.23.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.22.1...v4.23.0) (2026-08-21)
|
|
2
18
|
|
|
3
19
|
|
package/README.md
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Hook } from "@oclif/core";
|
|
2
|
+
/**
|
|
3
|
+
* Puts IPv4 addresses first for every lookup the CLI makes. This is the runtime equivalent of
|
|
4
|
+
* `NODE_OPTIONS=--dns-result-order=ipv4first`, so no entrypoint has to export that variable.
|
|
5
|
+
*
|
|
6
|
+
* Node resolves `localhost` to `::1` first. WSL in mirrored networking mode shares `localhost`
|
|
7
|
+
* between Windows and Linux, but Chrome's remote debugging port and the local dev server both
|
|
8
|
+
* listen on IPv4 only. A lookup that answers `::1` therefore never reaches the listener on the
|
|
9
|
+
* other side of the boundary, and the connection fails instead of crossing over.
|
|
10
|
+
*/
|
|
11
|
+
declare const hook: Hook<"init">;
|
|
12
|
+
export default hook;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { setDefaultResultOrder } from "node:dns";
|
|
2
|
+
/**
|
|
3
|
+
* Puts IPv4 addresses first for every lookup the CLI makes. This is the runtime equivalent of
|
|
4
|
+
* `NODE_OPTIONS=--dns-result-order=ipv4first`, so no entrypoint has to export that variable.
|
|
5
|
+
*
|
|
6
|
+
* Node resolves `localhost` to `::1` first. WSL in mirrored networking mode shares `localhost`
|
|
7
|
+
* between Windows and Linux, but Chrome's remote debugging port and the local dev server both
|
|
8
|
+
* listen on IPv4 only. A lookup that answers `::1` therefore never reaches the listener on the
|
|
9
|
+
* other side of the boundary, and the connection fails instead of crossing over.
|
|
10
|
+
*/
|
|
11
|
+
const hook = async () => {
|
|
12
|
+
setDefaultResultOrder("ipv4first");
|
|
13
|
+
};
|
|
14
|
+
export default hook;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { after, describe, it } from "mocha";
|
|
3
|
+
import { getDefaultResultOrder, setDefaultResultOrder } from "node:dns";
|
|
4
|
+
import hook from "./dns-result-order.js";
|
|
5
|
+
describe("dns-result-order hook", () => {
|
|
6
|
+
const previousOrder = getDefaultResultOrder();
|
|
7
|
+
after(() => {
|
|
8
|
+
setDefaultResultOrder(previousOrder);
|
|
9
|
+
});
|
|
10
|
+
it("puts IPv4 addresses first", async () => {
|
|
11
|
+
setDefaultResultOrder("verbatim");
|
|
12
|
+
await hook.call(null, {});
|
|
13
|
+
expect(getDefaultResultOrder()).to.equal("ipv4first");
|
|
14
|
+
});
|
|
15
|
+
});
|
package/dist/services/document-handler-runtime/context/document-handler-runtime-context.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const LOCAL_DYNAMIC_FETCHER_CONFIG = "config_dynamicBlocks";
|
|
|
6
6
|
export declare const LOCAL_STYLES = "config_dynamicStyles";
|
|
7
7
|
export declare const LATEST_DOCUMENT_HANDLER_URL = "https://www.baqend.com/speed-kit-handler/latest/DocumentHandler.js";
|
|
8
8
|
export declare const LATEST_DYNAMIC_FETCHER_URL = "https://www.baqend.com/speed-kit/latest/dynamic-fetcher.js";
|
|
9
|
+
export declare const LOCAL_TRIGGERED_BY = "STREAM:QA";
|
|
9
10
|
export declare const TEST_DOCUMENT_HANDLER_FILE_NAME = "testDocumentHandler";
|
|
10
11
|
export declare const TEST_DATABASE_MOCK_FILE_NAME = "database-mock";
|
|
11
12
|
export declare const TEST_ORESTES_MOCK_FILE_NAME = "orestes-mock";
|
|
@@ -6,6 +6,10 @@ export const LOCAL_DYNAMIC_FETCHER_CONFIG = "config_dynamicBlocks";
|
|
|
6
6
|
export const LOCAL_STYLES = "config_dynamicStyles";
|
|
7
7
|
export const LATEST_DOCUMENT_HANDLER_URL = "https://www.baqend.com/speed-kit-handler/latest/DocumentHandler.js";
|
|
8
8
|
export const LATEST_DYNAMIC_FETCHER_URL = "https://www.baqend.com/speed-kit/latest/dynamic-fetcher.js";
|
|
9
|
+
// Local runs have no persistent cache, so an origin HTML is served straight to the developer and may
|
|
10
|
+
// legitimately be older than the default five minutes. STREAM:QA relaxes the document handler's
|
|
11
|
+
// Shopify staleness check to one hour, see ONE_HOUR_STALENESS_TRIGGERS in document-handler.
|
|
12
|
+
export const LOCAL_TRIGGERED_BY = "STREAM:QA";
|
|
9
13
|
export const TEST_DOCUMENT_HANDLER_FILE_NAME = "testDocumentHandler";
|
|
10
14
|
export const TEST_DATABASE_MOCK_FILE_NAME = "database-mock";
|
|
11
15
|
export const TEST_ORESTES_MOCK_FILE_NAME = "orestes-mock";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baqend's runtime exposes `Abort` as a global to Document Handler code. Throwing it
|
|
3
|
+
* signals a deliberate stop — a `blacklistHTML` guard, or a pre-render that came back
|
|
4
|
+
* non-200 — rather than a fault. Baqend extracts the message and answers a Bad Request,
|
|
5
|
+
* and the service worker falls back to origin.
|
|
6
|
+
*
|
|
7
|
+
* The shape follows `typings/Abort/index.d.ts` in the document-handler repository:
|
|
8
|
+
* `name` is always `"Abort"`, plus a `status` and an optional `data` payload.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DocumentHandlerAbort<T = unknown> extends Error {
|
|
11
|
+
readonly name = "Abort";
|
|
12
|
+
status: number;
|
|
13
|
+
data?: T;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Document Handler abort.
|
|
16
|
+
*
|
|
17
|
+
* @param message - Reason the Document Handler stopped.
|
|
18
|
+
* @param data - Optional abort payload.
|
|
19
|
+
*/
|
|
20
|
+
constructor(message?: string, data?: T);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Status code Baqend answers a Document Handler abort with. It must stay below 500:
|
|
24
|
+
* the service worker treats 5xx from the Asset API as an infrastructure failure and
|
|
25
|
+
* disconnects Speed Kit, while a client error is classified from `server-timing`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ABORT_STATUS_CODE = 400;
|
|
28
|
+
/**
|
|
29
|
+
* Speed Kit error code for a Document Handler abort. Baqend reports the numeric code,
|
|
30
|
+
* not a cause name, and the backend resolves 303 to `ServerDocumentHandlerAbort`.
|
|
31
|
+
*
|
|
32
|
+
* Sibling code 314 is `ServerDocumentHandlerError`, which is what a Document Handler
|
|
33
|
+
* that throws an ordinary error produces. Keeping the two apart is the point of this
|
|
34
|
+
* module.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ABORT_ERROR_CODE = "303";
|
|
37
|
+
/**
|
|
38
|
+
* `server-timing` value that makes the service worker classify a response as an abort.
|
|
39
|
+
*
|
|
40
|
+
* Only the `errorcode` metric is sent, which is what Baqend sends. The service worker
|
|
41
|
+
* reads `errorCause` and `errorcode` separately and builds the cause as
|
|
42
|
+
* `` `${errorCause?.desc || ClientError}${errorCode ? `-${errorCode.desc}` : ''}` ``
|
|
43
|
+
* (`AssetAPIHandler.ts:200-205`). With no `errorCause` metric it therefore reports
|
|
44
|
+
* `ClientError-303`, and the cause name is resolved from the code further downstream.
|
|
45
|
+
* Sending an `errorCause` metric instead would invent a cause the backend never sends.
|
|
46
|
+
*/
|
|
47
|
+
export declare const ABORT_SERVER_TIMING = "errorcode;desc=303";
|
|
48
|
+
/**
|
|
49
|
+
* Whether an error is a Document Handler abort rather than a failure.
|
|
50
|
+
*
|
|
51
|
+
* Checks the `name` marker instead of `instanceof`: the error is constructed inside a
|
|
52
|
+
* `vm` context and is re-wrapped by `DocumentHandlerTransformError` on the way out, so
|
|
53
|
+
* neither the realm nor the prototype chain survives reliably. `isAbort` covers the
|
|
54
|
+
* wrapped case.
|
|
55
|
+
*
|
|
56
|
+
* @param error - Error to classify.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isDocumentHandlerAbort(error?: Error): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baqend's runtime exposes `Abort` as a global to Document Handler code. Throwing it
|
|
3
|
+
* signals a deliberate stop — a `blacklistHTML` guard, or a pre-render that came back
|
|
4
|
+
* non-200 — rather than a fault. Baqend extracts the message and answers a Bad Request,
|
|
5
|
+
* and the service worker falls back to origin.
|
|
6
|
+
*
|
|
7
|
+
* The shape follows `typings/Abort/index.d.ts` in the document-handler repository:
|
|
8
|
+
* `name` is always `"Abort"`, plus a `status` and an optional `data` payload.
|
|
9
|
+
*/
|
|
10
|
+
export class DocumentHandlerAbort extends Error {
|
|
11
|
+
name = "Abort";
|
|
12
|
+
status = ABORT_STATUS_CODE;
|
|
13
|
+
data;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Document Handler abort.
|
|
16
|
+
*
|
|
17
|
+
* @param message - Reason the Document Handler stopped.
|
|
18
|
+
* @param data - Optional abort payload.
|
|
19
|
+
*/
|
|
20
|
+
constructor(message, data) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.data = data;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Status code Baqend answers a Document Handler abort with. It must stay below 500:
|
|
27
|
+
* the service worker treats 5xx from the Asset API as an infrastructure failure and
|
|
28
|
+
* disconnects Speed Kit, while a client error is classified from `server-timing`.
|
|
29
|
+
*/
|
|
30
|
+
export const ABORT_STATUS_CODE = 400;
|
|
31
|
+
/**
|
|
32
|
+
* Speed Kit error code for a Document Handler abort. Baqend reports the numeric code,
|
|
33
|
+
* not a cause name, and the backend resolves 303 to `ServerDocumentHandlerAbort`.
|
|
34
|
+
*
|
|
35
|
+
* Sibling code 314 is `ServerDocumentHandlerError`, which is what a Document Handler
|
|
36
|
+
* that throws an ordinary error produces. Keeping the two apart is the point of this
|
|
37
|
+
* module.
|
|
38
|
+
*/
|
|
39
|
+
export const ABORT_ERROR_CODE = "303";
|
|
40
|
+
/**
|
|
41
|
+
* `server-timing` value that makes the service worker classify a response as an abort.
|
|
42
|
+
*
|
|
43
|
+
* Only the `errorcode` metric is sent, which is what Baqend sends. The service worker
|
|
44
|
+
* reads `errorCause` and `errorcode` separately and builds the cause as
|
|
45
|
+
* `` `${errorCause?.desc || ClientError}${errorCode ? `-${errorCode.desc}` : ''}` ``
|
|
46
|
+
* (`AssetAPIHandler.ts:200-205`). With no `errorCause` metric it therefore reports
|
|
47
|
+
* `ClientError-303`, and the cause name is resolved from the code further downstream.
|
|
48
|
+
* Sending an `errorCause` metric instead would invent a cause the backend never sends.
|
|
49
|
+
*/
|
|
50
|
+
export const ABORT_SERVER_TIMING = `errorcode;desc=${ABORT_ERROR_CODE}`;
|
|
51
|
+
/**
|
|
52
|
+
* Whether an error is a Document Handler abort rather than a failure.
|
|
53
|
+
*
|
|
54
|
+
* Checks the `name` marker instead of `instanceof`: the error is constructed inside a
|
|
55
|
+
* `vm` context and is re-wrapped by `DocumentHandlerTransformError` on the way out, so
|
|
56
|
+
* neither the realm nor the prototype chain survives reliably. `isAbort` covers the
|
|
57
|
+
* wrapped case.
|
|
58
|
+
*
|
|
59
|
+
* @param error - Error to classify.
|
|
60
|
+
*/
|
|
61
|
+
export function isDocumentHandlerAbort(error) {
|
|
62
|
+
if (!error) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return (error.name === "Abort" || error.isAbort === true);
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { ABORT_ERROR_CODE, ABORT_SERVER_TIMING, ABORT_STATUS_CODE, DocumentHandlerAbort, isDocumentHandlerAbort, } from "./document-handler-abort.js";
|
|
4
|
+
import { DocumentHandlerTransformError } from "../onboarding/error/document-handler-transform-error.js";
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors the service worker's `parseServerTimingHeader`, so the header this module
|
|
7
|
+
* emits is asserted against the parser that has to read it.
|
|
8
|
+
*/
|
|
9
|
+
function parseServerTiming(header) {
|
|
10
|
+
const metrics = {};
|
|
11
|
+
header.split(",").forEach((metric) => {
|
|
12
|
+
const key = metric.includes(";") ? metric.trim().split(";")[0] : metric;
|
|
13
|
+
const descMatch = metric.match(/desc=([^;]*)/);
|
|
14
|
+
metrics[key] = { desc: descMatch ? descMatch[1] : "" };
|
|
15
|
+
});
|
|
16
|
+
return metrics;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mirrors how the service worker builds the response cause in
|
|
20
|
+
* `AssetAPIHandler.ts:200-205`, to prove which cause the emitted header produces.
|
|
21
|
+
*/
|
|
22
|
+
function composeResponseCause(header) {
|
|
23
|
+
const metrics = parseServerTiming(header);
|
|
24
|
+
const errorCode = metrics.errorcode;
|
|
25
|
+
const errorCodeString = errorCode ? `-${errorCode.desc}` : "";
|
|
26
|
+
return `${metrics.errorCause?.desc || "ClientError"}${errorCodeString}`;
|
|
27
|
+
}
|
|
28
|
+
describe("DocumentHandlerAbort", () => {
|
|
29
|
+
it("matches the runtime contract the Document Handler expects", () => {
|
|
30
|
+
const abort = new DocumentHandlerAbort("blacklisted", { url: "/a" });
|
|
31
|
+
expect(abort.name).to.equal("Abort");
|
|
32
|
+
expect(abort.message).to.equal("blacklisted");
|
|
33
|
+
expect(abort.status).to.equal(ABORT_STATUS_CODE);
|
|
34
|
+
expect(abort.data).to.deep.equal({ url: "/a" });
|
|
35
|
+
expect(abort).to.be.instanceOf(Error);
|
|
36
|
+
});
|
|
37
|
+
it("keeps the abort status below the range that disconnects Speed Kit", () => {
|
|
38
|
+
expect(ABORT_STATUS_CODE).to.be.lessThan(500);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("isDocumentHandlerAbort", () => {
|
|
42
|
+
it("recognises an abort by its name marker", () => {
|
|
43
|
+
expect(isDocumentHandlerAbort(new DocumentHandlerAbort("stop"))).to.equal(true);
|
|
44
|
+
});
|
|
45
|
+
it("recognises an abort that only carries the wrapped flag", () => {
|
|
46
|
+
const wrapped = Object.assign(new Error("stop"), { isAbort: true });
|
|
47
|
+
expect(isDocumentHandlerAbort(wrapped)).to.equal(true);
|
|
48
|
+
});
|
|
49
|
+
it("does not treat an ordinary failure as an abort", () => {
|
|
50
|
+
expect(isDocumentHandlerAbort(new Error("boom"))).to.equal(false);
|
|
51
|
+
expect(isDocumentHandlerAbort(undefined)).to.equal(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("DocumentHandlerTransformError", () => {
|
|
55
|
+
it("carries the abort marker across the wrapper", () => {
|
|
56
|
+
const wrapped = new DocumentHandlerTransformError(new DocumentHandlerAbort("No category grid"));
|
|
57
|
+
expect(wrapped.isAbort).to.equal(true);
|
|
58
|
+
expect(wrapped.message).to.equal("No category grid");
|
|
59
|
+
expect(isDocumentHandlerAbort(wrapped)).to.equal(true);
|
|
60
|
+
});
|
|
61
|
+
it("leaves an ordinary failure unmarked", () => {
|
|
62
|
+
const wrapped = new DocumentHandlerTransformError(new Error("boom"));
|
|
63
|
+
expect(wrapped.isAbort).to.equal(false);
|
|
64
|
+
expect(isDocumentHandlerAbort(wrapped)).to.equal(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("ABORT_SERVER_TIMING", () => {
|
|
68
|
+
it("sends the error code under the metric name Baqend uses", () => {
|
|
69
|
+
const metrics = parseServerTiming(ABORT_SERVER_TIMING);
|
|
70
|
+
expect(metrics.errorcode.desc).to.equal(ABORT_ERROR_CODE);
|
|
71
|
+
});
|
|
72
|
+
it("sends no errorCause metric, which Baqend does not send either", () => {
|
|
73
|
+
expect(parseServerTiming(ABORT_SERVER_TIMING).errorCause).to.equal(undefined);
|
|
74
|
+
});
|
|
75
|
+
it("makes the service worker report ClientError-303", () => {
|
|
76
|
+
expect(composeResponseCause(ABORT_SERVER_TIMING)).to.equal("ClientError-303");
|
|
77
|
+
});
|
|
78
|
+
it("is matched by the prewarm error-code selector when a metric follows", () => {
|
|
79
|
+
// TIMING_HEADER_ERROR_CODE_SELECTOR expects a trailing comma, as production
|
|
80
|
+
// sends further metrics after the code.
|
|
81
|
+
const [, code] = `${ABORT_SERVER_TIMING},dur=0`.match(/errorcode;desc=([^,]*),/) || [];
|
|
82
|
+
expect(code).to.equal(ABORT_ERROR_CODE);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -8,6 +8,7 @@ import { INTEGRATION_FILES } from "../../models/files.js";
|
|
|
8
8
|
import fetch from "node-fetch";
|
|
9
9
|
import { Request } from "./server/request.js";
|
|
10
10
|
import { DocumentHandlerResponse } from "./server/document-handler-response.js";
|
|
11
|
+
import { DocumentHandlerAbort } from "./document-handler-abort.js";
|
|
11
12
|
import { safe } from "../../helpers/safe.js";
|
|
12
13
|
import { VmEmptyResponseError } from "../onboarding/error/vm-empty-response-error.js";
|
|
13
14
|
import { DocumentHandlerTransformError } from "../onboarding/error/document-handler-transform-error.js";
|
|
@@ -46,7 +47,7 @@ export class DocumentHandlerServer {
|
|
|
46
47
|
skInternalResponse,
|
|
47
48
|
skInternalHtml: "",
|
|
48
49
|
skInternalError: {},
|
|
49
|
-
Abort:
|
|
50
|
+
Abort: DocumentHandlerAbort,
|
|
50
51
|
APP: this.customerConfig.app,
|
|
51
52
|
DYNAMIC_FETCHER: await this.getDynamicFetcher(),
|
|
52
53
|
DYNAMIC_FETCHER_CONFIG: await this.getDynamicFetcherConfig(),
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { LOCAL_TRIGGERED_BY } from "../context/document-handler-runtime-context.js";
|
|
1
2
|
export class Request {
|
|
2
3
|
query;
|
|
3
4
|
body;
|
|
4
5
|
headers;
|
|
5
6
|
constructor(url, body, variation, originalHeaders, requestHeaders = {}) {
|
|
6
|
-
this.query = {
|
|
7
|
+
this.query = {
|
|
8
|
+
variation,
|
|
9
|
+
headers: JSON.stringify(originalHeaders),
|
|
10
|
+
url,
|
|
11
|
+
triggeredBy: LOCAL_TRIGGERED_BY,
|
|
12
|
+
};
|
|
7
13
|
this.body = body;
|
|
8
14
|
this.headers = {};
|
|
9
15
|
for (const key of Object.keys(requestHeaders)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AbstractTemplate from "./abstract-template.js";
|
|
2
|
+
import { LOCAL_TRIGGERED_BY } from "../context/document-handler-runtime-context.js";
|
|
2
3
|
export default class ExecuteDocumentHandler extends AbstractTemplate {
|
|
3
4
|
buildContext(domain, file) {
|
|
4
5
|
return { domain, file };
|
|
@@ -35,6 +36,7 @@ async function run(iterator) {
|
|
|
35
36
|
variation: item.variation || 'DEFAULT',
|
|
36
37
|
headers: item?.headers ? item.headers : JSON.stringify(DEFAULT_HEADERS),
|
|
37
38
|
url: item.url,
|
|
39
|
+
triggeredBy: '${LOCAL_TRIGGERED_BY}',
|
|
38
40
|
}
|
|
39
41
|
let req = {
|
|
40
42
|
query, body: file
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AbstractTemplate from "./abstract-template.js";
|
|
2
|
+
import { LOCAL_TRIGGERED_BY } from "../context/document-handler-runtime-context.js";
|
|
2
3
|
export default class OrestesMock extends AbstractTemplate {
|
|
3
4
|
buildContext() {
|
|
4
5
|
return {};
|
|
@@ -79,6 +80,7 @@ const server = http.createServer(async function (request, response) {
|
|
|
79
80
|
variation: UrlObject.searchParams.has('bqvariation')?UrlObject.searchParams.get('bqvariation'):'DEFAULT',
|
|
80
81
|
headers: '',
|
|
81
82
|
url: url,
|
|
83
|
+
triggeredBy: '${LOCAL_TRIGGERED_BY}',
|
|
82
84
|
}
|
|
83
85
|
let req = {query, body}
|
|
84
86
|
const output = await documentHandler.transform(database,req)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import AbstractTemplate from "./abstract-template.js";
|
|
2
|
+
import { LOCAL_TRIGGERED_BY } from "../context/document-handler-runtime-context.js";
|
|
2
3
|
export default class Test extends AbstractTemplate {
|
|
3
4
|
buildContext(domain, name, timeout = 2000) {
|
|
4
5
|
return { domain, name, timeout };
|
|
@@ -9,7 +10,9 @@ const database = require('./src/database-mock');
|
|
|
9
10
|
const documentHandler = require('./src/testDocumentHandler');
|
|
10
11
|
const fs = require('fs');
|
|
11
12
|
const {resolve} = require("path");
|
|
12
|
-
global.Abort = Error
|
|
13
|
+
global.Abort = class Abort extends Error {
|
|
14
|
+
constructor(message, data) { super(message); this.name = 'Abort'; this.status = 400; this.data = data; }
|
|
15
|
+
};
|
|
13
16
|
|
|
14
17
|
const url = '{{domain}}';
|
|
15
18
|
const file = '{{name}}';
|
|
@@ -25,6 +28,7 @@ describe(\`documentHandler run on \${file}\`, function () {
|
|
|
25
28
|
variation: 'DEFAULT',
|
|
26
29
|
headers: JSON.stringify(DEFAULT_HEADERS),
|
|
27
30
|
url: url,
|
|
31
|
+
triggeredBy: '${LOCAL_TRIGGERED_BY}',
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
let req = {query, body}
|
|
@@ -42,6 +46,7 @@ describe(\`create files to diff for \${file}\`,function (){
|
|
|
42
46
|
variation: 'DEFAULT',
|
|
43
47
|
headers: JSON.stringify(DEFAULT_HEADERS),
|
|
44
48
|
url: url,
|
|
49
|
+
triggeredBy: '${LOCAL_TRIGGERED_BY}',
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
let req = {query, body}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export declare class DocumentHandlerTransformError extends Error {
|
|
2
|
+
/**
|
|
3
|
+
* True when the Document Handler stopped deliberately by throwing `Abort`, so the
|
|
4
|
+
* caller can answer with a client error instead of a 500. The wrapper drops the
|
|
5
|
+
* original prototype, so the flag has to be carried explicitly.
|
|
6
|
+
*/
|
|
7
|
+
readonly isAbort: boolean;
|
|
2
8
|
constructor(error: Error);
|
|
3
9
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { isDocumentHandlerAbort } from "../../document-handler-runtime/document-handler-abort.js";
|
|
1
2
|
export class DocumentHandlerTransformError extends Error {
|
|
3
|
+
/**
|
|
4
|
+
* True when the Document Handler stopped deliberately by throwing `Abort`, so the
|
|
5
|
+
* caller can answer with a client error instead of a 500. The wrapper drops the
|
|
6
|
+
* original prototype, so the flag has to be carried explicitly.
|
|
7
|
+
*/
|
|
8
|
+
isAbort;
|
|
2
9
|
constructor(error) {
|
|
3
10
|
super(error.message);
|
|
4
11
|
this.stack = error.stack;
|
|
5
12
|
this.cause = error.cause;
|
|
13
|
+
this.isAbort = isDocumentHandlerAbort(error);
|
|
6
14
|
}
|
|
7
15
|
}
|
|
@@ -52,6 +52,14 @@ export declare class VirtualOrestesApp {
|
|
|
52
52
|
private recordLocalCacheError;
|
|
53
53
|
private clearLocalCacheError;
|
|
54
54
|
private emitLocalCacheErrors;
|
|
55
|
+
/**
|
|
56
|
+
* Answers a Document Handler abort the way Baqend does: a Bad Request carrying the
|
|
57
|
+
* Speed Kit error code in `server-timing`. The service worker reports
|
|
58
|
+
* `ClientError-303` and falls back to origin, and the backend resolves 303 to
|
|
59
|
+
* `ServerDocumentHandlerAbort`. Answering 500 instead makes the service worker treat
|
|
60
|
+
* the page as an infrastructure failure and disconnect Speed Kit.
|
|
61
|
+
*/
|
|
62
|
+
private returnAbortResponse;
|
|
55
63
|
private returnErrorResponse;
|
|
56
64
|
private convertResponseHeadersToOrestesFormat;
|
|
57
65
|
private returnRedirect;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaqendResponse } from "../browser/baqend-response.js";
|
|
2
|
+
import { ABORT_SERVER_TIMING, ABORT_STATUS_CODE, isDocumentHandlerAbort, } from "../../document-handler-runtime/document-handler-abort.js";
|
|
2
3
|
import { safe } from "../../../helpers/safe.js";
|
|
3
4
|
import { META_CONTENT_TYPE_REGEX, } from "../onboarding-model.js";
|
|
4
5
|
import iconv from "iconv-lite";
|
|
@@ -142,7 +143,10 @@ export class VirtualOrestesApp {
|
|
|
142
143
|
}
|
|
143
144
|
const customResponse = await safe(this.documentHandler.transform(`text/html;charset=${textEncoding}`, responseContent, variation, originUrl, this.convertResponseHeadersToOrestesFormat(response)));
|
|
144
145
|
if (customResponse.success === false) {
|
|
145
|
-
|
|
146
|
+
// An abort is a decision, not a fault, so it must not surface as a cache error.
|
|
147
|
+
if (!isDocumentHandlerAbort(customResponse.errorObj)) {
|
|
148
|
+
this.recordLocalCacheError(originUrl, requestUrl, variation, customResponse);
|
|
149
|
+
}
|
|
146
150
|
return this.returnErrorResponse(customResponse, customHeaders);
|
|
147
151
|
}
|
|
148
152
|
this.clearLocalCacheError(originUrl, variation);
|
|
@@ -231,7 +235,23 @@ export class VirtualOrestesApp {
|
|
|
231
235
|
data: [...this.localCacheErrors.values()],
|
|
232
236
|
});
|
|
233
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Answers a Document Handler abort the way Baqend does: a Bad Request carrying the
|
|
240
|
+
* Speed Kit error code in `server-timing`. The service worker reports
|
|
241
|
+
* `ClientError-303` and falls back to origin, and the backend resolves 303 to
|
|
242
|
+
* `ServerDocumentHandlerAbort`. Answering 500 instead makes the service worker treat
|
|
243
|
+
* the page as an infrastructure failure and disconnect Speed Kit.
|
|
244
|
+
*/
|
|
245
|
+
returnAbortResponse(customResponse, customHeaders) {
|
|
246
|
+
const message = customResponse.error;
|
|
247
|
+
this.cli.writeWarning(`[VirtualOrestes]: Document Handler aborted: ${message}`);
|
|
248
|
+
customHeaders.push({ name: "x-error", value: message }, { name: "server-timing", value: ABORT_SERVER_TIMING });
|
|
249
|
+
return new BaqendResponse(message, null, customHeaders, ABORT_STATUS_CODE);
|
|
250
|
+
}
|
|
234
251
|
returnErrorResponse(customResponse, customHeaders) {
|
|
252
|
+
if (isDocumentHandlerAbort(customResponse.errorObj)) {
|
|
253
|
+
return this.returnAbortResponse(customResponse, customHeaders);
|
|
254
|
+
}
|
|
235
255
|
const message = `Could not transform html: - ${customResponse.error}`;
|
|
236
256
|
this.cli.writeError(`[VirtualOrestes]: ${message}`);
|
|
237
257
|
this.cli.comment(customResponse.errorObj.stack);
|
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": "4.23.
|
|
4
|
+
"version": "4.23.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Baqend.com",
|
|
7
7
|
"email": "info@baqend.com"
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
],
|
|
65
65
|
"oclif": {
|
|
66
66
|
"commands": "./dist/commands",
|
|
67
|
+
"hooks": {
|
|
68
|
+
"init": [
|
|
69
|
+
"./dist/hooks/init/dns-result-order.js"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
67
72
|
"bin": "sk",
|
|
68
73
|
"dirname": "speed-kit-cli",
|
|
69
74
|
"plugins": [
|