@toa.io/operations 1.0.0-alpha.284 → 1.0.0-alpha.287

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 (50) hide show
  1. package/CHANGELOG.md +1 -224
  2. package/package.json +4 -4
  3. package/readme.md +43 -0
  4. package/src/deployment/.deployment/.describe/compositions.js +7 -5
  5. package/src/deployment/.deployment/.describe/events.js +7 -7
  6. package/src/deployment/.deployment/.describe/fold.js +10 -3
  7. package/src/deployment/.deployment/.describe/fold.test.js +30 -14
  8. package/src/deployment/.deployment/.describe/mounts.js +4 -7
  9. package/src/deployment/.deployment/.describe/resources.js +7 -5
  10. package/src/deployment/.deployment/.describe/resources.test.js +13 -6
  11. package/src/deployment/.deployment/.describe/services.js +10 -9
  12. package/src/deployment/.deployment/.describe/services.test.js +22 -5
  13. package/src/deployment/.deployment/.describe/variables.js +2 -3
  14. package/src/deployment/.deployment/describe.js +11 -7
  15. package/src/deployment/.deployment/merge.js +11 -6
  16. package/src/deployment/.deployment/merge.test.js +41 -10
  17. package/src/deployment/composition.js +1 -1
  18. package/src/deployment/deployment.js +35 -16
  19. package/src/deployment/factory.js +39 -19
  20. package/src/deployment/images/composition.Dockerfile +1 -1
  21. package/src/deployment/images/composition.js +10 -9
  22. package/src/deployment/images/factory.js +17 -6
  23. package/src/deployment/images/format.js +9 -5
  24. package/src/deployment/images/image.fixtures.js +2 -2
  25. package/src/deployment/images/image.js +38 -21
  26. package/src/deployment/images/image.test.js +7 -2
  27. package/src/deployment/images/mono.Dockerfile +1 -1
  28. package/src/deployment/images/mono.js +10 -9
  29. package/src/deployment/images/publish.js +98 -0
  30. package/src/deployment/images/runtime-base.test.js +12 -6
  31. package/src/deployment/images/service.Dockerfile +1 -1
  32. package/src/deployment/images/service.js +4 -4
  33. package/src/deployment/operator.js +8 -8
  34. package/src/deployment/registry.js +89 -61
  35. package/src/deployment/registry.test.js +117 -43
  36. package/src/deployment/service.js +3 -1
  37. package/src/deployment/workspace.js +1 -1
  38. package/src/process.js +1 -1
  39. package/src/process.test.js +8 -4
  40. package/types/_deployment/composition.d.ts +0 -2
  41. package/types/_deployment/dependency.d.ts +10 -6
  42. package/types/_deployment/deployment.d.ts +0 -4
  43. package/types/_deployment/factory.d.ts +0 -2
  44. package/types/_deployment/images/factory.d.ts +4 -6
  45. package/types/_deployment/images/image.d.ts +2 -4
  46. package/types/_deployment/images/registry.d.ts +7 -10
  47. package/types/_deployment/operator.d.ts +5 -7
  48. package/types/_deployment/registry.d.ts +6 -8
  49. package/types/_deployment/service.d.ts +0 -2
  50. package/types/dependency.ts +3 -0
@@ -1,12 +1,9 @@
1
- import { posix } from 'node:path'
2
1
  import * as workspace from './workspace.js'
3
2
 
4
3
  /**
5
4
  * @implements {toa.deployment.Registry}
6
5
  */
7
6
  export class Registry {
8
- #scope
9
-
10
7
  #registry
11
8
 
12
9
  #factory
@@ -15,29 +12,28 @@ export class Registry {
15
12
 
16
13
  #images = []
17
14
 
18
- /** @type {string | undefined} */
15
+ /** @type {Promise<string> | undefined} */
19
16
  #builder
20
17
 
21
- constructor (scope, registry, factory, process) {
22
- this.#scope = scope
18
+ constructor(registry, factory, process) {
23
19
  this.#registry = registry
24
20
  this.#factory = factory
25
21
  this.#process = process
26
22
  }
27
23
 
28
- composition (composition) {
24
+ composition(composition) {
29
25
  return this.#create('composition', composition)
30
26
  }
31
27
 
32
- service (path, service) {
28
+ service(path, service) {
33
29
  return this.#create('service', path, service)
34
30
  }
35
31
 
36
- mono (composition) {
32
+ mono(composition) {
37
33
  return this.#create('mono', composition)
38
34
  }
39
35
 
40
- async prepare (root) {
36
+ async prepare(root) {
41
37
  const path = await workspace.create('images', root)
42
38
 
43
39
  await Promise.all(this.#images.map((image) => image.prepare(path)))
@@ -45,20 +41,17 @@ export class Registry {
45
41
  return path
46
42
  }
47
43
 
48
- async build () {
44
+ async build() {
49
45
  await this.prepare()
50
-
51
- for (const image of this.#images)
52
- await this.#build(image)
46
+ await this.#run(false)
53
47
  }
54
48
 
55
- async push () {
49
+ async push() {
56
50
  await this.prepare()
57
-
58
- for (const image of this.#images) await this.#push(image)
51
+ await this.#run(true)
59
52
  }
60
53
 
61
- tags () {
54
+ tags() {
62
55
  return this.#images.map((image) => image.reference)
63
56
  }
64
57
 
@@ -67,7 +60,7 @@ export class Registry {
67
60
  * @param {...any} args
68
61
  * @returns {toa.deployment.images.Image}
69
62
  */
70
- #create (type, ...args) {
63
+ #create(type, ...args) {
71
64
  const image = this.#factory[type](...args)
72
65
 
73
66
  this.#images.push(image)
@@ -75,54 +68,65 @@ export class Registry {
75
68
  return image
76
69
  }
77
70
 
71
+ /**
72
+ * Every image is probed at once, and what is missing is built concurrently: a build is
73
+ * bound by the registry it uploads to, not by the runner, so one at a time leaves both idle.
74
+ *
75
+ * @param {boolean} push
76
+ * @returns {Promise<void>}
77
+ */
78
+ async #run(push) {
79
+ const existing = await Promise.all(
80
+ this.#images.map((image) => this.exists(image.reference))
81
+ )
82
+
83
+ const missing = this.#images.filter((image, index) => {
84
+ if (existing[index]) console.log('Image already exists, skipping:', image.reference)
85
+
86
+ return !existing[index]
87
+ })
88
+
89
+ await pool(missing, (image) => this.#build(image, push))
90
+ }
91
+
78
92
  /**
79
93
  * @param {toa.deployment.images.Image} image
80
94
  * @param {boolean} [push]
81
95
  * @returns {Promise<void>}
82
96
  */
83
- async #build (image, push = false) {
84
- if (await this.exists(image.reference)) {
85
- console.log('Image already exists, skipping:', image.reference)
86
- return
87
- }
88
-
97
+ async #build(image, push = false) {
89
98
  const args = ['--context=default', 'buildx', 'build']
90
99
 
91
- if (push)
92
- args.push('--push')
93
- else
94
- args.push('--load')
100
+ if (push) args.push('--push')
101
+ else args.push('--load')
95
102
 
96
103
  args.push('--tag', image.reference, image.context)
97
104
 
98
- const multiarch = this.#registry.platforms !== null
99
-
100
105
  if (this.#registry.build?.arguments !== undefined) {
101
- for (const arg of this.#registry.build.arguments) args.push('--build-arg', `${arg}=${process.env[arg]}`)
106
+ for (const arg of this.#registry.build.arguments)
107
+ args.push('--build-arg', `${arg}=${process.env[arg]}`)
102
108
  }
103
109
 
104
- if (multiarch) {
105
- const platform = this.#registry.platforms.join(',')
106
- const builder = await this.#ensureBuilder()
110
+ const platforms = this.#registry.platforms
111
+
112
+ if (platforms !== null) args.push('--platform', platforms.join(','))
107
113
 
108
- args.push('--platform', platform)
109
- args.push('--builder', builder)
114
+ // the container driver is what builds for a platform the runner is not; for a single
115
+ // platform it costs a buildkit image to pull and buys nothing
116
+ if (platforms !== null && platforms.length > 1) {
117
+ args.push('--builder', await this.#ensureBuilder())
110
118
 
111
- if (this.#registry.base !== undefined)
112
- this.#appendCache(args)
113
- } else
114
- args.push('--builder', 'default')
119
+ // the driver that produces attestations is this one, and nothing reads them;
120
+ // each is a manifest list to export and push per image
121
+ args.push('--provenance=false')
122
+ } else args.push('--builder', 'default')
115
123
 
116
124
  args.push('--progress', 'plain')
117
125
 
118
126
  await this.#process.execute('docker', args)
119
127
  }
120
128
 
121
- async #push (image) {
122
- await this.#build(image, true)
123
- }
124
-
125
- async exists (tag) {
129
+ async exists(tag) {
126
130
  const args = ['manifest', 'inspect', tag]
127
131
 
128
132
  try {
@@ -136,30 +140,54 @@ export class Registry {
136
140
  return true
137
141
  }
138
142
 
139
- async #ensureBuilder () {
140
- if (this.#builder !== undefined)
141
- return this.#builder
143
+ /** @returns {Promise<string>} */
144
+ #ensureBuilder() {
145
+ // the promise is what is memoized, not its value: concurrent builds ask before any answers
146
+ this.#builder ??= this.#createBuilder()
147
+
148
+ return this.#builder
149
+ }
142
150
 
151
+ /** @returns {Promise<string>} */
152
+ async #createBuilder() {
143
153
  try {
144
- await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], { silently: true })
154
+ await this.#process.execute('docker', ['buildx', 'inspect', BUILDER], {
155
+ silently: true
156
+ })
145
157
  } catch {
146
- await this.#process.execute('docker', ['buildx', 'create', '--name', BUILDER, '--bootstrap'])
158
+ await this.#process.execute('docker', [
159
+ 'buildx',
160
+ 'create',
161
+ '--name',
162
+ BUILDER,
163
+ '--bootstrap'
164
+ ])
147
165
  }
148
166
 
149
- this.#builder = BUILDER
150
-
151
167
  return BUILDER
152
168
  }
169
+ }
153
170
 
154
- /**
155
- * @param {string[]} args
156
- */
157
- #appendCache (args) {
158
- const ref = posix.join(this.#registry.base, this.#scope, 'buildcache')
171
+ /**
172
+ * @template T
173
+ * @param {T[]} items
174
+ * @param {(item: T) => Promise<void>} work
175
+ * @returns {Promise<void>}
176
+ */
177
+ async function pool(items, work) {
178
+ const queue = items.slice()
159
179
 
160
- args.push('--cache-from', `type=registry,ref=${ref}`)
161
- args.push('--cache-to', `type=registry,ref=${ref},mode=max,image-manifest=true`)
162
- }
180
+ const workers = Array.from(
181
+ { length: Math.min(CONCURRENCY, queue.length) },
182
+ async () => {
183
+ while (queue.length > 0) await work(queue.shift())
184
+ }
185
+ )
186
+
187
+ await Promise.all(workers)
163
188
  }
164
189
 
165
190
  const BUILDER = 'toa'
191
+
192
+ /** How many builds run at once. Past this the uploads contend for the same uplink. */
193
+ const CONCURRENCY = 3
@@ -17,8 +17,7 @@ beforeEach(() => {
17
17
  images = []
18
18
  process = /** @type {toa.operations.Process} */ {
19
19
  execute: mock.fn(async (cmd, args) => {
20
- if (args[0] === 'manifest')
21
- throw new Error('manifest unknown')
20
+ if (args[0] === 'manifest') throw new Error('manifest unknown')
22
21
 
23
22
  if (args[0] === 'buildx' && args[1] === 'inspect')
24
23
  throw new Error('builder not found')
@@ -29,30 +28,45 @@ beforeEach(() => {
29
28
 
30
29
  factory = /** @type {toa.deployment.images.Factory} */ {
31
30
  composition: () => createImage('composition-mono'),
32
- service: () => createImage('extension-realtime')
31
+ service: () => createImage('extension-realtime'),
32
+ mono: () => createImage('mono')
33
33
  }
34
34
  })
35
35
 
36
36
  it('should reuse named builder across images', async () => {
37
- const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
37
+ const registry = createRegistry({
38
+ base: 'example.com/reg',
39
+ platforms: ['linux/amd64', 'linux/arm64']
40
+ })
38
41
 
39
42
  registry.composition(/** @type {any} */ ({}))
40
43
  registry.service('.', /** @type {any} */ ({}))
41
44
 
42
45
  await registry.build()
43
46
 
44
- const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
45
- args[0] === 'buildx' && args[1] === 'create')
47
+ const creates = process.execute.mock.calls.filter(
48
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
49
+ )
46
50
 
47
51
  assert.strictEqual(creates.length, 1)
48
- assert.deepStrictEqual(creates[0].arguments[1], ['buildx', 'create', '--name', 'toa', '--bootstrap'])
49
-
50
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
51
- args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build')
52
+ assert.deepStrictEqual(creates[0].arguments[1], [
53
+ 'buildx',
54
+ 'create',
55
+ '--name',
56
+ 'toa',
57
+ '--bootstrap'
58
+ ])
59
+
60
+ const builds = process.execute.mock.calls.filter(
61
+ ({ arguments: [, args] }) =>
62
+ args[0] === '--context=default' && args[1] === 'buildx' && args[2] === 'build'
63
+ )
52
64
 
53
65
  assert.strictEqual(builds.length, 2)
54
66
 
55
- for (const { arguments: [, args] } of builds) {
67
+ for (const {
68
+ arguments: [, args]
69
+ } of builds) {
56
70
  assert.ok(args.includes('--builder'))
57
71
  assert.strictEqual(args[args.indexOf('--builder') + 1], 'toa')
58
72
  }
@@ -60,25 +74,28 @@ it('should reuse named builder across images', async () => {
60
74
 
61
75
  it('should not create builder when it already exists', async () => {
62
76
  process.execute = mock.fn(async (cmd, args) => {
63
- if (args[0] === 'manifest')
64
- throw new Error('manifest unknown')
77
+ if (args[0] === 'manifest') throw new Error('manifest unknown')
65
78
 
66
79
  return ''
67
80
  })
68
81
 
69
- const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
82
+ const registry = createRegistry({
83
+ base: 'example.com/reg',
84
+ platforms: ['linux/amd64', 'linux/arm64']
85
+ })
70
86
 
71
87
  registry.composition(/** @type {any} */ ({}))
72
88
 
73
89
  await registry.build()
74
90
 
75
- const creates = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
76
- args[0] === 'buildx' && args[1] === 'create')
91
+ const creates = process.execute.mock.calls.filter(
92
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
93
+ )
77
94
 
78
95
  assert.strictEqual(creates.length, 0)
79
96
  })
80
97
 
81
- it('should add shared registry cache flags when base is set', async () => {
98
+ it('should not export a build cache', async () => {
82
99
  const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
83
100
 
84
101
  registry.composition(/** @type {any} */ ({}))
@@ -86,35 +103,77 @@ it('should add shared registry cache flags when base is set', async () => {
86
103
 
87
104
  await registry.build()
88
105
 
89
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
90
- args[0] === '--context=default' && args[2] === 'build')
106
+ const builds = process.execute.mock.calls.filter(
107
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
108
+ )
91
109
 
92
110
  assert.strictEqual(builds.length, 2)
93
111
 
94
- for (const { arguments: [, args] } of builds) {
95
- assert.ok(args.includes('--cache-from'))
96
- assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache'))
97
- assert.ok(args.includes('--cache-to'))
98
- assert.ok(args.includes('type=registry,ref=example.com/reg/acme/buildcache,mode=max,image-manifest=true'))
112
+ for (const {
113
+ arguments: [, args]
114
+ } of builds) {
115
+ assert.ok(!args.includes('--cache-from'))
116
+ assert.ok(!args.includes('--cache-to'))
99
117
  }
100
118
  })
101
119
 
102
- it('should omit cache flags when base is not set', async () => {
103
- const registry = createRegistry({ platforms: ['linux/amd64'] })
120
+ it('should use the default builder for a single platform', async () => {
121
+ const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
104
122
 
105
123
  registry.composition(/** @type {any} */ ({}))
106
124
 
107
125
  await registry.build()
108
126
 
109
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
110
- args[0] === '--context=default' && args[2] === 'build')
127
+ const creates = process.execute.mock.calls.filter(
128
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
129
+ )
111
130
 
112
- assert.strictEqual(builds.length, 1)
131
+ assert.strictEqual(creates.length, 0)
132
+
133
+ const {
134
+ arguments: [, args]
135
+ } = process.execute.mock.calls.find(
136
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
137
+ )
138
+
139
+ assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
140
+ assert.ok(args.includes('--platform'))
141
+ assert.strictEqual(args[args.indexOf('--platform') + 1], 'linux/amd64')
142
+ })
143
+
144
+ it('should create the builder once when images build concurrently', async () => {
145
+ const registry = createRegistry({
146
+ base: 'example.com/reg',
147
+ platforms: ['linux/amd64', 'linux/arm64']
148
+ })
149
+
150
+ registry.composition(/** @type {any} */ ({}))
151
+ registry.service('.', /** @type {any} */ ({}))
152
+ registry.mono(/** @type {any} */ ({}))
153
+
154
+ await registry.build()
155
+
156
+ const creates = process.execute.mock.calls.filter(
157
+ ({ arguments: [, args] }) => args[0] === 'buildx' && args[1] === 'create'
158
+ )
159
+
160
+ assert.strictEqual(creates.length, 1)
161
+ })
162
+
163
+ it('should probe every image before building any', async () => {
164
+ const registry = createRegistry({ base: 'example.com/reg', platforms: ['linux/amd64'] })
165
+
166
+ registry.composition(/** @type {any} */ ({}))
167
+ registry.service('.', /** @type {any} */ ({}))
168
+
169
+ await registry.build()
113
170
 
114
- const { arguments: [, args] } = builds[0]
171
+ const calls = process.execute.mock.calls.map(({ arguments: [, args] }) => args[0])
172
+ const build = calls.indexOf('--context=default')
173
+ const probed = calls.slice(0, build).filter((argument) => argument === 'manifest')
115
174
 
116
- assert.ok(!(args.includes('--cache-from')))
117
- assert.ok(!(args.includes('--cache-to')))
175
+ // both probes are spent before the first build starts, rather than one before each
176
+ assert.strictEqual(probed.length, 2)
118
177
  })
119
178
 
120
179
  it('should use default builder when platforms is null', async () => {
@@ -124,16 +183,18 @@ it('should use default builder when platforms is null', async () => {
124
183
 
125
184
  await registry.build()
126
185
 
127
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
128
- args[0] === '--context=default' && args[2] === 'build')
186
+ const builds = process.execute.mock.calls.filter(
187
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
188
+ )
129
189
 
130
190
  assert.strictEqual(builds.length, 1)
131
191
 
132
- const { arguments: [, args] } = builds[0]
192
+ const {
193
+ arguments: [, args]
194
+ } = builds[0]
133
195
 
134
196
  assert.strictEqual(args[args.indexOf('--builder') + 1], 'default')
135
- assert.ok(!(args.includes('--cache-from')))
136
- assert.ok(!(args.includes('--platform')))
197
+ assert.ok(!args.includes('--platform'))
137
198
  })
138
199
 
139
200
  it('should skip build when image already exists', async () => {
@@ -145,26 +206,39 @@ it('should skip build when image already exists', async () => {
145
206
 
146
207
  await registry.build()
147
208
 
148
- const builds = process.execute.mock.calls.filter(({ arguments: [, args] }) =>
149
- args[0] === '--context=default' && args[2] === 'build')
209
+ const builds = process.execute.mock.calls.filter(
210
+ ({ arguments: [, args] }) => args[0] === '--context=default' && args[2] === 'build'
211
+ )
150
212
 
151
213
  assert.strictEqual(builds.length, 0)
152
- assert.ok(process.execute.mock.calls.some((call) => call.arguments.length === 3 && isDeepStrictEqual(call.arguments[0], 'docker') && isDeepStrictEqual(call.arguments[1], ['manifest', 'inspect', images[0].reference]) && isDeepStrictEqual(call.arguments[2], { silently: true })))
214
+ assert.ok(
215
+ process.execute.mock.calls.some(
216
+ (call) =>
217
+ call.arguments.length === 3 &&
218
+ isDeepStrictEqual(call.arguments[0], 'docker') &&
219
+ isDeepStrictEqual(call.arguments[1], [
220
+ 'manifest',
221
+ 'inspect',
222
+ images[0].reference
223
+ ]) &&
224
+ isDeepStrictEqual(call.arguments[2], { silently: true })
225
+ )
226
+ )
153
227
  })
154
228
 
155
229
  /**
156
230
  * @param {object} [registry]
157
231
  * @returns {Registry}
158
232
  */
159
- function createRegistry (registry = {}) {
160
- return new Registry('acme', registry, factory, process)
233
+ function createRegistry(registry = {}) {
234
+ return new Registry(registry, factory, process)
161
235
  }
162
236
 
163
237
  /**
164
238
  * @param {string} name
165
239
  * @returns {toa.deployment.images.Image}
166
240
  */
167
- function createImage (name) {
241
+ function createImage(name) {
168
242
  const image = /** @type {toa.deployment.images.Image} */ {
169
243
  reference: `example.com/reg/acme/${name}:abcdef12`,
170
244
  context: `/tmp/${name}`,
@@ -1,5 +1,7 @@
1
+ // a service is what a deployment carries, and it is constructed
2
+ // oxlint-disable-next-line typescript/no-extraneous-class
1
3
  export class Service {
2
- constructor (service, image) {
4
+ constructor(service, image) {
3
5
  Object.assign(this, service)
4
6
 
5
7
  this.name = service.group + '-' + service.name
@@ -7,7 +7,7 @@ import { tmpdir } from 'node:os'
7
7
  * @param {string} [path]
8
8
  * @return {Promise<string>}
9
9
  */
10
- export async function create (type, path) {
10
+ export async function create(type, path) {
11
11
  if (path === undefined) return await mkdtemp(join(tmpdir(), 'toa-' + type))
12
12
 
13
13
  path = resolve(path)
package/src/process.js CHANGED
@@ -4,7 +4,7 @@ import { execa } from 'execa'
4
4
  * @implements {toa.operations.Process}
5
5
  */
6
6
  export class Process {
7
- async execute (cmd, args, options = {}) {
7
+ async execute(cmd, args, options = {}) {
8
8
  console.log('toa>', cmd, args.join(' '))
9
9
 
10
10
  const command = execa(cmd, args)
@@ -7,14 +7,18 @@ describe('execute', () => {
7
7
  it('should answer what the command wrote', async () => {
8
8
  // `execa` is a named export, and calling the module namespace instead answers
9
9
  // `execa is not a function` — where only a deployment would have found out
10
- const output = await new Process().execute('node', ['-e', 'process.stdout.write("ok")'],
11
- { silently: true })
10
+ const output = await new Process().execute(
11
+ 'node',
12
+ ['-e', 'process.stdout.write("ok")'],
13
+ { silently: true }
14
+ )
12
15
 
13
16
  assert.strictEqual(output, 'ok')
14
17
  })
15
18
 
16
19
  it('should reject what the command failed at', async () => {
17
- await assert.rejects(new Process().execute('node', ['-e', 'process.exit(1)'],
18
- { silently: true }))
20
+ await assert.rejects(
21
+ new Process().execute('node', ['-e', 'process.exit(1)'], { silently: true })
22
+ )
19
23
  })
20
24
  })
@@ -1,11 +1,9 @@
1
1
  import type * as _deployment from './deployment.js'
2
2
 
3
3
  declare namespace toa.deployment {
4
-
5
4
  interface Composition extends _deployment.Deployable {
6
5
  components: Array<string>
7
6
  }
8
-
9
7
  }
10
8
 
11
9
  export type Composition = toa.deployment.Composition
@@ -2,10 +2,11 @@ import * as _service from './service.js'
2
2
  import { dependencies } from '@toa.io/norm/types/context'
3
3
 
4
4
  declare namespace toa.deployment {
5
-
6
5
  namespace dependency {
7
-
8
- type Constructor = (instances: dependencies.Instance[], annotation: any) => Declaration
6
+ type Constructor = (
7
+ instances: dependencies.Instance[],
8
+ annotation: any
9
+ ) => Declaration
9
10
 
10
11
  type Reference = {
11
12
  name: string
@@ -19,6 +20,11 @@ declare namespace toa.deployment {
19
20
  group: string
20
21
  name: string
21
22
  version: string
23
+
24
+ /** The repository the extension publishes this service's image to, without a tag.
25
+ * Stating it lets an application take the published image instead of building one. */
26
+ image?: string
27
+
22
28
  port: number
23
29
  ingress?: {
24
30
  host: string
@@ -36,7 +42,7 @@ declare namespace toa.deployment {
36
42
  name: string
37
43
  value?: string | number
38
44
  secret?: {
39
- name: string,
45
+ name: string
40
46
  key: string
41
47
  }
42
48
  }
@@ -52,7 +58,6 @@ declare namespace toa.deployment {
52
58
  variables?: Variables
53
59
  events?: string[]
54
60
  }
55
-
56
61
  }
57
62
 
58
63
  type Dependency = {
@@ -62,7 +67,6 @@ declare namespace toa.deployment {
62
67
  variables?: dependency.Variables
63
68
  events?: string[]
64
69
  }
65
-
66
70
  }
67
71
 
68
72
  export type Declaration = toa.deployment.dependency.Declaration
@@ -3,7 +3,6 @@ import type * as _service from './service.js'
3
3
  import type * as _dependency from './dependency.js'
4
4
 
5
5
  declare namespace toa.deployment {
6
-
7
6
  interface Declaration {
8
7
  apiVersion: string
9
8
  type: string
@@ -25,13 +24,11 @@ declare namespace toa.deployment {
25
24
  }
26
25
 
27
26
  namespace installation {
28
-
29
27
  interface Options {
30
28
  wait?: boolean
31
29
  target?: string
32
30
  namespace?: string
33
31
  }
34
-
35
32
  }
36
33
 
37
34
  namespace template {
@@ -54,7 +51,6 @@ declare namespace toa.deployment {
54
51
 
55
52
  variables(): _dependency.Variables
56
53
  }
57
-
58
54
  }
59
55
 
60
56
  export namespace installation {
@@ -2,13 +2,11 @@ import type * as _operator from './operator.js'
2
2
  import * as _registry from './registry.js'
3
3
 
4
4
  declare namespace toa.deployment {
5
-
6
5
  interface Factory {
7
6
  operator(): _operator.Operator
8
7
 
9
8
  registry(): _registry.Registry
10
9
  }
11
-
12
10
  }
13
11
 
14
12
  export type Factory = toa.deployment.Factory