@adobe/helix-config 4.3.0 → 4.3.1
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/package.json +1 -1
- package/src/config-legacy.js +15 -9
- package/src/config-merge.js +6 -3
- package/src/config-object.js +58 -0
- package/src/config-view.js +52 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [4.3.1](https://github.com/adobe/helix-config/compare/v4.3.0...v4.3.1) (2024-08-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* include and set last-modified ([#174](https://github.com/adobe/helix-config/issues/174)) ([b325569](https://github.com/adobe/helix-config/commit/b3255693807e1ee20609f4fc9411308a7174a50a)), closes [#171](https://github.com/adobe/helix-config/issues/171)
|
|
7
|
+
|
|
1
8
|
# [4.3.0](https://github.com/adobe/helix-config/compare/v4.2.0...v4.3.0) (2024-08-20)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/config-legacy.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
import { SCOPE_PIPELINE } from './ConfigContext.js';
|
|
13
|
+
import { ConfigObject } from './config-object.js';
|
|
13
14
|
|
|
14
15
|
const HELIX_CODE_BUS = 'helix-code-bus';
|
|
15
16
|
|
|
@@ -78,12 +79,17 @@ async function fetchConfigAll(ctx, contentBusId, partition) {
|
|
|
78
79
|
* @param {ConfigContext} ctx the context
|
|
79
80
|
* @param {string} owner
|
|
80
81
|
* @param {string} repo
|
|
81
|
-
* @returns {Promise<
|
|
82
|
+
* @returns {Promise<ConfigObject>} the robots.txt
|
|
82
83
|
*/
|
|
83
84
|
export async function fetchRobotsTxt(ctx, owner, repo) {
|
|
84
85
|
const key = `${owner}/${repo}/main/robots.txt`;
|
|
85
86
|
const res = await ctx.loader.getObject(HELIX_CODE_BUS, key);
|
|
86
|
-
|
|
87
|
+
const robots = new ConfigObject();
|
|
88
|
+
if (res.body) {
|
|
89
|
+
robots.txt = res.body;
|
|
90
|
+
robots.updateLastModified(res.headers);
|
|
91
|
+
}
|
|
92
|
+
return robots;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
async function resolveAdminAccess(ctx, admin) {
|
|
@@ -108,7 +114,7 @@ async function resolveAdminAccess(ctx, admin) {
|
|
|
108
114
|
* @param {ConfigContext} ctx
|
|
109
115
|
* @param {RSO} rso
|
|
110
116
|
* @param {string} scope
|
|
111
|
-
* @returns {Promise<
|
|
117
|
+
* @returns {Promise<ConfigObject|null>} the config object or {@code null} if not found.
|
|
112
118
|
*/
|
|
113
119
|
export async function resolveLegacyConfig(ctx, rso, scope) {
|
|
114
120
|
// set owner==org and repo==site and fetch from helix-config for now
|
|
@@ -178,12 +184,12 @@ export async function resolveLegacyConfig(ctx, rso, scope) {
|
|
|
178
184
|
if (configAllLive?.metadata) {
|
|
179
185
|
config.metadata.live = configAllLive.metadata;
|
|
180
186
|
}
|
|
181
|
-
|
|
182
|
-
if (robots) {
|
|
183
|
-
config.robots
|
|
184
|
-
txt: robots,
|
|
185
|
-
};
|
|
187
|
+
config.robots = await fetchRobotsTxt(ctx, rso.org, rso.site);
|
|
188
|
+
if (!config.robots.txt) {
|
|
189
|
+
delete config.robots;
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
|
-
|
|
192
|
+
const obj = new ConfigObject();
|
|
193
|
+
obj.data = config;
|
|
194
|
+
return obj;
|
|
189
195
|
}
|
package/src/config-merge.js
CHANGED
|
@@ -133,10 +133,13 @@ function mergeConfig(dst, src) {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Returns the merge configuration of the site and the profile.
|
|
138
|
+
* @param {ConfigObject} site
|
|
139
|
+
* @param {ConfigObject} profile
|
|
140
|
+
* @returns {*}
|
|
141
|
+
*/
|
|
136
142
|
export function getMergedConfig(site, profile) {
|
|
137
|
-
if (!profile) {
|
|
138
|
-
return site;
|
|
139
|
-
}
|
|
140
143
|
const ret = Object.create(null);
|
|
141
144
|
ret.version = site.version;
|
|
142
145
|
mergeConfig(ret, profile);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import { getMergedConfig } from './config-merge.js';
|
|
13
|
+
|
|
14
|
+
export class ConfigObject {
|
|
15
|
+
#lastModifiedTime = -Infinity;
|
|
16
|
+
|
|
17
|
+
lastModified;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update the last modified if newer.
|
|
21
|
+
* @param {string|map|ConfigObject} [httpDate]
|
|
22
|
+
*/
|
|
23
|
+
updateLastModified(httpDate) {
|
|
24
|
+
if (!httpDate) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (httpDate instanceof ConfigObject) {
|
|
28
|
+
if (httpDate.#lastModifiedTime > this.#lastModifiedTime) {
|
|
29
|
+
this.#lastModifiedTime = httpDate.#lastModifiedTime;
|
|
30
|
+
this.lastModified = httpDate.lastModified;
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (typeof httpDate.get === 'function') {
|
|
35
|
+
// eslint-disable-next-line no-param-reassign
|
|
36
|
+
httpDate = httpDate.get('x-source-last-modified') ?? httpDate.get('last-modified');
|
|
37
|
+
}
|
|
38
|
+
if (!httpDate || typeof httpDate !== 'string') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const time = new Date(httpDate).getTime();
|
|
42
|
+
if (Number.isNaN(time)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (time > this.#lastModifiedTime) {
|
|
46
|
+
this.#lastModifiedTime = time;
|
|
47
|
+
this.lastModified = httpDate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
merge(other) {
|
|
52
|
+
if (!other) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.data = getMergedConfig(this.data, other.data);
|
|
56
|
+
this.updateLastModified(other);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/config-view.js
CHANGED
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
SCOPE_RAW, SCOPE_PUBLIC,
|
|
21
21
|
} from './ConfigContext.js';
|
|
22
22
|
import { resolveLegacyConfig, fetchRobotsTxt, toArray } from './config-legacy.js';
|
|
23
|
-
import { getMergedConfig } from './config-merge.js';
|
|
24
23
|
import { deepGetOrCreate } from './utils.js';
|
|
24
|
+
import { ConfigObject } from './config-object.js';
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @typedef Config
|
|
@@ -152,7 +152,7 @@ export async function getAccessConfig(ctx, config, partition, rso) {
|
|
|
152
152
|
* @param ctx the context
|
|
153
153
|
* @param config the config
|
|
154
154
|
* @param partition the partition
|
|
155
|
-
* @returns {Promise<
|
|
155
|
+
* @returns {Promise<ConfigObject|{}>} the metadata
|
|
156
156
|
*/
|
|
157
157
|
async function loadMetadata(ctx, config, partition) {
|
|
158
158
|
const paths = config.metadata?.source ?? [];
|
|
@@ -161,6 +161,7 @@ async function loadMetadata(ctx, config, partition) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// generate the metadata-all.json first
|
|
164
|
+
const metadataConfig = new ConfigObject();
|
|
164
165
|
const metadata = [];
|
|
165
166
|
for (const path of paths) {
|
|
166
167
|
const key = `${config.content.contentBusId}/${partition}${path}`;
|
|
@@ -173,25 +174,25 @@ async function loadMetadata(ctx, config, partition) {
|
|
|
173
174
|
metadata.push(...data);
|
|
174
175
|
}
|
|
175
176
|
}
|
|
177
|
+
metadataConfig.updateLastModified(res.headers);
|
|
176
178
|
}
|
|
177
179
|
if (!metadata.length) {
|
|
178
180
|
return {};
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
// convert to modifiers map
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
data: ModifiersConfig.parseModifierSheet(metadata),
|
|
185
|
-
};
|
|
184
|
+
metadataConfig.data = ModifiersConfig.parseModifierSheet(metadata);
|
|
185
|
+
return metadataConfig;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
async function loadHeadHtml(ctx, config, ref) {
|
|
189
189
|
const key = `${config.code.owner}/${config.code.repo}/${ref}/head.html`;
|
|
190
190
|
const res = await ctx.loader.getObject(HELIX_CODE_BUS, key);
|
|
191
191
|
if (res.body) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
const head = new ConfigObject();
|
|
193
|
+
head.html = res.body;
|
|
194
|
+
head.updateLastModified(res.headers);
|
|
195
|
+
return head;
|
|
195
196
|
}
|
|
196
197
|
return {};
|
|
197
198
|
}
|
|
@@ -214,7 +215,10 @@ async function loadProfile(ctx, rso, name) {
|
|
|
214
215
|
const profileKey = `orgs/${rso.org}/profiles/${name}.json`;
|
|
215
216
|
const res = await ctx.loader.getObject(HELIX_CONFIG_BUS, profileKey);
|
|
216
217
|
if (res.body) {
|
|
217
|
-
|
|
218
|
+
const profile = new ConfigObject();
|
|
219
|
+
profile.data = res.json();
|
|
220
|
+
profile.updateLastModified(res.headers);
|
|
221
|
+
return profile;
|
|
218
222
|
}
|
|
219
223
|
return null;
|
|
220
224
|
}
|
|
@@ -224,7 +228,7 @@ async function loadProfile(ctx, rso, name) {
|
|
|
224
228
|
* @param ctx
|
|
225
229
|
* @param {RSO} rso
|
|
226
230
|
* @param {string} scope
|
|
227
|
-
* @return {Promise<
|
|
231
|
+
* @return {Promise<ConfigObject|null>}
|
|
228
232
|
*/
|
|
229
233
|
async function resolveConfig(ctx, rso, scope) {
|
|
230
234
|
// try to load site config from config-bus
|
|
@@ -235,39 +239,42 @@ async function resolveConfig(ctx, rso, scope) {
|
|
|
235
239
|
const config = await resolveLegacyConfig(ctx, rso, scope);
|
|
236
240
|
if (config) {
|
|
237
241
|
const profile = await loadProfile(ctx, rso, 'default');
|
|
238
|
-
config.tokens = profile?.tokens || {};
|
|
242
|
+
config.data.tokens = profile?.data.tokens || {};
|
|
239
243
|
}
|
|
240
244
|
return config;
|
|
241
245
|
} else {
|
|
242
246
|
return null;
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
|
-
const site =
|
|
249
|
+
const site = new ConfigObject();
|
|
250
|
+
site.data = res.json();
|
|
251
|
+
site.updateLastModified(res.headers);
|
|
246
252
|
|
|
247
253
|
// always load default profile if available
|
|
248
|
-
if (!site.extends?.profile) {
|
|
249
|
-
site.extends = {
|
|
254
|
+
if (!site.data.extends?.profile) {
|
|
255
|
+
site.data.extends = {
|
|
250
256
|
profile: 'default',
|
|
251
257
|
};
|
|
252
258
|
}
|
|
253
|
-
const profile = await loadProfile(ctx, rso, site.extends.profile);
|
|
254
|
-
|
|
259
|
+
const profile = await loadProfile(ctx, rso, site.data.extends.profile);
|
|
260
|
+
site.merge(profile);
|
|
261
|
+
const config = site.data;
|
|
255
262
|
if (scope === SCOPE_PIPELINE) {
|
|
256
263
|
config.metadata = {
|
|
257
264
|
preview: await loadMetadata(ctx, config, 'preview'),
|
|
258
265
|
live: await loadMetadata(ctx, config, 'live'),
|
|
259
266
|
};
|
|
260
267
|
if (!config.robots) {
|
|
261
|
-
const
|
|
262
|
-
if (txt) {
|
|
263
|
-
config.robots =
|
|
268
|
+
const robots = await fetchRobotsTxt(ctx, config.code.owner, config.code.repo);
|
|
269
|
+
if (robots.txt) {
|
|
270
|
+
config.robots = robots;
|
|
264
271
|
}
|
|
265
272
|
}
|
|
266
273
|
}
|
|
267
274
|
if (scope === SCOPE_PIPELINE || scope === SCOPE_DELIVERY) {
|
|
268
275
|
config.head = await loadHeadHtml(ctx, config, rso.ref);
|
|
269
276
|
}
|
|
270
|
-
return
|
|
277
|
+
return site;
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
async function getSurrogateKey(opts, config) {
|
|
@@ -392,21 +399,22 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
392
399
|
}
|
|
393
400
|
|
|
394
401
|
const rso = { ref, site, org };
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
'x-surrogate-key': await getSurrogateKey(opts,
|
|
402
|
+
const siteConfig = await resolveConfig(ctx, rso, scope);
|
|
403
|
+
const headers = {
|
|
404
|
+
'x-surrogate-key': await getSurrogateKey(opts, siteConfig?.data),
|
|
398
405
|
};
|
|
399
|
-
if (!
|
|
406
|
+
if (!siteConfig) {
|
|
400
407
|
return new PipelineResponse('', {
|
|
401
408
|
status: 404,
|
|
402
409
|
headers: {
|
|
403
|
-
...
|
|
410
|
+
...headers,
|
|
404
411
|
'x-error': 'config not found.',
|
|
405
412
|
},
|
|
406
413
|
});
|
|
407
414
|
}
|
|
408
415
|
|
|
409
|
-
|
|
416
|
+
const config = siteConfig.data;
|
|
417
|
+
if (scope !== SCOPE_RAW) {
|
|
410
418
|
delete config.extends;
|
|
411
419
|
}
|
|
412
420
|
|
|
@@ -425,10 +433,22 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
425
433
|
}
|
|
426
434
|
}
|
|
427
435
|
|
|
436
|
+
// todo: improve
|
|
437
|
+
siteConfig.updateLastModified(config.head);
|
|
438
|
+
siteConfig.updateLastModified(config.robots);
|
|
439
|
+
siteConfig.updateLastModified(config.metadata?.preview);
|
|
440
|
+
siteConfig.updateLastModified(config.metadata?.live);
|
|
441
|
+
|
|
442
|
+
if (siteConfig.lastModified) {
|
|
443
|
+
headers['last-modified'] = siteConfig.lastModified;
|
|
444
|
+
delete siteConfig.lastModified;
|
|
445
|
+
delete siteConfig.lastModifiedTime;
|
|
446
|
+
}
|
|
447
|
+
|
|
428
448
|
if (opts.scope === SCOPE_DELIVERY) {
|
|
429
449
|
return new PipelineResponse('', {
|
|
430
450
|
headers: {
|
|
431
|
-
...
|
|
451
|
+
...headers,
|
|
432
452
|
'x-hlx-contentbus-id': config.content.contentBusId,
|
|
433
453
|
'x-hlx-owner': config.code.owner,
|
|
434
454
|
'x-hlx-repo': config.code.repo,
|
|
@@ -464,7 +484,7 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
464
484
|
return new PipelineResponse(JSON.stringify(adminConfig, null, 2), {
|
|
465
485
|
headers: {
|
|
466
486
|
'content-type': 'application/json',
|
|
467
|
-
...
|
|
487
|
+
...headers,
|
|
468
488
|
},
|
|
469
489
|
});
|
|
470
490
|
}
|
|
@@ -474,7 +494,7 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
474
494
|
return new PipelineResponse(JSON.stringify(config, null, 2), {
|
|
475
495
|
headers: {
|
|
476
496
|
'content-type': 'application/json',
|
|
477
|
-
...
|
|
497
|
+
...headers,
|
|
478
498
|
},
|
|
479
499
|
});
|
|
480
500
|
}
|
|
@@ -501,7 +521,7 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
501
521
|
return new PipelineResponse(JSON.stringify(pipelineConfig, null, 2), {
|
|
502
522
|
headers: {
|
|
503
523
|
'content-type': 'application/json',
|
|
504
|
-
...
|
|
524
|
+
...headers,
|
|
505
525
|
},
|
|
506
526
|
});
|
|
507
527
|
}
|
|
@@ -515,7 +535,7 @@ export async function getConfigResponse(ctx, opts) {
|
|
|
515
535
|
return new PipelineResponse(JSON.stringify(publicConfig, null, 2), {
|
|
516
536
|
headers: {
|
|
517
537
|
'content-type': 'application/json',
|
|
518
|
-
...
|
|
538
|
+
...headers,
|
|
519
539
|
},
|
|
520
540
|
});
|
|
521
541
|
}
|