@synopackageland/cli 0.10.1 → 0.10.3
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 +219 -1669
- package/README.md +21 -2
- package/assets/login-style/mask.png +0 -0
- package/assets/login-style/wallpaper-thumbnail.jpg +0 -0
- package/assets/login-style/wallpaper.jpg +0 -0
- package/dist/build-spk/generator.js +10 -1
- package/dist/build-spk/login-style.d.ts +55 -0
- package/dist/build-spk/login-style.js +115 -0
- package/dist/cli.js +22 -1
- package/dist/error-registry.js +37 -0
- package/dist/host/dsm-client.js +6 -9
- package/dist/init.js +9 -5
- package/dist/runtime/delegate-contract.d.ts +2 -92
- package/dist/runtime/delegate-contract.js +2 -24
- package/dist/runtime/executor-build.js +10 -0
- package/dist/runtime/executor-digests.json +22 -0
- package/dist/runtime/executor-inspect.js +3 -0
- package/dist/runtime/executor-policy-check.d.ts +14 -0
- package/dist/runtime/executor-policy-check.js +70 -0
- package/dist/runtime/executor-policy.d.ts +2 -0
- package/dist/runtime/executor-policy.js +5 -1
- package/dist/runtime/root-programs.d.ts +2 -0
- package/dist/runtime/sdk-source.d.ts +4 -0
- package/dist/runtime/sdk-source.js +7 -0
- package/dist/runtime/sdk-timeouts.d.ts +23 -0
- package/dist/runtime/sdk-timeouts.js +44 -0
- package/dist/skill.js +1 -0
- package/dist/standalone/app-scripts.d.ts +26 -0
- package/dist/standalone/app-scripts.js +141 -0
- package/dist/standalone/bridge-app.d.ts +2 -0
- package/dist/standalone/bridge-app.js +9 -1
- package/dist/standalone/bridge-backend.d.ts +1 -1
- package/dist/standalone/build.js +5 -2
- package/dist/standalone/dsm-window.d.ts +14 -3
- package/dist/standalone/dsm-window.js +34 -12
- package/dist/standalone/host-adapter.d.ts +1 -1
- package/dist/standalone/host-adapter.js +1 -1
- package/dist/standalone/host-file-navigation.d.ts +6 -2
- package/dist/standalone/host-file-navigation.js +8 -2
- package/dist/standalone/inspect-spk.d.ts +9 -0
- package/dist/standalone/inspect-spk.js +60 -6
- package/dist/standalone/lifecycle.js +3 -1
- package/dist/standalone/manifest.d.ts +6 -0
- package/dist/standalone/manifest.js +51 -3
- package/dist/standalone/portal-window.d.ts +6 -6
- package/dist/standalone/portal-window.js +12 -32
- package/dist/standalone/request-timeout.d.ts +6 -3
- package/dist/standalone/request-timeout.js +10 -4
- package/dist/standalone/root-ops.d.ts +3 -2
- package/dist/standalone/root-ops.js +9 -3
- package/dist/standalone/root-window.d.ts +2 -2
- package/dist/standalone/root-window.js +173 -49
- package/dist/standalone/sdk-check.js +4 -1
- package/dist/standalone/spk.d.ts +3 -0
- package/dist/standalone/spk.js +10 -3
- package/dist/validate.js +6 -1
- package/package.json +2 -3
- package/skills/SKILL.md +3 -1
- package/skills/reference/create.md +5 -1
- package/skills/reference/identity.md +1 -1
- package/skills/reference/lifecycle.md +110 -0
- package/skills/reference/portal.md +12 -0
- package/dist/authorization-sdk.d.ts +0 -6
- package/dist/authorization-sdk.js +0 -6
- package/dist/standalone/native-authorization-window.d.ts +0 -2
- package/dist/standalone/native-authorization-window.js +0 -36
- package/dist/standalone/native-root-window.d.ts +0 -29
- package/dist/standalone/native-root-window.js +0 -307
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import { versionUnsupported } from "../runtime/dsm-version.js";
|
|
2
|
-
/**
|
|
3
|
-
* `createRootAccess(options)` → `{ state, authorize, run, remove }`, plain
|
|
4
|
-
* ES5 for DSM's desktop. Every DSM call goes through `options.send` (the
|
|
5
|
-
* AppWindow's sendWebAPI) as the signed-in administrator:
|
|
6
|
-
*
|
|
7
|
-
* - authorize: DSM's own password confirmation, then one disabled root task
|
|
8
|
-
* with exactly the embedded script. The one-use token goes into that single
|
|
9
|
-
* create call and is never kept.
|
|
10
|
-
* - run: re-reads the task and runs it only if it is still exactly ours (the
|
|
11
|
-
* first run after an authorize uses the task that authorize just read back), then
|
|
12
|
-
* waits for its history entry and asks the backend for the output. A queued
|
|
13
|
-
* or running task keeps the wait going, up to RUN_LIMIT_MS (loaded NAS took
|
|
14
|
-
* 4.5 minutes), and `options.progress({op, state, elapsedMs})` hears each poll. Repair
|
|
15
|
-
* additionally requires fresh PasswordConfirm and another exact task check.
|
|
16
|
-
* - an older version of our task is "outdated" and never runs; authorizing
|
|
17
|
-
* again replaces it. A same-named task that is not ours is a "conflict" and
|
|
18
|
-
* is never run, replaced or deleted.
|
|
19
|
-
*/
|
|
20
|
-
export const ROOT_RUNTIME = `function createRootAccess(options) {
|
|
21
|
-
var API = "SYNO.Core.TaskScheduler";
|
|
22
|
-
var POLL_MS = 500, MAX_POLL_MS = 1000, RUN_LIMIT_MS = 10 * 60 * 1000;
|
|
23
|
-
// The task an authorize just read back as exactly ours, for the run that follows.
|
|
24
|
-
var byOp = {}, running = {}, verified = {};
|
|
25
|
-
var legacy = null, probe = null;
|
|
26
|
-
var versionUnsupported = ${versionUnsupported.toString()};
|
|
27
|
-
options.tasks.forEach(function (task) { byOp[task.op] = task; });
|
|
28
|
-
|
|
29
|
-
function now() { return options.now ? options.now() : Date.now(); }
|
|
30
|
-
function report(task, state, started) {
|
|
31
|
-
if (typeof options.progress !== "function") return;
|
|
32
|
-
try { options.progress({ op: task.op, state: state, elapsedMs: now() - started }); } catch (error) {}
|
|
33
|
-
}
|
|
34
|
-
function pause(interval, deadline) {
|
|
35
|
-
return options.wait(Math.max(0, Math.min(interval, deadline - now())));
|
|
36
|
-
}
|
|
37
|
-
function fail(code) {
|
|
38
|
-
var error = new Error(code);
|
|
39
|
-
error.code = code;
|
|
40
|
-
return error;
|
|
41
|
-
}
|
|
42
|
-
function begin(op) {
|
|
43
|
-
if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail("root_unknown_op"));
|
|
44
|
-
if (options.user().admin !== true) return Promise.reject(fail("admin_required"));
|
|
45
|
-
return Promise.resolve(byOp[op]);
|
|
46
|
-
}
|
|
47
|
-
function readOutput(task) {
|
|
48
|
-
return task.output ? options.call("root.output", { op: task.op }) : Promise.resolve(null);
|
|
49
|
-
}
|
|
50
|
-
function isRootTask(summary) {
|
|
51
|
-
return summary.owner === "root" && summary.real_owner === "root" && summary.enable === false && summary.type === "script";
|
|
52
|
-
}
|
|
53
|
-
function listTasks() {
|
|
54
|
-
if (legacy === false) return options.send(API, "list", 3, { start: 0, limit: 500 });
|
|
55
|
-
if (legacy === true) return legacyList();
|
|
56
|
-
if (!probe) {
|
|
57
|
-
probe = options.send(API, "list", 3, { start: 0, limit: 500 }).then(function (data) {
|
|
58
|
-
legacy = false;
|
|
59
|
-
return data;
|
|
60
|
-
}, function (error) {
|
|
61
|
-
if (!error || !versionUnsupported(error.code)) {
|
|
62
|
-
probe = null;
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
legacy = true;
|
|
66
|
-
return legacyList();
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
return probe;
|
|
70
|
-
}
|
|
71
|
-
function legacyList() {
|
|
72
|
-
return options.send(API, "list", 2, { offset: 0, limit: 500 }).then(function (data) {
|
|
73
|
-
// Refuse a truncated snapshot: a same-named task could lie outside it.
|
|
74
|
-
if (!data || !Array.isArray(data.tasks) || data.tasks.length > 500
|
|
75
|
-
|| !(typeof data.total === "number" && isFinite(data.total) && Math.floor(data.total) === data.total) || data.total !== data.tasks.length) throw fail("root_bad_response");
|
|
76
|
-
return data;
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
function readTask(ref) {
|
|
80
|
-
return options.send(API, "get", legacy ? 3 : 4, ref);
|
|
81
|
-
}
|
|
82
|
-
function createTask(params) {
|
|
83
|
-
return options.send(legacy ? API : API + ".Root", "create", legacy ? 3 : 4, params);
|
|
84
|
-
}
|
|
85
|
-
function deleteTask(ref) {
|
|
86
|
-
return options.send(API, "delete", 2, { tasks: [ref] });
|
|
87
|
-
}
|
|
88
|
-
function runTask(ref) {
|
|
89
|
-
return options.send(API, "run", 2, { tasks: [ref] });
|
|
90
|
-
}
|
|
91
|
-
function find(task) {
|
|
92
|
-
return listTasks().then(function (data) {
|
|
93
|
-
if (!data || !Array.isArray(data.tasks)) throw fail("root_bad_response");
|
|
94
|
-
var named = data.tasks.filter(function (summary) { return summary && summary.name === task.name; });
|
|
95
|
-
if (named.length === 0) return { state: "missing" };
|
|
96
|
-
if (named.length > 1 || !isRootTask(named[0])) return { state: "conflict" };
|
|
97
|
-
var ref = { id: named[0].id, real_owner: named[0].real_owner };
|
|
98
|
-
return readTask(ref).then(function (detail) {
|
|
99
|
-
var script = detail && detail.extra && detail.extra.script;
|
|
100
|
-
if (!detail || String(detail.id) !== String(ref.id) || detail.name !== task.name
|
|
101
|
-
|| detail.owner !== "root" || detail.real_owner !== ref.real_owner || detail.enable !== false || detail.type !== "script" || typeof script !== "string") {
|
|
102
|
-
return { state: "conflict" };
|
|
103
|
-
}
|
|
104
|
-
if (script === task.script) return { state: "ready", ref: ref };
|
|
105
|
-
if (script.indexOf(task.header) === 0) return { state: "outdated", ref: ref };
|
|
106
|
-
return { state: "conflict" };
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function definition(task, user) {
|
|
111
|
-
var hours = [];
|
|
112
|
-
for (var h = 1; h <= 23; h++) hours.push(h);
|
|
113
|
-
return {
|
|
114
|
-
name: task.name,
|
|
115
|
-
real_owner: user,
|
|
116
|
-
owner: "root",
|
|
117
|
-
enable: false,
|
|
118
|
-
type: "script",
|
|
119
|
-
schedule: {
|
|
120
|
-
date_type: 0, week_day: "0,1,2,3,4,5,6", repeat_date: 1001, monthly_week: [], hour: 0, minute: 0,
|
|
121
|
-
repeat_hour: 0, repeat_min: 0, last_work_hour: 0,
|
|
122
|
-
repeat_min_store_config: [1, 5, 10, 15, 20, 30], repeat_hour_store_config: hours
|
|
123
|
-
},
|
|
124
|
-
extra: { notify_enable: false, script: task.script, notify_mail: "", notify_if_error: false }
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function newest(history) {
|
|
128
|
-
if (!Array.isArray(history)) throw fail("root_bad_response");
|
|
129
|
-
return history.reduce(function (latest, entry) {
|
|
130
|
-
return entry && (!latest || Number(entry.timestamp) > Number(latest.timestamp)) ? entry : latest;
|
|
131
|
-
}, null);
|
|
132
|
-
}
|
|
133
|
-
function history(ref) {
|
|
134
|
-
return options.send(API, "get_history_status_list", 1, { id: ref.id }).then(newest);
|
|
135
|
-
}
|
|
136
|
-
function waitForRun(task, ref, before, started, deadline, interval) {
|
|
137
|
-
return history(ref).then(function (latest) {
|
|
138
|
-
var fresh = latest && (!before || latest.timestamp !== before.timestamp);
|
|
139
|
-
if (fresh && latest.exit_type !== "running" && latest.stop_time) {
|
|
140
|
-
if (latest.exit_code !== 0) throw fail("root_task_failed");
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
report(task, fresh ? "running" : "queued", started);
|
|
144
|
-
if (now() >= deadline) throw fail("root_timeout");
|
|
145
|
-
return pause(interval, deadline).then(function () {
|
|
146
|
-
return waitForRun(task, ref, before, started, deadline, Math.min(interval * 2, MAX_POLL_MS));
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function validTime(value) {
|
|
152
|
-
if (typeof value !== "string" || !/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(value)) return false;
|
|
153
|
-
var normalized = value.replace(" ", "T") + "Z";
|
|
154
|
-
var date = new Date(normalized);
|
|
155
|
-
return !isNaN(date.getTime()) && date.toISOString().slice(0, 19) === normalized.slice(0, 19);
|
|
156
|
-
}
|
|
157
|
-
function bounded(call, deadline) {
|
|
158
|
-
// A poll count alone cannot bound a stalled DSM callback. Discard late replies.
|
|
159
|
-
return new Promise(function (resolve, reject) {
|
|
160
|
-
var remaining = deadline - now();
|
|
161
|
-
if (remaining <= 0) { reject(fail("root_timeout")); return; }
|
|
162
|
-
var timer = setTimeout(function () { reject(fail("root_timeout")); }, remaining);
|
|
163
|
-
Promise.resolve().then(call).then(function (value) {
|
|
164
|
-
clearTimeout(timer);
|
|
165
|
-
if (now() >= deadline) reject(fail("root_timeout"));
|
|
166
|
-
else resolve(value);
|
|
167
|
-
}, function (error) { clearTimeout(timer); reject(error); });
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
function legacyHistory(ref, deadline) {
|
|
171
|
-
return bounded(function () { return options.send(API, "view", 1, { id: ref.id }); }, deadline).then(function (records) {
|
|
172
|
-
// DSM 7.1 omits data entirely when a task has never run.
|
|
173
|
-
if (records === undefined) records = [];
|
|
174
|
-
if (!Array.isArray(records) || records.length > 500) throw fail("root_bad_response");
|
|
175
|
-
var seen = {};
|
|
176
|
-
records.forEach(function (record) {
|
|
177
|
-
if (!record || !validTime(record.start_time) || typeof record.stop_time !== "string"
|
|
178
|
-
|| (record.stop_time && (!validTime(record.stop_time) || record.stop_time < record.start_time))
|
|
179
|
-
|| typeof record.exit_type !== "string" || !(typeof record.exit_code === "number" && isFinite(record.exit_code) && Math.floor(record.exit_code) === record.exit_code)
|
|
180
|
-
|| seen[record.start_time]) throw fail("root_bad_response");
|
|
181
|
-
seen[record.start_time] = true;
|
|
182
|
-
});
|
|
183
|
-
return records;
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
function waitForLegacyRun(task, ref, before, started, deadline, interval) {
|
|
187
|
-
return legacyHistory(ref, deadline).then(function (records) {
|
|
188
|
-
var previous = before.map(function (record) { return record.start_time; });
|
|
189
|
-
var newestBefore = previous.slice().sort().pop();
|
|
190
|
-
var fresh = records.filter(function (record) { return previous.indexOf(record.start_time) === -1; });
|
|
191
|
-
if (fresh.length > 1) throw fail("root_bad_response");
|
|
192
|
-
var latest = fresh[0];
|
|
193
|
-
if (latest && (!newestBefore || latest.start_time > newestBefore) && latest.stop_time) {
|
|
194
|
-
if (latest.exit_code !== 0 || latest.exit_type !== "normal") throw fail("root_task_failed");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
report(task, latest ? "running" : "queued", started);
|
|
198
|
-
if (now() >= deadline) throw fail("root_timeout");
|
|
199
|
-
return pause(interval, deadline).then(function () {
|
|
200
|
-
return waitForLegacyRun(task, ref, before, started, deadline, Math.min(interval * 2, MAX_POLL_MS));
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function execute(task, verified) {
|
|
205
|
-
var started = now(), deadline = started + RUN_LIMIT_MS;
|
|
206
|
-
if (!legacy) {
|
|
207
|
-
return history(verified.ref).then(function (before) {
|
|
208
|
-
return runTask(verified.ref).then(function () { return waitForRun(task, verified.ref, before, started, deadline, POLL_MS); });
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return legacyHistory(verified.ref, deadline).then(function (before) {
|
|
212
|
-
if (before.some(function (record) { return !record.stop_time || record.exit_type === "running"; })) throw fail("root_not_ready");
|
|
213
|
-
// Mutating calls are sent exactly once, never retried with another profile.
|
|
214
|
-
return bounded(function () { return runTask(verified.ref); }, deadline).then(function () {
|
|
215
|
-
return waitForLegacyRun(task, verified.ref, before, started, deadline, POLL_MS);
|
|
216
|
-
});
|
|
217
|
-
}).then(function () {
|
|
218
|
-
return bounded(function () { return find(task); }, deadline).then(function (after) {
|
|
219
|
-
if (after.state !== "ready" || String(after.ref.id) !== String(verified.ref.id)) throw fail("root_verify_failed");
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
function verify(op) {
|
|
224
|
-
var ref = verified[op];
|
|
225
|
-
delete verified[op];
|
|
226
|
-
return ref && op !== "helper_repair" ? Promise.resolve({ state: "ready", ref: ref }) : find(byOp[op]);
|
|
227
|
-
}
|
|
228
|
-
function performRun(op) {
|
|
229
|
-
return begin(op).then(function (task) {
|
|
230
|
-
return verify(op).then(function (found) {
|
|
231
|
-
if (found.state !== "ready") throw fail("root_not_ready");
|
|
232
|
-
var consent = op === "helper_repair" ? options.confirmPassword() : Promise.resolve({ confirmed: true });
|
|
233
|
-
return consent.then(function (confirmation) {
|
|
234
|
-
if (!confirmation || confirmation.confirmed !== true) throw fail("root_cancelled");
|
|
235
|
-
return op === "helper_repair" ? begin(op).then(find) : found;
|
|
236
|
-
}).then(function (verified) {
|
|
237
|
-
if (verified.state !== "ready") throw fail("root_not_ready");
|
|
238
|
-
return execute(task, verified);
|
|
239
|
-
});
|
|
240
|
-
}).then(function () { return readOutput(task); });
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
state: function (op) {
|
|
246
|
-
return begin(op).then(find).then(function (found) { return found.state; });
|
|
247
|
-
},
|
|
248
|
-
authorize: function (op) {
|
|
249
|
-
delete verified[op];
|
|
250
|
-
return begin(op).then(function (task) {
|
|
251
|
-
return find(task).then(function (found) {
|
|
252
|
-
if (found.state === "ready") { verified[op] = found.ref; return "ready"; }
|
|
253
|
-
if (found.state === "conflict") throw fail("root_conflict");
|
|
254
|
-
return options.confirmPassword().then(function (confirmation) {
|
|
255
|
-
if (!confirmation || confirmation.confirmed !== true) return "cancelled";
|
|
256
|
-
if (!legacy && (typeof confirmation.token !== "string" || !confirmation.token)) throw fail("root_verify_failed");
|
|
257
|
-
var params = definition(task, options.user().name);
|
|
258
|
-
if (!legacy) params.SynoConfirmPWToken = confirmation.token;
|
|
259
|
-
// Re-read after the legacy client-side dialog before replacing a task.
|
|
260
|
-
var checked = legacy ? begin(op).then(find) : Promise.resolve(found);
|
|
261
|
-
return checked.then(function (current) {
|
|
262
|
-
if (current.state === "conflict") throw fail("root_conflict");
|
|
263
|
-
if (current.state === "ready") return;
|
|
264
|
-
var replaced = current.state === "outdated" ? deleteTask(current.ref) : Promise.resolve();
|
|
265
|
-
return replaced.then(function () { return createTask(params); });
|
|
266
|
-
}).then(function () {
|
|
267
|
-
return find(task);
|
|
268
|
-
}).then(function (after) {
|
|
269
|
-
if (after.state !== "ready") throw fail("root_verify_failed");
|
|
270
|
-
verified[op] = after.ref;
|
|
271
|
-
return "ready";
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
},
|
|
277
|
-
run: function (op) {
|
|
278
|
-
if (!Object.prototype.hasOwnProperty.call(byOp, op)) return Promise.reject(fail("root_unknown_op"));
|
|
279
|
-
if (options.user().admin !== true) return Promise.reject(fail("admin_required"));
|
|
280
|
-
if (running[op]) return running[op];
|
|
281
|
-
var pending = performRun(op).then(function (value) {
|
|
282
|
-
delete running[op];
|
|
283
|
-
return value;
|
|
284
|
-
}, function (error) { delete running[op]; throw error; });
|
|
285
|
-
running[op] = pending;
|
|
286
|
-
return pending;
|
|
287
|
-
},
|
|
288
|
-
remove: function (op) {
|
|
289
|
-
delete verified[op];
|
|
290
|
-
return begin(op).then(function (task) {
|
|
291
|
-
return find(task).then(function (found) {
|
|
292
|
-
if (found.state === "missing") return "missing";
|
|
293
|
-
if (found.state === "conflict") throw fail("root_conflict");
|
|
294
|
-
return deleteTask(found.ref).then(function () {
|
|
295
|
-
return find(task);
|
|
296
|
-
}).then(function (after) {
|
|
297
|
-
if (after.state !== "missing") throw fail("root_verify_failed");
|
|
298
|
-
return "missing";
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
},
|
|
303
|
-
output: function (op) {
|
|
304
|
-
return begin(op).then(readOutput);
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
}`;
|