mikro 0.18.1 → 0.18.2
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/README.md +1 -1
- package/dist/_exports/ota.d.ts +1 -1
- package/dist/_exports/ota.d.ts.map +1 -1
- package/dist/_exports/ota.js.map +1 -1
- package/dist/simulator/builtins/ota-client.d.ts.map +1 -1
- package/dist/simulator/builtins/ota-client.js +143 -8
- package/dist/simulator/builtins/ota-client.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ The core runtime is a standalone C++ library with no ESP-IDF dependencies. This
|
|
|
60
60
|
|
|
61
61
|
- **Engine**: [QuickJS-NG](https://github.com/quickjs-ng/quickjs) ([near-complete ES2024 coverage](https://test262.fyi/#|qjs_ng))
|
|
62
62
|
- **Runtime**: Standalone C++ library with cooperative, single-threaded event loop
|
|
63
|
-
- **Platform**: ESP-IDF >= 6.
|
|
63
|
+
- **Platform**: ESP-IDF >= 6.1 (ESP32, ESP32-C3, ESP32-S3, ESP32-C5, ESP32-C6), desktop (macOS, Linux)
|
|
64
64
|
- **Modules**: Four-source resolution: virtual modules, native C modules, bytecode builtins, filesystem (LittleFS)
|
|
65
65
|
- **Bytecode**: TypeScript bundled with esbuild, compiled to QuickJS bytecode at build time
|
|
66
66
|
- **Tooling**: Node.js CLI (Ink/React), Node-API addon for host-side development and simulation
|
package/dist/_exports/ota.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { ApplyOutcome, Diagnostic, DownloadFn, InstallOptions, InstallOutcome, Offer, Ota, OtaBeginError, OtaDownloadError, OtaError, OtaInstallError, OtaWriteError, RunningBuild, Update, } from '@mikrojs/native/runtime/ota/types';
|
|
1
|
+
export type { ApplyOutcome, ConfigErrorReport, ConfigState, ConfigWrite, Diagnostic, DownloadFn, InstallOptions, InstallOutcome, Offer, Ota, OtaBeginError, OtaDownloadError, OtaError, OtaInstallError, OtaWriteError, RunningBuild, StoredConfig, Update, } from '@mikrojs/native/runtime/ota/types';
|
|
2
2
|
export { ota } from '@mikrojs/native/runtime/ota/types';
|
|
3
3
|
//# sourceMappingURL=ota.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota.d.ts","sourceRoot":"","sources":["../../src/_exports/ota.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,KAAK,EACL,GAAG,EACH,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,aAAa,EACb,YAAY,EACZ,MAAM,GACP,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAA"}
|
|
1
|
+
{"version":3,"file":"ota.d.ts","sourceRoot":"","sources":["../../src/_exports/ota.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,KAAK,EACL,GAAG,EACH,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,MAAM,GACP,MAAM,mCAAmC,CAAA;AAC1C,OAAO,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAA"}
|
package/dist/_exports/ota.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota.js","sourceRoot":"","sources":["../../src/_exports/ota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ota.js","sourceRoot":"","sources":["../../src/_exports/ota.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota-client.d.ts","sourceRoot":"","sources":["../../../src/simulator/builtins/ota-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"ota-client.d.ts","sourceRoot":"","sources":["../../../src/simulator/builtins/ota-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,YAAY,CAAA;AAcjD,eAAO,MAAM,gBAAgB,EAAE,iBAuS9B,CAAA"}
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
// pretending to run a round. Everything else is simulated: the host has no app
|
|
9
9
|
// partition, but the staging -> finish -> reconcile -> confirm/revert sequence
|
|
10
10
|
// is the part an app's own update code drives, and it runs here in memory.
|
|
11
|
-
// `config()` serves whatever the session's mik.sys `ota.cfg` slot holds
|
|
11
|
+
// `config()` serves whatever the session's mik.sys `ota.cfg` slot holds, and
|
|
12
|
+
// `applyConfig()` is what puts a document there.
|
|
12
13
|
export const otaClientBuiltin = {
|
|
13
14
|
source: `
|
|
14
|
-
import {sysGet} from 'native:mikro/nvs_kv'
|
|
15
|
+
import {sysGet, sysRemove, sysSet} from 'native:mikro/nvs_kv'
|
|
15
16
|
import {readFile} from 'mikro/fs'
|
|
16
17
|
import {ok, err} from 'mikro/result'
|
|
17
18
|
|
|
@@ -38,6 +39,7 @@ export function config() {
|
|
|
38
39
|
const stored = sysGet('ota.cfg')
|
|
39
40
|
const doc = typeof stored === 'object' && stored !== null ? stored.doc : undefined
|
|
40
41
|
const hasDoc = typeof doc === 'object' && doc !== null
|
|
42
|
+
if (hasDoc) markConfigRead()
|
|
41
43
|
const defaults = manifestDefaults()
|
|
42
44
|
if (defaults === undefined) {
|
|
43
45
|
if (hasDoc) return doc
|
|
@@ -54,16 +56,31 @@ export function config() {
|
|
|
54
56
|
// materialized defaults is an app that declares no config schema, whose config
|
|
55
57
|
// is {}, not a failure. Mirrors the device reader (src/mik_ota_config.cpp).
|
|
56
58
|
function manifestDefaults() {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
+
const parsed = manifest()
|
|
60
|
+
if (parsed === undefined) return undefined
|
|
61
|
+
const held = parsed.configDefaults
|
|
62
|
+
return typeof held === 'object' && held !== null ? held : {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The running app's version, which is what decides where a delivered document
|
|
66
|
+
// belongs. The manifest carries the same value the device reads out of
|
|
67
|
+
// /app/package.json.
|
|
68
|
+
function manifestVersion() {
|
|
69
|
+
const parsed = manifest()
|
|
70
|
+
const version = parsed === undefined ? undefined : parsed.version
|
|
71
|
+
return typeof version === 'string' && version !== '' ? version : undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function manifest() {
|
|
75
|
+
const text = readFile('/app/mikro.app.json', 'utf-8')
|
|
76
|
+
if (!text.ok) return undefined
|
|
59
77
|
let parsed
|
|
60
78
|
try {
|
|
61
|
-
parsed = JSON.parse(
|
|
79
|
+
parsed = JSON.parse(text.value)
|
|
62
80
|
} catch {
|
|
63
81
|
return undefined
|
|
64
82
|
}
|
|
65
|
-
|
|
66
|
-
return typeof held === 'object' && held !== null ? held : {}
|
|
83
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : undefined
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
export function bearer() {
|
|
@@ -76,6 +93,113 @@ export function registry() {
|
|
|
76
93
|
return typeof url === 'string' && url !== '' ? url : undefined
|
|
77
94
|
}
|
|
78
95
|
|
|
96
|
+
// ── config delivery ──────────────────────────────────────────────────────────
|
|
97
|
+
// Mirrors src/mik_ota_config.cpp. A sim run is one boot, so a trial is armed
|
|
98
|
+
// and can be adopted here, but never burned down to a rollback: that needs the
|
|
99
|
+
// power cycles only a device has.
|
|
100
|
+
|
|
101
|
+
export function parseConfig(raw) {
|
|
102
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return undefined
|
|
103
|
+
const version = raw.version
|
|
104
|
+
// Bounded by the buffers the device stores them in. A truncated rev would
|
|
105
|
+
// never match the one the registry issued, so it is rejected, not cut.
|
|
106
|
+
if (typeof version !== 'string' || version === '' || version.length > 31) return undefined
|
|
107
|
+
const rev = raw.rev
|
|
108
|
+
const hasRev = rev !== undefined && rev !== null
|
|
109
|
+
if (hasRev && (typeof rev !== 'string' || rev.length > 64)) return undefined
|
|
110
|
+
const doc = raw.doc
|
|
111
|
+
const hasDoc = doc !== undefined && doc !== null
|
|
112
|
+
// The document is spread over the defaults top level: anything that is not a
|
|
113
|
+
// plain object has no keys to spread. An absent doc is the clear.
|
|
114
|
+
if (hasDoc && (typeof doc !== 'object' || Array.isArray(doc))) return undefined
|
|
115
|
+
const out = {version}
|
|
116
|
+
if (hasRev && rev !== '') out.rev = rev
|
|
117
|
+
if (hasDoc) out.doc = doc
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function applyConfig(config, options) {
|
|
122
|
+
const parsed = parseConfig(config)
|
|
123
|
+
if (parsed === undefined) return 'invalid'
|
|
124
|
+
const version = manifestVersion()
|
|
125
|
+
// Which slot the document belongs in is unknown without the running version,
|
|
126
|
+
// and storing it in either would be a guess the reader drops in silence.
|
|
127
|
+
if (version === undefined) return 'failed'
|
|
128
|
+
|
|
129
|
+
if (parsed.version !== version) {
|
|
130
|
+
// Stamped for another release: it applies with the build it names.
|
|
131
|
+
if (parsed.doc === undefined) sysRemove('ota.cfgNext')
|
|
132
|
+
else sysSet('ota.cfgNext', parsed)
|
|
133
|
+
return 'staged'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const held = sysGet('ota.cfg')
|
|
137
|
+
const heldCfg = typeof held === 'object' && held !== null ? held : undefined
|
|
138
|
+
|
|
139
|
+
if (parsed.doc === undefined) {
|
|
140
|
+
// A rev riding along does not turn a clear into a document.
|
|
141
|
+
sysRemove('ota.cfgTrial')
|
|
142
|
+
sysRemove('ota.cfgErr')
|
|
143
|
+
sysRemove('ota.cfgPrev')
|
|
144
|
+
if (heldCfg === undefined) return 'unchanged'
|
|
145
|
+
sysRemove('ota.cfg')
|
|
146
|
+
return 'cleared'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// An identical document must cost nothing: re-storing it would put a document
|
|
150
|
+
// that already passed its trial back on trial. The rev counts as part of it.
|
|
151
|
+
if (heldCfg !== undefined && sameConfig(heldCfg, parsed)) return 'unchanged'
|
|
152
|
+
|
|
153
|
+
// The document it replaces is the rollback baseline: a schema-valid value can
|
|
154
|
+
// still be fatal to the app.
|
|
155
|
+
if (heldCfg === undefined) sysRemove('ota.cfgPrev')
|
|
156
|
+
else sysSet('ota.cfgPrev', heldCfg)
|
|
157
|
+
sysSet('ota.cfg', parsed)
|
|
158
|
+
let trialBoots = 1
|
|
159
|
+
if (options !== undefined && typeof options.trialBoots === 'number' && options.trialBoots >= 1) {
|
|
160
|
+
trialBoots = options.trialBoots
|
|
161
|
+
}
|
|
162
|
+
sysSet('ota.cfgTrial', {left: trialBoots, read: false})
|
|
163
|
+
sysRemove('ota.cfgErr')
|
|
164
|
+
return 'applied'
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function configState() {
|
|
168
|
+
const out = {}
|
|
169
|
+
const report = sysGet('ota.cfgErr')
|
|
170
|
+
const hasError =
|
|
171
|
+
typeof report === 'object' && report !== null && typeof report.rev === 'string'
|
|
172
|
+
if (hasError) {
|
|
173
|
+
out.error = {
|
|
174
|
+
rev: report.rev,
|
|
175
|
+
message: typeof report.message === 'string' ? report.message : '',
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// After a rollback the FAILED document's rev is the one to echo, not the
|
|
179
|
+
// restored one's: that is what stops the registry serving it again.
|
|
180
|
+
const held = sysGet('ota.cfg')
|
|
181
|
+
const heldRev =
|
|
182
|
+
typeof held === 'object' && held !== null && typeof held.rev === 'string' ? held.rev : ''
|
|
183
|
+
const rev = hasError ? report.rev : heldRev
|
|
184
|
+
if (rev !== '') out.rev = rev
|
|
185
|
+
return out
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function sameConfig(held, next) {
|
|
189
|
+
const heldRev = typeof held.rev === 'string' ? held.rev : ''
|
|
190
|
+
const nextRev = typeof next.rev === 'string' ? next.rev : ''
|
|
191
|
+
if (heldRev !== nextRev || held.version !== next.version) return false
|
|
192
|
+
return JSON.stringify(held.doc) === JSON.stringify(next.doc)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// A document the app has read is a document the app ran with, which is what a
|
|
196
|
+
// later confirm() adopts on.
|
|
197
|
+
function markConfigRead() {
|
|
198
|
+
const trial = sysGet('ota.cfgTrial')
|
|
199
|
+
if (typeof trial !== 'object' || trial === null || trial.read === true) return
|
|
200
|
+
sysSet('ota.cfgTrial', {left: trial.left, read: true})
|
|
201
|
+
}
|
|
202
|
+
|
|
79
203
|
// ── the in-memory build state ────────────────────────────────────────────────
|
|
80
204
|
|
|
81
205
|
let current = {checksum: undefined, version: undefined, trial: false}
|
|
@@ -89,11 +213,22 @@ function install(checksum) {
|
|
|
89
213
|
}
|
|
90
214
|
|
|
91
215
|
export function running() {
|
|
92
|
-
|
|
216
|
+
// The version falls back to the manifest, the way mik__ota_policy_running
|
|
217
|
+
// falls back to read_app_version: an app needs it to stamp what it delivers.
|
|
218
|
+
const version = current.version === undefined ? manifestVersion() : current.version
|
|
219
|
+
return {checksum: current.checksum, version, trial: current.trial}
|
|
93
220
|
}
|
|
94
221
|
|
|
95
222
|
export function confirm() {
|
|
96
223
|
current = {checksum: current.checksum, version: current.version, trial: false}
|
|
224
|
+
// One confirm settles both trials, as on device (mik__ota_policy_confirm).
|
|
225
|
+
// The config trial waits additionally for the app to have read the document.
|
|
226
|
+
const trial = sysGet('ota.cfgTrial')
|
|
227
|
+
const armed = typeof trial === 'object' && trial !== null
|
|
228
|
+
if (!armed || trial.read === true) {
|
|
229
|
+
sysRemove('ota.cfgPrev')
|
|
230
|
+
sysRemove('ota.cfgTrial')
|
|
231
|
+
}
|
|
97
232
|
}
|
|
98
233
|
|
|
99
234
|
export function revert() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota-client.js","sourceRoot":"","sources":["../../../src/simulator/builtins/ota-client.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,6DAA6D;AAC7D,4EAA4E;AAC5E,gFAAgF;AAChF,yDAAyD;AACzD,EAAE;AACF,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"ota-client.js","sourceRoot":"","sources":["../../../src/simulator/builtins/ota-client.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,6DAA6D;AAC7D,4EAA4E;AAC5E,gFAAgF;AAChF,yDAAyD;AACzD,EAAE;AACF,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,iDAAiD;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqST;CACA,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mikro",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"description": "Mikro.js: TypeScript for microcontrollers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"embedded",
|
|
@@ -97,11 +97,11 @@
|
|
|
97
97
|
"tar": "^7.5.13",
|
|
98
98
|
"uint8array-extras": "^1.5.0",
|
|
99
99
|
"update-notifier": "^7.3.1",
|
|
100
|
-
"@mikrojs/analyze-imports": "0.18.
|
|
101
|
-
"@mikrojs/
|
|
102
|
-
"@mikrojs/
|
|
103
|
-
"@mikrojs/
|
|
104
|
-
"@mikrojs/
|
|
100
|
+
"@mikrojs/analyze-imports": "0.18.2",
|
|
101
|
+
"@mikrojs/firmware": "0.18.2",
|
|
102
|
+
"@mikrojs/native": "0.18.2",
|
|
103
|
+
"@mikrojs/quickjs": "0.18.2",
|
|
104
|
+
"@mikrojs/esptool": "0.18.2"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@swc/core": "^1.15.30",
|