@speedkit/cli 2.27.0 → 2.28.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 +7 -0
- package/README.md +1 -1
- package/dist/services/customer-config/customer-config-service.js +0 -3
- package/dist/services/customer-config/templates/config_documentHandler.js.hbs +0 -4
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/services/customer-config/templates/deploymentDetection.es6.hbs +0 -303
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [2.28.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.27.0...v2.28.0) (2024-05-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **customer-config:** remove old deployment detection ([a3661c7](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a3661c7db68ade9907b15caaa1edf370e51783ca))
|
|
7
|
+
|
|
1
8
|
# [2.27.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v2.26.0...v2.27.0) (2024-05-03)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -264,9 +264,6 @@ class CustomerConfigService {
|
|
|
264
264
|
async generateConfig() {
|
|
265
265
|
this.cli.write("\nLet's generate a config...\n");
|
|
266
266
|
const configSettings = await this.getConfigSettings();
|
|
267
|
-
if (configSettings.isSalesforce) {
|
|
268
|
-
this.templates.push("deploymentDetection.es6.hbs");
|
|
269
|
-
}
|
|
270
267
|
const addDowntimeDetection = await this.cli.confirm("Add Downtime Detection?", false);
|
|
271
268
|
if (addDowntimeDetection) {
|
|
272
269
|
this.templates.push("downtimeDetection.js.hbs");
|
|
@@ -87,11 +87,7 @@ const config = {
|
|
|
87
87
|
{{else}}
|
|
88
88
|
delayScriptPath: "/speed-kit-dom-ready.js",
|
|
89
89
|
{{/if}}
|
|
90
|
-
{{#if isSalesforce}}
|
|
91
|
-
executeDeploymentDetection: false, // Salesforce specific => deploymentDetection.es6 is being used instead
|
|
92
|
-
{{else}}
|
|
93
90
|
executeDeploymentDetection: true,
|
|
94
|
-
{{/if}}
|
|
95
91
|
{{#unless addPreRendering}}
|
|
96
92
|
{{#if removeLazyLoading}}
|
|
97
93
|
lazyLoadList,
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
// Salesforce specific:
|
|
2
|
-
|
|
3
|
-
const { run: executeRefresh } = require('./speedKit.CronJob');
|
|
4
|
-
const fetch = require('node-fetch');
|
|
5
|
-
|
|
6
|
-
const minutesToMilliseconds = (minutes) => {
|
|
7
|
-
return minutes * 60000;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const LOCALES_BY_LAYOUT = [
|
|
11
|
-
// TODO: adjust/replace this exemplary list
|
|
12
|
-
ExampleLayoutA: ["de_DE", "en_DE"],
|
|
13
|
-
ExampleLayoutB: ["en_US"],
|
|
14
|
-
];
|
|
15
|
-
const ASSET_SUB_PATHS = [
|
|
16
|
-
// These assets are expected to be found via the following base URL:
|
|
17
|
-
// https://<ORIGIN>/on/demandware.static/Sites-<LAYOUT>-Site/-/<LOCALE>/v<VERSION>/...
|
|
18
|
-
|
|
19
|
-
// TODO: adjust/replace this exemplary list
|
|
20
|
-
|
|
21
|
-
// JS:
|
|
22
|
-
"js/main.js",
|
|
23
|
-
"internal/jscript/dwanalytics-(.*).js",
|
|
24
|
-
|
|
25
|
-
// CSS:
|
|
26
|
-
"css/style.css",
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
function getAssetList() {
|
|
30
|
-
return Object.entries(LOCALES_BY_LAYOUT).flatMap(([layout, locales]) =>
|
|
31
|
-
locales.flatMap((locale) =>
|
|
32
|
-
ASSET_SUB_PATHS.map(
|
|
33
|
-
(subPath) =>
|
|
34
|
-
{{#if staging.host}}
|
|
35
|
-
`^https://(?:{{production.host}}|{{staging.host}})/on/demandware.static/Sites-${layout}-Site/-/${locale}/v(\\d{13})/${subPath}`,
|
|
36
|
-
{{else}}
|
|
37
|
-
`^https://{{production.host}}/on/demandware.static/Sites-${layout}-Site/-/${locale}/v(\\d{13})/${subPath}`,
|
|
38
|
-
{{/if}}
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const deploymentConfig = {
|
|
44
|
-
slack: "<1Password deployment token>",
|
|
45
|
-
origins: [
|
|
46
|
-
// Production:
|
|
47
|
-
"https://{{production.host}}",
|
|
48
|
-
{{#if staging.host}}
|
|
49
|
-
|
|
50
|
-
// Staging:
|
|
51
|
-
"https://{{staging.host}}",
|
|
52
|
-
{{/if}}
|
|
53
|
-
|
|
54
|
-
// TODO: also check for other origins like e.g. https://*.my.salesforce.com
|
|
55
|
-
],
|
|
56
|
-
jobType: "deployment", // "instant" | "deployment"
|
|
57
|
-
|
|
58
|
-
// configure how long the origin deployment takes in milliseconds:
|
|
59
|
-
deploymentDelay: minutesToMilliseconds(15),
|
|
60
|
-
|
|
61
|
-
// throttling the HTML refresh:
|
|
62
|
-
revalidationDelay: minutesToMilliseconds(0),
|
|
63
|
-
|
|
64
|
-
compareVersionStrings: false, // Compares version strings of the new and previous assets and only triggers deployment, when the current is newer. The version must be the first group in the regex
|
|
65
|
-
compareContentHashes: true,
|
|
66
|
-
|
|
67
|
-
assetList: [
|
|
68
|
-
...getAssetList(),
|
|
69
|
-
|
|
70
|
-
// TODO: add additional non-demandware asset urls as needed
|
|
71
|
-
],
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function getQuery(assetRegex, origin) {
|
|
75
|
-
const query = getBaseQuery(origin);
|
|
76
|
-
query.url = { "$regex": assetRegex };
|
|
77
|
-
return query;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function getBaseQuery(...origins) {
|
|
81
|
-
return {
|
|
82
|
-
"contentType": { "$in": ["script", "style"] },
|
|
83
|
-
"variation": "DEFAULT",
|
|
84
|
-
"origin": { "$in": origins },
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const DEPLOYMENT_REFRESH_JOB_ID_TEMPLATE = '/db/jobs.Definition/deployment-refresh'
|
|
89
|
-
const DEPLOYMENT_CHECKER_JOB_ID = '/db/jobs.Definition/deployment-check'
|
|
90
|
-
const MINUTE = 60 * 1000;
|
|
91
|
-
|
|
92
|
-
exports.get = async function(db, req, res) {
|
|
93
|
-
if (!db.User.me || db.User.me.key !== '1') {
|
|
94
|
-
throw new Abort("Unauthorized");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const assetsByOrigin = {};
|
|
98
|
-
|
|
99
|
-
for (const origin of deploymentConfig.origins) {
|
|
100
|
-
assetsByOrigin[origin] = await Promise.all(deploymentConfig.assetList.map(async asset => {
|
|
101
|
-
const { url } = await findAsset(db, asset, origin) || { url: null };
|
|
102
|
-
return url;
|
|
103
|
-
}))
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
res.json(assetsByOrigin);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
exports.post = async function(db, req, res) {
|
|
111
|
-
if (!db.User.me || db.User.me.key !== '1') {
|
|
112
|
-
throw new Abort("Unauthorized");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const deploymentJobs = await Promise.all(deploymentConfig.origins.map(
|
|
116
|
-
origin => createDeploymentJob(db, deploymentConfig, origin)));
|
|
117
|
-
|
|
118
|
-
const deploymentChecker = await (new db['jobs.Definition']({
|
|
119
|
-
id: DEPLOYMENT_CHECKER_JOB_ID,
|
|
120
|
-
module: 'deploymentDetection',
|
|
121
|
-
cronpattern: '42 */10 * * * *',
|
|
122
|
-
startsAt: new Date(),
|
|
123
|
-
}).save().catch(e => e.message));
|
|
124
|
-
|
|
125
|
-
res.json({ deploymentChecker, deploymentJobs });
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getDeploymentJobId(origin) {
|
|
129
|
-
const originWithoutSlashes = origin.replace(/https:/g, '').replace(/\//g, '');
|
|
130
|
-
return `${DEPLOYMENT_REFRESH_JOB_ID_TEMPLATE}-${originWithoutSlashes}`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function getDeploymentJobName(origin) {
|
|
134
|
-
const originWithoutSlashes = origin.replace(/https:/g, '').replace(/\//g, '');
|
|
135
|
-
return `Deployment ${originWithoutSlashes}`
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function createDeploymentJob(db, deploymentConfig, origin) {
|
|
139
|
-
return (new db['jobs.Definition']({
|
|
140
|
-
id: getDeploymentJobId(origin),
|
|
141
|
-
module: 'speedKit.CronJob',
|
|
142
|
-
cronpattern: '*/60 */24 */30 */12 *',
|
|
143
|
-
startsAt: new Date(),
|
|
144
|
-
expiresAt: new Date(1970,0, 1),
|
|
145
|
-
data: {
|
|
146
|
-
"name": getDeploymentJobName(origin),
|
|
147
|
-
"jobType": deploymentConfig.jobType || "deployment",
|
|
148
|
-
"filter": {
|
|
149
|
-
"contentTypes": [ "document" ],
|
|
150
|
-
"origin": [ origin ],
|
|
151
|
-
},
|
|
152
|
-
"scheduleIn": deploymentConfig.deploymentDelay,
|
|
153
|
-
"revalidationDelay": deploymentConfig.revalidationDelay,
|
|
154
|
-
},
|
|
155
|
-
}).save({ force: true }).catch(e => e.message));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
exports.run = async function triggerRefreshAfterDeployment(db, status) {
|
|
159
|
-
const interval = new Date(Date.now() - 10 * MINUTE - 10);
|
|
160
|
-
const origins = await findNewAsset(db, interval, deploymentConfig.origins);
|
|
161
|
-
const app = db.connection.host;
|
|
162
|
-
|
|
163
|
-
if (origins.size === 0) {
|
|
164
|
-
status.data = {refreshing: false, reason: "no new asset" };
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const data = status.data = {};
|
|
169
|
-
for (const origin of origins) {
|
|
170
|
-
try {
|
|
171
|
-
const changedAsset = await getChangedAssets(db, origin);
|
|
172
|
-
if (!changedAsset) {
|
|
173
|
-
data[origin] = {
|
|
174
|
-
refreshing: false, origin, reason: "asset hash not changed",
|
|
175
|
-
};
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const reason = `Asset changed: ${changedAsset}`;
|
|
180
|
-
fetch(deploymentConfig.slack, {
|
|
181
|
-
username: 'Deployment Detection',
|
|
182
|
-
method: 'post',
|
|
183
|
-
body: JSON.stringify({text: `Deployment on ${app} on ${origin} detected. Starting HTML ${deploymentConfig.jobType || "deployment"} Refresh. ${reason}`}),
|
|
184
|
-
}).catch(db.log.warn);
|
|
185
|
-
|
|
186
|
-
await runDeploymentJob(db, origin, reason);
|
|
187
|
-
|
|
188
|
-
data[origin] = {
|
|
189
|
-
refreshing: true, origin, latestAsset: changedAsset,
|
|
190
|
-
};
|
|
191
|
-
} catch (e) {
|
|
192
|
-
fetch(deploymentConfig.slack, {
|
|
193
|
-
username: 'Deployment Detection',
|
|
194
|
-
method: 'post',
|
|
195
|
-
body: JSON.stringify({text: `Deployment detection on ${app} and ${origin} failed. ${e.message}`}),
|
|
196
|
-
}).catch(db.log.warn);
|
|
197
|
-
data[origin] = {
|
|
198
|
-
refreshing: false, origin, reason: e.stack,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return data;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
async function getChangedAssets(db, origin) {
|
|
206
|
-
for (const urlRegex of deploymentConfig.assetList) {
|
|
207
|
-
const interval = new Date(Date.now() - 10 * MINUTE - 10);
|
|
208
|
-
const current = await findAsset(db, urlRegex, origin, interval);
|
|
209
|
-
const previous = await findAsset(db, urlRegex, origin, null, interval);
|
|
210
|
-
|
|
211
|
-
if (!current) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// trigger deployment since we see a new matching asset
|
|
216
|
-
if (!previous) {
|
|
217
|
-
return current.url;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (deploymentConfig.compareContentHashes) {
|
|
221
|
-
if (current.contentHash === previous.contentHash) {
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (await hasCachedAsset(db, current, urlRegex, interval)) {
|
|
226
|
-
db.log.info('contentHash already known', {current, previous});
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (deploymentConfig.compareVersionStrings) {
|
|
232
|
-
const prevVersion = previous.url.match(urlRegex)[1];
|
|
233
|
-
const currentVersion = current.url.match(urlRegex)[1];
|
|
234
|
-
if (currentVersion < prevVersion) {
|
|
235
|
-
db.log.info('content version has not increased', {current, previous});
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return current.url;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function findAsset(db, urlRegex, origin, from, to) {
|
|
247
|
-
return await db["speedKit.Asset"].find()
|
|
248
|
-
.where(getQuery(urlRegex, origin))
|
|
249
|
-
.le('createdAt', to || new Date(Date.now() + MINUTE))
|
|
250
|
-
.gt('createdAt', from || new Date(1970, 0, 1))
|
|
251
|
-
.descending('createdAt')
|
|
252
|
-
.singleResult();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async function findNewAsset(db, from, origins) {
|
|
256
|
-
const result = await db["speedKit.Asset"].find()
|
|
257
|
-
.where(getBaseQuery(...origins))
|
|
258
|
-
.gt('createdAt', from || new Date(1970, 0, 1))
|
|
259
|
-
.resultList();
|
|
260
|
-
|
|
261
|
-
return new Set(result.map(asset => asset.origin));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function hasAssetChanged(asset1, asset2) {
|
|
265
|
-
const res1 = await fetch(asset1.url);
|
|
266
|
-
const text1 = await res1.text();
|
|
267
|
-
|
|
268
|
-
const res2 = await fetch(asset2.url);
|
|
269
|
-
const text2 = await res2.text();
|
|
270
|
-
|
|
271
|
-
return text1 !== text2;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Check whether we have seen the hash before
|
|
276
|
-
*/
|
|
277
|
-
async function hasCachedAsset(db, asset, urlRegex, to) {
|
|
278
|
-
return (await db["speedKit.Asset"].find()
|
|
279
|
-
.where(getQuery(urlRegex, asset.origin))
|
|
280
|
-
.equal('contentType', asset.contentType)
|
|
281
|
-
.equal('variation', asset.variation)
|
|
282
|
-
.equal('contentHash', asset.contentHash)
|
|
283
|
-
.le('createdAt', to || new Date(Date.now() + MINUTE))
|
|
284
|
-
.count()
|
|
285
|
-
) > 0;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function runDeploymentJob(db, origin, reason) {
|
|
289
|
-
const status = new db['jobs.Status']();
|
|
290
|
-
const jobId = getDeploymentJobId(origin);
|
|
291
|
-
const cronjob = await db['jobs.Definition'].load(jobId);
|
|
292
|
-
// TODO: quite hacky right now we may need a way to pass additional parameters to the run job code
|
|
293
|
-
cronjob.data.reason = reason;
|
|
294
|
-
status.cronjob = cronjob;
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
await executeRefresh(db, status, cronjob);
|
|
298
|
-
} catch(error) {
|
|
299
|
-
status.status = 'failed';
|
|
300
|
-
status.error = error;
|
|
301
|
-
await status.save();
|
|
302
|
-
}
|
|
303
|
-
}
|