@toa.io/operations 1.0.0-alpha.296 → 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
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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
|
+
|
|
6
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)
|
|
7
15
|
|
|
8
16
|
### Bug Fixes
|
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
|
}
|
|
@@ -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
|