@toa.io/norm 1.0.0-alpha.292 → 1.0.0-alpha.294
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 +16 -0
- package/package.json +6 -7
- package/src/.component/.expand/index.js +0 -1
- package/src/.component/expand.js +0 -2
- package/src/.component/schema.yaml +0 -3
- package/src/.context/.dependencies/resolve.js +22 -1
- package/src/.context/dependencies.js +1 -1
- package/src/index.js +1 -0
- package/src/manifest.js +154 -0
- package/src/shortcuts.js +0 -1
- package/test/component/expand.test.js +0 -11
- package/test/manifest.test.js +126 -0
- package/types/component.d.ts +2 -1
- package/types/context.d.ts +4 -0
- package/src/.component/.expand/properties.js +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.0.0-alpha.294](https://github.com/toa-io/toa/compare/v1.0.0-alpha.293...v1.0.0-alpha.294) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/norm
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1.0.0-alpha.293](https://github.com/toa-io/toa/compare/v1.0.0-alpha.292...v1.0.0-alpha.293) (2026-09-07)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @toa.io/norm
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# [1.0.0-alpha.292](https://github.com/toa-io/toa/compare/v1.0.0-alpha.291...v1.0.0-alpha.292) (2026-09-07)
|
|
7
23
|
|
|
8
24
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.294",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,13 +21,12 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/definitions": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.293",
|
|
25
|
+
"@toa.io/definitions": "1.0.0-alpha.294",
|
|
26
26
|
"@toa.io/generic": "1.0.0-alpha.292",
|
|
27
27
|
"@toa.io/prototype": "1.0.0-alpha.289",
|
|
28
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
29
|
-
"fast-glob": "3.3.3"
|
|
30
|
-
"js-yaml": "5.4.1"
|
|
28
|
+
"@toa.io/schemas": "1.0.0-alpha.293",
|
|
29
|
+
"fast-glob": "3.3.3"
|
|
31
30
|
},
|
|
32
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "50b2b596a46a4d639d9f198f834d70c190f0a3c6"
|
|
33
32
|
}
|
|
@@ -3,6 +3,5 @@ export { entity } from './entity.js'
|
|
|
3
3
|
export { events } from './events.js'
|
|
4
4
|
export { extensions } from './extensions.js'
|
|
5
5
|
export { operations } from './operations.js'
|
|
6
|
-
export { properties } from './properties.js'
|
|
7
6
|
export { receivers } from './receivers.js'
|
|
8
7
|
export { version } from './version.js'
|
package/src/.component/expand.js
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
events,
|
|
6
6
|
receivers,
|
|
7
7
|
extensions,
|
|
8
|
-
properties,
|
|
9
8
|
version
|
|
10
9
|
} from './.expand/index.js'
|
|
11
10
|
|
|
@@ -15,7 +14,6 @@ export async function expand(manifest) {
|
|
|
15
14
|
operations(manifest)
|
|
16
15
|
events(manifest)
|
|
17
16
|
receivers(manifest)
|
|
18
|
-
properties(manifest)
|
|
19
17
|
extensions(manifest)
|
|
20
18
|
|
|
21
19
|
await version(manifest)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { definition } from '../../definition.js'
|
|
2
2
|
|
|
3
|
-
export const resolve = async (references, annotations = {}) => {
|
|
3
|
+
export const resolve = async (references, annotations = {}, context = {}) => {
|
|
4
4
|
const dependencies = {}
|
|
5
5
|
|
|
6
6
|
for (const [dependency, components] of Object.entries(references)) {
|
|
@@ -19,6 +19,27 @@ export const resolve = async (references, annotations = {}) => {
|
|
|
19
19
|
if (annotation !== undefined && module.annotation !== undefined) {
|
|
20
20
|
annotations[id] = module.annotation(annotation, instances)
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
// what a declaration costs to install, read from the definition rather than from the
|
|
24
|
+
// package: a deploy installs it, and nothing that declares nothing carries it. A component
|
|
25
|
+
// is asked for its own instance; a composition that runs the package's service is asked
|
|
26
|
+
// with none, because what a service brings is not a component's to declare
|
|
27
|
+
if (module.installs !== undefined) {
|
|
28
|
+
for (const instance of instances)
|
|
29
|
+
Object.assign(
|
|
30
|
+
(instance.component.packages ??= {}),
|
|
31
|
+
module.installs(instance, annotations[id])
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const service = module.installs(undefined, annotations[id])
|
|
35
|
+
|
|
36
|
+
// mono runs every service in one process, so it takes every service's packages
|
|
37
|
+
Object.assign((context.packages ??= {}), service)
|
|
38
|
+
|
|
39
|
+
for (const composition of context.compositions ?? [])
|
|
40
|
+
if (composition.services?.includes(dependency) === true)
|
|
41
|
+
Object.assign((composition.packages ??= {}), service)
|
|
42
|
+
}
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
for (const dependency of Object.keys(annotations)) {
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { context } from './context.js'
|
|
2
2
|
export { component } from './component.js'
|
|
3
|
+
export { plain, revive, NORMALIZED } from './manifest.js'
|
|
3
4
|
export { definition } from './definition.js'
|
|
4
5
|
export * as shortcuts from './shortcuts.js'
|
|
5
6
|
export * as entity from './entity.js'
|
package/src/manifest.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, isAbsolute, join, parse, relative, sep } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { Locator } from '@toa.io/core'
|
|
5
|
+
import { find } from '@toa.io/generic'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A normalised manifest, as it is carried in a file beside the component it is of.
|
|
9
|
+
*
|
|
10
|
+
* A build normalises every component to tag its image; the process that runs it normalised them
|
|
11
|
+
* again at start, reading every operation module's source through its bridge to do it. What the
|
|
12
|
+
* build derived is written instead, and the two cannot disagree: the sources it was read from are
|
|
13
|
+
* what the image's tag digests, and the version of Toa that read them is in that tag as well.
|
|
14
|
+
*
|
|
15
|
+
* What cannot be carried is where things are. A workspace and an image put a component in one
|
|
16
|
+
* directory and Toa's own packages in another, so every path is written as what it is of rather
|
|
17
|
+
* than as where it was — the component itself, or the package that holds it — and is resolved
|
|
18
|
+
* again on the other side, the way norm resolved it.
|
|
19
|
+
*
|
|
20
|
+
* A key with no value is not carried: a file has no way to say `undefined`, and nothing reads
|
|
21
|
+
* one apart from a key that is not there.
|
|
22
|
+
*/
|
|
23
|
+
export function plain(manifest) {
|
|
24
|
+
const { locator, packages, path, ...rest } = manifest
|
|
25
|
+
|
|
26
|
+
// what is carried is written, not the manifest the caller goes on using, and it is written
|
|
27
|
+
// as a file holds it: a key with no value does not survive and is not meant to
|
|
28
|
+
const declared = JSON.parse(JSON.stringify(rest))
|
|
29
|
+
|
|
30
|
+
walk(declared, (item) => {
|
|
31
|
+
item.path = carried(item.path, path)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
local(declared, path)
|
|
35
|
+
|
|
36
|
+
return declared
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What `plain` wrote, read back beside the component it is of. */
|
|
40
|
+
export function revive(declared, path) {
|
|
41
|
+
const manifest = { ...structuredClone(declared), path }
|
|
42
|
+
|
|
43
|
+
walk(manifest, (item) => {
|
|
44
|
+
item.path = resolved(item.path, path)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
manifest.locator = new Locator(manifest.name, manifest.namespace)
|
|
48
|
+
|
|
49
|
+
return manifest
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Everything in a manifest that says where a module of it is: what an inherited one declares is
|
|
54
|
+
* in the prototype it came from, and what the component declares is in the component.
|
|
55
|
+
*/
|
|
56
|
+
function walk(manifest, rewrite) {
|
|
57
|
+
for (const property of ROOTED)
|
|
58
|
+
for (const item of Object.values(manifest[property] ?? {}))
|
|
59
|
+
if (item.path !== undefined) rewrite(item)
|
|
60
|
+
|
|
61
|
+
for (let prototype = manifest.prototype; prototype != null; prototype = prototype.prototype)
|
|
62
|
+
if (prototype.path !== undefined) rewrite(prototype)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Where it is written. A workspace has none: it is a build's output, read by a process. */
|
|
66
|
+
export const NORMALIZED = 'manifest.toa.json'
|
|
67
|
+
|
|
68
|
+
/** Where norm says a module is, beside what it says about it. */
|
|
69
|
+
const ROOTED = ['events', 'receivers', 'guards']
|
|
70
|
+
|
|
71
|
+
/** The component's own directory, which is where it is read back. */
|
|
72
|
+
const SELF = '.'
|
|
73
|
+
|
|
74
|
+
function carried(path, root) {
|
|
75
|
+
if (path === root) return SELF
|
|
76
|
+
if (path.startsWith(root + sep)) return SELF + '/' + relative(root, path).split(sep).join('/')
|
|
77
|
+
|
|
78
|
+
return specifier(path)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolved(path, root) {
|
|
82
|
+
if (path === SELF) return root
|
|
83
|
+
if (path.startsWith(SELF + '/')) return join(root, path.slice(2))
|
|
84
|
+
|
|
85
|
+
return find(path, root, MANIFEST)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A directory in a package, named the way a manifest would name it, so that it is found again
|
|
90
|
+
* wherever the package is installed. A prototype that is a directory of the application's own is
|
|
91
|
+
* refused here rather than in the container: an image carries components and nothing between
|
|
92
|
+
* them, so nothing would be there to find, whether it is normalised here or there.
|
|
93
|
+
*/
|
|
94
|
+
function specifier(path) {
|
|
95
|
+
const root = above(path)
|
|
96
|
+
|
|
97
|
+
if (root !== undefined) {
|
|
98
|
+
const { name, private: hidden } = JSON.parse(
|
|
99
|
+
readFileSync(join(root, 'package.json'), 'utf8')
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if (name !== undefined && hidden !== true)
|
|
103
|
+
return [name, ...relative(root, path).split(sep)].filter(Boolean).join('/')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
`'${path}' is in no package, so an image cannot carry what is in it: ` +
|
|
108
|
+
'what a component inherits is reached by the reference of the package it is in.'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The directory of the package a path is in. */
|
|
113
|
+
function above(path) {
|
|
114
|
+
const { root } = parse(path)
|
|
115
|
+
|
|
116
|
+
let current = path
|
|
117
|
+
|
|
118
|
+
while (current !== root) {
|
|
119
|
+
if (existsSync(join(current, 'package.json'))) return current
|
|
120
|
+
|
|
121
|
+
current = dirname(current)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Nothing that is carried may say where it was on the machine that read it. What is checked is
|
|
129
|
+
* what can be told apart from a value of the application's own — an HTTP route is `/accounts`
|
|
130
|
+
* and is a path to nobody — so: anything inside the component, and any directory a manifest
|
|
131
|
+
* declares a component or a prototype in. This is what catches a path norm starts writing
|
|
132
|
+
* somewhere this file does not know about, at the build rather than at the start of a container
|
|
133
|
+
* that cannot find it.
|
|
134
|
+
*/
|
|
135
|
+
function local(value, root, at = '') {
|
|
136
|
+
if (typeof value === 'string') {
|
|
137
|
+
if (value === root || value.startsWith(root + sep) || component(value))
|
|
138
|
+
throw new Error(`'${at}' carries a path of the machine that read it: ${value}`)
|
|
139
|
+
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (value === null || typeof value !== 'object') return
|
|
144
|
+
|
|
145
|
+
for (const [key, item] of Object.entries(value))
|
|
146
|
+
local(item, root, at === '' ? key : `${at}.${key}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Whether a value is a directory a component or a prototype is declared in. */
|
|
150
|
+
function component(value) {
|
|
151
|
+
return isAbsolute(value) && existsSync(join(value, MANIFEST))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const MANIFEST = 'manifest.toa.yaml'
|
package/src/shortcuts.js
CHANGED
|
@@ -41,7 +41,6 @@ export const SHORTCUTS = {
|
|
|
41
41
|
node: '@toa.io/bridges.node',
|
|
42
42
|
bash: '@toa.io/bridges.bash',
|
|
43
43
|
mongodb: '@toa.io/storages.mongodb',
|
|
44
|
-
queues: '@toa.io/storages.queues',
|
|
45
44
|
exposition: '@toa.io/extensions.exposition',
|
|
46
45
|
realtime: '@toa.io/extensions.realtime',
|
|
47
46
|
configuration: '@toa.io/extensions.configuration',
|
|
@@ -30,14 +30,3 @@ it('should keep declared version', async () => {
|
|
|
30
30
|
|
|
31
31
|
assert.deepStrictEqual(source.version, '1.0.0')
|
|
32
32
|
})
|
|
33
|
-
|
|
34
|
-
it('should recognize storages.queues', async () => {
|
|
35
|
-
const queues = { foo: 'bar' }
|
|
36
|
-
|
|
37
|
-
source.queues = clone(queues)
|
|
38
|
-
|
|
39
|
-
await expand(source)
|
|
40
|
-
|
|
41
|
-
assert.strictEqual(source.queues, undefined)
|
|
42
|
-
assert.partialDeepStrictEqual(source.properties['@toa.io/storages.queues'], queues)
|
|
43
|
-
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { it, describe } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { component } from '../src/component.js'
|
|
7
|
+
import { plain, revive } from '../src/manifest.js'
|
|
8
|
+
|
|
9
|
+
/** the prototype itself: an entity, operations and events, and no prototype of its own */
|
|
10
|
+
const PROTOTYPE = resolve(import.meta.dirname, '../../prototype')
|
|
11
|
+
|
|
12
|
+
/** a component on the default prototype, with an entity, events and receivers */
|
|
13
|
+
const COMPONENT = resolve(
|
|
14
|
+
import.meta.dirname,
|
|
15
|
+
'../../../features/steps/.workspace/components/collection/external.consumer'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
/** a component whose prototype is a directory of the workspace's own */
|
|
19
|
+
const EXTENDED = resolve(
|
|
20
|
+
import.meta.dirname,
|
|
21
|
+
'../../../features/steps/.workspace/components/collection/configuration.extended'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const carry = (manifest) => JSON.parse(JSON.stringify(plain(manifest)))
|
|
25
|
+
|
|
26
|
+
describe('a manifest carried through a file', () => {
|
|
27
|
+
for (const [name, path] of [
|
|
28
|
+
['the prototype', PROTOTYPE],
|
|
29
|
+
['a component', COMPONENT]
|
|
30
|
+
])
|
|
31
|
+
it(`comes back the same: ${name}`, async () => {
|
|
32
|
+
const manifest = await component(path)
|
|
33
|
+
|
|
34
|
+
assert.deepStrictEqual(revive(carry(manifest), path), manifest)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not carry a key with no value', () => {
|
|
38
|
+
const carried = plain({ name: 'a', namespace: 'b', path: '/x', operations: { echo: { input: undefined } } })
|
|
39
|
+
|
|
40
|
+
assert.deepStrictEqual(carried.operations.echo, {})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('leaves the manifest it was given alone', async () => {
|
|
44
|
+
const manifest = await component(COMPONENT)
|
|
45
|
+
const before = JSON.stringify(manifest)
|
|
46
|
+
|
|
47
|
+
plain(manifest)
|
|
48
|
+
|
|
49
|
+
assert.strictEqual(JSON.stringify(manifest), before)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('is read beside the component wherever it is', async () => {
|
|
53
|
+
const carried = carry(await component(COMPONENT))
|
|
54
|
+
const revived = revive(carried, '/composition/external-consumer')
|
|
55
|
+
|
|
56
|
+
assert.strictEqual(revived.path, '/composition/external-consumer')
|
|
57
|
+
|
|
58
|
+
// what the component declares is in the component
|
|
59
|
+
assert.strictEqual(
|
|
60
|
+
revived.receivers['something_happened'].path,
|
|
61
|
+
'/composition/external-consumer'
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('reads what is inherited where the prototype is', async () => {
|
|
66
|
+
const prototype = (await component(COMPONENT)).prototype.path
|
|
67
|
+
const revived = revive(carry(await component(COMPONENT)), '/composition/external-consumer')
|
|
68
|
+
|
|
69
|
+
// an event of the prototype's is in the prototype, wherever the component is
|
|
70
|
+
assert.strictEqual(revived.events.created.path, prototype)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('carries what is inherited by the package that holds it', async () => {
|
|
74
|
+
const carried = carry(await component(COMPONENT))
|
|
75
|
+
|
|
76
|
+
assert.strictEqual(carried.prototype.path, '@toa.io/prototype')
|
|
77
|
+
assert.strictEqual(carried.events.created.path, '@toa.io/prototype')
|
|
78
|
+
assert.strictEqual(carried.receivers['something_happened'].path, '.')
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('what cannot be carried', () => {
|
|
83
|
+
it('leaves out where the manifest is and what only a deploy reads', async () => {
|
|
84
|
+
const manifest = await component(PROTOTYPE)
|
|
85
|
+
|
|
86
|
+
manifest.packages = { cloudinary: '2.11.0' }
|
|
87
|
+
|
|
88
|
+
const carried = plain(manifest)
|
|
89
|
+
|
|
90
|
+
assert.ok(!('locator' in carried))
|
|
91
|
+
assert.ok(!('path' in carried))
|
|
92
|
+
assert.ok(!('packages' in carried))
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('refuses a prototype an image would not have', async () => {
|
|
96
|
+
const manifest = await component(EXTENDED)
|
|
97
|
+
|
|
98
|
+
assert.throws(() => plain(manifest), /is in no package, so an image cannot carry what is in it/)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('refuses a path inside the component it does not know about', () => {
|
|
102
|
+
assert.throws(
|
|
103
|
+
() => plain({ name: 'a', namespace: 'b', path: '/x', invented: { at: '/x/y' } }),
|
|
104
|
+
/carries a path of the machine that read it/
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('refuses a component directory it does not know about', async () => {
|
|
109
|
+
const manifest = await component(PROTOTYPE)
|
|
110
|
+
|
|
111
|
+
manifest.invented = { at: COMPONENT }
|
|
112
|
+
|
|
113
|
+
assert.throws(() => plain(manifest), /carries a path of the machine that read it/)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('leaves a value of the application\'s own alone', () => {
|
|
117
|
+
const carried = plain({
|
|
118
|
+
name: 'a',
|
|
119
|
+
namespace: 'b',
|
|
120
|
+
path: '/x',
|
|
121
|
+
extensions: { exposition: { routes: [{ path: '/accounts' }] } }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
assert.strictEqual(carried.extensions.exposition.routes[0].path, '/accounts')
|
|
125
|
+
})
|
|
126
|
+
})
|
package/types/component.d.ts
CHANGED
|
@@ -65,10 +65,11 @@ type Declaration = {
|
|
|
65
65
|
events?: Events
|
|
66
66
|
receivers?: Record<string, Receiver>
|
|
67
67
|
extensions?: Record<string, object>
|
|
68
|
-
properties?: Record<string, object>
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
export type Manifest = Declaration & {
|
|
72
71
|
locator: Locator
|
|
73
72
|
path: string
|
|
73
|
+
/** what a deploy installs beside this component for what it declares, by package name */
|
|
74
|
+
packages?: Record<string, string>
|
|
74
75
|
}
|
package/types/context.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ interface Composition {
|
|
|
23
23
|
name: string
|
|
24
24
|
components: Manifest[]
|
|
25
25
|
services?: string[]
|
|
26
|
+
/** what a deploy installs for the services this composition runs, by package name */
|
|
27
|
+
packages?: Record<string, string>
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface Dependency<T = undefined> {
|
|
@@ -38,6 +40,8 @@ interface Context extends Declaration {
|
|
|
38
40
|
compositions?: Composition[]
|
|
39
41
|
components?: Manifest[]
|
|
40
42
|
dependencies?: Record<string, Dependency[]>
|
|
43
|
+
/** what a deploy installs for every service, which is what `mono` runs */
|
|
44
|
+
packages?: Record<string, string>
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
export function context(path: string, environment?: string): Promise<Context>
|