@speedkit/cli 2.89.0 → 2.91.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,18 @@
1
+ # [2.91.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.90.0...v2.91.0) (2025-07-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * **wizard:** templates ([6d328fd](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/6d328fd400769e329f80c89edc6f34185e8ed04d))
7
+
8
+ # [2.90.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.89.0...v2.90.0) (2025-07-08)
9
+
10
+
11
+ ### Features
12
+
13
+ * **config-wizard:** add new "affid" affiliate id tracking param to the strip query param list ([eefc936](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/eefc93665c993af2a1369062029196f9a4b3ee61))
14
+ * **config-wizard:** add new "affid" affiliate id tracking param to the strip query param list ([9028cc4](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/9028cc499d65bdd5ee4f1e3d7ffa8515df0cd1e7))
15
+
1
16
  # [2.89.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.88.0...v2.89.0) (2025-07-04)
2
17
 
3
18
 
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/2.89.0 linux-x64 node-v20.19.3
24
+ @speedkit/cli/2.91.0 linux-x64 node-v20.19.3
25
25
  $ sk --help [COMMAND]
26
26
  USAGE
27
27
  $ sk COMMAND
@@ -371,6 +371,7 @@
371
371
  "yclid", // Yandex Click ID
372
372
  "zanpid", // AWIN (former Zanox Affiliate Network)
373
373
  /^(mb|nb|nx|ny)$/,
374
+ /^affid$/i, // Affiliate ID
374
375
  /^WT\./, // WebTrends
375
376
  /^[gw]braid$/, // Google's iOS web-to-app campaign measurement
376
377
  /^bv(state|messageType|notificationId|recipientDomain)$/, // Bazaarvoice
@@ -1,77 +1,156 @@
1
- const fetch = require('node-fetch');
2
- const { SLACK_WEB_HOOK_URL, APP_API_TOKEN } = require('./parameters');
1
+ const { message } = require("baqend");
2
+
3
+ const pageToCheck = "https://{{production.host}}/"; // TODO change to the actual page to check
4
+ const maintenancePageRegex = /maintenance\.{{appName}}/g; // TODO change to the actual regex to identify maintenance mode
5
+
6
+ /*
7
+ create the job with the following data object:
8
+ the slack webhook token can be pulled from this page:
9
+ https://api.slack.com/apps/A090W8Q33TR/incoming-webhooks
10
+ {
11
+ "updateStatus": true,
12
+ "slackToken": "<webhook slack token from the downtime detection app>",
13
+ "dryRun": true
14
+ }
15
+ */
3
16
 
4
17
  /**
5
- * Run this as a CRON job every 5 minutes.
18
+ * Checks the status of a given page to determine if it is down or healthy.
6
19
  *
7
- * NOTE: Do not forget to change page specific parts for your customer!
8
- * NOTE: Do not forget to create the parameters module with slack URL and admin API token
20
+ * @param {Object} db - The database connection or object used for reference or logging.
21
+ * @param {string} pageToCheck - The URL or identifier of the page to check the status for.
22
+ * @param {RegExp} maintenancePageRegex - A regular expression to identify if the page is in maintenance mode.
23
+ * @return {Promise<{ state: string }>} An object representing the state of the page, where "state" is either "healthy" or "downtime".
9
24
  */
10
- async function call(db, data, req) {
11
- const landingPage = 'https://{{production.host}}/';
12
- const maintenancePageRegex = /maintenance\.{{appName}}/g;
25
+ async function checkStatus(db, pageToCheck, maintenancePageRegex) {
26
+ const pageDown = await isPageDown(pageToCheck, maintenancePageRegex);
13
27
 
14
- const landingPageDown = await isLandingPageDown(landingPage, maintenancePageRegex);
15
-
16
- if (!landingPageDown) {
17
- return { status: 'success' };
28
+ if (!pageDown) {
29
+ return { state: "healthy" };
18
30
  }
19
31
 
20
- loginAdmin(db);
32
+ return { state: "downtime" };
33
+ }
34
+
35
+ /**
36
+ * Hook to test the current status
37
+ * @param {baqend} db - the baqend instance
38
+ * @return {Promise<void>}
39
+ */
40
+ async function get(db, req, res) {
41
+ if (db.User.me?.key !== "1") throw new Abort("Unauthorized");
42
+ res.json(await checkStatus(db, pageToCheck, maintenancePageRegex));
43
+ }
21
44
 
22
- const deactivated = await deactiveSpeedKit(db);
23
- if (deactivated) {
24
- notifyBaqend(landingPage);
45
+ {{{{raw}}}}
46
+ /**
47
+ * Hook which is executed by the cron job
48
+ * @param {baqend} db - the baqend instance
49
+ * @param {{status: string, error: Error, data?: { state: "healthy" | "downtime", revId?: string } | null}} status - The latest status of this scheduled run
50
+ * @param {{ data: { slackToken?: string, dryRun?: boolean }}} job - The job definition
51
+ * @return {Promise<void>}
52
+ */
53
+ {{{{/raw}}}}
54
+ async function run(db, status, job) {
55
+ const newStatus = await checkStatus(db, pageToCheck, maintenancePageRegex);
56
+ newStatus.revId = status?.data?.revId ?? null;
57
+
58
+ db.log.info(`Downtime detection resulted in actual: ${newStatus.state}, previous: ${status.data?.state}`);
59
+
60
+ const domain = new URL(pageToCheck).hostname;
61
+ if (newStatus.state === "healthy" && status.data?.state === "downtime") {
62
+ if (!job.data.dryRun) {
63
+ try {
64
+ await resumeJob(db, newStatus.revId);
65
+ db.log.info(`Downtime detection resumed job ${newStatus.revId} for ${domain}`);
66
+ } catch (e) {
67
+ db.log.warn(
68
+ `Downtime detection failed to resume job ${newStatus.revId} for ${domain}, scheduling a new job instead`,
69
+ e,
70
+ );
71
+ const revId = await scheduleJob(db, domain, "deployment", 0);
72
+ db.log.info(`Downtime detection started new job ${revId} for ${domain}`);
73
+ }
74
+ newStatus.revId = null;
75
+ }
76
+ sendSlackMessage(job.data.slackToken, `A downtime was resolved on ${domain}, deployment job resumed.`).catch((e) =>
77
+ db.log.warn("Can't send slack message", e),
78
+ );
79
+ }
80
+
81
+ if (newStatus.state === "downtime" && status.data?.state === "healthy") {
82
+ if (!job.data.dryRun) {
83
+ // block refresh while the page is down
84
+ newStatus.revId = await scheduleJob(db, domain, "deployment", 12 * 60 * 60 * 1000);
85
+ db.log.info(`Downtime detection scheduled new job ${newStatus.revId} for ${domain}`);
86
+ }
87
+ sendSlackMessage(job.data.slackToken, `A downtime was detected on ${domain}, deployment job scheduled.`).catch(
88
+ (e) => db.log.warn("Can't send slack message", e),
89
+ );
25
90
  }
26
91
 
27
- return { status: 'downtime' };
92
+ // persisted by the job itself
93
+ status.error = null;
94
+ status.data = newStatus;
28
95
  }
29
96
 
30
97
  /**
31
- * Logs in an admin.
98
+ * Schedules a job to handle downtime detection for a given domain.
32
99
  *
33
- * @param db The database to perform the login with
100
+ * @param {Object} db - The database connection or message dispatch object used for sending messages.
101
+ * @param {string} domain - The domain for which the downtime job should be scheduled.
102
+ * @param {string} jobType - The type of job to be scheduled.
103
+ * @param {number} [delay=0] - The delay (in milliseconds) before the job is executed.
104
+ * @return {Promise<string>} A promise that resolves to the unique identifier of the scheduled job.
34
105
  */
35
- async function loginAdmin(db) {
36
- await db.loginWithToken(APP_API_TOKEN);
37
- }
38
-
39
- function notifyBaqend(landingPage) {
40
- const body = JSON.stringify({
41
- "username": 'Downtime Detection',
42
- "text": `Speed Kit was deactivated due to downtime of ${landingPage}`,
43
- });
44
- return fetch(SLACK_WEB_HOOK_URL, { method: 'POST', body })
106
+ async function scheduleJob(db, domain, jobType, delay = 0) {
107
+ const data = {
108
+ name: `Downtime ${domain}`,
109
+ type: jobType,
110
+ filter: {
111
+ contentTypes: ["document"],
112
+ origins: [`https://${domain}`],
113
+ },
114
+ triggeredBy: "DowntimeDetection",
115
+ scheduleIn: delay,
116
+ };
117
+
118
+ const msg = new message.RevalidateAssets(data);
119
+ const response = await db.send(msg);
120
+ const jobId = response.entity.statusId;
121
+
122
+ return jobId;
45
123
  }
46
124
 
47
125
  /**
48
- * Deactivates Speed Kit via the config.
126
+ * Resumes a job by updating its status to "RUNNING".
127
+ *
128
+ * @param {baqend} db - The baqend instance
129
+ * @param {string} jobId - The unique identifier of the job to be resumed.
130
+ * @return {Promise<void>} A promise that resolves once the job status update has been sent.
49
131
  */
50
- async function deactiveSpeedKit(db) {
51
- const config = (await db.send(new db.message.GetOrestesConfig())).entity;
52
-
53
- if (!config.speedKit.enabled) {
54
- // Speed Kit already deactivated
55
- return false;
132
+ async function resumeJob(db, jobId) {
133
+ const msg = new message.EditRevalidationJob(jobId, { status: "RUNNING" });
134
+ try {
135
+ await db.send(msg);
136
+ } catch (e) {
137
+ if (e.status === 200) {
138
+ return; // there is a bug in the server that it currently returns 200 instead of 204 that confuse the SDK
139
+ }
140
+ throw e;
56
141
  }
57
-
58
- // Deactivate Speed Kit
59
- config.speedKit.enabled = false;
60
- const configUpdate = new db.message.UpdateOrestesConfig(config).ifMatch(config.revision.version);
61
- await db.send(configUpdate);
62
- return true;
63
142
  }
64
143
 
65
144
  /**
66
145
  * Returns whether the landing page is not reachable.
67
146
  *
68
- * @param landingPage The page to test.
69
- * @param maintenancePageRegex The page to identify redirects to maintenance page.
147
+ * @param {string} landingPage The page to test.
148
+ * @param {RegExp} maintenancePageRegex The pattern used to identify redirects to maintenance page.
70
149
  * @param retry The number of already failed tries to load the page.
71
150
  * @returns `true` if the landing page is not reachable, `false` otherwise.
72
151
  */
73
- async function isLandingPageDown(landingPage, maintenancePageRegex, retry = 0) {
74
- const res = await fetch(landingPage);
152
+ async function isPageDown(landingPage, maintenancePageRegex, retry = 0) {
153
+ const res = await fetch(landingPage, { redirect: "manual" });
75
154
 
76
155
  // Valid response
77
156
  if (res.status < 300) {
@@ -90,21 +169,41 @@ async function isLandingPageDown(landingPage, maintenancePageRegex, retry = 0) {
90
169
 
91
170
  // Retry after error
92
171
  await sleep(10000);
93
- return isLandingPageDown(retry + 1);
172
+ return isPageDown(landingPage, maintenancePageRegex, retry + 1);
94
173
  }
95
174
 
96
175
  /**
97
176
  * Returns whether the response redirects to the maintenance page.
98
177
  *
178
+ * @param res {Response} The fetch result of
99
179
  * @param maintenancePageRegex The page to identify redirects to maintenance page.
100
180
  */
101
181
  async function isMaintenanceRedirect(res, maintenancePageRegex) {
102
- const location = res.headers.get('Location') || '';
103
- return maintenancePageRegex.test(location);
182
+ const location = res.headers.get("Location") || "";
183
+ return !!location.match(maintenancePageRegex);
104
184
  }
105
185
 
106
186
  async function sleep(milliseconds) {
107
- return new Promise(res => setTimeout(res, milliseconds));
187
+ return new Promise((res) => setTimeout(res, milliseconds));
188
+ }
189
+
190
+ /**
191
+ * Sends a message to a Slack channel using the provided webhook token.
192
+ *
193
+ * @param {string} token - The Slack webhook URL to which the message will be sent.
194
+ * @param {string} message - The message content to be sent to the Slack channel.
195
+ * @return {Promise<void>} A promise resolving to the response of the fetch request.
196
+ */
197
+ async function sendSlackMessage(token, message) {
198
+ if (!token) {
199
+ return;
200
+ }
201
+ const body = JSON.stringify({
202
+ username: "Downtime Detection",
203
+ text: message,
204
+ });
205
+ await fetch(token, { method: "POST", body });
108
206
  }
109
207
 
110
- exports.call = call;
208
+ exports.get = get;
209
+ exports.run = run;
@@ -712,5 +712,5 @@
712
712
  ]
713
713
  }
714
714
  },
715
- "version": "2.89.0"
715
+ "version": "2.91.0"
716
716
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@speedkit/cli",
3
3
  "description": "Speed Kit CLI",
4
- "version": "2.89.0",
4
+ "version": "2.91.0",
5
5
  "author": {
6
6
  "name": "Baqend.com",
7
7
  "email": "info@baqend.com"