@synopackageland/cli 0.10.3 → 0.10.4
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
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.4 (2026-10-14)
|
|
4
|
+
|
|
5
|
+
Builds with SDK 0.10.4 and templates 0.10.4: `init` writes SDK v0.10.4 (Go
|
|
6
|
+
module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.4 and
|
|
7
|
+
`@synopackageland/ui` 0.9.6. `check` and `build` still accept SDK 0.10.3 or
|
|
8
|
+
later: nothing in this CLI needs 0.10.4, and an App on 0.10.3 never sees the
|
|
9
|
+
stop and start codes the window now repeats. The executor digest table adds
|
|
10
|
+
SDK 0.10.4, whose executor and lifecycle digests equal 0.10.3, so an App
|
|
11
|
+
rebuilt on it keeps its approval unless its own root programs change. The
|
|
12
|
+
gate refuses a stale SDK `ts/dist` and builds the executor flow's race binary
|
|
13
|
+
before its timeout.
|
|
14
|
+
|
|
15
|
+
繁中摘要:使用 SDK 0.10.4 與 templates 0.10.4(UI 0.9.6);`check`/`build` 仍接受 SDK
|
|
16
|
+
0.10.3 以上。`ctx.call` 自動重試套件停止/啟動中的碼;非管理員的
|
|
17
|
+
`ctx.executor.status()` 可讀到已核准的 executor。digest 表新增 SDK 0.10.4(與 0.10.3
|
|
18
|
+
相同),重建的 App 不需重新核准(除非自己的 root 程式改變)。
|
|
19
|
+
|
|
20
|
+
- `ctx.call` repeats a call the package refused with `APP_STOPPED`,
|
|
21
|
+
`APP_STARTING` or `EXECUTOR_STARTING` (the executor op was not sent): after
|
|
22
|
+
0.5 s, doubling to at most 8 s, for 30 s of waiting in all, so an App comes
|
|
23
|
+
back by itself once the package has started. Other codes reject at once; past
|
|
24
|
+
the budget the last refusal rejects; leaving the page cancels the wait.
|
|
25
|
+
繁中:套件停止/啟動中時 `ctx.call` 自動有界重試(最多等 30 s)。
|
|
26
|
+
- The window approval of an upgrade (outdated tasks) makes 11 scheduler calls
|
|
27
|
+
instead of 12: the outdated boot task is deleted by the approval's own first
|
|
28
|
+
EventScheduler get, made moments before the password, instead of a second
|
|
29
|
+
get (2.9 s on vt-nas in t-0484). The control task is still read again by id
|
|
30
|
+
before its delete (spec/scheduler/v1). A fresh approval stays at 8.
|
|
31
|
+
繁中:升級核准的排程呼叫由 12 降為 11(boot 任務不再於刪除前重讀)。
|
|
32
|
+
- A scope approval reads the executor's selection back every second at most
|
|
33
|
+
(it backed off to 2 s), so the window sees the landed scope within a second
|
|
34
|
+
of `scope.confirm` (the Dialog closed 4.8 s after it in t-0482). Removing
|
|
35
|
+
the temporary selection task still lists Task Scheduler first, as
|
|
36
|
+
spec/scheduler/v1 requires of a delete.
|
|
37
|
+
繁中:範圍核准讀回間隔上限 2 s → 1 s,confirmed 後 1 s 內看到結果。
|
|
38
|
+
- `ctx.executor.status()` for a user who is not an administrator asks the
|
|
39
|
+
App's `executor.status` (open to every signed-in user) instead of refusing
|
|
40
|
+
before any request: the pinned, running executor reads `ready`; anything
|
|
41
|
+
else stays `blocked` with `admin_required`. Task Scheduler is still read only
|
|
42
|
+
for administrators. A user no longer sees an approved executor as waiting
|
|
43
|
+
for approval (t-0482 F7).
|
|
44
|
+
繁中:非管理員的 `ctx.executor.status()` 會向 App 的 `executor.status` 詢問,已核准且
|
|
45
|
+
執行中即為 `ready`,不再一律回 `admin_required`。
|
|
46
|
+
|
|
3
47
|
## 0.10.3 (2026-10-14)
|
|
4
48
|
|
|
5
49
|
Builds with SDK 0.10.3 and templates 0.10.3: `init` writes SDK v0.10.3 (Go
|
|
@@ -42,5 +42,16 @@
|
|
|
42
42
|
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
43
43
|
},
|
|
44
44
|
"goToolchain": "go1.26.9"
|
|
45
|
+
},
|
|
46
|
+
"0.10.4": {
|
|
47
|
+
"x86_64": "4ba7040761a719e74d6b047ef68b9415e658ebb8b373b69555b8c307524e26ba",
|
|
48
|
+
"aarch64": "ba80b389e3291419389b66be7b0a0e1085d3dfec55c9c651fecae427279bb3d5",
|
|
49
|
+
"armv7": "49474467b9c9a7f31b30b5053b70eb4f71a95f2c06fbf3566e8de8f16972a299",
|
|
50
|
+
"lifecycle": {
|
|
51
|
+
"x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
|
|
52
|
+
"aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
|
|
53
|
+
"armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
|
|
54
|
+
},
|
|
55
|
+
"goToolchain": "go1.26.9"
|
|
45
56
|
}
|
|
46
57
|
}
|
|
@@ -144,7 +144,7 @@ Ext.ns(${JSON.stringify(ns)});
|
|
|
144
144
|
// The SDK's clientTimeout: past the backend's request deadline
|
|
145
145
|
// (ctx.requestTimeout, from backend.timeout_seconds), so its request_timeout
|
|
146
146
|
// reply arrives first. options.signal aborts the fetch, for a page the user leaves.
|
|
147
|
-
${ns}.
|
|
147
|
+
${ns}.callOnce = function (method, params, options, requestTimeout) {
|
|
148
148
|
var signal = options && options.signal;
|
|
149
149
|
var canceled = { code: "request_canceled", message: "The request was canceled" };
|
|
150
150
|
if (signal && signal.aborted) {
|
|
@@ -191,6 +191,39 @@ ${ns}.call = function (method, params, options, requestTimeout) {
|
|
|
191
191
|
});
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
+
// APP_STOPPED, APP_STARTING and EXECUTOR_STARTING mean the executor op was not
|
|
195
|
+
// sent: the package is stopped, or starting. The call repeats on its own, half a
|
|
196
|
+
// second, then doubling up to 8 s, for 30 s of waiting in all; each attempt keeps
|
|
197
|
+
// its own deadline. Past that it rejects with the last refusal, and leaving the
|
|
198
|
+
// page during a wait rejects request_canceled at once.
|
|
199
|
+
${ns}.retryCodes = ["APP_STOPPED", "APP_STARTING", "EXECUTOR_STARTING"];
|
|
200
|
+
${ns}.call = function (method, params, options, requestTimeout) {
|
|
201
|
+
var signal = options && options.signal;
|
|
202
|
+
var left = 30000;
|
|
203
|
+
var delay = 500;
|
|
204
|
+
var attempt = function () {
|
|
205
|
+
return ${ns}.callOnce(method, params, options, requestTimeout).catch(function (error) {
|
|
206
|
+
if (left <= 0 || !error || ${ns}.retryCodes.indexOf(error.code) < 0) throw error;
|
|
207
|
+
var wait = Math.min(delay, left);
|
|
208
|
+
left -= wait;
|
|
209
|
+
delay = Math.min(delay * 2, 8000);
|
|
210
|
+
return new Promise(function (resolve, reject) {
|
|
211
|
+
var leave = function () {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
reject({ code: "request_canceled", message: "The request was canceled" });
|
|
214
|
+
};
|
|
215
|
+
var timer = setTimeout(function () {
|
|
216
|
+
if (signal) signal.removeEventListener("abort", leave);
|
|
217
|
+
resolve(attempt());
|
|
218
|
+
}, wait);
|
|
219
|
+
if (signal && signal.aborted) leave();
|
|
220
|
+
else if (signal) signal.addEventListener("abort", leave);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
return attempt();
|
|
225
|
+
};
|
|
226
|
+
|
|
194
227
|
${ns}.messages = ${JSON.stringify(manifest.i18n?.catalogs ?? { en: {} })};
|
|
195
228
|
${ns}.createI18n = (function () {
|
|
196
229
|
${I18N_RUNTIME}
|
|
@@ -58,7 +58,8 @@ export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
|
58
58
|
var boot = task.kind === "boot";
|
|
59
59
|
return send(boot ? eventAPI : taskAPI, "delete", boot ? 1 : 2, boot ? ref : {tasks: [ref]});
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function isAdmin() { return !!options.user() && options.user().admin === true; }
|
|
62
|
+
function admin() { if (!isAdmin()) throw fail("admin_required"); }
|
|
62
63
|
function missing() { return {state: "needs-admin-approval", reason: "Task is missing.", code: "task_missing"}; }
|
|
63
64
|
// EventScheduler keys a task by its name (DSM refuses a second one with 117
|
|
64
65
|
// DUPLICATE_PRIMARY_KEY) and get answers no data for a missing name, so get
|
|
@@ -155,16 +156,19 @@ export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
|
155
156
|
var errors = {}, created = [], removed = [], withdrawFailed;
|
|
156
157
|
// Delete only a verified, same-header outdated task, then create the pinned
|
|
157
158
|
// replacement. set has no measured wire contract, and a same-named task
|
|
158
|
-
// cannot be created first.
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
159
|
+
// cannot be created first. The boot task keeps this approval's first
|
|
160
|
+
// read: EventScheduler get by name is the whole lookup, it was made in
|
|
161
|
+
// this step moments ago, and delete and create address the name. A
|
|
162
|
+
// control task to delete is read again by the id that read listed
|
|
163
|
+
// (spec/scheduler/v1); a missing one keeps the first read: DSM refuses a
|
|
164
|
+
// second boot task name, and the readback after the writes lists control
|
|
165
|
+
// tasks for duplicates. Stop at the first error.
|
|
162
166
|
var chain = Promise.resolve();
|
|
163
167
|
options.tasks.forEach(function (task) {
|
|
164
168
|
chain = chain.then(function () {
|
|
165
169
|
if (Object.keys(errors).length) return;
|
|
166
170
|
var first = before.tasks[task.kind];
|
|
167
|
-
return (first.ref ? find(task, true) : Promise.resolve(first)).then(function (current) {
|
|
171
|
+
return (first.ref && task.kind !== "boot" ? find(task, true) : Promise.resolve(first)).then(function (current) {
|
|
168
172
|
if (current.state === "ready") return;
|
|
169
173
|
if (current.state === "blocked") throw refused(current);
|
|
170
174
|
var deleted = current.ref ? remove(task, current.ref).then(function () { removed.push({kind: task.kind, name: task.name, header: task.header, script: current.script}); }) : Promise.resolve();
|
|
@@ -227,9 +231,10 @@ export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
|
227
231
|
}
|
|
228
232
|
// The task waits up to 35 s for the executor, whose account, AppPriv and
|
|
229
233
|
// share lookups took 16-20 s on a measured NAS: read back for 40 s,
|
|
230
|
-
// backing off from 500 ms to
|
|
234
|
+
// backing off from 500 ms to 1 s, so the window closes within a second
|
|
235
|
+
// of the selection landing.
|
|
231
236
|
function readSelection(budget, delay) {
|
|
232
|
-
delay = Math.min(delay || 500,
|
|
237
|
+
delay = Math.min(delay || 500, 1000);
|
|
233
238
|
verified.state = "ready";
|
|
234
239
|
return health(verified, 0).then(function (current) {
|
|
235
240
|
if (current.state === "ready" && current.executor.selection && current.executor.selection.scope === selectedScope && Number.isSafeInteger(current.executor.selection.generation) && current.executor.selection.generation > 0) return current;
|
|
@@ -323,8 +328,17 @@ export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
|
323
328
|
// result reaches every view so neither shows a stale state.
|
|
324
329
|
var listeners = [];
|
|
325
330
|
function publish(result) { listeners.forEach(function (listener) { listener(result); }); return result; }
|
|
331
|
+
// TaskScheduler answers administrators only. Anyone else reads the approved
|
|
332
|
+
// executor through the App's executor.status, which answers every user: the
|
|
333
|
+
// pinned, running executor is ready; otherwise an administrator is needed.
|
|
334
|
+
function userStatus() {
|
|
335
|
+
var needed = block({tasks: {}}, fail("admin_required"));
|
|
336
|
+
return options.call("executor.status", {}).then(function (status) {
|
|
337
|
+
return pinned(status) ? {state: "ready", tasks: {}, executor: status} : needed;
|
|
338
|
+
}, function () { return needed; });
|
|
339
|
+
}
|
|
326
340
|
return {
|
|
327
|
-
status: function () { return read().then(function (verified) { return health(verified, 0); }).then(publish); },
|
|
341
|
+
status: function () { return (isAdmin() ? read().then(function (verified) { return health(verified, 0); }) : userStatus()).then(publish); },
|
|
328
342
|
authorize: function (password, selectedScope) {
|
|
329
343
|
if (!busy) busy = Promise.resolve().then(function () { return approve(password, selectedScope); }).catch(function (e) { var r = block({tasks: {}}, e); r.retry = !!(e && e.passwordConfirm); return r; }).then(function (r) { busy = null; return publish(r); });
|
|
330
344
|
return busy;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Syno Package Land Developer Kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "rm -rf dist && tsc",
|
|
21
21
|
"test": "npm run build && node --import tsx --test test/**/*.test.ts",
|
|
22
|
-
"test:scripts": "node --test scripts/test/gate.test.mjs scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs scripts/test/smoke-exports.test.mjs scripts/test/executor-digests.test.mjs",
|
|
23
|
-
"gates": "bash scripts/ci/
|
|
22
|
+
"test:scripts": "node --test scripts/test/gate.test.mjs scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs scripts/test/smoke-exports.test.mjs scripts/test/executor-digests.test.mjs scripts/test/parallel.test.mjs scripts/test/scaffold-matrix.test.mjs scripts/test/frontend-deps.test.mjs scripts/test/docker-gate.test.mjs scripts/test/gate-plan.test.mjs scripts/test/gates.test.mjs",
|
|
23
|
+
"gates": "bash scripts/ci/gates.sh",
|
|
24
|
+
"gates:fast": "bash scripts/ci/gates.sh --fast",
|
|
24
25
|
"prepublishOnly": "npm run build"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@synopackageland/templates": "0.10.
|
|
28
|
+
"@synopackageland/templates": "0.10.4",
|
|
28
29
|
"ajv": "^8.17.1",
|
|
29
30
|
"commander": "^13.1.0",
|
|
30
31
|
"tar": "^7.4.3",
|