@adobe/helix-config 1.3.3 → 2.1.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,22 @@
1
+ # [2.1.0](https://github.com/adobe/helix-config/compare/v2.0.0...v2.1.0) (2024-03-27)
2
+
3
+
4
+ ### Features
5
+
6
+ * return 404 for missing branch ([#33](https://github.com/adobe/helix-config/issues/33)) ([b3ea0f4](https://github.com/adobe/helix-config/commit/b3ea0f419f34ee0a94b02b9a67c06c64eec06d2d))
7
+
8
+ # [2.0.0](https://github.com/adobe/helix-config/compare/v1.3.3...v2.0.0) (2024-03-14)
9
+
10
+
11
+ ### Features
12
+
13
+ * refactor s3 loader abstraction ([#31](https://github.com/adobe/helix-config/issues/31)) ([086cce5](https://github.com/adobe/helix-config/commit/086cce51e76da932ed68acef095e262db0e50be5))
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * replace HelixStorage with S3Loader
19
+
1
20
  ## [1.3.3](https://github.com/adobe/helix-config/compare/v1.3.2...v1.3.3) (2024-03-14)
2
21
 
3
22
 
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@adobe/helix-config",
3
- "version": "1.3.3",
3
+ "version": "2.1.0",
4
4
  "description": "Helix Config",
5
5
  "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
6
10
  "type": "module",
7
11
  "scripts": {
8
12
  "test": "c8 mocha",
@@ -32,7 +36,7 @@
32
36
  "@adobe/eslint-config-helix": "2.0.6",
33
37
  "@semantic-release/changelog": "6.0.3",
34
38
  "@semantic-release/git": "10.0.1",
35
- "@semantic-release/npm": "11.0.3",
39
+ "@semantic-release/npm": "12.0.0",
36
40
  "c8": "9.1.0",
37
41
  "eslint": "8.57.0",
38
42
  "husky": "9.0.11",
@@ -9,7 +9,7 @@
9
9
  * OF ANY KIND; either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import {HelixStorage} from "./HelixStorage";
12
+ import {S3Loader} from "./S3Loader";
13
13
 
14
14
  type Fetch = (url: string|Request, options?: RequestOptions) => Promise<Response>;
15
15
 
@@ -23,7 +23,7 @@ export declare enum ConfigScope {
23
23
  export declare class ConfigContext {
24
24
  withLog(log: Console): ConfigContext;
25
25
  withEnv(env: object): ConfigContext;
26
- withHelixStorage(storage: HelixStorage): ConfigContext;
26
+ withS3Loader(loader: S3Loader): ConfigContext;
27
27
  withFetch(fetch: Fetch): ConfigContext;
28
28
  }
29
29
 
@@ -45,7 +45,7 @@ export class ConfigContext {
45
45
 
46
46
  env = {};
47
47
 
48
- storage = null;
48
+ loader = null;
49
49
 
50
50
  fetch = null;
51
51
 
@@ -59,8 +59,8 @@ export class ConfigContext {
59
59
  return this;
60
60
  }
61
61
 
62
- withHelixStorage(storage) {
63
- this.storage = storage;
62
+ withS3Loader(loader) {
63
+ this.loader = loader;
64
64
  return this;
65
65
  }
66
66
 
@@ -0,0 +1,32 @@
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 {PipelineResponse} from "./PipelineResponse";
13
+
14
+
15
+ /**
16
+ * S3 Loader abstration used to load S3 objects.
17
+ */
18
+ export declare interface S3Loader {
19
+ /**
20
+ * Loads a S3 object from the given bucket and key.
21
+ * @param {string} bucketId
22
+ * @param {string} key
23
+ */
24
+ getObject(bucketId, key): Promise<PipelineResponse>;
25
+
26
+ /**
27
+ * Retrieves the head metadata of a S3 object from the given bucket and key.
28
+ * @param {string} bucketId
29
+ * @param {string} key
30
+ */
31
+ headObject(bucketId, key): Promise<PipelineResponse>;
32
+ }
@@ -11,6 +11,10 @@
11
11
  */
12
12
  import { SCOPE_PIPELINE } from './ConfigContext.js';
13
13
 
14
+ const HELIX_CODE_BUS = 'helix-code-bus';
15
+
16
+ const HELIX_CONTENT_BUS = 'helix-content-bus';
17
+
14
18
  /**
15
19
  * Retrieves the helix-config.json which is an aggregate from fstab.yaml and head.html.
16
20
  * Note: it always uses the branch `main`.
@@ -20,16 +24,14 @@ import { SCOPE_PIPELINE } from './ConfigContext.js';
20
24
  * @returns {Promise<HelixConfig|null>} the helix-config or {@code null} if optional and not found.
21
25
  */
22
26
  async function fetchHelixConfig(ctx, rso) {
23
- const codeBus = ctx.storage.codeBus();
24
27
  const key = `${rso.org}/${rso.site}/main/helix-config.json`;
25
- const meta = {};
26
- const data = await codeBus.get(key, meta);
27
- if (!data) {
28
+ const res = await ctx.loader.getObject(HELIX_CODE_BUS, key);
29
+ if (!res.body) {
28
30
  return null;
29
31
  }
30
- const config = JSON.parse(data);
32
+ const config = res.json();
31
33
  // set contentbus id, if present in header
32
- const cbid = meta['x-contentbus-id'];
34
+ const cbid = res.headers.get('x-amz-meta-x-contentbus-id');
33
35
  if (cbid && !config.content) {
34
36
  // create the content section if not already present
35
37
  config.content = {
@@ -57,13 +59,12 @@ async function fetchHelixConfig(ctx, rso) {
57
59
  * @returns {Promise<ConfigAll|null>} the project configuration
58
60
  */
59
61
  async function fetchConfigAll(ctx, contentBusId, partition) {
60
- const contentBus = ctx.storage.contentBus();
61
62
  const key = `${contentBusId}/${partition}/.helix/config-all.json`;
62
- const data = await contentBus.get(key);
63
- if (!data) {
63
+ const res = await ctx.loader.getObject(HELIX_CONTENT_BUS, key);
64
+ if (!res.body) {
64
65
  return null;
65
66
  }
66
- return JSON.parse(data);
67
+ return res.json();
67
68
  }
68
69
 
69
70
  /**
@@ -9,7 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { ModifiersConfig } from '@adobe/helix-shared-config';
12
+ import { ModifiersConfig } from '@adobe/helix-shared-config/modifiers';
13
13
  import { computeSurrogateKey } from '@adobe/helix-shared-utils';
14
14
  import { PipelineResponse } from './PipelineResponse.js';
15
15
  import { SCOPE_ADMIN, SCOPE_PIPELINE, SCOPE_DELIVERY } from './ConfigContext.js';
@@ -34,6 +34,12 @@ import { resolveLegacyConfig } from './config-legacy.js';
34
34
 
35
35
  const METADATA_JSON = '/metadata.json';
36
36
 
37
+ const HELIX_CODE_BUS = 'helix-code-bus';
38
+
39
+ const HELIX_CONFIG_BUS = 'helix-config-bus';
40
+
41
+ const HELIX_CONTENT_BUS = 'helix-content-bus';
42
+
37
43
  export function toArray(v) {
38
44
  if (!v) {
39
45
  return [];
@@ -77,7 +83,6 @@ export function getAccessConfig(access, partition) {
77
83
  * @returns {Promise<{data: ModifierMap}|{}>} the metadata
78
84
  */
79
85
  async function loadMetadata(ctx, config, partition) {
80
- const contentBus = ctx.storage.contentBus();
81
86
  const paths = config.metadata?.source ?? [];
82
87
  if (!paths.length) {
83
88
  paths.push(METADATA_JSON);
@@ -86,11 +91,11 @@ async function loadMetadata(ctx, config, partition) {
86
91
  // generate the metadata-all.json first
87
92
  const metadata = [];
88
93
  for (const path of paths) {
89
- const key = `/${config.content.contentBusId}/${partition}${path}`;
94
+ const key = `${config.content.contentBusId}/${partition}${path}`;
90
95
  // eslint-disable-next-line no-await-in-loop
91
- const buf = await contentBus.get(key);
92
- if (buf) {
93
- const json = JSON.parse(buf.toString('utf-8'));
96
+ const res = await ctx.loader.getObject(HELIX_CONTENT_BUS, key);
97
+ if (res.body) {
98
+ const json = res.json();
94
99
  const data = json.data || json.default?.data;
95
100
  if (data) {
96
101
  metadata.push(...data);
@@ -109,12 +114,11 @@ async function loadMetadata(ctx, config, partition) {
109
114
  }
110
115
 
111
116
  async function loadHeadHtml(ctx, config, ref) {
112
- const codeBus = ctx.storage.codeBus();
113
117
  const key = `${config.code.owner}/${config.code.repo}/${ref}/head.html`;
114
- const buf = await codeBus.get(key);
115
- if (buf) {
118
+ const res = await ctx.loader.getObject(HELIX_CODE_BUS, key);
119
+ if (res.body) {
116
120
  return {
117
- html: buf.toString('utf-8'),
121
+ html: res.body,
118
122
  };
119
123
  }
120
124
  return {};
@@ -143,17 +147,16 @@ function retainProperty(obj, prop) {
143
147
  */
144
148
  async function resolveConfig(ctx, rso, scope) {
145
149
  // try to load site config from config-bus
146
- const configBus = ctx.storage.configBus();
147
- const key = `/orgs/${rso.org}/sites/${rso.site}.json`;
148
- const buf = await configBus.get(key);
149
- if (!buf) {
150
+ const key = `orgs/${rso.org}/sites/${rso.site}.json`;
151
+ const res = await ctx.loader.getObject(HELIX_CONFIG_BUS, key);
152
+ if (!res.body) {
150
153
  if (scope !== SCOPE_ADMIN) {
151
154
  return resolveLegacyConfig(ctx, rso, scope);
152
155
  } else {
153
156
  return null;
154
157
  }
155
158
  }
156
- const config = JSON.parse(buf.toString('utf-8'));
159
+ const config = res.json();
157
160
  if (scope === SCOPE_PIPELINE) {
158
161
  config.metadata = {
159
162
  preview: await loadMetadata(ctx, config, 'preview'),
@@ -178,10 +181,14 @@ export async function getConfigResponse(ctx, opts) {
178
181
  } = opts;
179
182
  const rso = { ref, site, org };
180
183
  const config = await resolveConfig(ctx, rso, scope);
184
+ const surrogateHeaders = {
185
+ 'x-surrogate-key': await getSurrogateKey(opts),
186
+ };
181
187
  if (!config) {
182
188
  return new PipelineResponse('', {
183
189
  status: 404,
184
190
  headers: {
191
+ ...surrogateHeaders,
185
192
  'x-error': 'config not found.',
186
193
  },
187
194
  });
@@ -200,15 +207,10 @@ export async function getConfigResponse(ctx, opts) {
200
207
  }
201
208
  }
202
209
 
203
- const headers = {
204
- 'content-type': 'application/json',
205
- 'x-surrogate-key': await getSurrogateKey(opts),
206
- };
207
-
208
210
  if (opts.scope === SCOPE_DELIVERY) {
209
211
  return new PipelineResponse('', {
210
212
  headers: {
211
- 'x-surrogate-key': await getSurrogateKey(opts),
213
+ ...surrogateHeaders,
212
214
  'x-hlx-contentbus-id': config.content.contentBusId,
213
215
  'x-hlx-owner': config.code.owner,
214
216
  'x-hlx-repo': config.code.repo,
@@ -234,11 +236,25 @@ export async function getConfigResponse(ctx, opts) {
234
236
  },
235
237
  };
236
238
  return new PipelineResponse(JSON.stringify(adminConfig, null, 2), {
237
- headers,
239
+ headers: {
240
+ 'content-type': 'application/json',
241
+ ...surrogateHeaders,
242
+ },
238
243
  });
239
244
  }
240
245
 
241
246
  if (opts.scope === SCOPE_PIPELINE) {
247
+ // validate that ref exists in code-bus
248
+ if (!config.head?.html) {
249
+ return new PipelineResponse('', {
250
+ status: 404,
251
+ headers: {
252
+ 'x-error': 'ref does not exit (no head.html)',
253
+ ...surrogateHeaders,
254
+ },
255
+ });
256
+ }
257
+
242
258
  // remove all properties except `host`. pipeline doesn't need the others.
243
259
  retainProperty(config.cdn, 'host');
244
260
  const pipelineConfig = {
@@ -255,7 +271,10 @@ export async function getConfigResponse(ctx, opts) {
255
271
  folders: config.folders,
256
272
  };
257
273
  return new PipelineResponse(JSON.stringify(pipelineConfig, null, 2), {
258
- headers,
274
+ headers: {
275
+ 'content-type': 'application/json',
276
+ ...surrogateHeaders,
277
+ },
259
278
  });
260
279
  }
261
280
 
@@ -266,6 +285,9 @@ export async function getConfigResponse(ctx, opts) {
266
285
  public: {},
267
286
  };
268
287
  return new PipelineResponse(JSON.stringify(publicConfig, null, 2), {
269
- headers,
288
+ headers: {
289
+ 'content-type': 'application/json',
290
+ ...surrogateHeaders,
291
+ },
270
292
  });
271
293
  }
@@ -12,10 +12,16 @@
12
12
  import { PipelineResponse } from './PipelineResponse.js';
13
13
 
14
14
  export async function getDomainResponse(ctx, domain) {
15
- const configBus = ctx.storage.configBus();
16
- const key = `/domains/${domain}`;
17
- const data = await configBus.head(key);
18
- if (!data?.Metadata?.location) {
15
+ const res = await ctx.loader.headObject('helix-config-bus', `domains/${domain}`);
16
+ const location = res.headers.get('x-amz-meta-location');
17
+ if (res.status === 200 && location) {
18
+ return new PipelineResponse('', {
19
+ status: 200,
20
+ headers: {
21
+ location,
22
+ },
23
+ });
24
+ } else {
19
25
  return new PipelineResponse('', {
20
26
  status: 404,
21
27
  headers: {
@@ -23,10 +29,4 @@ export async function getDomainResponse(ctx, domain) {
23
29
  },
24
30
  });
25
31
  }
26
- return new PipelineResponse('', {
27
- status: 200,
28
- headers: {
29
- Location: data.Metadata.location,
30
- },
31
- });
32
32
  }
package/src/index.d.ts CHANGED
@@ -12,6 +12,9 @@
12
12
  import { PipelineResponse } from './PipelineResponse';
13
13
  import { ConfigContext, ConfigScope } from "./ConfigContext";
14
14
 
15
+ export * from './PipelineResponse';
16
+ export * from './S3Loader';
17
+
15
18
  export declare class ConfigRequestOptions {
16
19
  scope: ConfigScope;
17
20
  org: string;
@@ -1,47 +0,0 @@
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
- export declare interface Bucket {
13
- /**
14
- * Return an object contents.
15
- *
16
- * @param {string} key object key
17
- * @param {object} [meta] output object to receive metadata if specified
18
- * @returns {Promise<Buffer>}object contents as a Buffer or null if no found.
19
- * @throws an error if the object could not be loaded due to an unexpected error.
20
- */
21
- get(key:string, meta?:object): Promise<Buffer|null>
22
-
23
- /**
24
- * Performs a head request
25
- * @param key
26
- * @returns the head metadata or null
27
- */
28
- head(key:string): Promise<object|null>;
29
- }
30
-
31
- export declare interface HelixStorage {
32
- /**
33
- * Returns a bucket with the given identifier.
34
- * @param {string} bucketId
35
- * @returns {Bucket} the s3 object or null if not found
36
- * @throws {Error} if the s3 object cannot be retrieved
37
- */
38
- bucket(bucketId:string): Bucket;
39
-
40
- contentBus(): Bucket;
41
-
42
- codeBus(): Bucket;
43
-
44
- configBus(): Bucket;
45
-
46
- mediaBus(): Bucket;
47
- }