@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
|
@@ -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
|
|
2
|
-
|
|
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
|
-
*
|
|
18
|
+
* Checks the status of a given page to determine if it is down or healthy.
|
|
6
19
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const maintenancePageRegex = /maintenance\.{{appName}}/g;
|
|
25
|
+
async function checkStatus(db, pageToCheck, maintenancePageRegex) {
|
|
26
|
+
const pageDown = await isPageDown(pageToCheck, maintenancePageRegex);
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!landingPageDown) {
|
|
17
|
-
return { status: 'success' };
|
|
28
|
+
if (!pageDown) {
|
|
29
|
+
return { state: "healthy" };
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
92
|
+
// persisted by the job itself
|
|
93
|
+
status.error = null;
|
|
94
|
+
status.data = newStatus;
|
|
28
95
|
}
|
|
29
96
|
|
|
30
97
|
/**
|
|
31
|
-
*
|
|
98
|
+
* Schedules a job to handle downtime detection for a given domain.
|
|
32
99
|
*
|
|
33
|
-
* @param db The database
|
|
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
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
*
|
|
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
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
103
|
-
return
|
|
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.
|
|
208
|
+
exports.get = get;
|
|
209
|
+
exports.run = run;
|
package/oclif.manifest.json
CHANGED