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

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/package.json +4 -4
  2. package/readme.md +43 -0
  3. package/src/deployment/.deployment/.describe/compositions.js +7 -5
  4. package/src/deployment/.deployment/.describe/events.js +7 -7
  5. package/src/deployment/.deployment/.describe/fold.js +10 -3
  6. package/src/deployment/.deployment/.describe/fold.test.js +30 -14
  7. package/src/deployment/.deployment/.describe/mounts.js +4 -7
  8. package/src/deployment/.deployment/.describe/resources.js +7 -5
  9. package/src/deployment/.deployment/.describe/resources.test.js +13 -6
  10. package/src/deployment/.deployment/.describe/services.js +10 -9
  11. package/src/deployment/.deployment/.describe/services.test.js +22 -5
  12. package/src/deployment/.deployment/.describe/variables.js +2 -3
  13. package/src/deployment/.deployment/describe.js +11 -7
  14. package/src/deployment/.deployment/merge.js +11 -6
  15. package/src/deployment/.deployment/merge.test.js +41 -10
  16. package/src/deployment/composition.js +1 -1
  17. package/src/deployment/deployment.js +35 -16
  18. package/src/deployment/factory.js +39 -19
  19. package/src/deployment/images/composition.Dockerfile +1 -1
  20. package/src/deployment/images/composition.js +10 -9
  21. package/src/deployment/images/factory.js +17 -6
  22. package/src/deployment/images/format.js +9 -5
  23. package/src/deployment/images/image.fixtures.js +2 -2
  24. package/src/deployment/images/image.js +38 -21
  25. package/src/deployment/images/image.test.js +7 -2
  26. package/src/deployment/images/mono.Dockerfile +1 -1
  27. package/src/deployment/images/mono.js +10 -9
  28. package/src/deployment/images/publish.js +98 -0
  29. package/src/deployment/images/runtime-base.test.js +12 -6
  30. package/src/deployment/images/service.Dockerfile +1 -1
  31. package/src/deployment/images/service.js +4 -4
  32. package/src/deployment/operator.js +8 -8
  33. package/src/deployment/registry.js +89 -61
  34. package/src/deployment/registry.test.js +117 -43
  35. package/src/deployment/service.js +3 -1
  36. package/src/deployment/workspace.js +1 -1
  37. package/src/process.js +1 -1
  38. package/src/process.test.js +8 -4
  39. package/types/_deployment/composition.d.ts +0 -2
  40. package/types/_deployment/dependency.d.ts +10 -6
  41. package/types/_deployment/deployment.d.ts +0 -4
  42. package/types/_deployment/factory.d.ts +0 -2
  43. package/types/_deployment/images/factory.d.ts +4 -6
  44. package/types/_deployment/images/image.d.ts +2 -4
  45. package/types/_deployment/images/registry.d.ts +7 -10
  46. package/types/_deployment/operator.d.ts +5 -7
  47. package/types/_deployment/registry.d.ts +6 -8
  48. package/types/_deployment/service.d.ts +0 -2
  49. package/types/dependency.ts +3 -0
  50. package/CHANGELOG.md +0 -231
@@ -11,7 +11,7 @@ export class Deployment {
11
11
  #process
12
12
  #target
13
13
 
14
- constructor (context, compositions, dependencies, process, image) {
14
+ constructor(context, compositions, dependencies, process, image) {
15
15
  const dependency = merge(dependencies)
16
16
 
17
17
  this.#chart = declare(context, dependency)
@@ -19,7 +19,7 @@ export class Deployment {
19
19
  this.#process = process
20
20
  }
21
21
 
22
- async export (target) {
22
+ async export(target) {
23
23
  const chart = dump(this.#chart)
24
24
  const values = dump(this.#values)
25
25
 
@@ -32,9 +32,9 @@ export class Deployment {
32
32
  this.#target = target
33
33
  }
34
34
 
35
- async install (options) {
35
+ async install(options) {
36
36
  if (options.target) this.#target = options.target
37
- if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
37
+ if (this.#target === undefined) throw new Error("Deployment hasn't been exported")
38
38
 
39
39
  const args = []
40
40
 
@@ -42,25 +42,46 @@ export class Deployment {
42
42
  if (options.wait === true) args.push('--wait')
43
43
  if (options.timeout !== undefined) args.push('--timeout', options.timeout)
44
44
 
45
- await this.#process.execute('helm', ['dependency', 'update', this.#target])
46
- await this.#process.execute('helm', ['upgrade', this.#chart.name, '-i', ...args, this.#target])
45
+ await this.#update()
46
+ await this.#process.execute('helm', [
47
+ 'upgrade',
48
+ this.#chart.name,
49
+ '-i',
50
+ ...args,
51
+ this.#target
52
+ ])
47
53
  }
48
54
 
49
- async template (options) {
50
- if (this.#target === undefined) throw new Error('Deployment hasn\'t been exported')
55
+ async template(options) {
56
+ if (this.#target === undefined) throw new Error("Deployment hasn't been exported")
51
57
 
52
- await this.#process.execute('helm', ['dependency', 'update', this.#target], { silently: true })
58
+ await this.#update({ silently: true })
53
59
 
54
60
  const args = []
55
61
 
56
62
  if (options.namespace !== undefined) args.push('-n', options.namespace)
57
63
 
58
- return await this.#process.execute('helm',
64
+ return await this.#process.execute(
65
+ 'helm',
59
66
  ['template', this.#chart.name, ...args, this.#target],
60
- { silently: true })
67
+ { silently: true }
68
+ )
69
+ }
70
+
71
+ /**
72
+ * A chart with no subcharts has nothing to resolve, and the call still reaches out for
73
+ * the repositories it would have read.
74
+ *
75
+ * @param {object} [options]
76
+ * @returns {Promise<void>}
77
+ */
78
+ async #update(options = {}) {
79
+ if (this.#chart.dependencies.length === 0) return
80
+
81
+ await this.#process.execute('helm', ['dependency', 'update', this.#target], options)
61
82
  }
62
83
 
63
- variables () {
84
+ variables() {
64
85
  const variables = []
65
86
  const used = new Set()
66
87
 
@@ -74,7 +95,7 @@ export class Deployment {
74
95
  }
75
96
  }
76
97
 
77
- function addVariables (list, variables, used = new Set()) {
98
+ function addVariables(list, variables, used = new Set()) {
78
99
  for (const item of list) {
79
100
  if (item.variables === undefined) continue
80
101
 
@@ -89,9 +110,7 @@ function addVariables (list, variables, used = new Set()) {
89
110
 
90
111
  const TEMPLATES = join(import.meta.dirname, 'chart/templates')
91
112
 
92
-
93
-
94
- function dump (object) {
113
+ function dump(object) {
95
114
  // js-yaml writes plain objects only, and the values carry locators and images
96
115
  return jsyaml.dump(JSON.parse(JSON.stringify(object)), { noRefs: true, lineWidth: -1 })
97
116
  }
@@ -29,14 +29,18 @@ export class Factory {
29
29
  * @type {Map<string, string[]>} */
30
30
  #claims
31
31
 
32
- constructor (context, options = {}) {
32
+ constructor(context, options = {}) {
33
33
  this.#context = context
34
34
  this.#mono = options.mono === true
35
35
  this.#process = new Process()
36
36
 
37
- const imagesFactory = new ImagesFactory(context.name, context.runtime, context.registry)
37
+ const imagesFactory = new ImagesFactory(
38
+ context.name,
39
+ context.runtime,
40
+ context.registry
41
+ )
38
42
 
39
- this.#registry = new Registry(context.name, context.registry, imagesFactory, this.#process)
43
+ this.#registry = new Registry(context.registry, imagesFactory, this.#process)
40
44
  this.#claims = claims(context)
41
45
  this.#dependencies = this.#getDependencies()
42
46
  this.#compositions = []
@@ -46,10 +50,12 @@ export class Factory {
46
50
  components: context.components
47
51
  })
48
52
  else
49
- this.#compositions = context.compositions.map((composition) => this.#composition(composition))
53
+ this.#compositions = context.compositions.map((composition) =>
54
+ this.#composition(composition)
55
+ )
50
56
  }
51
57
 
52
- async operator () {
58
+ async operator() {
53
59
  const deployment = new Deployment(
54
60
  this.#context,
55
61
  this.#compositions,
@@ -62,17 +68,17 @@ export class Factory {
62
68
  return new Operator(deployment, this.#registry)
63
69
  }
64
70
 
65
- registry () {
71
+ registry() {
66
72
  return this.#registry
67
73
  }
68
74
 
69
- #composition (composition) {
75
+ #composition(composition) {
70
76
  const image = this.#registry.composition(composition)
71
77
 
72
78
  return new Composition(composition, image)
73
79
  }
74
80
 
75
- async #getDependencies () {
81
+ async #getDependencies() {
76
82
  /** @type {toa.deployment.Dependency[]} */
77
83
  const dependencies = []
78
84
 
@@ -95,17 +101,21 @@ export class Factory {
95
101
  // point where a reference that yields nothing can be told from one that yields a service
96
102
  for (const [reference, compositions] of this.#claims)
97
103
  if (!contributing.has(reference))
98
- throw new Error(`Composition '${compositions[0]}' lists '${reference}', ` +
99
- 'which contributes no service.')
104
+ throw new Error(
105
+ `Composition '${compositions[0]}' lists '${reference}', ` +
106
+ 'which contributes no service.'
107
+ )
100
108
 
101
109
  return dependencies
102
110
  }
103
111
 
104
- async #getDependency (reference, instances) {
112
+ async #getDependency(reference, instances) {
105
113
  // a dependency may be named the way a package is or written as a directory,
106
114
  // and a module is loaded by file
107
115
  const module = await import(pathToFileURL(require.resolve(reference)).href)
108
- const pkg = JSON.parse(readFileSync(require.resolve(join(reference, 'package.json')), 'utf8'))
116
+ const pkg = JSON.parse(
117
+ readFileSync(require.resolve(join(reference, 'package.json')), 'utf8')
118
+ )
109
119
 
110
120
  if (module.deployment === undefined) return
111
121
 
@@ -121,9 +131,10 @@ export class Factory {
121
131
  /** @type {toa.deployment.Service[]} */
122
132
  const services = dependency.services?.map((service) =>
123
133
  workload === undefined
124
- ? this.#service(reference, service) // its own deployment, its own image
125
- // named the way `Service` would name it, since it skips that wrapper
126
- : { ...service, name: `${service.group}-${service.name}`, workload })
134
+ ? this.#service(reference, service) // its own deployment, its own image
135
+ : // named the way `Service` would name it, since it skips that wrapper
136
+ { ...service, name: `${service.group}-${service.name}`, workload }
137
+ )
127
138
 
128
139
  return { ...dependency, services }
129
140
  }
@@ -133,13 +144,20 @@ export class Factory {
133
144
  * @param service {toa.deployment.dependency.Service}
134
145
  * @returns {Service}
135
146
  */
136
- #service (path, service) {
137
- const image = this.#registry.service(path, service)
147
+ #service(path, service) {
148
+ // an extension that publishes its service's image is taken at its word: nothing is
149
+ // added to the registry, so nothing is prepared, probed, built or pushed for it
150
+ const published =
151
+ service.image !== undefined && this.#context.registry?.services === PUBLISHED
152
+
153
+ const image = published
154
+ ? { reference: `${service.image}:${this.#context.runtime.version}` }
155
+ : this.#registry.service(path, service)
138
156
 
139
157
  return new Service(service, image)
140
158
  }
141
159
 
142
- static async create (path, environment, options = {}) {
160
+ static async create(path, environment, options = {}) {
143
161
  const context = await load(path, environment)
144
162
 
145
163
  return new Factory(context, options)
@@ -148,11 +166,13 @@ export class Factory {
148
166
 
149
167
  const MONO = 'mono'
150
168
 
169
+ const PUBLISHED = 'published'
170
+
151
171
  /**
152
172
  * @param {toa.norm.Context} context
153
173
  * @returns {Map<string, string[]>}
154
174
  */
155
- function claims (context) {
175
+ function claims(context) {
156
176
  const map = new Map()
157
177
 
158
178
  for (const composition of context.compositions ?? [])
@@ -12,7 +12,7 @@ COPY --chown=node:node . /composition
12
12
  {{build.run}}
13
13
 
14
14
  # run 'npm i' in each component
15
- RUN --mount=type=cache,target=/root/.npm \
15
+ RUN --mount=type=cache,target=/root/.npm,sharing=locked \
16
16
  for entry in *; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
17
17
 
18
18
  USER node
@@ -12,7 +12,7 @@ export class Composition extends Image {
12
12
  #image
13
13
  #components
14
14
 
15
- constructor (scope, runtime, registry, composition) {
15
+ constructor(scope, runtime, registry, composition) {
16
16
  super(scope, runtime, registry)
17
17
 
18
18
  this.#name = composition.name
@@ -20,11 +20,11 @@ export class Composition extends Image {
20
20
  this.#components = composition.components
21
21
  }
22
22
 
23
- get name () {
23
+ get name() {
24
24
  return 'composition-' + this.#name
25
25
  }
26
26
 
27
- get version () {
27
+ get version() {
28
28
  const hash = createHash('sha256')
29
29
 
30
30
  for (const component of this.#components) {
@@ -35,31 +35,32 @@ export class Composition extends Image {
35
35
  return hash.digest('hex').slice(0, 8)
36
36
  }
37
37
 
38
- get base () {
38
+ get base() {
39
39
  if (this.#image !== undefined) return this.#image
40
40
 
41
41
  const images = new Set(this.#components.map((component) => component.build?.image))
42
42
 
43
43
  if (images.size > 1)
44
- throw new Error(`Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`)
44
+ throw new Error(
45
+ `Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`
46
+ )
45
47
 
46
48
  return images.values().next().value
47
49
  }
48
50
 
49
- get run () {
51
+ get run() {
50
52
  const commands = []
51
53
 
52
54
  for (const component of this.#components) {
53
55
  const run = component.build?.run
54
56
 
55
- if (run !== undefined)
56
- commands.push(run)
57
+ if (run !== undefined) commands.push(run)
57
58
  }
58
59
 
59
60
  return commands.join('\n')
60
61
  }
61
62
 
62
- async prepare (root) {
63
+ async prepare(root) {
63
64
  const context = await super.prepare(root)
64
65
 
65
66
  for (const component of this.#components) {
@@ -17,7 +17,7 @@ export class Factory {
17
17
  * @param {toa.norm.context.Runtime} runtime
18
18
  * @param {toa.norm.context.Registry} registry
19
19
  */
20
- constructor (scope, runtime, registry) {
20
+ constructor(scope, runtime, registry) {
21
21
  this.#scope = scope
22
22
  this.#runtime = runtime
23
23
  this.#registry = registry
@@ -26,8 +26,13 @@ export class Factory {
26
26
  /**
27
27
  * @returns {Composition}
28
28
  */
29
- composition (composition) {
30
- const instance = new Composition(this.#scope, this.#runtime, this.#registry, composition)
29
+ composition(composition) {
30
+ const instance = new Composition(
31
+ this.#scope,
32
+ this.#runtime,
33
+ this.#registry,
34
+ composition
35
+ )
31
36
 
32
37
  instance.tag()
33
38
 
@@ -37,8 +42,14 @@ export class Factory {
37
42
  /**
38
43
  * @returns {Service}
39
44
  */
40
- service (path, service) {
41
- const instance = new Service(this.#scope, this.#runtime, this.#registry, path, service)
45
+ service(path, service) {
46
+ const instance = new Service(
47
+ this.#scope,
48
+ this.#runtime,
49
+ this.#registry,
50
+ path,
51
+ service
52
+ )
42
53
 
43
54
  instance.tag()
44
55
 
@@ -48,7 +59,7 @@ export class Factory {
48
59
  /**
49
60
  * @returns {Mono}
50
61
  */
51
- mono (composition) {
62
+ mono(composition) {
52
63
  const instance = new Mono(this.#scope, this.#runtime, this.#registry, composition)
53
64
 
54
65
  instance.tag()
@@ -14,11 +14,13 @@ import { dirname, join, parse } from 'node:path'
14
14
  * @param {string} target where it was copied
15
15
  * @param {string} name
16
16
  */
17
- export async function declare (source, target, name) {
17
+ export async function declare(source, target, name) {
18
18
  if (existsSync(join(target, MANIFEST))) return
19
19
 
20
- await writeFile(join(target, MANIFEST),
21
- JSON.stringify({ name, private: true, type: format(source) }, null, 2) + '\n')
20
+ await writeFile(
21
+ join(target, MANIFEST),
22
+ JSON.stringify({ name, private: true, type: format(source) }, null, 2) + '\n'
23
+ )
22
24
  }
23
25
 
24
26
  /**
@@ -28,7 +30,7 @@ export async function declare (source, target, name) {
28
30
  * @param {string} directory
29
31
  * @returns {'module' | 'commonjs'}
30
32
  */
31
- export function format (directory) {
33
+ export function format(directory) {
32
34
  const { root } = parse(directory)
33
35
 
34
36
  let current = directory
@@ -37,7 +39,9 @@ export function format (directory) {
37
39
  const manifest = join(current, MANIFEST)
38
40
 
39
41
  if (existsSync(manifest))
40
- return JSON.parse(readFileSync(manifest, 'utf8')).type === 'module' ? 'module' : 'commonjs'
42
+ return JSON.parse(readFileSync(manifest, 'utf8')).type === 'module'
43
+ ? 'module'
44
+ : 'commonjs'
41
45
 
42
46
  current = dirname(current)
43
47
  }
@@ -8,11 +8,11 @@ const name = generate()
8
8
  * @implements {toa.deployment.images.Image}
9
9
  */
10
10
  class Class extends Image {
11
- get name () {
11
+ get name() {
12
12
  return name
13
13
  }
14
14
 
15
- get version () {
15
+ get version() {
16
16
  return version
17
17
  }
18
18
  }
@@ -21,13 +21,13 @@ export class Image {
21
21
  build: {}
22
22
  }
23
23
 
24
- constructor (scope, runtime, registry) {
24
+ constructor(scope, runtime, registry) {
25
25
  this.#scope = scope
26
26
  this.#registry = registry
27
27
  this.#runtime = runtime
28
28
  }
29
29
 
30
- tag () {
30
+ tag() {
31
31
  const hash = createHash('sha256')
32
32
 
33
33
  hash.update(this.#runtime.version)
@@ -35,25 +35,37 @@ export class Image {
35
35
 
36
36
  const tag = hash.digest('hex').slice(0, 8)
37
37
 
38
- this.reference = posix.join(this.#registry.base ?? '', this.#scope, `${this.name}:${tag}`)
38
+ this.reference = posix.join(
39
+ this.#registry.base ?? '',
40
+ this.#scope,
41
+ `${this.name}:${tag}`
42
+ )
39
43
  }
40
44
 
41
45
  /** @returns {string | undefined} */
42
- get name () { return undefined }
46
+ get name() {
47
+ return undefined
48
+ }
43
49
 
44
50
  /** @returns {string | undefined} */
45
- get version () { return undefined }
51
+ get version() {
52
+ return undefined
53
+ }
46
54
 
47
55
  /** The image to build `FROM`. Undefined takes the runtime's, which is what a service does.
48
56
  * @returns {string | undefined} */
49
- get base () { return undefined }
57
+ get base() {
58
+ return undefined
59
+ }
50
60
 
51
61
  /** Build commands to add. Undefined adds none, which is what a service does.
52
62
  * @returns {string | undefined} */
53
- get run () { return undefined }
63
+ get run() {
64
+ return undefined
65
+ }
54
66
 
55
- async prepare (root) {
56
- if (this.dockerfile === undefined) throw new Error('Dockerfile isn\'t specified')
67
+ async prepare(root) {
68
+ if (this.dockerfile === undefined) throw new Error("Dockerfile isn't specified")
57
69
 
58
70
  this.#setValues()
59
71
 
@@ -73,38 +85,43 @@ export class Image {
73
85
  return path
74
86
  }
75
87
 
76
- #setValues () {
88
+ #setValues() {
77
89
  this.#values.runtime = this.#runtime
78
- this.#values.build = overwrite({
79
- image: `${RUNTIME_IMAGE}:${this.#runtime.version}`
80
- }, this.#registry.build)
90
+ this.#values.build = overwrite(
91
+ {
92
+ image: `${RUNTIME_IMAGE}:${this.#runtime.version}`
93
+ },
94
+ this.#registry.build
95
+ )
81
96
 
82
97
  const image = this.base
83
98
 
84
- if (image !== undefined)
85
- this.#values.build.image = image
99
+ if (image !== undefined) this.#values.build.image = image
86
100
 
87
101
  const run = this.run
88
102
 
89
103
  if (run !== undefined)
90
- this.#values.build.run = (this.#values.build.run === undefined ? '' : this.#values.build.run + '\n') + run
104
+ this.#values.build.run =
105
+ (this.#values.build.run === undefined ? '' : this.#values.build.run + '\n') + run
91
106
 
92
- if (this.#values.build.arguments !== undefined) this.#values.build.arguments = createArguments(this.#values.build.arguments)
93
- if (this.#values.build.run !== undefined) this.#values.build.run = createRunCommands(this.#values.build.run)
107
+ if (this.#values.build.arguments !== undefined)
108
+ this.#values.build.arguments = createArguments(this.#values.build.arguments)
109
+ if (this.#values.build.run !== undefined)
110
+ this.#values.build.run = createRunCommands(this.#values.build.run)
94
111
  }
95
112
 
96
113
  /**
97
114
  * @param key {string}
98
115
  * @returns {string}
99
116
  */
100
- #value (key) {
117
+ #value(key) {
101
118
  const [source, property] = key.split('.')
102
119
 
103
120
  return this.#values[source]?.[property] ?? ''
104
121
  }
105
122
  }
106
123
 
107
- function createRunCommands (input) {
124
+ function createRunCommands(input) {
108
125
  const lines = input.split('\n')
109
126
 
110
127
  return lines.reduce((commands, command) => {
@@ -114,7 +131,7 @@ function createRunCommands (input) {
114
131
  }, '')
115
132
  }
116
133
 
117
- function createArguments (variables) {
134
+ function createArguments(variables) {
118
135
  const args = []
119
136
 
120
137
  for (const variable of variables) {
@@ -14,11 +14,16 @@ beforeEach(() => {
14
14
  it('should assign url', () => {
15
15
  instance.tag()
16
16
 
17
- assert.deepStrictEqual(instance.reference, `${fixtures.registry.base}/${fixtures.scope}/${fixtures.name}:${fixtures.version}`)
17
+ assert.deepStrictEqual(
18
+ instance.reference,
19
+ `${fixtures.registry.base}/${fixtures.scope}/${fixtures.name}:${fixtures.version}`
20
+ )
18
21
  })
19
22
 
20
23
  describe('prepare', () => {
21
24
  it('should throw error if no dockerfile specified', async () => {
22
- await assert.rejects(instance.prepare(generate()), (error) => /Dockerfile isn't specified/.test(error.message))
25
+ await assert.rejects(instance.prepare(generate()), (error) =>
26
+ /Dockerfile isn't specified/.test(error.message)
27
+ )
23
28
  })
24
29
  })
@@ -11,7 +11,7 @@ COPY --chown=node:node . /composition
11
11
 
12
12
  {{build.run}}
13
13
 
14
- RUN --mount=type=cache,target=/root/.npm \
14
+ RUN --mount=type=cache,target=/root/.npm,sharing=locked \
15
15
  for entry in *; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
16
16
 
17
17
  USER node
@@ -11,18 +11,18 @@ export class Mono extends Image {
11
11
  #image
12
12
  #components
13
13
 
14
- constructor (scope, runtime, registry, composition) {
14
+ constructor(scope, runtime, registry, composition) {
15
15
  super(scope, runtime, registry)
16
16
 
17
17
  this.#image = composition.image
18
18
  this.#components = composition.components
19
19
  }
20
20
 
21
- get name () {
21
+ get name() {
22
22
  return 'mono'
23
23
  }
24
24
 
25
- get version () {
25
+ get version() {
26
26
  const hash = createHash('sha256')
27
27
 
28
28
  for (const component of this.#components) {
@@ -33,31 +33,32 @@ export class Mono extends Image {
33
33
  return hash.digest('hex').slice(0, 8)
34
34
  }
35
35
 
36
- get base () {
36
+ get base() {
37
37
  if (this.#image !== undefined) return this.#image
38
38
 
39
39
  const images = new Set(this.#components.map((component) => component.build?.image))
40
40
 
41
41
  if (images.size > 1)
42
- throw new Error('Mono deployment requires different base images for its components. Specify base image for the composition in the context.')
42
+ throw new Error(
43
+ 'Mono deployment requires different base images for its components. Specify base image for the composition in the context.'
44
+ )
43
45
 
44
46
  return images.values().next().value
45
47
  }
46
48
 
47
- get run () {
49
+ get run() {
48
50
  const commands = []
49
51
 
50
52
  for (const component of this.#components) {
51
53
  const run = component.build?.run
52
54
 
53
- if (run !== undefined)
54
- commands.push(run)
55
+ if (run !== undefined) commands.push(run)
55
56
  }
56
57
 
57
58
  return commands.join('\n')
58
59
  }
59
60
 
60
- async prepare (root) {
61
+ async prepare(root) {
61
62
  const context = await super.prepare(root)
62
63
 
63
64
  for (const component of this.#components) {