@toa.io/norm 1.0.0-alpha.288 → 1.0.0-alpha.289
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 +11 -0
- package/package.json +5 -5
- package/src/.component/collapse.js +29 -3
- package/src/.component/schema.yaml +3 -0
- package/src/.context/normalize.js +3 -2
- package/src/component.js +7 -0
- package/src/context.js +2 -2
- package/test/component/collapse.test.js +39 -7
- package/test/context/normalize.test.js +9 -0
- package/types/component.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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.289](https://github.com/toa-io/toa/compare/v1.0.0-alpha.288...v1.0.0-alpha.289) (2026-09-07)
|
|
7
|
+
|
|
8
|
+
* A deploy moves only what changed, and a component sees none of the runtime's environment (#1073) ([f38e3db](https://github.com/toa-io/toa/commit/f38e3db533f24db866c57bfa1ef294eff1eaf499)), closes [#1073](https://github.com/toa-io/toa/issues/1073) [#1064](https://github.com/toa-io/toa/issues/1064) [#1066](https://github.com/toa-io/toa/issues/1066) [#1067](https://github.com/toa-io/toa/issues/1067) [#1068](https://github.com/toa-io/toa/issues/1068) [#1069](https://github.com/toa-io/toa/issues/1069) [#1071](https://github.com/toa-io/toa/issues/1071) [#1070](https://github.com/toa-io/toa/issues/1070) [#1072](https://github.com/toa-io/toa/issues/1072)
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* a component that read `process.env.TOA_*` reads `context` instead;
|
|
13
|
+
`echo(input)` no longer substitutes from the environment; a bash operation sees no
|
|
14
|
+
`TOA_*`; images no longer set `USER node` — see migrations/289.md.
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [1.0.0-alpha.288](https://github.com/toa-io/toa/compare/v1.0.0-alpha.287...v1.0.0-alpha.288) (2026-09-06)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @toa.io/norm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/norm",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.289",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Toa declarations normalization and validation",
|
|
6
6
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
25
|
-
"@toa.io/generic": "1.0.0-alpha.
|
|
26
|
-
"@toa.io/schemas": "1.0.0-alpha.
|
|
24
|
+
"@toa.io/core": "1.0.0-alpha.289",
|
|
25
|
+
"@toa.io/generic": "1.0.0-alpha.289",
|
|
26
|
+
"@toa.io/schemas": "1.0.0-alpha.289",
|
|
27
27
|
"fast-glob": "3.3.3",
|
|
28
28
|
"js-yaml": "5.4.1"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "4fcb482beac5896305d8084cdbc1d5fcf372d108"
|
|
31
31
|
}
|
|
@@ -62,12 +62,38 @@ export const collapse = (manifest, prototype) => {
|
|
|
62
62
|
for (const event of Object.keys(prototype.events))
|
|
63
63
|
if (event in manifest.events) delete prototype.events[event]
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
// to the component that declared it, so it is not collapsed into this one
|
|
67
|
-
if (entity?.migrations !== undefined) delete entity.migrations
|
|
65
|
+
if (entity?.migrations !== undefined) inherit(manifest, prototype)
|
|
68
66
|
|
|
69
67
|
merge(manifest, { entity, events, extensions })
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
/**
|
|
71
|
+
* What a prototype declares is in every record of its descendants, so its migrations are applied
|
|
72
|
+
* to each descendant's collection — ahead of the descendant's own, which are written against
|
|
73
|
+
* what they leave. An inherited one is recorded under the prototype's name, so that it collides
|
|
74
|
+
* with nothing the component names itself; one inherited from further up already carries its
|
|
75
|
+
* name and is passed on as it is.
|
|
76
|
+
*/
|
|
77
|
+
function inherit(manifest, prototype) {
|
|
78
|
+
const inherited = prototype.entity.migrations.map((migration) =>
|
|
79
|
+
migration.prototype === undefined
|
|
80
|
+
? {
|
|
81
|
+
...migration,
|
|
82
|
+
id: `${prototype.name}:${migration.id}`,
|
|
83
|
+
prototype: prototype.name
|
|
84
|
+
}
|
|
85
|
+
: migration
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const own = manifest.entity?.migrations ?? []
|
|
89
|
+
|
|
90
|
+
if (manifest.entity === undefined) manifest.entity = {}
|
|
91
|
+
|
|
92
|
+
manifest.entity.migrations = [...inherited, ...own]
|
|
93
|
+
|
|
94
|
+
// merged below by `merge`, which would append them a second time
|
|
95
|
+
delete prototype.entity.migrations
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
/** What the runtime writes into every record. `id` is not among them: a component may own it. */
|
|
73
99
|
const SYSTEM = ['VERSION', 'CREATED', 'UPDATED', 'DELETED']
|
|
@@ -100,6 +100,9 @@ properties:
|
|
|
100
100
|
# what a step means belongs to the storage, which is the only thing that reads it
|
|
101
101
|
steps:
|
|
102
102
|
type: array
|
|
103
|
+
# the prototype it is inherited from; absent on the component's own
|
|
104
|
+
prototype:
|
|
105
|
+
type: string
|
|
103
106
|
required: [id, steps]
|
|
104
107
|
additionalProperties: false
|
|
105
108
|
associated:
|
|
@@ -13,8 +13,9 @@ export const normalize = (context) => {
|
|
|
13
13
|
|
|
14
14
|
if (typeof context.registry === 'string') context.registry = { base: context.registry }
|
|
15
15
|
|
|
16
|
-
// what an image is built for, where the Context does not say
|
|
17
|
-
if (context.registry !== undefined
|
|
16
|
+
// what an image is built for, where the Context does not say; `~` says the runner's own
|
|
17
|
+
if (context.registry !== undefined && context.registry.platforms === undefined)
|
|
18
|
+
context.registry.platforms = PLATFORMS
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const PLATFORMS = ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
|
package/src/component.js
CHANGED
|
@@ -37,12 +37,19 @@ const load = async (path, base, proto = false) => {
|
|
|
37
37
|
|
|
38
38
|
manifest.path = path
|
|
39
39
|
|
|
40
|
+
const anonymous = manifest.name === undefined
|
|
41
|
+
|
|
40
42
|
defaults(manifest, proto)
|
|
41
43
|
await expand(manifest)
|
|
42
44
|
|
|
43
45
|
await merge(path, manifest)
|
|
44
46
|
await migrations(path, manifest)
|
|
45
47
|
|
|
48
|
+
// an inherited migration is recorded under the prototype's name, and a generated one is not
|
|
49
|
+
// the same on the next machine
|
|
50
|
+
if (proto && anonymous && manifest.entity?.migrations !== undefined)
|
|
51
|
+
throw new Error(`Prototype at '${path}' declares migrations, so it has to be named`)
|
|
52
|
+
|
|
46
53
|
if (manifest.prototype !== null) {
|
|
47
54
|
const prototype = await load(manifest.prototype, path, true)
|
|
48
55
|
|
package/src/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
|
-
import { convolve } from '@toa.io/generic'
|
|
2
|
+
import { convolve, environment as variables } from '@toa.io/generic'
|
|
3
3
|
import glob from 'fast-glob'
|
|
4
4
|
import { readFile } from 'node:fs/promises'
|
|
5
5
|
import { yaml as jsyaml } from '@toa.io/generic'
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
validate
|
|
16
16
|
} from './.context/index.js'
|
|
17
17
|
|
|
18
|
-
export const context = async (root, environment =
|
|
18
|
+
export const context = async (root, environment = variables.get('TOA_ENV')) => {
|
|
19
19
|
const path = resolve(root, CONTEXT)
|
|
20
20
|
const context = /** @type {toa.norm.Context} */ await read(path)
|
|
21
21
|
|
|
@@ -50,7 +50,10 @@ describe('entity', () => {
|
|
|
50
50
|
|
|
51
51
|
it('should refuse a component that states a property the runtime writes', () => {
|
|
52
52
|
const prototype = { entity: { properties: { DELETED: { type: 'integer' } } } }
|
|
53
|
-
const manifest = {
|
|
53
|
+
const manifest = {
|
|
54
|
+
name: 'pot',
|
|
55
|
+
entity: { properties: { DELETED: { type: 'integer' } } }
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
assert.throws(
|
|
56
59
|
() => collapse(manifest, prototype),
|
|
@@ -58,25 +61,54 @@ describe('entity', () => {
|
|
|
58
61
|
)
|
|
59
62
|
})
|
|
60
63
|
|
|
61
|
-
it('should
|
|
64
|
+
it('should inherit migrations ahead of its own, under the prototype name', () => {
|
|
62
65
|
const manifest = { entity: { migrations: [{ id: '0002', steps: [] }] } }
|
|
63
|
-
const prototype = {
|
|
66
|
+
const prototype = {
|
|
67
|
+
name: 'base',
|
|
68
|
+
entity: { migrations: [{ id: '0001', steps: [] }] }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
collapse(manifest, prototype)
|
|
72
|
+
|
|
73
|
+
assert.deepStrictEqual(manifest.entity.migrations, [
|
|
74
|
+
{ id: 'base:0001', steps: [], prototype: 'base' },
|
|
75
|
+
{ id: '0002', steps: [] }
|
|
76
|
+
])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should pass on what the prototype inherited as it is', () => {
|
|
80
|
+
const manifest = { entity: { migrations: [{ id: '0002', steps: [] }] } }
|
|
81
|
+
const prototype = {
|
|
82
|
+
name: 'base',
|
|
83
|
+
entity: {
|
|
84
|
+
migrations: [
|
|
85
|
+
{ id: 'system:0001', steps: [], prototype: 'system' },
|
|
86
|
+
{ id: '0001', steps: [] }
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
64
90
|
|
|
65
91
|
collapse(manifest, prototype)
|
|
66
92
|
|
|
67
93
|
assert.deepStrictEqual(
|
|
68
94
|
manifest.entity.migrations.map(({ id }) => id),
|
|
69
|
-
['0002']
|
|
95
|
+
['system:0001', 'base:0001', '0002']
|
|
70
96
|
)
|
|
71
97
|
})
|
|
72
98
|
|
|
73
|
-
it('should
|
|
99
|
+
it('should inherit migrations into a component that declares none', () => {
|
|
74
100
|
const manifest = {}
|
|
75
|
-
const prototype = {
|
|
101
|
+
const prototype = {
|
|
102
|
+
name: 'base',
|
|
103
|
+
entity: { migrations: [{ id: '0001', steps: [] }] }
|
|
104
|
+
}
|
|
76
105
|
|
|
77
106
|
collapse(manifest, prototype)
|
|
78
107
|
|
|
79
|
-
assert.
|
|
108
|
+
assert.deepStrictEqual(
|
|
109
|
+
manifest.entity.migrations.map(({ id }) => id),
|
|
110
|
+
['base:0001']
|
|
111
|
+
)
|
|
80
112
|
})
|
|
81
113
|
})
|
|
82
114
|
|
|
@@ -47,3 +47,12 @@ it('should set default platforms', () => {
|
|
|
47
47
|
'linux/arm64'
|
|
48
48
|
])
|
|
49
49
|
})
|
|
50
|
+
|
|
51
|
+
it('should keep platforms declared as none', () => {
|
|
52
|
+
// `platforms: ~` in the context: the runner's own platform, not the default list
|
|
53
|
+
context.registry = { base: generate(), platforms: null }
|
|
54
|
+
|
|
55
|
+
normalize(context)
|
|
56
|
+
|
|
57
|
+
assert.strictEqual(context.registry.platforms, null)
|
|
58
|
+
})
|