@toa.io/extensions.configuration 0.20.0-dev.31 → 0.20.0-dev.35

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.
Files changed (51) hide show
  1. package/package.json +16 -9
  2. package/readme.md +68 -155
  3. package/schemas/annotation.cos.yaml +1 -0
  4. package/schemas/manifest.cos.yaml +2 -0
  5. package/source/Aspect.test.ts +15 -0
  6. package/source/Aspect.ts +23 -0
  7. package/source/Factory.ts +12 -0
  8. package/source/configuration.test.ts +89 -0
  9. package/source/configuration.ts +52 -0
  10. package/source/deployment.test.ts +21 -0
  11. package/source/deployment.ts +69 -0
  12. package/source/index.ts +3 -0
  13. package/source/manifest.test.ts +15 -0
  14. package/source/manifest.ts +15 -0
  15. package/source/schemas.ts +8 -0
  16. package/tsconfig.json +9 -0
  17. package/docs/discussion.md +0 -109
  18. package/source/.deployment/index.js +0 -7
  19. package/source/.deployment/secrets.js +0 -33
  20. package/source/.deployment/variables.js +0 -26
  21. package/source/.manifest/.normalize/verbose.js +0 -51
  22. package/source/.manifest/index.js +0 -7
  23. package/source/.manifest/normalize.js +0 -16
  24. package/source/.manifest/schema.yaml +0 -9
  25. package/source/.manifest/validate.js +0 -13
  26. package/source/.provider/env.js +0 -19
  27. package/source/.provider/form.js +0 -20
  28. package/source/.provider/index.js +0 -7
  29. package/source/annotation.fixtures.js +0 -39
  30. package/source/annotation.js +0 -36
  31. package/source/annotation.test.js +0 -43
  32. package/source/aspect.fixtures.js +0 -32
  33. package/source/aspect.js +0 -36
  34. package/source/aspect.test.js +0 -76
  35. package/source/configuration.js +0 -19
  36. package/source/deployment.fixtures.js +0 -38
  37. package/source/deployment.js +0 -20
  38. package/source/deployment.test.js +0 -70
  39. package/source/factory.js +0 -39
  40. package/source/factory.test.js +0 -22
  41. package/source/index.js +0 -13
  42. package/source/manifest.js +0 -13
  43. package/source/manifest.test.js +0 -123
  44. package/source/provider.js +0 -119
  45. package/source/provider.test.js +0 -130
  46. package/source/secrets.js +0 -28
  47. package/source/secrets.test.js +0 -47
  48. package/types/aspect.d.ts +0 -11
  49. package/types/factory.d.ts +0 -12
  50. package/types/provider.d.ts +0 -22
  51. /package/{docs → notes}/consistency.md +0 -0
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.configuration",
3
- "version": "0.20.0-dev.31",
3
+ "version": "0.20.0-dev.35",
4
4
  "description": "Toa Configuration",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
7
- "main": "source/index.js",
7
+ "main": "transpiled/index.js",
8
+ "types": "transpiled/index.d.ts",
8
9
  "repository": {
9
10
  "type": "git",
10
11
  "url": "git+https://github.com/toa-io/toa.git"
@@ -15,12 +16,18 @@
15
16
  "publishConfig": {
16
17
  "access": "public"
17
18
  },
18
- "dependencies": {
19
- "@toa.io/core": "0.20.0-dev.31",
20
- "@toa.io/generic": "0.20.0-dev.31",
21
- "@toa.io/schema": "0.20.0-dev.31",
22
- "@toa.io/yaml": "0.20.0-dev.31",
23
- "clone-deep": "4.0.1"
19
+ "scripts": {
20
+ "prepublishOnly": "npm run transpile",
21
+ "transpile": "tsc"
22
+ },
23
+ "jest": {
24
+ "preset": "ts-jest",
25
+ "testEnvironment": "node"
24
26
  },
25
- "gitHead": "f9ad6bf2bab1298b96ca52557b7e36b6041cfc88"
27
+ "gitHead": "04d3317272256b98003a414ee1eb2555b65163a5",
28
+ "dependencies": {
29
+ "@toa.io/core": "0.20.0-dev.35",
30
+ "@toa.io/generic": "0.20.0-dev.35",
31
+ "@toa.io/schemas": "0.20.0-dev.35"
32
+ }
26
33
  }
package/readme.md CHANGED
@@ -5,20 +5,24 @@
5
5
  ### Define
6
6
 
7
7
  ```yaml
8
- # component.toa.yaml
8
+ # manifest.toa.yaml
9
9
  name: dummy
10
10
  namespace: dummies
11
11
 
12
12
  configuration:
13
- foo: bar
14
- baz: 1
13
+ schema:
14
+ foo: string
15
+ bar: number
16
+ defaults:
17
+ foo: bar
18
+ bar: 1
15
19
  ```
16
20
 
17
21
  ### Use
18
22
 
19
23
  ```javascript
20
24
  function transition (input, entity, context) {
21
- const { foo, baz } = context.configuration
25
+ const { foo, bar } = context.configuration
22
26
 
23
27
  // ...
24
28
  }
@@ -30,9 +34,9 @@ function transition (input, entity, context) {
30
34
  # context.toa.yaml
31
35
  configuration:
32
36
  dummies.dummy:
33
- foo: qux # override default value defined by dummies.dummy
37
+ foo: qux # override default value
34
38
  foo@staging: quux # deployment environment discriminator
35
- baz: $BAZ_VALUE # secret
39
+ bar: $BAZ_VALUE # secret
36
40
  ```
37
41
 
38
42
  ### Deploy secrets
@@ -45,130 +49,96 @@ $ toa conceal configuration BAZ_VALUE=$ecr3t
45
49
 
46
50
  ## Problem Definition
47
51
 
48
- - Components must be reusable in different contexts and deployment environments that are in
49
- different configurations.
50
- - Some algorithm parameters must be deployed secretly.
51
-
52
- ## Definitions
53
-
54
- ### Configuration (Distributed System Configuration)
55
-
56
- Set of static[^1] parameters for all algorithms within a given system.
57
-
58
- ### Configuration Schema
59
-
60
- Schema is defining component's algorithm parameters (optionally with default values).
52
+ - Components should be runnable in different deployment environments.
53
+ - Some algorithm's parameters should be deployed secretly.
54
+ - Components should be reusable in different contexts.
61
55
 
62
- ### Configuration Object
56
+ ## Manifest
63
57
 
64
- Value valid against Configuration Schema.
58
+ Component's configuration is declared using `configuration` manifest,
59
+ containing `schema` and optionnaly `defaults` properties.
65
60
 
66
- ### Configuration Value
61
+ ### Schema
67
62
 
68
- The merge result of Configuration Schema's defaults and Configuration Object.
63
+ Configuration schema is declared with [COS](/libraries/concise).
69
64
 
70
- ### Context Configuration
71
-
72
- Map of Configuration Objects for components added to a given context.
73
-
74
- ## Responsibility Segregation
65
+ ```yaml
66
+ # manifest.toa.yaml
67
+ name: dummy
68
+ namespace: dummies
75
69
 
76
- Configuration Schema is a *form* of configuration defined by component. Specific *values* for
77
- specific contexts and deployment environments are defined by Context Configuration according to the
78
- Schema.
70
+ configuration:
71
+ schema:
72
+ foo: string
73
+ bar: number
74
+ ```
79
75
 
80
- See [Reusable Components](#).
76
+ > Introducing non-backward compatible changes to a configuration schema will result in a loss of
77
+ > compatibility with existing contexts and deployment environments.
78
+ > Therefore, configuration schema changes are subject to component versioning.
81
79
 
82
- ## Configuration Schema
80
+ If `configuration` object doesn't contain property `schema`, then it is considered to be schema.
83
81
 
84
- Configuration Schema is declared as a component extension
85
- using [JSON Schema](https://json-schema.org) `object` type.
82
+ ```yaml
83
+ # manifest.toa.yaml
84
+ name: dummy
85
+ namespace: dummies
86
86
 
87
- > ![Warning](https://img.shields.io/badge/Warning-yellow)<br/>
88
- > By introducing non-backward compatible changes to a Configuration Schema, the compatibility
89
- > with existent contexts and deployment environments will be broken. That is, Configuration
90
- > Schema changes are subjects of component versioning.
87
+ configuration:
88
+ foo: string
89
+ bar: number
90
+ ```
91
91
 
92
- > ![Recommendation](https://img.shields.io/badge/Recommendation-green)<br/>
93
- > Having default values for all required parameters will allow components to be runnable
94
- > without configuration (i.e. on local environment).
92
+ ### Defaults
95
93
 
96
- ### Example
94
+ The default configuration value can be provided using the `defaults` property, which should conform
95
+ to the configuration schema.
97
96
 
98
97
  ```yaml
99
- # component.toa.yaml
98
+ # manifest.toa.yaml
100
99
  name: dummy
101
100
  namespace: dummies
102
101
 
103
- extensions:
104
- "@toa.io/extensions.configuration":
105
- properties:
106
- foo:
107
- type: string
108
- default: 'baz'
109
- bar:
110
- type: number
111
- required: [foo]
102
+ configuration:
103
+ schema:
104
+ foo: string
105
+ bar: number
106
+ defaults:
107
+ foo: hello
108
+ bar: 0
112
109
  ```
113
110
 
114
- ### Concise Declaration
115
-
116
- As it is known that Configuration Schema is declared with a JSON Schema `object` type, any
117
- configuration declaration without defined `properties` considered as concise. Properties of concise
118
- declaration are treated as required Configuration Schema properties with the same type as its value
119
- type and no additional properties allowed.
120
-
121
- Also note that a well-known shortcut `configuration` is available.
111
+ #### Schema defaults hint
122
112
 
123
- The next two declarations are equivalent.
113
+ The configuration schema itself can contain default primitive values using the COS syntax.
124
114
 
125
115
  ```yaml
126
- # component.toa.yaml
127
- configuration:
128
- foo: baz
129
- bar: 1
130
- ```
116
+ # manifest.toa.yaml
117
+ name: dummy
118
+ namespace: dummies
131
119
 
132
- ```yaml
133
- # component.toa.yaml
134
- extensions:
135
- "@toa.io/extensions.configuration":
136
- properties:
137
- foo:
138
- type: string
139
- default: baz
140
- bar:
141
- type: number
142
- default: 1
143
- additionalProperties: false
144
- required: [foo, bar]
120
+ configuration:
121
+ schema:
122
+ foo: hello
123
+ bar: 0
145
124
  ```
146
125
 
147
- ## Context Configuration
148
-
149
- Context Configuration is declared as a context annotation. Its keys must be
150
- component identifiers, and its values must be Configuration Objects for those
151
- components.
152
-
153
- Context Configuration keys and Configuration Object keys may be defined
154
- with [deployment environment discriminators](#).
126
+ ## Annotation
155
127
 
156
- ### Example
128
+ A component's configuration can be overridden using the configuration context annotation.
157
129
 
158
130
  ```yaml
159
131
  # context.toa.yaml
160
132
  configuration:
161
133
  dummies.dummy:
162
- foo: quu
134
+ foo: bye
163
135
  bar: 1
164
136
  bar@staging: 2
165
137
  ```
166
138
 
167
- ## Configuration Secrets
139
+ ## Secrets
168
140
 
169
- Context Configuration values which are uppercase strings prefixed with `$` considered as Secrets.
170
-
171
- ### Example
141
+ Configuration annotation top-level values which are uppercase strings prefixed with `$` considered as secrets.
172
142
 
173
143
  ```yaml
174
144
  # context.toa.yaml
@@ -177,14 +147,10 @@ configuration:
177
147
  api-key: $STRIPE_API_KEY
178
148
  ```
179
149
 
180
- Configuration values that are assigned with a reference to the Secret must be of type `string`.
181
-
182
- ### Secrets Deployment
183
-
184
150
  Secrets are not being deployed with context
185
- deployment ([`toa deploy`](../../runtime/cli/readme.md#deploy)),
186
- thus must be deployed separately at least once for each deployment environment
187
- manually ([`toa conceal`](../../runtime/cli/readme.md#conceal)).
151
+ deployment ([`toa deploy`](/runtime/cli/readme.md#deploy)), thus must be deployed separately at
152
+ least once for each deployment environment
153
+ manually ([`toa conceal`](/runtime/cli/readme.md#conceal)).
188
154
 
189
155
  Deployed kubernetes secret's name is predefined as `configuration`.
190
156
 
@@ -194,65 +160,12 @@ $ toa conceal configuration STRIPE_API_KEY=xxxxxxxx
194
160
 
195
161
  ## Aspect
196
162
 
197
- Configuration Value is available as a well-known operation Aspect `configuration`.
163
+ Component's configuration values are available as a well-known Aspect `configuration`.
198
164
 
199
165
  ```javascript
200
- // Node.js bridge
201
-
202
166
  function transition (input, entity, context) {
203
167
  const foo = context.configiuration.foo
204
168
 
205
169
  // ...
206
170
  }
207
171
  ```
208
-
209
- > ![Warning](https://img.shields.io/badge/Warning-yellow)<br/>
210
- > It is strongly **not** recommended to store a copy of value type configuration
211
- > values outside operation scope, thus it prevents operation to benefit
212
- > from [hot updates](#).
213
- >
214
- > ```javascript
215
- > // NOT RECOMMENDED
216
- > let foo
217
- >
218
- > function transition (input, entity, context) {
219
- > // NOT RECOMMENDED
220
- > if (foo === undefined) foo = context.configuration.foo
221
- >
222
- > // ...
223
- > }
224
- > ```
225
- > See [Genuine operations](/documentation/design.md#genuine-operations).
226
-
227
- ## Development Configuration
228
-
229
- Configuration can be exported by [`toa env`](/runtime/cli/readme.md#env).
230
-
231
- ### Local Environment Placeholders
232
-
233
- Context Configuration values may contain placeholders that reference environment variables.
234
- Placeholders are replaced with values if the corresponding environment variables are set.
235
-
236
- > Placeholders can only be used with local environment (exported by `toa env`), as these values are
237
- > not
238
- > deployed.
239
-
240
- ```yaml
241
- # context.toa.yaml
242
- configuration:
243
- dummies.dummy:
244
- url@local: https://stage${STAGE}.intranet/
245
- ```
246
-
247
- ```dotenv
248
- # .env
249
- STAGE=82
250
- ```
251
-
252
- ## Appendix
253
-
254
- - [Discussion](./docs/discussion.md)
255
- - [Configuration consistency](./docs/consistency.md)
256
-
257
- [^1]: Cannot be changed without a deployment as new values are considered to be a subject of
258
- testing. [#146](https://github.com/toa-io/toa/issues/146)
@@ -0,0 +1 @@
1
+ <object>
@@ -0,0 +1,2 @@
1
+ schema: object
2
+ defaults?: object
@@ -0,0 +1,15 @@
1
+ import { Aspect } from './Aspect'
2
+
3
+ it('should return value', async () => {
4
+ const configuration = {
5
+ foo: 'bar',
6
+ bar: {
7
+ baz: 'quux'
8
+ }
9
+ }
10
+
11
+ const aspect = new Aspect(configuration)
12
+
13
+ expect(aspect.invoke(['foo'])).toStrictEqual(configuration.foo)
14
+ expect(aspect.invoke(['bar', 'baz'])).toStrictEqual(configuration.bar.baz)
15
+ })
@@ -0,0 +1,23 @@
1
+ import { Connector, type extensions } from '@toa.io/core'
2
+
3
+ export class Aspect extends Connector implements extensions.Aspect {
4
+ public readonly name = 'configuration'
5
+
6
+ private readonly value: object
7
+
8
+ public constructor (value: object) {
9
+ super()
10
+
11
+ this.value = value
12
+ }
13
+
14
+ public invoke (path: string[]): any {
15
+ let cursor: any = this.value
16
+
17
+ if (path !== undefined)
18
+ for (const segment of path)
19
+ cursor = cursor[segment]
20
+
21
+ return cursor
22
+ }
23
+ }
@@ -0,0 +1,12 @@
1
+ import { type Locator, type extensions } from '@toa.io/core'
2
+ import { Aspect } from './Aspect'
3
+ import { type Manifest } from './manifest'
4
+ import { get } from './configuration'
5
+
6
+ export class Factory implements extensions.Factory {
7
+ public aspect (locator: Locator, manifest: Manifest): extensions.Aspect {
8
+ const value = get(locator, manifest)
9
+
10
+ return new Aspect(value)
11
+ }
12
+ }
@@ -0,0 +1,89 @@
1
+ import { encode } from '@toa.io/generic'
2
+ import { Locator } from '@toa.io/core'
3
+ import { generate } from 'randomstring'
4
+ import { get } from './configuration'
5
+ import { type Manifest } from './manifest'
6
+
7
+ let locator: Locator
8
+ let manifest: Manifest
9
+
10
+ beforeEach(() => {
11
+ locator = new Locator(generate(), generate())
12
+ manifest = { schema: { foo: 'string' } }
13
+ })
14
+
15
+ afterEach(() => {
16
+ for (const name of used)
17
+ process.env[name] = undefined
18
+
19
+ used = []
20
+ })
21
+
22
+ it('should read value', async () => {
23
+ manifest.schema = { foo: 'string' }
24
+ const value: object = { foo: generate() }
25
+
26
+ set(value)
27
+
28
+ const result = get(locator, manifest)
29
+
30
+ expect(result).toStrictEqual(value)
31
+ })
32
+
33
+ it('should return empty object if no value set', async () => {
34
+ expect(get(locator, manifest)).toStrictEqual({})
35
+ })
36
+
37
+ it('should substitute secrets', async () => {
38
+ const value: object = { foo: '$BAR' }
39
+
40
+ set(value)
41
+ set('bar', '_BAR')
42
+
43
+ const result = get(locator, manifest)
44
+
45
+ expect(result).toStrictEqual({ foo: 'bar' })
46
+ })
47
+
48
+ it('should use defaults', async () => {
49
+ manifest.schema = { foo: 'string', bar: ['number'], 'baz?': 'string' }
50
+ manifest.defaults = { foo: 'bar', bar: [1] }
51
+
52
+ const values = { bar: [2], baz: 'foo' }
53
+
54
+ set(values)
55
+
56
+ const result = get(locator, manifest)
57
+
58
+ expect(result).toStrictEqual({
59
+ foo: 'bar',
60
+ bar: [2],
61
+ baz: 'foo'
62
+ })
63
+ })
64
+
65
+ it('should validate', async () => {
66
+ manifest.schema = { foo: 'hello', bar: 'number' }
67
+
68
+ const values = { bar: 5 }
69
+
70
+ set(values)
71
+
72
+ const result = get(locator, manifest)
73
+
74
+ expect(result).toStrictEqual({
75
+ foo: 'hello',
76
+ bar: 5
77
+ })
78
+ })
79
+
80
+ function set (value: object | string, key = locator.uppercase): void {
81
+ const string = typeof value === 'string' ? value : encode(value)
82
+ const name = 'TOA_CONFIGURATION_' + key
83
+
84
+ process.env[name] = string
85
+
86
+ used.push(name)
87
+ }
88
+
89
+ let used: string[] = []
@@ -0,0 +1,52 @@
1
+ import { type Locator } from '@toa.io/core'
2
+ import { decode, add } from '@toa.io/generic'
3
+ import * as schemas from '@toa.io/schemas'
4
+ import { PREFIX, SECRET_RX } from './deployment'
5
+ import { type Manifest } from './manifest'
6
+
7
+ export function get (locator: Locator, manifest: Manifest): Configuration {
8
+ const values = getConfiguration(locator.uppercase)
9
+
10
+ substituteSecrets(values)
11
+
12
+ if (manifest.defaults !== undefined) add(values, manifest.defaults)
13
+
14
+ const schema = schemas.schema(manifest.schema)
15
+
16
+ schema.validate(values)
17
+
18
+ return values
19
+ }
20
+
21
+ function getConfiguration (suffix: string): Configuration {
22
+ const variable = PREFIX + suffix
23
+ const string = process.env[variable]
24
+
25
+ if (string === undefined) return {}
26
+ else return decode(string)
27
+ }
28
+
29
+ function substituteSecrets (configuration: Configuration): void {
30
+ for (const [key, value] of Object.entries(configuration)) {
31
+ if (typeof value !== 'string') continue
32
+
33
+ const match = value.match(SECRET_RX)
34
+
35
+ if (match === null) continue
36
+
37
+ const name = match.groups?.variable as string
38
+
39
+ configuration[key] = getSecret(name)
40
+ }
41
+ }
42
+
43
+ function getSecret (name: string): string {
44
+ const variable = PREFIX + '_' + name
45
+ const value = process.env[variable]
46
+
47
+ if (value === undefined) throw new Error(`${variable} is not set.`)
48
+
49
+ return value
50
+ }
51
+
52
+ export type Configuration = Record<string, any>
@@ -0,0 +1,21 @@
1
+ import { type Annotation, deployment, type Instance } from './deployment'
2
+ import { type Manifest } from './manifest'
3
+
4
+ it('should validate annotation', async () => {
5
+ const wrongType = 'not ok' as unknown as Annotation
6
+
7
+ expect(() => deployment([], wrongType)).toThrow('object')
8
+ })
9
+
10
+ it('should validate values', async () => {
11
+ const manifest: Manifest = {
12
+ schema: { foo: 'string', bar: 'number' },
13
+ defaults: { foo: 'ok', bar: 0 }
14
+ }
15
+
16
+ const locator = { id: 'component' }
17
+ const instances = [{ manifest, locator }] as unknown as Instance[]
18
+ const annotation: Annotation = { [locator.id]: { bar: 'not a number' } }
19
+
20
+ expect(() => deployment(instances, annotation)).toThrow('number')
21
+ })
@@ -0,0 +1,69 @@
1
+ import { type Dependency, type Variable, type Variables } from '@toa.io/operations'
2
+ import * as schemas from '@toa.io/schemas'
3
+ import { encode, overwrite } from '@toa.io/generic'
4
+ import { type Manifest } from './manifest'
5
+ import * as validators from './schemas'
6
+ import type { context } from '@toa.io/norm'
7
+
8
+ export function deployment (instances: Instance[], annotation: Annotation): Dependency {
9
+ validators.annotation.validate(annotation)
10
+
11
+ const variables: Variables = {}
12
+
13
+ for (const instance of instances) {
14
+ const values = annotation[instance.locator.id]
15
+
16
+ if (values === undefined) continue
17
+
18
+ validate(instance, values)
19
+
20
+ variables[instance.locator.label] = [{
21
+ name: PREFIX + instance.locator.uppercase,
22
+ value: encode(values)
23
+ }]
24
+
25
+ const secrets = createSecrets(values)
26
+
27
+ variables[instance.locator.label].push(...secrets)
28
+ }
29
+
30
+ return { variables }
31
+ }
32
+
33
+ function createSecrets (values: object): Variable[] {
34
+ const secrets: Variable[] = []
35
+
36
+ for (const value of Object.values(values)) {
37
+ if (typeof value !== 'string') continue
38
+
39
+ const match = value.match(SECRET_RX)
40
+
41
+ if (match === null) continue
42
+
43
+ const name = match.groups?.variable as string
44
+
45
+ secrets.push({
46
+ name: PREFIX + '_' + name,
47
+ secret: {
48
+ name: 'toa-configuration',
49
+ key: name
50
+ }
51
+ })
52
+ }
53
+
54
+ return secrets
55
+ }
56
+
57
+ function validate (instace: Instance, values: object): void {
58
+ const defaults = instace.manifest.defaults ?? {}
59
+ const configuration = overwrite(defaults, values)
60
+ const schema = schemas.schema(instace.manifest.schema)
61
+
62
+ schema.validate(configuration)
63
+ }
64
+
65
+ export const SECRET_RX = /^\$(?<variable>[A-Z0-9_]{1,32})$/
66
+ export const PREFIX = 'TOA_CONFIGURATION_'
67
+
68
+ export type Annotation = Record<string, object>
69
+ export type Instance = context.Dependency<Manifest>
@@ -0,0 +1,3 @@
1
+ export { manifest } from './manifest'
2
+ export { deployment } from './deployment'
3
+ export { Factory } from './Factory'
@@ -0,0 +1,15 @@
1
+ import { type Manifest, manifest } from './manifest'
2
+
3
+ it('should validate', async () => {
4
+ const additional = { schema: {}, foo: 'bar' } as unknown as Manifest
5
+
6
+ expect(() => {
7
+ manifest(additional)
8
+ }).toThrow('additional')
9
+
10
+ const wrongType = { schema: 'not ok' } as unknown as Manifest
11
+
12
+ expect(() => {
13
+ manifest(wrongType)
14
+ }).toThrow('object')
15
+ })
@@ -0,0 +1,15 @@
1
+ import * as schemas from './schemas'
2
+ import { type Configuration } from './configuration'
3
+
4
+ export function manifest (manifest: Manifest): Manifest {
5
+ if (manifest.schema === undefined) manifest = { schema: manifest }
6
+
7
+ schemas.manifest.validate(manifest)
8
+
9
+ return manifest
10
+ }
11
+
12
+ export interface Manifest {
13
+ schema: object
14
+ defaults?: Configuration
15
+ }