@speedkit/cli 4.27.0 → 4.27.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 +14 -0
- package/README.md +46 -14
- package/dist/commands/auto-prewarm.d.ts +8 -3
- package/dist/commands/auto-prewarm.js +16 -7
- package/dist/commands/query/parameter.d.ts +2 -0
- package/dist/commands/query/parameter.js +2 -1
- package/dist/commands/query/prewarm.d.ts +2 -0
- package/dist/commands/query/prewarm.js +2 -1
- package/dist/helpers/clipboard.d.ts +6 -0
- package/dist/helpers/clipboard.js +7 -1
- package/dist/models/cli-parameters.d.ts +40 -1
- package/dist/models/cli-parameters.js +51 -7
- package/dist/services/athena/athena-service-factory.d.ts +8 -1
- package/dist/services/athena/athena-service-factory.js +15 -4
- package/dist/services/athena/athena-service-model.d.ts +17 -0
- package/dist/services/athena/athena-service-model.js +19 -0
- package/dist/services/athena/athena-service.d.ts +21 -1
- package/dist/services/athena/athena-service.js +21 -3
- package/dist/services/athena/athena-service.spec.js +36 -1
- package/dist/services/athena/baqend-athena-service.d.ts +13 -4
- package/dist/services/athena/baqend-athena-service.js +25 -10
- package/dist/services/athena/baqend-athena-service.spec.d.ts +1 -0
- package/dist/services/athena/baqend-athena-service.spec.js +51 -0
- package/dist/services/cli/cli-service.d.ts +9 -0
- package/dist/services/cli/cli-service.js +57 -3
- package/dist/services/cli/cli-service.spec.d.ts +1 -0
- package/dist/services/cli/cli-service.spec.js +67 -0
- package/dist/services/prewarm/auto-pre-warm-factory.d.ts +10 -0
- package/dist/services/prewarm/auto-pre-warm-factory.js +5 -0
- package/dist/services/query-builder/queries/page-source.d.ts +11 -2
- package/dist/services/query-builder/queries/page-source.js +15 -3
- package/dist/services/query-builder/queries/query-model.d.ts +2 -0
- package/dist/services/query-builder/queries/spec/queries.spec.js +17 -0
- package/dist/services/query-builder/query-builder-factory.js +2 -1
- package/dist/services/query-builder/query-builder-model.d.ts +6 -0
- package/dist/services/query-builder/query-builder-service.js +12 -7
- package/dist/services/query-builder/query-builder-service.spec.d.ts +1 -0
- package/dist/services/query-builder/query-builder-service.spec.js +65 -0
- package/dist/services/query-builder/query-command.d.ts +2 -0
- package/dist/services/query-builder/query-command.js +2 -0
- package/oclif.manifest.json +100 -19
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ export interface QueryFilterOptions {
|
|
|
16
16
|
}
|
|
17
17
|
export interface QueryBuilderContext extends ParameterOptions, PrewarmOptions, QueryFilterOptions {
|
|
18
18
|
readonly app: string;
|
|
19
|
+
/** Athena data catalog the rum tables live in. */
|
|
20
|
+
readonly catalog?: string;
|
|
19
21
|
readonly execute?: boolean;
|
|
20
22
|
/** Report every rule the query leaves out, reduces or approximates. */
|
|
21
23
|
readonly explain?: boolean;
|
|
@@ -26,6 +28,8 @@ export interface QueryBuilderContext extends ParameterOptions, PrewarmOptions, Q
|
|
|
26
28
|
readonly quiet?: boolean;
|
|
27
29
|
}
|
|
28
30
|
export interface QueryBuilderFactoryContext extends ParameterOptions, PrewarmOptions, QueryFilterOptions {
|
|
31
|
+
/** Athena data catalog the rum tables live in. */
|
|
32
|
+
readonly catalog?: string;
|
|
29
33
|
readonly configName: string;
|
|
30
34
|
readonly customerPath: string;
|
|
31
35
|
readonly execute?: boolean;
|
|
@@ -33,4 +37,6 @@ export interface QueryBuilderFactoryContext extends ParameterOptions, PrewarmOpt
|
|
|
33
37
|
readonly explain?: boolean;
|
|
34
38
|
readonly isWindows?: boolean;
|
|
35
39
|
readonly quiet?: boolean;
|
|
40
|
+
/** Athena workgroup to execute in, when the query is executed at all. */
|
|
41
|
+
readonly workgroup?: string;
|
|
36
42
|
}
|
|
@@ -44,10 +44,10 @@ export class QueryBuilderService {
|
|
|
44
44
|
this.cli.startAction(action, `build ${this.context.queryType}-query`);
|
|
45
45
|
const result = safe(() => this.build());
|
|
46
46
|
if (result.success !== true) {
|
|
47
|
+
// Fail the action before throwing: the spinner is a timer, and one left
|
|
48
|
+
// running keeps the process alive with nothing to do.
|
|
47
49
|
this.cli.failAction(action, result.error);
|
|
48
|
-
|
|
49
|
-
console.error(result.errorObj);
|
|
50
|
-
return;
|
|
50
|
+
throw result.errorObj ?? new Error(result.error);
|
|
51
51
|
}
|
|
52
52
|
this.cli.successAction(action);
|
|
53
53
|
const query = result.data;
|
|
@@ -99,6 +99,7 @@ export class QueryBuilderService {
|
|
|
99
99
|
build() {
|
|
100
100
|
const input = {
|
|
101
101
|
app: this.context.app,
|
|
102
|
+
catalog: this.context.catalog,
|
|
102
103
|
bySet: this.context.bySet,
|
|
103
104
|
count: this.context.count,
|
|
104
105
|
days: this.context.days ?? DEFAULT_QUERY_DAYS[this.context.queryType],
|
|
@@ -178,9 +179,12 @@ export class QueryBuilderService {
|
|
|
178
179
|
this.cli.startAction("QUERY:COUNT", "count the pre-warm list");
|
|
179
180
|
const result = await safe(this.runQuery(query));
|
|
180
181
|
if (result.success !== true) {
|
|
182
|
+
// Thrown, not reported and swallowed. A count that did not run is a
|
|
183
|
+
// failed command, and something driving this from a script — a CI job
|
|
184
|
+
// reading the exit code — has no other way to find that out. Printing
|
|
185
|
+
// the message and exiting 0 made a broken query look like a clean run.
|
|
181
186
|
this.cli.failAction("QUERY:COUNT");
|
|
182
|
-
|
|
183
|
-
return;
|
|
187
|
+
throw result.errorObj ?? new Error(result.error);
|
|
184
188
|
}
|
|
185
189
|
this.cli.successAction("QUERY:COUNT");
|
|
186
190
|
const rows = result.data;
|
|
@@ -210,9 +214,10 @@ export class QueryBuilderService {
|
|
|
210
214
|
this.cli.startAction("QUERY:EXECUTE", "execute query and export csv");
|
|
211
215
|
const result = await safe(this.runQuery(query));
|
|
212
216
|
if (result.success !== true) {
|
|
217
|
+
// Thrown for the same reason as the count path: an export that produced
|
|
218
|
+
// no file must not exit 0.
|
|
213
219
|
this.cli.failAction("QUERY:EXECUTE");
|
|
214
|
-
|
|
215
|
-
return;
|
|
220
|
+
throw result.errorObj ?? new Error(result.error);
|
|
216
221
|
}
|
|
217
222
|
const rows = result.data;
|
|
218
223
|
const date = new Date().toISOString().split("T")[0];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { QueryBuilderService } from "./query-builder-service.js";
|
|
4
|
+
import { QueryType } from "./query-builder-model.js";
|
|
5
|
+
import { parseRuleSets } from "./rules/index.js";
|
|
6
|
+
/** Records the actions so a left-running spinner shows up as a missing fail. */
|
|
7
|
+
function recordingCli() {
|
|
8
|
+
const calls = [];
|
|
9
|
+
const cli = {
|
|
10
|
+
comment: () => calls.push("comment"),
|
|
11
|
+
failAction: (name) => calls.push(`fail:${name}`),
|
|
12
|
+
startAction: (name) => calls.push(`start:${name}`),
|
|
13
|
+
successAction: (name) => calls.push(`success:${name}`),
|
|
14
|
+
table: () => calls.push("table"),
|
|
15
|
+
writeError: (message) => calls.push(`error:${message}`),
|
|
16
|
+
writeSuccess: () => calls.push("success"),
|
|
17
|
+
writeWarning: () => calls.push("warning"),
|
|
18
|
+
};
|
|
19
|
+
return { calls, cli };
|
|
20
|
+
}
|
|
21
|
+
/** An Athena that always refuses, the way a bad catalog or workgroup does. */
|
|
22
|
+
const refusingAthena = {
|
|
23
|
+
getResult: () => Promise.reject(new Error("The Amazon Athena query failed to run with error message: CATALOG_NOT_FOUND")),
|
|
24
|
+
};
|
|
25
|
+
function countService(cli) {
|
|
26
|
+
return new QueryBuilderService({
|
|
27
|
+
app: "obi-de",
|
|
28
|
+
count: true,
|
|
29
|
+
hosts: ["www.obi.de"],
|
|
30
|
+
queryType: QueryType.prewarm,
|
|
31
|
+
quiet: true,
|
|
32
|
+
},
|
|
33
|
+
// The real parser on an empty config: a hand-rolled stand-in would fail the
|
|
34
|
+
// build step and the test would pass for the wrong reason.
|
|
35
|
+
parseRuleSets({}), cli, refusingAthena);
|
|
36
|
+
}
|
|
37
|
+
describe("QueryBuilderService — a query that could not run", () => {
|
|
38
|
+
// Regression: this used to print the message and return, so the process
|
|
39
|
+
// exited 0. A CI job counting urls then reported success with no count, and
|
|
40
|
+
// the pipeline that read it had no way to tell a broken query from an empty
|
|
41
|
+
// one.
|
|
42
|
+
it("fails the command instead of exiting cleanly", async () => {
|
|
43
|
+
const { cli } = recordingCli();
|
|
44
|
+
const outcome = await countService(cli)
|
|
45
|
+
.run()
|
|
46
|
+
.then(() => "resolved")
|
|
47
|
+
.catch(() => "rejected");
|
|
48
|
+
expect(outcome).to.equal("rejected");
|
|
49
|
+
});
|
|
50
|
+
it("carries the reason, so the log says what went wrong", async () => {
|
|
51
|
+
const { cli } = recordingCli();
|
|
52
|
+
const error = await countService(cli)
|
|
53
|
+
.run()
|
|
54
|
+
.then(() => undefined)
|
|
55
|
+
.catch((thrown) => thrown);
|
|
56
|
+
expect(error?.message).to.contain("CATALOG_NOT_FOUND");
|
|
57
|
+
});
|
|
58
|
+
it("ends the spinner before throwing — it is a timer that holds the process open", async () => {
|
|
59
|
+
const { calls, cli } = recordingCli();
|
|
60
|
+
await countService(cli)
|
|
61
|
+
.run()
|
|
62
|
+
.catch(() => undefined);
|
|
63
|
+
expect(calls).to.contain("fail:QUERY:COUNT");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -8,12 +8,14 @@ import { QueryFilterOptions, QueryType } from "./query-builder-model.js";
|
|
|
8
8
|
* the two the caller gives rather than the list the builder works on.
|
|
9
9
|
*/
|
|
10
10
|
export interface QueryCommandFlags extends ParameterOptions, Omit<PrewarmOptions, "devices" | "variations">, QueryFilterOptions {
|
|
11
|
+
readonly catalog?: string;
|
|
11
12
|
readonly configName: string;
|
|
12
13
|
readonly device?: readonly string[];
|
|
13
14
|
readonly execute?: boolean;
|
|
14
15
|
readonly explain?: boolean;
|
|
15
16
|
readonly quiet?: boolean;
|
|
16
17
|
readonly variation?: readonly string[];
|
|
18
|
+
readonly workgroup?: string;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Builds one of the config-derived queries and hands it over.
|
|
@@ -12,6 +12,7 @@ import { normalizeDevices } from "./queries/device.js";
|
|
|
12
12
|
export async function runQuery(queryType, customerPath, flags, config) {
|
|
13
13
|
const service = await new QueryBuilderFactory({
|
|
14
14
|
bySet: flags.bySet,
|
|
15
|
+
catalog: flags.catalog,
|
|
15
16
|
configName: flags.configName,
|
|
16
17
|
count: flags.count,
|
|
17
18
|
customerPath,
|
|
@@ -30,6 +31,7 @@ export async function runQuery(queryType, customerPath, flags, config) {
|
|
|
30
31
|
minImpressions: flags.minImpressions,
|
|
31
32
|
quiet: flags.quiet,
|
|
32
33
|
variations: flags.variation,
|
|
34
|
+
workgroup: flags.workgroup,
|
|
33
35
|
}, new CliConfig(config).load()).buildService(queryType);
|
|
34
36
|
await service.run();
|
|
35
37
|
}
|
package/oclif.manifest.json
CHANGED
|
@@ -14,9 +14,30 @@
|
|
|
14
14
|
"$ sk auto-prewarm <customerPath> -c <configName>",
|
|
15
15
|
"$ sk auto-prewarm customers/decathlon.de -c production",
|
|
16
16
|
"$ sk auto-prewarm customers/decathlon.de --device desktop,mobile",
|
|
17
|
-
"$ sk auto-prewarm customers/decathlon.de -v MOBILE,DESKTOP"
|
|
17
|
+
"$ sk auto-prewarm customers/decathlon.de -v MOBILE,DESKTOP",
|
|
18
|
+
"$ sk auto-prewarm customers/decathlon.de --limit 5000 --minHits 5",
|
|
19
|
+
"$ sk auto-prewarm customers/decathlon.de --workgroup prewarming"
|
|
18
20
|
],
|
|
19
21
|
"flags": {
|
|
22
|
+
"catalog": {
|
|
23
|
+
"description": "Athena data catalog holding the rum tables. Names the catalog both in the query and in the context it runs in, so the two always agree",
|
|
24
|
+
"env": "SK_ATHENA_CATALOG",
|
|
25
|
+
"name": "catalog",
|
|
26
|
+
"required": false,
|
|
27
|
+
"default": "live",
|
|
28
|
+
"hasDynamicHelp": false,
|
|
29
|
+
"multiple": false,
|
|
30
|
+
"type": "option"
|
|
31
|
+
},
|
|
32
|
+
"workgroup": {
|
|
33
|
+
"description": "Athena workgroup to run the query in, e.g. 'prewarming'. Defaults to the account's default workgroup",
|
|
34
|
+
"env": "SK_ATHENA_WORKGROUP",
|
|
35
|
+
"name": "workgroup",
|
|
36
|
+
"required": false,
|
|
37
|
+
"hasDynamicHelp": false,
|
|
38
|
+
"multiple": false,
|
|
39
|
+
"type": "option"
|
|
40
|
+
},
|
|
20
41
|
"configName": {
|
|
21
42
|
"char": "c",
|
|
22
43
|
"description": "The costumer config name",
|
|
@@ -60,14 +81,6 @@
|
|
|
60
81
|
"allowNo": false,
|
|
61
82
|
"type": "boolean"
|
|
62
83
|
},
|
|
63
|
-
"match": {
|
|
64
|
-
"description": "Keep only urls whose path matches this regular expression, e.g. '^/p/'. The path is matched with its query string, the way a version 1 pathname rule matches it, so '[?&]size=' works too. Repeat the flag to keep the urls matching any of several patterns, and start a pattern with (?i) to match case-insensitively",
|
|
65
|
-
"name": "match",
|
|
66
|
-
"required": false,
|
|
67
|
-
"hasDynamicHelp": false,
|
|
68
|
-
"multiple": true,
|
|
69
|
-
"type": "option"
|
|
70
|
-
},
|
|
71
84
|
"device": {
|
|
72
85
|
"description": "Split the list by device class, so every url is warmed in the variations it was actually requested in. A class not named is left out",
|
|
73
86
|
"exclusive": [
|
|
@@ -98,6 +111,36 @@
|
|
|
98
111
|
"multiple": true,
|
|
99
112
|
"type": "option"
|
|
100
113
|
},
|
|
114
|
+
"excludeNotFound": {
|
|
115
|
+
"description": "Leave out urls the origin answers with a 404 more often than not",
|
|
116
|
+
"name": "excludeNotFound",
|
|
117
|
+
"allowNo": false,
|
|
118
|
+
"type": "boolean"
|
|
119
|
+
},
|
|
120
|
+
"limit": {
|
|
121
|
+
"description": "Keep only the most requested urls",
|
|
122
|
+
"name": "limit",
|
|
123
|
+
"required": false,
|
|
124
|
+
"hasDynamicHelp": false,
|
|
125
|
+
"multiple": false,
|
|
126
|
+
"type": "option"
|
|
127
|
+
},
|
|
128
|
+
"match": {
|
|
129
|
+
"description": "Keep only urls whose path matches this regular expression, e.g. '^/p/'. The path is matched with its query string, the way a version 1 pathname rule matches it, so '[?&]size=' works too. Repeat the flag to keep the urls matching any of several patterns, and start a pattern with (?i) to match case-insensitively",
|
|
130
|
+
"name": "match",
|
|
131
|
+
"required": false,
|
|
132
|
+
"hasDynamicHelp": false,
|
|
133
|
+
"multiple": true,
|
|
134
|
+
"type": "option"
|
|
135
|
+
},
|
|
136
|
+
"minHits": {
|
|
137
|
+
"description": "Keep only urls requested at least this often in the window",
|
|
138
|
+
"name": "minHits",
|
|
139
|
+
"required": false,
|
|
140
|
+
"hasDynamicHelp": false,
|
|
141
|
+
"multiple": false,
|
|
142
|
+
"type": "option"
|
|
143
|
+
},
|
|
101
144
|
"variationPath": {
|
|
102
145
|
"char": "p",
|
|
103
146
|
"description": "path to variations.csv",
|
|
@@ -1309,6 +1352,25 @@
|
|
|
1309
1352
|
"$ sk query parameter customers/decathlon.de --explain"
|
|
1310
1353
|
],
|
|
1311
1354
|
"flags": {
|
|
1355
|
+
"catalog": {
|
|
1356
|
+
"description": "Athena data catalog holding the rum tables. Names the catalog both in the query and in the context it runs in, so the two always agree",
|
|
1357
|
+
"env": "SK_ATHENA_CATALOG",
|
|
1358
|
+
"name": "catalog",
|
|
1359
|
+
"required": false,
|
|
1360
|
+
"default": "live",
|
|
1361
|
+
"hasDynamicHelp": false,
|
|
1362
|
+
"multiple": false,
|
|
1363
|
+
"type": "option"
|
|
1364
|
+
},
|
|
1365
|
+
"workgroup": {
|
|
1366
|
+
"description": "Athena workgroup to run the query in, e.g. 'prewarming'. Defaults to the account's default workgroup",
|
|
1367
|
+
"env": "SK_ATHENA_WORKGROUP",
|
|
1368
|
+
"name": "workgroup",
|
|
1369
|
+
"required": false,
|
|
1370
|
+
"hasDynamicHelp": false,
|
|
1371
|
+
"multiple": false,
|
|
1372
|
+
"type": "option"
|
|
1373
|
+
},
|
|
1312
1374
|
"configName": {
|
|
1313
1375
|
"char": "c",
|
|
1314
1376
|
"description": "The costumer config name",
|
|
@@ -1420,6 +1482,25 @@
|
|
|
1420
1482
|
"$ sk query prewarm customers/decathlon.de --count"
|
|
1421
1483
|
],
|
|
1422
1484
|
"flags": {
|
|
1485
|
+
"catalog": {
|
|
1486
|
+
"description": "Athena data catalog holding the rum tables. Names the catalog both in the query and in the context it runs in, so the two always agree",
|
|
1487
|
+
"env": "SK_ATHENA_CATALOG",
|
|
1488
|
+
"name": "catalog",
|
|
1489
|
+
"required": false,
|
|
1490
|
+
"default": "live",
|
|
1491
|
+
"hasDynamicHelp": false,
|
|
1492
|
+
"multiple": false,
|
|
1493
|
+
"type": "option"
|
|
1494
|
+
},
|
|
1495
|
+
"workgroup": {
|
|
1496
|
+
"description": "Athena workgroup to run the query in, e.g. 'prewarming'. Defaults to the account's default workgroup",
|
|
1497
|
+
"env": "SK_ATHENA_WORKGROUP",
|
|
1498
|
+
"name": "workgroup",
|
|
1499
|
+
"required": false,
|
|
1500
|
+
"hasDynamicHelp": false,
|
|
1501
|
+
"multiple": false,
|
|
1502
|
+
"type": "option"
|
|
1503
|
+
},
|
|
1423
1504
|
"configName": {
|
|
1424
1505
|
"char": "c",
|
|
1425
1506
|
"description": "The costumer config name",
|
|
@@ -1507,15 +1588,6 @@
|
|
|
1507
1588
|
"multiple": true,
|
|
1508
1589
|
"type": "option"
|
|
1509
1590
|
},
|
|
1510
|
-
"count": {
|
|
1511
|
-
"description": "Run the query and report how large the list is instead of what is in it: urls, the impressions they stand for, and a row per variation where the list carries variations. Every other flag still applies, so this counts the list the same command would produce",
|
|
1512
|
-
"exclusive": [
|
|
1513
|
-
"execute"
|
|
1514
|
-
],
|
|
1515
|
-
"name": "count",
|
|
1516
|
-
"allowNo": false,
|
|
1517
|
-
"type": "boolean"
|
|
1518
|
-
},
|
|
1519
1591
|
"excludeNotFound": {
|
|
1520
1592
|
"description": "Leave out urls the origin answers with a 404 more often than not",
|
|
1521
1593
|
"name": "excludeNotFound",
|
|
@@ -1545,6 +1617,15 @@
|
|
|
1545
1617
|
"hasDynamicHelp": false,
|
|
1546
1618
|
"multiple": false,
|
|
1547
1619
|
"type": "option"
|
|
1620
|
+
},
|
|
1621
|
+
"count": {
|
|
1622
|
+
"description": "Run the query and report how large the list is instead of what is in it: urls, the impressions they stand for, and a row per variation where the list carries variations. Every other flag still applies, so this counts the list the same command would produce",
|
|
1623
|
+
"exclusive": [
|
|
1624
|
+
"execute"
|
|
1625
|
+
],
|
|
1626
|
+
"name": "count",
|
|
1627
|
+
"allowNo": false,
|
|
1628
|
+
"type": "boolean"
|
|
1548
1629
|
}
|
|
1549
1630
|
},
|
|
1550
1631
|
"hasDynamicHelp": false,
|
|
@@ -1564,5 +1645,5 @@
|
|
|
1564
1645
|
]
|
|
1565
1646
|
}
|
|
1566
1647
|
},
|
|
1567
|
-
"version": "4.27.
|
|
1648
|
+
"version": "4.27.2"
|
|
1568
1649
|
}
|