@speedkit/cli 4.11.0 → 4.13.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,19 @@
1
+ # [4.13.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.12.0...v4.13.0) (2026-06-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * **site-analyzer:** rocket loader check ([229a037](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/229a037e0dc7eebb6bd46ac934799b6874556c88))
7
+
8
+ # [4.12.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.11.0...v4.12.0) (2026-06-19)
9
+
10
+
11
+ ### Features
12
+
13
+ * adapt queryBuilderParam ([7cca445](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/7cca445b200a96ee2fb4d1241dd803fefab0a1e4))
14
+ * prefer direct AWS over baqend Athena api ([b388456](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/b388456e4a0a073bac1c54e5e33b3178f375b7d7))
15
+ * prefere baqendAthenaApi ofer native awsLogin ([4ce13eb](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/4ce13eb5fc9332fa140d474d256418ed8866910c))
16
+
1
17
  # [4.11.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.10.0...v4.11.0) (2026-06-19)
2
18
 
3
19
 
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.11.0 linux-x64 node-v22.23.0
24
+ @speedkit/cli/4.13.0 linux-x64 node-v22.23.1
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -152,7 +152,7 @@ export default class GeneratePopConfig extends Command {
152
152
  async run() {
153
153
  const { args: { app }, flags: { coverage, deploy, "pop-limit": popLimit, "traffic-share": minimalShare, "use-rum": useRum, "force-shield": forceShield, }, } = await this.parse(GeneratePopConfig);
154
154
  // @todo refactor businessCode from this command to src/pop-config/pop-config-service
155
- const popConfigService = await new PopConfigFactory().getService();
155
+ const popConfigService = await new PopConfigFactory().getService(app);
156
156
  const result = await popConfigService.getUsedPopsPerOrigin(app, useRum);
157
157
  const originStats = this.generateOriginStats(result)
158
158
  .filter((stats) => {
@@ -9,11 +9,11 @@ const EMPTY_FLAGS = {
9
9
  hasSoftNavigations: false,
10
10
  handleUsercentrics: false,
11
11
  };
12
- function detectFeatures(html, headers) {
12
+ function detectFeatures(html) {
13
13
  return {
14
14
  useGATracking: /dataLayer|gtm\.js|googletagmanager\.com|gtag\/js/.test(html),
15
15
  withGoogleOptimize: /googleoptimize\.com|optimize\.google\.com/.test(html),
16
- activateCfRocketLoaderPlugin: /data-cfasync|rocket-loader/.test(html) || "cf-ray" in headers,
16
+ activateCfRocketLoaderPlugin: /data-cfasync|rocket-loader/.test(html),
17
17
  includeServiceWorker: /navigator\.serviceWorker\.register|serviceWorker\.register/.test(html),
18
18
  removeLazyLoading: /loading=["']lazy["']|data-src=|class=["'][^"']*lazyload|lazysizes/.test(html),
19
19
  hasSoftNavigations: /__NEXT_DATA__|__NUXT__|vue-router|react-router|history\.pushState/.test(html),
@@ -98,7 +98,7 @@ async function fetchAndAnalyze(host, log) {
98
98
  const homePage = await fetchPage(`https://${host}`, controller.signal);
99
99
  if (!homePage)
100
100
  return null;
101
- featureResults.push(detectFeatures(homePage.html, homePage.headers));
101
+ featureResults.push(detectFeatures(homePage.html));
102
102
  // 2. Detect shop system from homepage
103
103
  const shopSystem = detectShopSystem(homePage.html);
104
104
  // 3. Check install.js presence in homepage HTML
@@ -112,7 +112,7 @@ async function fetchAndAnalyze(host, log) {
112
112
  log(`Fetching PLP: ${plpUrl}`);
113
113
  plpPage = await fetchPage(plpUrl, controller.signal);
114
114
  if (plpPage) {
115
- featureResults.push(detectFeatures(plpPage.html, plpPage.headers));
115
+ featureResults.push(detectFeatures(plpPage.html));
116
116
  // 6. If homepage didn't have a PDP link, try extracting one from PLP
117
117
  if (!pdpUrl) {
118
118
  pdpUrl = findUrl(plpPage.html, host, PDP_PATTERN);
@@ -124,7 +124,7 @@ async function fetchAndAnalyze(host, log) {
124
124
  log(`Fetching PDP: ${pdpUrl}`);
125
125
  const pdpPage = await fetchPage(pdpUrl, controller.signal);
126
126
  if (pdpPage) {
127
- featureResults.push(detectFeatures(pdpPage.html, pdpPage.headers));
127
+ featureResults.push(detectFeatures(pdpPage.html));
128
128
  }
129
129
  }
130
130
  return {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Analytics buckets that exist in every app and map 1:1 to the Athena
3
+ * databases. Passed to {@link AthenaService.getResult} so the Baqend native
4
+ * query path knows which `/db/:bucket/query` endpoint to hit. The database is
5
+ * derived server-side from the part before the dot (e.g. `fastly.Logs` -> the
6
+ * `fastly` database).
7
+ */
8
+ export declare enum AthenaBucket {
9
+ FastlyLogs = "fastly.Logs",
10
+ RumPi = "rum.Pi",
11
+ RumPiAgg = "rum.PiAgg"
12
+ }
13
+ export type AthenaParameter = boolean | number | string;
14
+ /**
15
+ * Contract shared by every Athena query implementation (AWS SDK, Baqend native
16
+ * query, and the fallback that combines them). Consumers depend on this
17
+ * interface, never on a concrete class.
18
+ */
19
+ export interface AthenaService {
20
+ /**
21
+ * Run a query and return the rows as objects keyed by column name.
22
+ *
23
+ * @param query - the SQL to execute
24
+ * @param parameters - values substituted for the `?` placeholders, in order
25
+ * @param maxAgeInMinutes - result-reuse cache window
26
+ * @param bucket - the analytics bucket the query targets. Required for the
27
+ * Baqend native query path; when omitted the AWS path is used.
28
+ */
29
+ getResult<T>(query: string, parameters?: AthenaParameter[], maxAgeInMinutes?: number, bucket?: AthenaBucket): Promise<T[]>;
30
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Analytics buckets that exist in every app and map 1:1 to the Athena
3
+ * databases. Passed to {@link AthenaService.getResult} so the Baqend native
4
+ * query path knows which `/db/:bucket/query` endpoint to hit. The database is
5
+ * derived server-side from the part before the dot (e.g. `fastly.Logs` -> the
6
+ * `fastly` database).
7
+ */
8
+ export var AthenaBucket;
9
+ (function (AthenaBucket) {
10
+ AthenaBucket["FastlyLogs"] = "fastly.Logs";
11
+ AthenaBucket["RumPi"] = "rum.Pi";
12
+ AthenaBucket["RumPiAgg"] = "rum.PiAgg";
13
+ })(AthenaBucket || (AthenaBucket = {}));
@@ -1,13 +1,27 @@
1
- import { AthenaService } from "./athena-service.js";
1
+ import { AthenaService } from "./athena-query-service.js";
2
2
  export declare class AthenaServiceFactory {
3
+ private app?;
3
4
  service?: AthenaService;
5
+ /**
6
+ * @param app - the app to query. Enables the Baqend native query path (app
7
+ * token only) as a fallback when no AWS login is present. Without it, only
8
+ * the AWS path is available.
9
+ */
10
+ constructor(app?: string);
4
11
  getService(): Promise<AthenaService>;
12
+ /**
13
+ * Prefer direct AWS Athena whenever AWS credentials are present — they
14
+ * usually carry higher access rights than the Baqend app-token path. Only
15
+ * when no AWS login is available do we fall back to the Baqend native query
16
+ * path (which needs an `app`).
17
+ */
5
18
  private createService;
6
19
  /**
7
- * create awsAthenaClient
20
+ * Resolve the AWS credential chain (env, SSO cache, ...) without prompting.
21
+ * Returns a ready client when credentials exist, otherwise `undefined` so the
22
+ * caller can fall back to the Baqend path.
8
23
  *
9
- * @param cli
10
24
  * @private
11
25
  */
12
- private getAthenaClient;
26
+ private tryGetAthenaClient;
13
27
  }
@@ -1,36 +1,56 @@
1
1
  import { AthenaClient } from "@aws-sdk/client-athena";
2
2
  import { safe } from "../../helpers/safe.js";
3
3
  import { CliServiceFactory } from "../cli/index.js";
4
- import { ATHENA_CONFIG, FetchCredentialsError } from "./index.js";
5
- import { AthenaService } from "./athena-service.js";
4
+ import { ATHENA_CONFIG } from "./athena-service-model.js";
5
+ import { FetchCredentialsError } from "./error/fetch-credentials-error.js";
6
+ import { AwsAthenaService } from "./athena-service.js";
7
+ import { BaqendAthenaService } from "./baqend-athena-service.js";
8
+ import { EntityManagerFactory } from "../config-api/entity-manager-factory.js";
6
9
  export class AthenaServiceFactory {
10
+ app;
7
11
  service;
12
+ /**
13
+ * @param app - the app to query. Enables the Baqend native query path (app
14
+ * token only) as a fallback when no AWS login is present. Without it, only
15
+ * the AWS path is available.
16
+ */
17
+ constructor(app) {
18
+ this.app = app;
19
+ }
8
20
  async getService() {
9
- if (!(this.service instanceof AthenaService)) {
21
+ if (!this.service) {
10
22
  this.service = await this.createService();
11
23
  }
12
24
  return this.service;
13
25
  }
26
+ /**
27
+ * Prefer direct AWS Athena whenever AWS credentials are present — they
28
+ * usually carry higher access rights than the Baqend app-token path. Only
29
+ * when no AWS login is available do we fall back to the Baqend native query
30
+ * path (which needs an `app`).
31
+ */
14
32
  async createService() {
15
33
  const cli = new CliServiceFactory().getService();
16
- const client = await this.getAthenaClient(cli);
17
- return new AthenaService(client, cli);
34
+ const client = await this.tryGetAthenaClient();
35
+ if (client) {
36
+ cli.comment("Using AWS credentials for Athena queries.");
37
+ return new AwsAthenaService(client, cli);
38
+ }
39
+ if (this.app) {
40
+ return new BaqendAthenaService(this.app, new EntityManagerFactory(), cli);
41
+ }
42
+ throw new FetchCredentialsError("Cannot send Athena queries: no AWS credentials found and no app given for the Baqend API.");
18
43
  }
19
44
  /**
20
- * create awsAthenaClient
45
+ * Resolve the AWS credential chain (env, SSO cache, ...) without prompting.
46
+ * Returns a ready client when credentials exist, otherwise `undefined` so the
47
+ * caller can fall back to the Baqend path.
21
48
  *
22
- * @param cli
23
49
  * @private
24
50
  */
25
- async getAthenaClient(cli) {
51
+ async tryGetAthenaClient() {
26
52
  const client = new AthenaClient(ATHENA_CONFIG);
27
- cli.startAction("ATHENA", "check for aws credentials");
28
53
  const credentialsResult = await safe(client.config.credentials());
29
- if (credentialsResult.success === true) {
30
- cli.successAction("ATHENA");
31
- return client;
32
- }
33
- cli.failAction("ATHENA");
34
- throw new FetchCredentialsError(credentialsResult.error);
54
+ return credentialsResult.success === true ? client : undefined;
35
55
  }
36
56
  }
@@ -1,6 +1,13 @@
1
1
  import { AthenaClient } from "@aws-sdk/client-athena";
2
+ import { AthenaService } from "./athena-query-service.js";
2
3
  import { CliServiceInterface } from "../cli/index.js";
3
- export declare class AthenaService {
4
+ /**
5
+ * Athena query implementation backed by the AWS SDK. Requires a valid AWS
6
+ * login (SSO). Preferred whenever AWS credentials are present, as they usually
7
+ * grant higher access than the Baqend app-token path; see
8
+ * {@link AthenaServiceFactory}.
9
+ */
10
+ export declare class AwsAthenaService implements AthenaService {
4
11
  private client;
5
12
  private cli;
6
13
  constructor(client: AthenaClient, cli: CliServiceInterface);
@@ -2,7 +2,13 @@ import { GetQueryExecutionCommand, GetQueryResultsCommand, QueryExecutionState,
2
2
  import { DEFAULT_MAX_AGE_IN_MINUTES } from "./athena-service-model.js";
3
3
  import { AthenaQueryError } from "./error/athena-query-error.js";
4
4
  import { safe } from "../../helpers/safe.js";
5
- export class AthenaService {
5
+ /**
6
+ * Athena query implementation backed by the AWS SDK. Requires a valid AWS
7
+ * login (SSO). Preferred whenever AWS credentials are present, as they usually
8
+ * grant higher access than the Baqend app-token path; see
9
+ * {@link AthenaServiceFactory}.
10
+ */
11
+ export class AwsAthenaService {
6
12
  client;
7
13
  cli;
8
14
  constructor(client, cli) {
@@ -0,0 +1,30 @@
1
+ import { AthenaBucket, AthenaParameter, AthenaService } from "./athena-query-service.js";
2
+ import { EntityManagerFactory } from "../config-api/entity-manager-factory.js";
3
+ import { CliServiceInterface } from "../cli/index.js";
4
+ /**
5
+ * Runs Athena SQL through the Baqend API, authenticated by the app token only
6
+ * (no AWS login). Hits the native query path via
7
+ * `db["<bucket>"].executeQuery(sql, triggeredBy, ttl)`.
8
+ *
9
+ * Two normalizations vs the AWS path:
10
+ * - `?` placeholders are inlined (the native path has no parameter binding).
11
+ * - the `live.` catalog prefix is stripped — the server derives catalog and
12
+ * database itself, and a 3-part `live.<db>.<table>` reference fails there
13
+ * (verified against a live app), whereas 2-part `<db>.<table>` works.
14
+ */
15
+ export declare class BaqendAthenaService implements AthenaService {
16
+ private app;
17
+ private entityManagerFactory;
18
+ private cli;
19
+ private static readonly TRIGGERED_BY;
20
+ private static readonly CATALOG_PREFIX;
21
+ constructor(app: string, entityManagerFactory: EntityManagerFactory, cli: CliServiceInterface);
22
+ getResult<T>(query: string, parameters?: AthenaParameter[], maxAgeInMinutes?: number, bucket?: AthenaBucket): Promise<T[]>;
23
+ /**
24
+ * Inline the `?` placeholders with the given parameters (same quoting as the
25
+ * AWS path) and strip any `live.` catalog prefix. Done in a single
26
+ * string-literal-aware pass so `?` and `live.` occurring inside single-quoted
27
+ * literals (e.g. `split(url, '?')`, `'https://live.foo.com'`) are left intact.
28
+ */
29
+ private prepareQuery;
30
+ }
@@ -0,0 +1,92 @@
1
+ import { DEFAULT_MAX_AGE_IN_MINUTES } from "./athena-service-model.js";
2
+ import { safe } from "../../helpers/safe.js";
3
+ /**
4
+ * Runs Athena SQL through the Baqend API, authenticated by the app token only
5
+ * (no AWS login). Hits the native query path via
6
+ * `db["<bucket>"].executeQuery(sql, triggeredBy, ttl)`.
7
+ *
8
+ * Two normalizations vs the AWS path:
9
+ * - `?` placeholders are inlined (the native path has no parameter binding).
10
+ * - the `live.` catalog prefix is stripped — the server derives catalog and
11
+ * database itself, and a 3-part `live.<db>.<table>` reference fails there
12
+ * (verified against a live app), whereas 2-part `<db>.<table>` works.
13
+ */
14
+ export class BaqendAthenaService {
15
+ app;
16
+ entityManagerFactory;
17
+ cli;
18
+ static TRIGGERED_BY = "speed-kit-cli";
19
+ // a `live.` catalog qualifier in front of a `<db>.<table>` reference
20
+ static CATALOG_PREFIX = /^live\.[a-z_]\w*\.[a-z_]/i;
21
+ constructor(app, entityManagerFactory, cli) {
22
+ this.app = app;
23
+ this.entityManagerFactory = entityManagerFactory;
24
+ this.cli = cli;
25
+ }
26
+ async getResult(query, parameters = [], maxAgeInMinutes = DEFAULT_MAX_AGE_IN_MINUTES, bucket) {
27
+ if (!bucket) {
28
+ throw new Error("BaqendAthenaService.getResult requires a bucket to target");
29
+ }
30
+ this.cli.startAction("ATHENA:QUERY", "execute athena query via baqend");
31
+ const db = await this.entityManagerFactory.getEntityManager(this.app);
32
+ const sql = this.prepareQuery(query, parameters);
33
+ const ttl = `${maxAgeInMinutes}m`;
34
+ const result = await safe(db[bucket].executeQuery(sql, BaqendAthenaService.TRIGGERED_BY, ttl));
35
+ if (result.success === true) {
36
+ this.cli.successAction("ATHENA:QUERY");
37
+ return result.data;
38
+ }
39
+ this.cli.failAction("ATHENA:QUERY");
40
+ this.cli.writeError("Cannot send Athena queries via the Baqend API for this app.");
41
+ throw result.errorObj ?? new Error(result.error);
42
+ }
43
+ /**
44
+ * Inline the `?` placeholders with the given parameters (same quoting as the
45
+ * AWS path) and strip any `live.` catalog prefix. Done in a single
46
+ * string-literal-aware pass so `?` and `live.` occurring inside single-quoted
47
+ * literals (e.g. `split(url, '?')`, `'https://live.foo.com'`) are left intact.
48
+ */
49
+ prepareQuery(query, parameters) {
50
+ let out = "";
51
+ let inString = false;
52
+ let paramIndex = 0;
53
+ for (let index = 0; index < query.length; index++) {
54
+ const char = query[index];
55
+ if (inString) {
56
+ out += char;
57
+ // a doubled '' is an escaped quote that stays inside the string
58
+ if (char === "'") {
59
+ if (query[index + 1] === "'") {
60
+ out += query[++index];
61
+ }
62
+ else {
63
+ inString = false;
64
+ }
65
+ }
66
+ continue;
67
+ }
68
+ if (char === "'") {
69
+ inString = true;
70
+ out += char;
71
+ continue;
72
+ }
73
+ if (char === "?") {
74
+ if (paramIndex >= parameters.length) {
75
+ throw new Error("Not enough parameters provided for the query");
76
+ }
77
+ const value = parameters[paramIndex++];
78
+ out += typeof value === "string" ? `'${value}'` : String(value);
79
+ continue;
80
+ }
81
+ // strip a `live.` catalog qualifier at a word boundary
82
+ if ((char === "l" || char === "L") &&
83
+ (index === 0 || !/\w/.test(query[index - 1])) &&
84
+ BaqendAthenaService.CATALOG_PREFIX.test(query.slice(index))) {
85
+ index += 4; // skip "live." (the loop's index++ skips the dot)
86
+ continue;
87
+ }
88
+ out += char;
89
+ }
90
+ return out;
91
+ }
92
+ }
@@ -1,4 +1,6 @@
1
+ export * from "./athena-query-service.js";
1
2
  export * from "./athena-service.js";
3
+ export * from "./baqend-athena-service.js";
2
4
  export * from "./athena-service-factory.js";
3
5
  export * from "./athena-service-model.js";
4
6
  export * from "./error/fetch-credentials-error.js";
@@ -1,4 +1,6 @@
1
+ export * from "./athena-query-service.js";
1
2
  export * from "./athena-service.js";
3
+ export * from "./baqend-athena-service.js";
2
4
  export * from "./athena-service-factory.js";
3
5
  export * from "./athena-service-model.js";
4
6
  export * from "./error/fetch-credentials-error.js";
@@ -1,6 +1,7 @@
1
1
  import { BaqendResponse } from "../browser/baqend-response.js";
2
2
  import { safe } from "../../../helpers/safe.js";
3
3
  import json2Csv from "json2csv";
4
+ import { AthenaBucket } from "../../athena/index.js";
4
5
  export class Dashboard {
5
6
  customerConfig;
6
7
  parameterQueryBuilder;
@@ -32,7 +33,7 @@ export class Dashboard {
32
33
  if (url.pathname.startsWith("/query/execute")) {
33
34
  const postData = request.postData;
34
35
  const data = JSON.parse(postData);
35
- const response = await safe(this.athenaClient.getResult(data.query, []));
36
+ const response = await safe(this.athenaClient.getResult(data.query, [], undefined, AthenaBucket.RumPi));
36
37
  if (response.success === true) {
37
38
  return this.createBaqendResponse(requestOrigin, JSON.stringify(response.data), "application/json; charset=UTF-8");
38
39
  }
@@ -81,7 +81,7 @@ export class OnboardingServiceFactory {
81
81
  cli.writeWarning("Running in headless mode.");
82
82
  }
83
83
  const fileWatcher = new FileWatcher(cli, customerConfig, files, documentHandler, browserContext, cache);
84
- const athenaClient = (await this.getAthenaClient()) || null;
84
+ const athenaClient = (await this.getAthenaClient(customerConfig.app)) || null;
85
85
  const messageHandler = await this.prepareMessagehandler(athenaClient, files, customerConfig);
86
86
  const fetchHandler = await this.getFetchEventHandler(files, customerConfig, documentHandler, cache, cli, browserContext, athenaClient, messageHandler);
87
87
  const extensionBridge = new ExtensionBridge(cli);
@@ -134,8 +134,8 @@ export class OnboardingServiceFactory {
134
134
  }
135
135
  cli.failAction("ONBOARDING:BUILD:INSTALL_RESOURCE", result.error);
136
136
  }
137
- async getAthenaClient() {
138
- const athenaFactory = new AthenaServiceFactory();
137
+ async getAthenaClient(app) {
138
+ const athenaFactory = new AthenaServiceFactory(app);
139
139
  const result = await safe(athenaFactory.getService());
140
140
  if (result.success === true) {
141
141
  return result.data;
@@ -1,4 +1,4 @@
1
1
  import { PopConfigService } from "./pop-config-service.js";
2
2
  export declare class PopConfigFactory {
3
- getService(): Promise<PopConfigService>;
3
+ getService(app?: string): Promise<PopConfigService>;
4
4
  }
@@ -1,8 +1,8 @@
1
1
  import { PopConfigService } from "./pop-config-service.js";
2
2
  import { AthenaServiceFactory } from "../athena/index.js";
3
3
  export class PopConfigFactory {
4
- async getService() {
5
- const athenaService = await new AthenaServiceFactory().getService();
4
+ async getService(app) {
5
+ const athenaService = await new AthenaServiceFactory(app).getService();
6
6
  return new PopConfigService(athenaService);
7
7
  }
8
8
  }
@@ -1,3 +1,4 @@
1
+ import { AthenaBucket } from "../athena/index.js";
1
2
  export class PopConfigService {
2
3
  athenaService;
3
4
  constructor(athenaService) {
@@ -30,6 +31,6 @@ export class PopConfigService {
30
31
  const startDate = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate() - 7);
31
32
  const start = startDate.toISOString().slice(0, 10);
32
33
  const end = endDate.toISOString().slice(0, 10);
33
- return await this.athenaService.getResult(query, [start, end, app], 12 * 60);
34
+ return await this.athenaService.getResult(query, [start, end, app], 12 * 60, AthenaBucket.FastlyLogs);
34
35
  }
35
36
  }
@@ -37,7 +37,7 @@ from urlWithParams
37
37
  group by paramKeys
38
38
  )
39
39
 
40
- select paramKeys, cachedUrls - uniqueWithoutParam as potentialGain, cachedUrls, PIs, example1, example2, example3
40
+ select '[' || array_join(paramKeys, ', ') || ']' as paramKeys, cachedUrls - uniqueWithoutParam as potentialGain, cachedUrls, PIs, example1, example2, example3
41
41
  from prepared
42
42
  where cardinality(paramKeys) > 0
43
43
  order by potentialGain desc, cachedUrls desc, PIs desc`;
@@ -759,5 +759,5 @@
759
759
  ]
760
760
  }
761
761
  },
762
- "version": "4.11.0"
762
+ "version": "4.13.0"
763
763
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "4.11.0",
4
+ "version": "4.13.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"