@toa.io/operations 1.0.0-alpha.274 → 1.0.0-alpha.277

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 (55) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +8 -7
  3. package/src/deployment/.deployment/.describe/components.js +1 -5
  4. package/src/deployment/.deployment/.describe/compositions.js +3 -7
  5. package/src/deployment/.deployment/.describe/dependencies.js +1 -6
  6. package/src/deployment/.deployment/.describe/events.js +1 -5
  7. package/src/deployment/.deployment/.describe/index.js +4 -13
  8. package/src/deployment/.deployment/.describe/mounts.js +1 -5
  9. package/src/deployment/.deployment/.describe/resources.js +1 -5
  10. package/src/deployment/.deployment/.describe/services.js +2 -6
  11. package/src/deployment/.deployment/.describe/services.test.js +11 -11
  12. package/src/deployment/.deployment/.describe/variables.js +1 -5
  13. package/src/deployment/.deployment/declare.js +1 -5
  14. package/src/deployment/.deployment/describe.js +10 -10
  15. package/src/deployment/.deployment/index.js +3 -9
  16. package/src/deployment/.deployment/merge.js +1 -5
  17. package/src/deployment/.deployment/merge.test.js +10 -12
  18. package/src/deployment/chart/templates/mono.yaml +4 -0
  19. package/src/deployment/chart/templates/services.yaml +4 -0
  20. package/src/deployment/composition.js +1 -5
  21. package/src/deployment/deployment.js +10 -11
  22. package/src/deployment/factory.js +28 -22
  23. package/src/deployment/images/composition.Dockerfile +1 -1
  24. package/src/deployment/images/composition.js +11 -11
  25. package/src/deployment/images/factory.js +4 -8
  26. package/src/deployment/images/format.js +48 -0
  27. package/src/deployment/images/format.test.js +74 -0
  28. package/src/deployment/images/image.fixtures.js +11 -11
  29. package/src/deployment/images/image.js +7 -18
  30. package/src/deployment/images/image.test.js +6 -5
  31. package/src/deployment/images/index.js +1 -5
  32. package/src/deployment/images/mono.Dockerfile +1 -1
  33. package/src/deployment/images/mono.js +13 -12
  34. package/src/deployment/images/runtime-base.test.js +15 -14
  35. package/src/deployment/images/service.js +8 -8
  36. package/src/deployment/index.js +1 -5
  37. package/src/deployment/operator.js +2 -6
  38. package/src/deployment/operator.test.js +8 -7
  39. package/src/deployment/registry.js +3 -7
  40. package/src/deployment/registry.test.js +39 -37
  41. package/src/deployment/service.js +1 -5
  42. package/src/deployment/workspace.js +4 -8
  43. package/src/index.js +1 -3
  44. package/src/process.js +2 -6
  45. package/types/_deployment/composition.d.ts +1 -1
  46. package/types/_deployment/dependency.d.ts +1 -1
  47. package/types/_deployment/deployment.d.ts +3 -3
  48. package/types/_deployment/factory.d.ts +2 -2
  49. package/types/_deployment/images/factory.d.ts +2 -2
  50. package/types/_deployment/images/registry.d.ts +2 -2
  51. package/types/_deployment/operator.d.ts +2 -2
  52. package/types/_deployment/registry.d.ts +2 -2
  53. package/types/_deployment/service.d.ts +4 -1
  54. package/types/dependency.ts +2 -0
  55. package/types/index.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
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.277](https://github.com/toa-io/toa/compare/v1.0.0-alpha.276...v1.0.0-alpha.277) (2026-09-03)
7
+
8
+ ### Bug Fixes
9
+
10
+ * read a manifest the way js-yaml read one before ([fcea1b2](https://github.com/toa-io/toa/commit/fcea1b21996d6efa6c3198acbfc9f70f1ce0c35c))
11
+ * settle what the module loader made asynchronous ([d4ab9df](https://github.com/toa-io/toa/commit/d4ab9dfb09e382da311dba08f52214e7645f4a6c))
12
+ * what the loader resolves, and what it will not ([1b8a02f](https://github.com/toa-io/toa/commit/1b8a02ff0a5e0f18ee62e3f71bc81116f86f9af1))
13
+
14
+
15
+ # [1.0.0-alpha.276](https://github.com/toa-io/toa/compare/v1.0.0-alpha.275...v1.0.0-alpha.276) (2026-09-03)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **operations:** let a deployment state that it has no probe ([477b483](https://github.com/toa-io/toa/commit/477b483560cf2464d486a78e432f8e9b47f011dd))
21
+
22
+
23
+
24
+
25
+
6
26
  # [1.0.0-alpha.274](https://github.com/toa-io/toa/compare/v1.0.0-alpha.273...v1.0.0-alpha.274) (2026-09-02)
7
27
 
8
28
  **Note:** Version bump only for package @toa.io/operations
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/operations",
3
- "version": "1.0.0-alpha.274",
3
+ "version": "1.0.0-alpha.277",
4
+ "type": "module",
4
5
  "description": "Toa Deployment",
5
6
  "homepage": "https://toa.io",
6
7
  "author": {
@@ -27,11 +28,11 @@
27
28
  "test": "echo \"Error: run tests from root\" && exit 1"
28
29
  },
29
30
  "dependencies": {
30
- "@toa.io/generic": "1.0.0-alpha.273",
31
- "@toa.io/norm": "1.0.0-alpha.274",
32
- "execa": "5.1.1",
33
- "fs-extra": "11.1.1",
34
- "js-yaml": "4.3.1"
31
+ "@toa.io/generic": "1.0.0-alpha.277",
32
+ "@toa.io/norm": "1.0.0-alpha.277",
33
+ "execa": "10.0.1",
34
+ "fs-extra": "11.4.0",
35
+ "js-yaml": "5.4.1"
35
36
  },
36
- "gitHead": "7df32eb2872155c45681c299036b1efbeefd6518"
37
+ "gitHead": "0c0071418c2c56a44e621ba5ced5cdd23dff1738"
37
38
  }
@@ -1,10 +1,8 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * @param {toa.deployment.Composition[]} compositions
5
3
  * @returns {string[]}
6
4
  */
7
- const components = (compositions) => {
5
+ export const components = (compositions) => {
8
6
  /** @type {Set<string>} */
9
7
  const components = new Set()
10
8
 
@@ -16,5 +14,3 @@ const components = (compositions) => {
16
14
 
17
15
  return Array.from(components)
18
16
  }
19
-
20
- exports.components = components
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import { addVariables } from './variables.js'
2
+ import { addMounts } from './mounts.js'
2
3
 
3
- const { addVariables } = require('./variables')
4
- const { addMounts } = require('./mounts')
5
-
6
- function compositions (compositions, dependency) {
4
+ export function compositions (compositions, dependency) {
7
5
  for (const composition of compositions) {
8
6
  addVariables(composition, dependency.variables)
9
7
  addMounts(composition, dependency.mounts)
@@ -12,5 +10,3 @@ function compositions (compositions, dependency) {
12
10
  composition.probe ??= dependency.probe
13
11
  }
14
12
  }
15
-
16
- exports.compositions = compositions
@@ -1,10 +1,8 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * @param {toa.deployment.dependency.Reference[]} references
5
3
  * @returns {*}
6
4
  */
7
- const dependencies = (references) => {
5
+ export const dependencies = (references) => {
8
6
  return references?.reduce((map, reference) => {
9
7
  const { name, alias, values } = reference
10
8
 
@@ -13,6 +11,3 @@ const dependencies = (references) => {
13
11
  return map
14
12
  }, {})
15
13
  }
16
-
17
-
18
- exports.dependencies = dependencies
@@ -1,5 +1,3 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * Tells each component which of its events something consumes. An event nobody consumes gets
5
3
  * no emitter, no exchange and no outbox row, and a component none of whose events are consumed
@@ -11,7 +9,7 @@
11
9
  * @param {toa.norm.Context} context
12
10
  * @param {toa.deployment.Dependency} dependency
13
11
  */
14
- function events (context, dependency) {
12
+ export function events (context, dependency) {
15
13
  const components = deployed(context)
16
14
  const consumed = collect(components, context.events, dependency.events)
17
15
 
@@ -63,5 +61,3 @@ function collect (components, declared, contributed) {
63
61
  }
64
62
 
65
63
  const VARIABLE = 'TOA_EVENTS_'
66
-
67
- exports.events = events
@@ -1,13 +1,4 @@
1
- 'use strict'
2
-
3
- const { dependencies } = require('./dependencies')
4
- const { components } = require('./components')
5
- const { compositions } = require('./compositions')
6
- const { variables } = require('./variables')
7
- const { services } = require('./services')
8
-
9
- exports.dependencies = dependencies
10
- exports.components = components
11
- exports.compositions = compositions
12
- exports.variables = variables
13
- exports.services = services
1
+ export { dependencies } from './dependencies.js'
2
+ export { components } from './components.js'
3
+ export { compositions } from './compositions.js'
4
+ export { services } from './services.js'
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- function addMounts (composition, mounts, keys = composition.components) {
1
+ export function addMounts (composition, mounts, keys = composition.components) {
4
2
  if (mounts === undefined)
5
3
  return
6
4
 
@@ -20,5 +18,3 @@ function addMounts (composition, mounts, keys = composition.components) {
20
18
  }
21
19
  }
22
20
  }
23
-
24
- exports.addMounts = addMounts
@@ -1,5 +1,3 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * Every deployment states what it may take. One that states nothing is `BestEffort`: the
5
3
  * first evicted under memory pressure and the last given CPU under contention — which on
@@ -10,7 +8,7 @@
10
8
  * Deploying without any is a decision rather than an omission, and it is spelled
11
9
  * `resources: null` — at either place.
12
10
  */
13
- function resources (context, values) {
11
+ export function resources (context, values) {
14
12
  for (const unit of units(values)) {
15
13
  // `null` is an answer and `undefined` is not one, so the fallback reads only the latter
16
14
  if (unit.deployment.resources === undefined)
@@ -33,5 +31,3 @@ function * units (values) {
33
31
  for (const service of values.services ?? [])
34
32
  yield { deployment: service, subject: `Service '${service.name}'` }
35
33
  }
36
-
37
- exports.resources = resources
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { addVariables } from './variables.js'
2
2
 
3
- const { addVariables } = require('./variables')
4
-
5
- function services (services, variables, probe, ingress) {
3
+ export function services (services, variables, probe, ingress) {
6
4
  for (const service of services) {
7
5
  addVariables(service, variables)
8
6
 
@@ -34,5 +32,3 @@ function expose (service, ingress = {}) {
34
32
  if (service.port === undefined)
35
33
  throw new Error(`Service '${service.name}' declares an ingress, but no port.`)
36
34
  }
37
-
38
- exports.services = services
@@ -1,6 +1,7 @@
1
- 'use strict'
1
+ import { it } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { services } = require('./services')
4
+ import { services } from './services.js'
4
5
 
5
6
  const service = (extra = {}) => ({ group: 'group', name: 'group-one', version: '0', ...extra })
6
7
 
@@ -9,7 +10,7 @@ it('should leave a service without an ingress alone', () => {
9
10
 
10
11
  services(list, {}, undefined, { hosts: ['api.dev'] })
11
12
 
12
- expect(list[0].ingress).toBeUndefined()
13
+ assert.strictEqual(list[0].ingress, undefined)
13
14
  })
14
15
 
15
16
  /*
@@ -21,7 +22,7 @@ it('should supply hosts, class and annotations from the context', () => {
21
22
 
22
23
  services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb', annotations: { a: 'b' } })
23
24
 
24
- expect(list[0].ingress).toStrictEqual({
25
+ assert.deepStrictEqual(list[0].ingress, {
25
26
  path: '/.introspection',
26
27
  hosts: ['api.dev'],
27
28
  class: 'alb',
@@ -34,8 +35,8 @@ it('should keep what the service declared itself', () => {
34
35
 
35
36
  services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb' })
36
37
 
37
- expect(list[0].ingress.hosts).toStrictEqual(['own.dev'])
38
- expect(list[0].ingress.class).toStrictEqual('alb')
38
+ assert.deepStrictEqual(list[0].ingress.hosts, ['own.dev'])
39
+ assert.deepStrictEqual(list[0].ingress.class, 'alb')
39
40
  })
40
41
 
41
42
  it('should ignore properties the service left undefined', () => {
@@ -43,19 +44,18 @@ it('should ignore properties the service left undefined', () => {
43
44
 
44
45
  services(list, {}, undefined, { hosts: ['api.dev'], class: 'alb' })
45
46
 
46
- expect(list[0].ingress.class).toStrictEqual('alb')
47
+ assert.deepStrictEqual(list[0].ingress.class, 'alb')
47
48
  })
48
49
 
49
50
  it('should reject an ingress with nowhere to land', () => {
50
51
  const list = [service({ port: 8002, ingress: { path: '/.introspection' } })]
51
52
 
52
- expect(() => services(list, {}, undefined, undefined))
53
- .toThrow("Service 'group-one' declares an ingress, but no hosts are defined")
53
+ assert.throws(() => services(list, {}, undefined, undefined), (error) => /Service 'group-one' declares an ingress, but no hosts are defined/.test(error.message))
54
54
  })
55
55
 
56
56
  it('should reject an ingress without a port', () => {
57
57
  const list = [service({ ingress: { path: '/.introspection' } })]
58
58
 
59
- expect(() => services(list, {}, undefined, { hosts: ['api.dev'] }))
60
- .toThrow("Service 'group-one' declares an ingress, but no port")
59
+ assert.throws(() => services(list, {}, undefined, { hosts: ['api.dev'] }), (error) => /Service 'group-one' declares an ingress, but no port/.test(error.message))
61
60
  })
61
+
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- function addVariables (composition, variables, keys = composition.components) {
1
+ export function addVariables (composition, variables, keys = composition.components) {
4
2
  composition.variables ??= []
5
3
 
6
4
  const used = new Set(composition.variables.map((variable) => variable.name))
@@ -17,5 +15,3 @@ function addVariables (composition, variables, keys = composition.components) {
17
15
  }
18
16
  }
19
17
  }
20
-
21
- exports.addVariables = addVariables
@@ -1,11 +1,9 @@
1
- 'use strict'
2
-
3
1
  /**
4
2
  * @param {toa.norm.Context} context
5
3
  * @param {toa.deployment.Dependency} dependency
6
4
  * @returns {toa.deployment.Declaration}
7
5
  */
8
- const declare = (context, dependency) => {
6
+ export const declare = (context, dependency) => {
9
7
  const { references } = dependency
10
8
  const { name, description, version } = context
11
9
 
@@ -25,5 +23,3 @@ const DECLARATION = {
25
23
  apiVersion: 'v2',
26
24
  type: 'application'
27
25
  }
28
-
29
- exports.declare = declare
@@ -1,12 +1,10 @@
1
- 'use strict'
1
+ import * as desc from './.describe/index.js'
2
+ import { addVariables } from './.describe/variables.js'
3
+ import { addMounts } from './.describe/mounts.js'
4
+ import { resources } from './.describe/resources.js'
5
+ import { events } from './.describe/events.js'
2
6
 
3
- const desc = require('./.describe')
4
- const { addVariables } = require('./.describe/variables')
5
- const { addMounts } = require('./.describe/mounts')
6
- const { resources } = require('./.describe/resources')
7
- const { events } = require('./.describe/events')
8
-
9
- const describe = (context, compositions, dependency, image) => {
7
+ export const describe = (context, compositions, dependency, image) => {
10
8
  const { services } = dependency
11
9
 
12
10
  dependency.variables.global ??= []
@@ -132,6 +130,10 @@ function unit (context, dependency) {
132
130
  if (service.port !== undefined)
133
131
  (mono.backends ??= []).push({ port: service.port, path: service.ingress?.path ?? '/' })
134
132
 
133
+ // one Service fronts every backend, so its annotations are the union
134
+ if (service.annotations !== undefined)
135
+ mono.annotations = Object.assign(mono.annotations ?? {}, service.annotations)
136
+
135
137
  if (service.ingress !== undefined) {
136
138
  const { path, hosts, ...ingress } = service.ingress
137
139
 
@@ -154,5 +156,3 @@ function unit (context, dependency) {
154
156
 
155
157
  return mono
156
158
  }
157
-
158
- exports.describe = describe
@@ -1,9 +1,3 @@
1
- 'use strict'
2
-
3
- const { declare } = require('./declare')
4
- const { describe } = require('./describe')
5
- const { merge } = require('./merge')
6
-
7
- exports.declare = declare
8
- exports.describe = describe
9
- exports.merge = merge
1
+ export { declare } from './declare.js'
2
+ export { describe } from './describe.js'
3
+ export { merge } from './merge.js'
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const merge = (dependencies) => {
1
+ export const merge = (dependencies) => {
4
2
  /** @type {toa.deployment.dependency.Reference[]} */
5
3
  const references = []
6
4
 
@@ -74,5 +72,3 @@ const append = (merged, variables) => {
74
72
  merged[component].push(...vars)
75
73
  }
76
74
  }
77
-
78
- exports.merge = merge
@@ -1,6 +1,7 @@
1
- 'use strict'
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const { merge } = require('./merge')
4
+ import { merge } from './merge.js'
4
5
 
5
6
  const service = (name, extra = {}) => ({ group: 'group', name, version: '0', ...extra })
6
7
 
@@ -10,7 +11,7 @@ it('should merge services of all dependencies', () => {
10
11
  { services: [service('two', { port: 8001 })] }
11
12
  ])
12
13
 
13
- expect(merged.services).toHaveLength(2)
14
+ assert.strictEqual(merged.services.length, 2)
14
15
  })
15
16
 
16
17
  /*
@@ -24,8 +25,7 @@ describe('port reservation', () => {
24
25
  { services: [service('two', { port: 8000 })] }
25
26
  ]
26
27
 
27
- expect(() => merge(dependencies))
28
- .toThrow("Port 8000 is claimed by both 'group-one' and 'group-two'")
28
+ assert.throws(() => merge(dependencies), (error) => /Port 8000 is claimed by both 'group-one' and 'group-two'/.test(error.message))
29
29
  })
30
30
 
31
31
  it('should reject a service claiming the port of the readiness probe', () => {
@@ -34,8 +34,7 @@ describe('port reservation', () => {
34
34
  { services: [service('one', { port: 8001 })] }
35
35
  ]
36
36
 
37
- expect(() => merge(dependencies))
38
- .toThrow("Port 8001 is claimed by both the readiness probe and 'group-one'")
37
+ assert.throws(() => merge(dependencies), (error) => /Port 8001 is claimed by both the readiness probe and 'group-one'/.test(error.message))
39
38
  })
40
39
 
41
40
  it('should reject a probe claiming the port of another service', () => {
@@ -44,8 +43,7 @@ describe('port reservation', () => {
44
43
  { services: [service('two', { port: 8002, probe: { path: '/.ready', port: 8000 } })] }
45
44
  ]
46
45
 
47
- expect(() => merge(dependencies))
48
- .toThrow("Port 8000 is claimed by both 'group-one' and the readiness probe of 'group-two'")
46
+ assert.throws(() => merge(dependencies), (error) => /Port 8000 is claimed by both 'group-one' and the readiness probe of 'group-two'/.test(error.message))
49
47
  })
50
48
 
51
49
  it('should allow a service to probe its own port', () => {
@@ -53,7 +51,7 @@ describe('port reservation', () => {
53
51
  { services: [service('one', { port: 8000, probe: { path: '/.ready', port: 8000 } })] }
54
52
  ]
55
53
 
56
- expect(() => merge(dependencies)).not.toThrow()
54
+ assert.doesNotThrow(() => merge(dependencies))
57
55
  })
58
56
 
59
57
  it('should ignore services without a port', () => {
@@ -61,7 +59,7 @@ describe('port reservation', () => {
61
59
  { services: [service('one'), service('two')] }
62
60
  ]
63
61
 
64
- expect(() => merge(dependencies)).not.toThrow()
62
+ assert.doesNotThrow(() => merge(dependencies))
65
63
  })
66
64
 
67
65
  it('should ignore a disabled probe', () => {
@@ -69,6 +67,6 @@ describe('port reservation', () => {
69
67
  { probe: false, services: [service('one', { port: 8000, probe: false })] }
70
68
  ]
71
69
 
72
- expect(() => merge(dependencies)).not.toThrow()
70
+ assert.doesNotThrow(() => merge(dependencies))
73
71
  })
74
72
  })
@@ -116,6 +116,10 @@ apiVersion: v1
116
116
  kind: Service
117
117
  metadata:
118
118
  name: mono
119
+ {{- if .annotations }}
120
+ annotations:
121
+ {{ toYaml .annotations | indent 4 }}
122
+ {{- end }}
119
123
  spec:
120
124
  type: ClusterIP
121
125
  selector:
@@ -88,6 +88,10 @@ apiVersion: v1
88
88
  kind: Service
89
89
  metadata:
90
90
  name: extension-{{ .name }}
91
+ {{- if .annotations }}
92
+ annotations:
93
+ {{ toYaml .annotations | indent 4 }}
94
+ {{- end }}
91
95
  spec:
92
96
  type: ClusterIP
93
97
  selector:
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- class Composition {
1
+ export class Composition {
4
2
  name
5
3
  image
6
4
  /** @type {string[]} */
@@ -15,5 +13,3 @@ class Composition {
15
13
  }
16
14
 
17
15
  const component = (component) => component.locator.label
18
-
19
- exports.Composition = Composition
@@ -1,13 +1,11 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import { writeFile as write } from 'node:fs/promises'
3
+ import { yaml as jsyaml } from '@toa.io/generic'
4
+ import fs from 'fs-extra'
2
5
 
3
- const { join } = require('node:path')
4
- const { writeFile: write } = require('node:fs/promises')
5
- const jsyaml = require('js-yaml')
6
- const fs = require('fs-extra')
6
+ import { merge, declare, describe } from './.deployment/index.js'
7
7
 
8
- const { merge, declare, describe } = require('./.deployment')
9
-
10
- class Deployment {
8
+ export class Deployment {
11
9
  #chart
12
10
  #values
13
11
  #process
@@ -89,10 +87,11 @@ function addVariables (list, variables, used = new Set()) {
89
87
  }
90
88
  }
91
89
 
92
- const TEMPLATES = join(__dirname, 'chart/templates')
90
+ const TEMPLATES = join(import.meta.dirname, 'chart/templates')
91
+
93
92
 
94
- exports.Deployment = Deployment
95
93
 
96
94
  function dump (object) {
97
- return jsyaml.dump(object, { noRefs: true, lineWidth: -1 })
95
+ // js-yaml writes plain objects only, and the values carry locators and images
96
+ return jsyaml.dump(JSON.parse(JSON.stringify(object)), { noRefs: true, lineWidth: -1 })
98
97
  }
@@ -1,15 +1,20 @@
1
- 'use strict'
2
-
3
- const { context: load } = require('@toa.io/norm')
4
- const { Process } = require('../process')
5
- const { Operator } = require('./operator')
6
- const { Factory: ImagesFactory } = require('./images')
7
- const { Deployment } = require('./deployment')
8
- const { Registry } = require('./registry')
9
- const { Composition } = require('./composition')
10
- const { Service } = require('./service')
11
-
12
- class Factory {
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { createRequire } from 'node:module'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { context as load } from '@toa.io/norm'
6
+ import { Process } from '../process.js'
7
+ import { Operator } from './operator.js'
8
+ import { Factory as ImagesFactory } from './images/index.js'
9
+ import { Deployment } from './deployment.js'
10
+
11
+ // a dependency is resolved the way a package is
12
+ const require = createRequire(import.meta.url)
13
+ import { Registry } from './registry.js'
14
+ import { Composition } from './composition.js'
15
+ import { Service } from './service.js'
16
+
17
+ export class Factory {
13
18
  #context
14
19
  #mono
15
20
  #compositions
@@ -37,11 +42,12 @@ class Factory {
37
42
  this.#compositions = context.compositions.map((composition) => this.#composition(composition))
38
43
  }
39
44
 
40
- operator () {
45
+ async operator () {
41
46
  const deployment = new Deployment(
42
47
  this.#context,
43
48
  this.#compositions,
44
- this.#dependencies,
49
+ // the constructor cannot await, so what it started is settled here
50
+ await this.#dependencies,
45
51
  this.#process,
46
52
  this.#image
47
53
  )
@@ -59,14 +65,14 @@ class Factory {
59
65
  return new Composition(composition, image)
60
66
  }
61
67
 
62
- #getDependencies () {
68
+ async #getDependencies () {
63
69
  /** @type {toa.deployment.Dependency[]} */
64
70
  const dependencies = []
65
71
 
66
72
  if (this.#context.dependencies === undefined) return dependencies
67
73
 
68
74
  for (const [reference, instances] of Object.entries(this.#context.dependencies)) {
69
- const dependency = this.#getDependency(reference, instances)
75
+ const dependency = await this.#getDependency(reference, instances)
70
76
 
71
77
  if (dependency !== undefined) dependencies.push(dependency)
72
78
  }
@@ -74,9 +80,11 @@ class Factory {
74
80
  return dependencies
75
81
  }
76
82
 
77
- #getDependency (path, instances) {
78
- const module = require(path)
79
- const pkg = require(path + '/package.json')
83
+ async #getDependency (reference, instances) {
84
+ // a dependency may be named the way a package is or written as a directory,
85
+ // and a module is loaded by file
86
+ const module = await import(pathToFileURL(require.resolve(reference)).href)
87
+ const pkg = JSON.parse(readFileSync(require.resolve(join(reference, 'package.json')), 'utf8'))
80
88
 
81
89
  if (module.deployment === undefined) return
82
90
 
@@ -87,7 +95,7 @@ class Factory {
87
95
 
88
96
  /** @type {toa.deployment.Service[]} */
89
97
  const services = dependency.services?.map((service) =>
90
- this.#mono ? service : this.#service(path, service))
98
+ this.#mono ? service : this.#service(reference, service))
91
99
 
92
100
  return { ...dependency, services }
93
101
  }
@@ -109,5 +117,3 @@ class Factory {
109
117
  return new Factory(context, options)
110
118
  }
111
119
  }
112
-
113
- exports.Factory = Factory
@@ -13,7 +13,7 @@ COPY --chown=node:node . /composition
13
13
 
14
14
  # run 'npm i' in each component
15
15
  RUN --mount=type=cache,target=/root/.npm \
16
- for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
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
19
19
  CMD toa compose *
@@ -1,13 +1,12 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import fs from 'fs-extra'
3
+ import { createHash } from 'node:crypto'
2
4
 
3
- const { join } = require('node:path')
4
- const fs = require('fs-extra')
5
- const { createHash } = require('node:crypto')
5
+ import { Image } from './image.js'
6
+ import { declare } from './format.js'
6
7
 
7
- const { Image } = require('./image')
8
-
9
- class Composition extends Image {
10
- dockerfile = join(__dirname, 'composition.Dockerfile')
8
+ export class Composition extends Image {
9
+ dockerfile = join(import.meta.dirname, 'composition.Dockerfile')
11
10
 
12
11
  #name
13
12
  #image
@@ -64,11 +63,12 @@ class Composition extends Image {
64
63
  const context = await super.prepare(root)
65
64
 
66
65
  for (const component of this.#components) {
67
- await fs.copy(component.path, join(context, component.locator.label))
66
+ const target = join(context, component.locator.label)
67
+
68
+ await fs.copy(component.path, target)
69
+ await declare(component.path, target, component.locator.label)
68
70
  }
69
71
 
70
72
  return context
71
73
  }
72
74
  }
73
-
74
- exports.Composition = Composition