@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +46 -14
  3. package/dist/commands/auto-prewarm.d.ts +8 -3
  4. package/dist/commands/auto-prewarm.js +16 -7
  5. package/dist/commands/query/parameter.d.ts +2 -0
  6. package/dist/commands/query/parameter.js +2 -1
  7. package/dist/commands/query/prewarm.d.ts +2 -0
  8. package/dist/commands/query/prewarm.js +2 -1
  9. package/dist/helpers/clipboard.d.ts +6 -0
  10. package/dist/helpers/clipboard.js +7 -1
  11. package/dist/models/cli-parameters.d.ts +40 -1
  12. package/dist/models/cli-parameters.js +51 -7
  13. package/dist/services/athena/athena-service-factory.d.ts +8 -1
  14. package/dist/services/athena/athena-service-factory.js +15 -4
  15. package/dist/services/athena/athena-service-model.d.ts +17 -0
  16. package/dist/services/athena/athena-service-model.js +19 -0
  17. package/dist/services/athena/athena-service.d.ts +21 -1
  18. package/dist/services/athena/athena-service.js +21 -3
  19. package/dist/services/athena/athena-service.spec.js +36 -1
  20. package/dist/services/athena/baqend-athena-service.d.ts +13 -4
  21. package/dist/services/athena/baqend-athena-service.js +25 -10
  22. package/dist/services/athena/baqend-athena-service.spec.d.ts +1 -0
  23. package/dist/services/athena/baqend-athena-service.spec.js +51 -0
  24. package/dist/services/cli/cli-service.d.ts +9 -0
  25. package/dist/services/cli/cli-service.js +57 -3
  26. package/dist/services/cli/cli-service.spec.d.ts +1 -0
  27. package/dist/services/cli/cli-service.spec.js +67 -0
  28. package/dist/services/prewarm/auto-pre-warm-factory.d.ts +10 -0
  29. package/dist/services/prewarm/auto-pre-warm-factory.js +5 -0
  30. package/dist/services/query-builder/queries/page-source.d.ts +11 -2
  31. package/dist/services/query-builder/queries/page-source.js +15 -3
  32. package/dist/services/query-builder/queries/query-model.d.ts +2 -0
  33. package/dist/services/query-builder/queries/spec/queries.spec.js +17 -0
  34. package/dist/services/query-builder/query-builder-factory.js +2 -1
  35. package/dist/services/query-builder/query-builder-model.d.ts +6 -0
  36. package/dist/services/query-builder/query-builder-service.js +12 -7
  37. package/dist/services/query-builder/query-builder-service.spec.d.ts +1 -0
  38. package/dist/services/query-builder/query-builder-service.spec.js +65 -0
  39. package/dist/services/query-builder/query-command.d.ts +2 -0
  40. package/dist/services/query-builder/query-command.js +2 -0
  41. package/oclif.manifest.json +100 -19
  42. 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
- this.cli.writeError(result.error);
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
- this.cli.writeError(result.error);
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
- this.cli.writeError(result.error);
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,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
  }
@@ -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.0"
1648
+ "version": "4.27.2"
1568
1649
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "4.27.0",
4
+ "version": "4.27.2",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"