@toa.io/operations 1.0.0-alpha.295 → 1.0.0-alpha.297
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 +15 -0
- package/package.json +3 -3
- package/src/deployment/images/dependencies.Dockerfile +3 -1
- package/src/deployment/images/dependencies.test.js +3 -1
- package/src/deployment/images/format.test.js +36 -0
- package/src/deployment/images/service.Dockerfile +6 -1
- package/src/deployment/images/service.js +37 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
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.297](https://github.com/toa-io/toa/compare/v1.0.0-alpha.296...v1.0.0-alpha.297) (2026-09-08)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @toa.io/operations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [1.0.0-alpha.296](https://github.com/toa-io/toa/compare/v1.0.0-alpha.295...v1.0.0-alpha.296) (2026-09-08)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **exposition:** the gateway's image carries every storage provider ([ab19d25](https://github.com/toa-io/toa/commit/ab19d2567764d5a3148b067f186819d7e646908f))
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
# [1.0.0-alpha.295](https://github.com/toa-io/toa/compare/v1.0.0-alpha.294...v1.0.0-alpha.295) (2026-09-07)
|
|
7
22
|
|
|
8
23
|
**Note:** Version bump only for package @toa.io/operations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/operations",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.297",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa Deployment",
|
|
6
6
|
"homepage": "https://toa.io",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@toa.io/generic": "1.0.0-alpha.292",
|
|
32
32
|
"@toa.io/kubernetes": "1.0.0-alpha.293",
|
|
33
|
-
"@toa.io/norm": "1.0.0-alpha.
|
|
33
|
+
"@toa.io/norm": "1.0.0-alpha.297",
|
|
34
34
|
"paseto": "4.0.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "52959bba83817238725f50553d45afde1f231af1"
|
|
37
37
|
}
|
|
@@ -19,5 +19,7 @@ RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
|
19
19
|
# what the extensions need for what these components declare: installed beside the extension
|
|
20
20
|
# that reads the declaration, in Toa's own install, because that is where Node resolves an
|
|
21
21
|
# extension's imports from — and one copy for the composition rather than one per component
|
|
22
|
+
# `--legacy-peer-deps` for the reason the base image installs with it: what is added here
|
|
23
|
+
# re-resolves the tree, and a peer nothing imports comes back with it — 23 MB of `typescript`
|
|
22
24
|
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
23
|
-
if [ -s .packages ]; then npm i --prefix /toa --omit=dev $(cat .packages); fi
|
|
25
|
+
if [ -s .packages ]; then npm i --prefix /toa --omit=dev --legacy-peer-deps $(cat .packages); fi
|
|
@@ -176,7 +176,9 @@ describe('prepare', () => {
|
|
|
176
176
|
|
|
177
177
|
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
178
178
|
|
|
179
|
-
assert.ok(
|
|
179
|
+
assert.ok(
|
|
180
|
+
dockerfile.includes('npm i --prefix /toa --omit=dev --legacy-peer-deps $(cat .packages)')
|
|
181
|
+
)
|
|
180
182
|
})
|
|
181
183
|
|
|
182
184
|
it('should hold what a service the composition runs brings', async () => {
|
|
@@ -111,3 +111,39 @@ describe('normalized', () => {
|
|
|
111
111
|
assert.strictEqual(read.locator.id, 'todos.tasks')
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
|
+
|
|
115
|
+
describe('a service image', () => {
|
|
116
|
+
it('installs what its components declare beside Toa as well', async () => {
|
|
117
|
+
const { Service } = await import('./service.js')
|
|
118
|
+
|
|
119
|
+
const path = join(root, 'extension')
|
|
120
|
+
const component = join(path, 'components', 'octets')
|
|
121
|
+
|
|
122
|
+
await mkdir(component, { recursive: true })
|
|
123
|
+
await writeFile(join(path, 'package.json'), JSON.stringify({ name: 'ext', version: '1' }))
|
|
124
|
+
await writeFile(
|
|
125
|
+
join(component, 'package.json'),
|
|
126
|
+
JSON.stringify({ dependencies: { cloudinary: '2.11.0', jose: '6.2.10' } })
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const service = new Service('', { version: '1' }, {}, path, {
|
|
130
|
+
group: 'g',
|
|
131
|
+
name: 'n',
|
|
132
|
+
version: '1'
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
service.reference = 'probe'
|
|
136
|
+
|
|
137
|
+
const context = await service.prepare(join(root, 'service'))
|
|
138
|
+
|
|
139
|
+
// what an extension imports on a component's behalf is imported from where Toa is
|
|
140
|
+
assert.strictEqual(
|
|
141
|
+
await readFile(join(context, '.packages'), 'utf8'),
|
|
142
|
+
'cloudinary@2.11.0\njose@6.2.10'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
146
|
+
|
|
147
|
+
assert.ok(dockerfile.includes('npm i --prefix /toa --omit=dev --legacy-peer-deps $(cat .packages)'))
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -14,7 +14,12 @@ RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
|
14
14
|
# component rather than beside the extension: an application that runs none of them, and the
|
|
15
15
|
# runtime image every application is built on, carry nothing for them
|
|
16
16
|
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
17
|
-
for entry in components/*; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
|
|
17
|
+
for entry in components/*; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev --legacy-peer-deps); fi; done
|
|
18
|
+
|
|
19
|
+
# and beside Toa as well, because what an extension imports on a component's behalf — a storage
|
|
20
|
+
# provider's SDK — is imported from where the extension is, which is `/toa` and not here
|
|
21
|
+
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
|
|
22
|
+
if [ -s .packages ]; then npm i --prefix /toa --omit=dev --legacy-peer-deps $(cat .packages); fi
|
|
18
23
|
|
|
19
24
|
# no USER: the runtime drops to `node` itself, and only a process that started as root can
|
|
20
25
|
# close its environment under /proc — see runtime/runtime/bin/toa
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'
|
|
2
2
|
import { join, dirname } from 'node:path'
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
3
4
|
|
|
4
5
|
import { Image } from './image.js'
|
|
5
|
-
import { cp } from 'node:fs/promises'
|
|
6
|
+
import { cp, writeFile } from 'node:fs/promises'
|
|
6
7
|
|
|
7
8
|
// a service is named the way a package is, and its directory is where it lives
|
|
8
9
|
const require = createRequire(import.meta.url)
|
|
@@ -60,11 +61,46 @@ export class Service extends Image {
|
|
|
60
61
|
const context = await super.prepare(root)
|
|
61
62
|
|
|
62
63
|
await cp(this.#path, context, { recursive: true })
|
|
64
|
+
await writeFile(join(context, PACKAGES), this.#packages().join('\n'))
|
|
63
65
|
|
|
64
66
|
return context
|
|
65
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* What the components this service runs declare, as `name@version`.
|
|
71
|
+
*
|
|
72
|
+
* Each is installed beside its component, where the component's own modules import it, and
|
|
73
|
+
* beside Toa, where an extension that imports on a component's behalf is — a storage
|
|
74
|
+
* provider's SDK is imported by `@toa.io/extensions.storages`, not by the component that
|
|
75
|
+
* declares the storage.
|
|
76
|
+
*
|
|
77
|
+
* @returns {string[]}
|
|
78
|
+
*/
|
|
79
|
+
#packages() {
|
|
80
|
+
const directory = join(this.#path, 'components')
|
|
81
|
+
|
|
82
|
+
if (!existsSync(directory)) return []
|
|
83
|
+
|
|
84
|
+
/** @type {Record<string, string>} */
|
|
85
|
+
const packages = {}
|
|
86
|
+
|
|
87
|
+
for (const label of readdirSync(directory)) {
|
|
88
|
+
const manifest = join(directory, label, 'package.json')
|
|
89
|
+
|
|
90
|
+
if (!existsSync(manifest)) continue
|
|
91
|
+
|
|
92
|
+
Object.assign(packages, JSON.parse(readFileSync(manifest, 'utf8')).dependencies)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Object.entries(packages)
|
|
96
|
+
.map(([name, version]) => `${name}@${version}`)
|
|
97
|
+
.sort()
|
|
98
|
+
}
|
|
66
99
|
}
|
|
67
100
|
|
|
101
|
+
/** Where the install reads what the components need, one `name@version` per line. */
|
|
102
|
+
const PACKAGES = '.packages'
|
|
103
|
+
|
|
68
104
|
/**
|
|
69
105
|
* Where the extension is installed, which is what its image is built from. A deploy install
|
|
70
106
|
* carries what an extension declares and not the extension, so a service is built only where
|