@toa.io/operations 1.0.0-alpha.34 → 1.0.0-alpha.341
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 +457 -0
- package/image/runtime.Dockerfile +20 -0
- package/package.json +10 -10
- package/readme.md +224 -0
- package/src/deployment/.deployment/.describe/components.js +1 -5
- package/src/deployment/.deployment/.describe/compositions.js +26 -7
- package/src/deployment/.deployment/.describe/dependencies.js +1 -6
- package/src/deployment/.deployment/.describe/events.js +63 -0
- package/src/deployment/.deployment/.describe/fold.js +42 -0
- package/src/deployment/.deployment/.describe/fold.test.js +108 -0
- package/src/deployment/.deployment/.describe/index.js +4 -13
- package/src/deployment/.deployment/.describe/mounts.js +17 -0
- package/src/deployment/.deployment/.describe/resources.js +129 -0
- package/src/deployment/.deployment/.describe/resources.test.js +129 -0
- package/src/deployment/.deployment/.describe/services.js +32 -6
- package/src/deployment/.deployment/.describe/services.test.js +78 -0
- package/src/deployment/.deployment/.describe/variables.js +5 -9
- package/src/deployment/.deployment/declare.js +1 -5
- package/src/deployment/.deployment/describe.js +168 -10
- package/src/deployment/.deployment/index.js +3 -9
- package/src/deployment/.deployment/merge.js +66 -6
- package/src/deployment/.deployment/merge.test.js +133 -0
- package/src/deployment/chart/templates/components.configmap.yaml +13 -0
- package/src/deployment/chart/templates/components.yaml +4 -3
- package/src/deployment/chart/templates/compositions.yaml +109 -4
- package/src/deployment/chart/templates/mono.yaml +199 -0
- package/src/deployment/chart/templates/services.yaml +97 -7
- package/src/deployment/chart/values.yaml +23 -1
- package/src/deployment/composition.js +8 -6
- package/src/deployment/deployment.js +175 -35
- package/src/deployment/drain.js +70 -0
- package/src/deployment/drain.test.js +64 -0
- package/src/deployment/factory.js +178 -37
- package/src/deployment/images/bundle.js +93 -0
- package/src/deployment/images/composition.Dockerfile +9 -17
- package/src/deployment/images/composition.js +9 -56
- package/src/deployment/images/dependencies.Dockerfile +23 -0
- package/src/deployment/images/dependencies.js +171 -0
- package/src/deployment/images/dependencies.test.js +271 -0
- package/src/deployment/images/factory.js +31 -15
- package/src/deployment/images/format.js +66 -0
- package/src/deployment/images/format.test.js +150 -0
- package/src/deployment/images/image.fixtures.js +17 -19
- package/src/deployment/images/image.js +79 -38
- package/src/deployment/images/image.test.js +11 -5
- package/src/deployment/images/index.js +1 -5
- package/src/deployment/images/mono.Dockerfile +11 -0
- package/src/deployment/images/mono.js +15 -0
- package/src/deployment/images/publish.js +91 -0
- package/src/deployment/images/runtime-base.test.js +97 -0
- package/src/deployment/images/service.Dockerfile +17 -5
- package/src/deployment/images/service.js +63 -14
- package/src/deployment/index.js +1 -5
- package/src/deployment/operator.js +25 -16
- package/src/deployment/operator.test.js +98 -7
- package/src/deployment/registry.js +210 -46
- package/src/deployment/registry.test.js +539 -0
- package/src/deployment/secrets.js +92 -0
- package/src/deployment/secrets.test.js +180 -0
- package/src/deployment/service.js +4 -6
- package/src/deployment/workspace.js +13 -8
- package/src/index.js +3 -2
- package/src/process.js +51 -13
- package/src/process.test.js +33 -0
- package/types/_deployment/composition.d.ts +2 -3
- package/types/_deployment/dependency.d.ts +15 -7
- package/types/_deployment/deployment.d.ts +10 -8
- package/types/_deployment/factory.d.ts +2 -4
- package/types/_deployment/images/factory.d.ts +6 -8
- package/types/_deployment/images/image.d.ts +16 -1
- package/types/_deployment/images/registry.d.ts +8 -11
- package/types/_deployment/operator.d.ts +10 -9
- package/types/_deployment/registry.d.ts +7 -4
- package/types/_deployment/service.d.ts +4 -3
- package/types/dependency.ts +79 -0
- package/types/index.ts +1 -0
- package/types/dependency.d.ts +0 -39
- package/types/index.d.ts +0 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { mkdtemp, mkdir, writeFile, readdir, readFile } from 'node:fs/promises'
|
|
6
|
+
import { tmpdir } from 'node:os'
|
|
7
|
+
|
|
8
|
+
import { Composition } from './composition.js'
|
|
9
|
+
import { Mono } from './mono.js'
|
|
10
|
+
|
|
11
|
+
/** @type {string} */
|
|
12
|
+
let root
|
|
13
|
+
|
|
14
|
+
/** @type {toa.norm.context.Runtime} */
|
|
15
|
+
let runtime
|
|
16
|
+
|
|
17
|
+
/** @type {toa.norm.context.Registry} */
|
|
18
|
+
let registry
|
|
19
|
+
|
|
20
|
+
/** @type {object} */
|
|
21
|
+
let composition
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
root = await mkdtemp(join(tmpdir(), 'toa-dependencies-test'))
|
|
25
|
+
runtime = { version: '1.0.0-alpha.288' }
|
|
26
|
+
registry = { base: 'example.com/reg', platforms: ['linux/amd64'] }
|
|
27
|
+
|
|
28
|
+
composition = {
|
|
29
|
+
name: 'mono',
|
|
30
|
+
components: [
|
|
31
|
+
await component('one', { dependencies: { left: '1.0.0' } }),
|
|
32
|
+
await component('two'),
|
|
33
|
+
await component('three', { devDependencies: { right: '1.0.0' } }, 'lock')
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('reference', () => {
|
|
39
|
+
it('should share the repository and stand a prefix apart', () => {
|
|
40
|
+
const image = create()
|
|
41
|
+
|
|
42
|
+
assert.match(
|
|
43
|
+
image.dependencies.reference,
|
|
44
|
+
/^example\.com\/reg\/acme\/composition-mono:deps-[0-9a-f]{8}$/
|
|
45
|
+
)
|
|
46
|
+
assert.match(
|
|
47
|
+
image.reference,
|
|
48
|
+
/^example\.com\/reg\/acme\/composition-mono:[0-9a-f]{8}$/
|
|
49
|
+
)
|
|
50
|
+
assert.strictEqual(image.base, image.dependencies.reference)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should be the same for the same inputs', () => {
|
|
54
|
+
assert.strictEqual(create().dependencies.reference, create().dependencies.reference)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should change with a manifest', async () => {
|
|
58
|
+
const before = create()
|
|
59
|
+
|
|
60
|
+
await writeFile(
|
|
61
|
+
join(composition.components[0].path, 'package.json'),
|
|
62
|
+
JSON.stringify({ dependencies: { left: '1.0.1' } })
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const after = create()
|
|
66
|
+
|
|
67
|
+
assert.notStrictEqual(after.dependencies.reference, before.dependencies.reference)
|
|
68
|
+
assert.notStrictEqual(after.reference, before.reference)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should change with a lockfile', async () => {
|
|
72
|
+
const before = create()
|
|
73
|
+
|
|
74
|
+
await writeFile(join(composition.components[2].path, 'package-lock.json'), 'other')
|
|
75
|
+
|
|
76
|
+
assert.notStrictEqual(create().dependencies.reference, before.dependencies.reference)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should change with what is run and with the runtime', () => {
|
|
80
|
+
const before = create()
|
|
81
|
+
|
|
82
|
+
composition.components[1].build = { run: 'apk add git' }
|
|
83
|
+
|
|
84
|
+
const run = create()
|
|
85
|
+
|
|
86
|
+
assert.notStrictEqual(run.dependencies.reference, before.dependencies.reference)
|
|
87
|
+
|
|
88
|
+
runtime.version = '1.0.0-alpha.289'
|
|
89
|
+
|
|
90
|
+
assert.notStrictEqual(create().dependencies.reference, run.dependencies.reference)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should change with what an extension installs', () => {
|
|
94
|
+
const before = create()
|
|
95
|
+
|
|
96
|
+
composition.components[1].packages = { cloudinary: '2.11.0' }
|
|
97
|
+
|
|
98
|
+
const component = create()
|
|
99
|
+
|
|
100
|
+
assert.notStrictEqual(component.dependencies.reference, before.dependencies.reference)
|
|
101
|
+
|
|
102
|
+
composition.packages = { 'lru-cache': '11.5.2' }
|
|
103
|
+
|
|
104
|
+
assert.notStrictEqual(
|
|
105
|
+
create().dependencies.reference,
|
|
106
|
+
component.dependencies.reference
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should change with the registry build settings', () => {
|
|
111
|
+
const before = create()
|
|
112
|
+
|
|
113
|
+
registry.build = { arguments: ['TOKEN'] }
|
|
114
|
+
|
|
115
|
+
assert.notStrictEqual(create().dependencies.reference, before.dependencies.reference)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should not change with the sources', async () => {
|
|
119
|
+
const before = create()
|
|
120
|
+
|
|
121
|
+
await writeFile(
|
|
122
|
+
join(composition.components[0].path, 'index.js'),
|
|
123
|
+
'export const changed = true'
|
|
124
|
+
)
|
|
125
|
+
composition.components[0].version = 'ffffffff'
|
|
126
|
+
|
|
127
|
+
const after = create()
|
|
128
|
+
|
|
129
|
+
assert.strictEqual(after.dependencies.reference, before.dependencies.reference)
|
|
130
|
+
assert.notStrictEqual(after.reference, before.reference)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('prepare', () => {
|
|
135
|
+
it('should hold the manifests and nothing else', async () => {
|
|
136
|
+
const image = create()
|
|
137
|
+
const context = await image.dependencies.prepare(root)
|
|
138
|
+
|
|
139
|
+
assert.strictEqual(
|
|
140
|
+
context,
|
|
141
|
+
join(root, 'dependencies', `composition-mono.${image.dependencies.version}`)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const entries = (await readdir(context, { recursive: true })).sort()
|
|
145
|
+
|
|
146
|
+
assert.deepStrictEqual(entries, [
|
|
147
|
+
'.dockerignore',
|
|
148
|
+
'.packages',
|
|
149
|
+
'Dockerfile',
|
|
150
|
+
'one',
|
|
151
|
+
'one/package.json',
|
|
152
|
+
'three',
|
|
153
|
+
'three/package-lock.json',
|
|
154
|
+
'three/package.json'
|
|
155
|
+
])
|
|
156
|
+
|
|
157
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
158
|
+
|
|
159
|
+
assert.ok(dockerfile.includes('FROM ghcr.io/toa-io/runtime:1.0.0-alpha.288'))
|
|
160
|
+
assert.ok(dockerfile.includes('npm i --omit=dev'))
|
|
161
|
+
assert.ok(dockerfile.includes('WORKDIR /composition'))
|
|
162
|
+
assert.doesNotMatch(dockerfile, /USER node|CMD /)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should hold what the extensions install, deduplicated and ordered', async () => {
|
|
166
|
+
composition.components[0].packages = { cloudinary: '2.11.0' }
|
|
167
|
+
composition.components[1].packages = {
|
|
168
|
+
cloudinary: '2.11.0',
|
|
169
|
+
'@aws-sdk/client-s3': '3.1125.0'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const image = create()
|
|
173
|
+
const context = await image.dependencies.prepare(root)
|
|
174
|
+
|
|
175
|
+
assert.strictEqual(
|
|
176
|
+
await readFile(join(context, '.packages'), 'utf8'),
|
|
177
|
+
'@aws-sdk/client-s3@3.1125.0\ncloudinary@2.11.0'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
181
|
+
|
|
182
|
+
assert.ok(dockerfile.includes('npm i --prefix /toa --omit=dev $(cat .packages)'))
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('should hold what a service the composition runs brings', async () => {
|
|
186
|
+
composition.packages = { 'lru-cache': '11.5.2' }
|
|
187
|
+
composition.components[0].packages = { cloudinary: '2.11.0' }
|
|
188
|
+
|
|
189
|
+
const context = await create().dependencies.prepare(root)
|
|
190
|
+
|
|
191
|
+
assert.strictEqual(
|
|
192
|
+
await readFile(join(context, '.packages'), 'utf8'),
|
|
193
|
+
'cloudinary@2.11.0\nlru-cache@11.5.2'
|
|
194
|
+
)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should hold an empty list where nothing is declared', async () => {
|
|
198
|
+
const context = await create().dependencies.prepare(root)
|
|
199
|
+
|
|
200
|
+
assert.strictEqual(await readFile(join(context, '.packages'), 'utf8'), '')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should lay the sources over the dependencies', async () => {
|
|
204
|
+
const image = create()
|
|
205
|
+
|
|
206
|
+
image.dependencies.tag()
|
|
207
|
+
|
|
208
|
+
const context = await image.prepare(root)
|
|
209
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
210
|
+
const lines = dockerfile
|
|
211
|
+
.split('\n')
|
|
212
|
+
.filter((line) => line !== '' && !line.startsWith('#'))
|
|
213
|
+
|
|
214
|
+
// no USER: the runtime starts as root and drops to `node` itself, and nothing else
|
|
215
|
+
// touches the filesystem, which is what lets the layer be laid over a base never pulled
|
|
216
|
+
assert.deepStrictEqual(lines, [
|
|
217
|
+
`FROM ${image.dependencies.reference}`,
|
|
218
|
+
'COPY --link . /composition',
|
|
219
|
+
'CMD toa compose * --map /etc/toa/.map.json.gz'
|
|
220
|
+
])
|
|
221
|
+
|
|
222
|
+
const entries = (await readdir(context, { recursive: true })).sort()
|
|
223
|
+
|
|
224
|
+
assert.ok(entries.includes('one/index.js'))
|
|
225
|
+
assert.ok(entries.includes('two/package.json')) // declared for the component that had none
|
|
226
|
+
assert.ok(entries.includes('one/manifest.toa.json')) // what the process reads instead of normalizing
|
|
227
|
+
assert.ok(!entries.some((entry) => entry.includes('node_modules')))
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should run mono the same way', async () => {
|
|
231
|
+
const image = new Mono('acme', runtime, registry, composition)
|
|
232
|
+
|
|
233
|
+
image.tag()
|
|
234
|
+
|
|
235
|
+
assert.match(image.dependencies.reference, /\/mono:deps-[0-9a-f]{8}$/)
|
|
236
|
+
|
|
237
|
+
const context = await image.prepare(root)
|
|
238
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
239
|
+
|
|
240
|
+
assert.ok(dockerfile.includes('CMD toa mono *'))
|
|
241
|
+
assert.doesNotMatch(dockerfile, /^USER /m)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
/** @returns {Composition} */
|
|
246
|
+
function create() {
|
|
247
|
+
const image = new Composition('acme', runtime, registry, composition)
|
|
248
|
+
|
|
249
|
+
image.tag()
|
|
250
|
+
|
|
251
|
+
return image
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {string} label
|
|
256
|
+
* @param {object} [manifest]
|
|
257
|
+
* @param {string} [lock]
|
|
258
|
+
*/
|
|
259
|
+
async function component(label, manifest, lock) {
|
|
260
|
+
const path = join(root, 'components', label)
|
|
261
|
+
|
|
262
|
+
await mkdir(join(path, 'node_modules', 'left'), { recursive: true })
|
|
263
|
+
await writeFile(join(path, 'index.js'), 'export const changed = false')
|
|
264
|
+
await writeFile(join(path, 'node_modules', 'left', 'index.js'), '')
|
|
265
|
+
|
|
266
|
+
if (manifest !== undefined)
|
|
267
|
+
await writeFile(join(path, 'package.json'), JSON.stringify(manifest))
|
|
268
|
+
if (lock !== undefined) await writeFile(join(path, 'package-lock.json'), lock)
|
|
269
|
+
|
|
270
|
+
return { path, version: 'abcdef12', locator: { id: `default.${label}`, label } }
|
|
271
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { Composition } from './composition.js'
|
|
2
|
+
import { Service } from './service.js'
|
|
3
|
+
import { Mono } from './mono.js'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const { Service } = require('./service')
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @implements {toa.deployment.images.Factory}
|
|
8
|
-
*/
|
|
9
|
-
class Factory {
|
|
5
|
+
export class Factory {
|
|
10
6
|
/** @type {string} */
|
|
11
7
|
#scope
|
|
12
8
|
|
|
@@ -21,7 +17,7 @@ class Factory {
|
|
|
21
17
|
* @param {toa.norm.context.Runtime} runtime
|
|
22
18
|
* @param {toa.norm.context.Registry} registry
|
|
23
19
|
*/
|
|
24
|
-
constructor
|
|
20
|
+
constructor(scope, runtime, registry) {
|
|
25
21
|
this.#scope = scope
|
|
26
22
|
this.#runtime = runtime
|
|
27
23
|
this.#registry = registry
|
|
@@ -30,8 +26,13 @@ class Factory {
|
|
|
30
26
|
/**
|
|
31
27
|
* @returns {Composition}
|
|
32
28
|
*/
|
|
33
|
-
composition
|
|
34
|
-
const instance = new Composition(
|
|
29
|
+
composition(composition) {
|
|
30
|
+
const instance = new Composition(
|
|
31
|
+
this.#scope,
|
|
32
|
+
this.#runtime,
|
|
33
|
+
this.#registry,
|
|
34
|
+
composition
|
|
35
|
+
)
|
|
35
36
|
|
|
36
37
|
instance.tag()
|
|
37
38
|
|
|
@@ -41,13 +42,28 @@ class Factory {
|
|
|
41
42
|
/**
|
|
42
43
|
* @returns {Service}
|
|
43
44
|
*/
|
|
44
|
-
service
|
|
45
|
-
const instance = new Service(
|
|
45
|
+
service(path, service) {
|
|
46
|
+
const instance = new Service(
|
|
47
|
+
this.#scope,
|
|
48
|
+
this.#runtime,
|
|
49
|
+
this.#registry,
|
|
50
|
+
path,
|
|
51
|
+
service
|
|
52
|
+
)
|
|
46
53
|
|
|
47
54
|
instance.tag()
|
|
48
55
|
|
|
49
56
|
return instance
|
|
50
57
|
}
|
|
51
|
-
}
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
/**
|
|
60
|
+
* @returns {Mono}
|
|
61
|
+
*/
|
|
62
|
+
mono(composition) {
|
|
63
|
+
const instance = new Mono(this.#scope, this.#runtime, this.#registry, composition)
|
|
64
|
+
|
|
65
|
+
instance.tag()
|
|
66
|
+
|
|
67
|
+
return instance
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { writeFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join, parse } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { NORMALIZED, plain } from '@toa.io/norm'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A component is copied into the image on its own, away from the package that
|
|
9
|
+
* declared what its files are. Node reads a `.js` as CommonJS unless a manifest
|
|
10
|
+
* beside it says otherwise, so the one the component was written under is
|
|
11
|
+
* restated where it lands.
|
|
12
|
+
*
|
|
13
|
+
* A component that ships its own manifest already says so, and is left alone.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} source the component in the workspace
|
|
16
|
+
* @param {string} target where it was copied
|
|
17
|
+
* @param {string} name
|
|
18
|
+
*/
|
|
19
|
+
export async function declare(source, target, name) {
|
|
20
|
+
if (existsSync(join(target, MANIFEST))) return
|
|
21
|
+
|
|
22
|
+
await writeFile(
|
|
23
|
+
join(target, MANIFEST),
|
|
24
|
+
JSON.stringify({ name, private: true, type: format(source) }, null, 2) + '\n'
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The manifest as this build read it, written beside the component so that the process which
|
|
30
|
+
* runs it does not read it again. See `plain` in `@toa.io/norm` for what is left out and why
|
|
31
|
+
* a build's answer is the one to trust.
|
|
32
|
+
*
|
|
33
|
+
* @param {toa.norm.Component} component
|
|
34
|
+
* @param {string} target where it was copied
|
|
35
|
+
*/
|
|
36
|
+
export async function normalized(component, target) {
|
|
37
|
+
await writeFile(join(target, NORMALIZED), JSON.stringify(plain(component)))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The module format a directory's files are read as, which the nearest manifest
|
|
42
|
+
* above it decides.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} directory
|
|
45
|
+
* @returns {'module' | 'commonjs'}
|
|
46
|
+
*/
|
|
47
|
+
export function format(directory) {
|
|
48
|
+
const { root } = parse(directory)
|
|
49
|
+
|
|
50
|
+
let current = directory
|
|
51
|
+
|
|
52
|
+
while (current !== root) {
|
|
53
|
+
const manifest = join(current, MANIFEST)
|
|
54
|
+
|
|
55
|
+
if (existsSync(manifest))
|
|
56
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).type === 'module'
|
|
57
|
+
? 'module'
|
|
58
|
+
: 'commonjs'
|
|
59
|
+
|
|
60
|
+
current = dirname(current)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return 'commonjs'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const MANIFEST = 'package.json'
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { it, describe, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { Locator } from '@toa.io/core'
|
|
8
|
+
import { revive } from '@toa.io/norm'
|
|
9
|
+
|
|
10
|
+
import { declare, format, normalized } from './format.js'
|
|
11
|
+
|
|
12
|
+
let root
|
|
13
|
+
|
|
14
|
+
before(async () => {
|
|
15
|
+
root = await mkdtemp(join(tmpdir(), 'format-'))
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
after(async () => {
|
|
19
|
+
await rm(root, { recursive: true, force: true })
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('format', () => {
|
|
23
|
+
it('should read the nearest manifest above', async () => {
|
|
24
|
+
const workspace = join(root, 'modules')
|
|
25
|
+
const component = join(workspace, 'components', 'one')
|
|
26
|
+
|
|
27
|
+
await mkdir(component, { recursive: true })
|
|
28
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
|
|
29
|
+
|
|
30
|
+
assert.strictEqual(format(component), 'module')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should default to commonjs', async () => {
|
|
34
|
+
const workspace = join(root, 'scripts')
|
|
35
|
+
const component = join(workspace, 'components', 'one')
|
|
36
|
+
|
|
37
|
+
await mkdir(component, { recursive: true })
|
|
38
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ name: 'scripts' }))
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(format(component), 'commonjs')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('declare', () => {
|
|
45
|
+
it('should state the format the component was written under', async () => {
|
|
46
|
+
const workspace = join(root, 'stated')
|
|
47
|
+
const source = join(workspace, 'components', 'one')
|
|
48
|
+
const target = join(root, 'image', 'one')
|
|
49
|
+
|
|
50
|
+
await mkdir(source, { recursive: true })
|
|
51
|
+
await mkdir(target, { recursive: true })
|
|
52
|
+
await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
|
|
53
|
+
|
|
54
|
+
await declare(source, target, 'default-one')
|
|
55
|
+
|
|
56
|
+
const manifest = JSON.parse(await readFile(join(target, 'package.json'), 'utf8'))
|
|
57
|
+
|
|
58
|
+
assert.strictEqual(manifest.type, 'module')
|
|
59
|
+
assert.strictEqual(manifest.name, 'default-one')
|
|
60
|
+
assert.strictEqual(manifest.private, true)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should leave a manifest the component ships alone', async () => {
|
|
64
|
+
const source = join(root, 'own', 'components', 'one')
|
|
65
|
+
const target = join(root, 'image', 'own')
|
|
66
|
+
|
|
67
|
+
await mkdir(source, { recursive: true })
|
|
68
|
+
await mkdir(target, { recursive: true })
|
|
69
|
+
|
|
70
|
+
const own = JSON.stringify({ name: 'own', dependencies: { matchacho: '0.6.0' } })
|
|
71
|
+
|
|
72
|
+
await writeFile(join(target, 'package.json'), own)
|
|
73
|
+
await declare(source, target, 'default-own')
|
|
74
|
+
|
|
75
|
+
assert.strictEqual(await readFile(join(target, 'package.json'), 'utf8'), own)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('normalized', () => {
|
|
80
|
+
it('should write what a process reads instead of normalizing', async () => {
|
|
81
|
+
const target = join(root, 'image', 'normalized')
|
|
82
|
+
|
|
83
|
+
await mkdir(target, { recursive: true })
|
|
84
|
+
|
|
85
|
+
const component = {
|
|
86
|
+
name: 'tasks',
|
|
87
|
+
namespace: 'todos',
|
|
88
|
+
version: 'abcdef12',
|
|
89
|
+
operations: { compute: { type: 'computation' } },
|
|
90
|
+
path: '/workspace/components/todos.tasks',
|
|
91
|
+
locator: new Locator('tasks', 'todos'),
|
|
92
|
+
packages: { cloudinary: '2.11.0' }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await normalized(component, target)
|
|
96
|
+
|
|
97
|
+
const carried = JSON.parse(await readFile(join(target, 'manifest.toa.json'), 'utf8'))
|
|
98
|
+
|
|
99
|
+
// where the manifest is, and what only a deploy reads, are not what it declares
|
|
100
|
+
assert.ok(!('path' in carried))
|
|
101
|
+
assert.ok(!('locator' in carried))
|
|
102
|
+
assert.ok(!('packages' in carried))
|
|
103
|
+
|
|
104
|
+
const read = revive(carried, '/composition/todos-tasks')
|
|
105
|
+
|
|
106
|
+
assert.deepStrictEqual(read.operations, component.operations)
|
|
107
|
+
assert.strictEqual(read.version, component.version)
|
|
108
|
+
assert.strictEqual(read.path, '/composition/todos-tasks')
|
|
109
|
+
assert.strictEqual(read.locator.id, 'todos.tasks')
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('a service image', () => {
|
|
114
|
+
it('installs what its components declare beside Toa as well', async () => {
|
|
115
|
+
const { Service } = await import('./service.js')
|
|
116
|
+
|
|
117
|
+
const path = join(root, 'extension')
|
|
118
|
+
const component = join(path, 'components', 'octets')
|
|
119
|
+
|
|
120
|
+
await mkdir(component, { recursive: true })
|
|
121
|
+
await writeFile(
|
|
122
|
+
join(path, 'package.json'),
|
|
123
|
+
JSON.stringify({ name: 'ext', version: '1' })
|
|
124
|
+
)
|
|
125
|
+
await writeFile(
|
|
126
|
+
join(component, 'package.json'),
|
|
127
|
+
JSON.stringify({ dependencies: { cloudinary: '2.11.0', jose: '6.2.10' } })
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const service = new Service('', { version: '1' }, {}, path, {
|
|
131
|
+
group: 'g',
|
|
132
|
+
name: 'n',
|
|
133
|
+
version: '1'
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
service.reference = 'probe'
|
|
137
|
+
|
|
138
|
+
const context = await service.prepare(join(root, 'service'))
|
|
139
|
+
|
|
140
|
+
// what an extension imports on a component's behalf is imported from where Toa is
|
|
141
|
+
assert.strictEqual(
|
|
142
|
+
await readFile(join(context, '.packages'), 'utf8'),
|
|
143
|
+
'cloudinary@2.11.0\njose@6.2.10'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
const dockerfile = await readFile(join(context, 'Dockerfile'), 'utf8')
|
|
147
|
+
|
|
148
|
+
assert.ok(dockerfile.includes('npm i --prefix /toa --omit=dev $(cat .packages)'))
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
import { Image } from './image.js'
|
|
2
|
+
import { generate } from 'randomstring'
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const version = '168b04ff'
|
|
7
|
-
const name = generate()
|
|
4
|
+
const reported = '168b04ff'
|
|
5
|
+
export const name = generate()
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* @implements {toa.deployment.images.Image}
|
|
11
9
|
*/
|
|
12
|
-
class Class extends Image {
|
|
13
|
-
get name
|
|
10
|
+
export class Class extends Image {
|
|
11
|
+
get name() {
|
|
14
12
|
return name
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
get version
|
|
18
|
-
return
|
|
15
|
+
get version() {
|
|
16
|
+
return reported
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
/** @type {toa.norm.context.Runtime} */
|
|
23
|
-
const runtime = {
|
|
21
|
+
export const runtime = {
|
|
24
22
|
version: '0.0.0'
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/** @type {toa.norm.context.Registry} */
|
|
28
|
-
const registry = {
|
|
26
|
+
export const registry = {
|
|
29
27
|
base: 'node:alpine'
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
export const scope = generate()
|
|
31
|
+
|
|
32
|
+
// distinct from the version `Class` reports
|
|
33
|
+
export const version = 'ba2409fc'
|
|
34
|
+
|
|
35
|
+
// the fixture stands in for the global process
|
|
36
|
+
export const process = globalThis.process
|