@swipt/cli 0.1.0-nightly.38 → 0.1.0-nightly.40
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/connection.mjs +30 -22
- package/device.mjs +179 -180
- package/output.mjs +5 -5
- package/package.json +2 -3
- package/project.mjs +8 -5
- package/proto/repl_pb.js +1 -1
- package/proto/system_pb.js +1 -1
- package/swipt.mjs +141 -97
- package/sourcemap.mjs +0 -77
package/connection.mjs
CHANGED
|
@@ -7,7 +7,8 @@ import { setTimeout as sleep } from "node:timers/promises";
|
|
|
7
7
|
import Bonjour from "bonjour-service";
|
|
8
8
|
|
|
9
9
|
import { Code, RpcError, UsageError, open } from "./client.mjs";
|
|
10
|
-
import { columns } from "./output.mjs";
|
|
10
|
+
import { columns, enumText } from "./output.mjs";
|
|
11
|
+
import { DeviceConnectionProblem, DeviceConnectionProblemSchema, DeviceConnectionStateSchema } from "./proto/common_pb.js";
|
|
11
12
|
|
|
12
13
|
const browseLimit = 1500;
|
|
13
14
|
const browseRequery = 300;
|
|
@@ -128,7 +129,7 @@ async function discover(signal, stored, until) {
|
|
|
128
129
|
return found;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
const requiresToken = found => `${found.name} requires a token: swipt
|
|
132
|
+
const requiresToken = found => `${found.name} requires a token: swipt device add ${withToken(found.url)}`;
|
|
132
133
|
|
|
133
134
|
export async function attach(context) {
|
|
134
135
|
const given = context.options.device ?? process.env.SWIPT_DEVICE;
|
|
@@ -142,7 +143,7 @@ export async function attach(context) {
|
|
|
142
143
|
if (known === undefined) {
|
|
143
144
|
const found = (await discover(context.signal, stored, services => services.some(it => sameName(it.name, name)))).find(it => sameName(it.name, name));
|
|
144
145
|
if (!found) {
|
|
145
|
-
throw new Error(`there is no device ${name}; "swipt
|
|
146
|
+
throw new Error(`there is no device ${name}; "swipt device list" lists them`);
|
|
146
147
|
}
|
|
147
148
|
if (found.authenticates) {
|
|
148
149
|
throw new Error(requiresToken(found));
|
|
@@ -161,7 +162,7 @@ export async function attach(context) {
|
|
|
161
162
|
if (locked.length > 0) {
|
|
162
163
|
throw new Error(locked.map(requiresToken).join("\n"));
|
|
163
164
|
}
|
|
164
|
-
throw new Error('there is no device; turn on Swipt › Developer › Local Network, or connect one with "swipt
|
|
165
|
+
throw new Error('there is no device; turn on Swipt › Developer › Local Network, or connect one with "swipt device add URL"');
|
|
165
166
|
}
|
|
166
167
|
if (names.length > 1) {
|
|
167
168
|
throw new Error(`there is more than one device: ${names.join(", ")}; choose one with -d or SWIPT_DEVICE`);
|
|
@@ -172,35 +173,42 @@ export async function attach(context) {
|
|
|
172
173
|
return open(device);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
function connectionText(connection) {
|
|
177
|
+
let text = enumText(DeviceConnectionStateSchema, connection?.state ?? 0, "DEVICE_CONNECTION_STATE_");
|
|
178
|
+
if (connection && connection.problem !== DeviceConnectionProblem.UNSPECIFIED) {
|
|
179
|
+
text += `, ${enumText(DeviceConnectionProblemSchema, connection.problem, "DEVICE_CONNECTION_PROBLEM_")}`;
|
|
180
|
+
}
|
|
181
|
+
if (connection?.message) {
|
|
182
|
+
text += `: ${connection.message}`;
|
|
183
|
+
}
|
|
184
|
+
return text;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function probe(url, signal) {
|
|
176
188
|
try {
|
|
177
|
-
await open(
|
|
178
|
-
return
|
|
189
|
+
const info = await open(parseURL(url)).system.getInfo({}, { signal });
|
|
190
|
+
return [`${info.appVersion} (${info.appBuild})`, connectionText(info.deviceConnection)];
|
|
179
191
|
} catch (error) {
|
|
180
|
-
return error instanceof RpcError && error.code === Code.Unauthenticated;
|
|
192
|
+
return [error instanceof RpcError && error.code === Code.Unauthenticated ? "requires a token" : "offline"];
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
|
|
184
|
-
export async function
|
|
196
|
+
export async function list({ signal }) {
|
|
185
197
|
const stored = load();
|
|
186
198
|
const deadline = AbortSignal.any([signal, AbortSignal.timeout(browseLimit)]);
|
|
187
|
-
const probes = new Map(Object.
|
|
188
|
-
|
|
199
|
+
const probes = new Map(Object.values(stored).map(url => [url, probe(url, deadline)]));
|
|
200
|
+
await discover(signal, stored);
|
|
189
201
|
const names = Object.keys(stored).sort((a, b) => a.localeCompare(b));
|
|
190
202
|
if (names.length === 0) {
|
|
191
|
-
console.error('no device publishes Swipt on the local network; turn on Swipt › Developer › Local Network, or connect one with "swipt
|
|
203
|
+
console.error('no device publishes Swipt on the local network; turn on Swipt › Developer › Local Network, or connect one with "swipt device add URL"');
|
|
192
204
|
return;
|
|
193
205
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
const { url } = parseURL(stored[name]);
|
|
197
|
-
const online = found.some(it => sameName(it.name, name) && it.url === url) || (probes.has(name) && (await probes.get(name)));
|
|
198
|
-
rows.push([name, url, online ? "" : "offline"]);
|
|
199
|
-
}
|
|
206
|
+
const late = AbortSignal.any([signal, AbortSignal.timeout(browseLimit)]);
|
|
207
|
+
const rows = await Promise.all(names.map(async name => [name, parseURL(stored[name]).url, ...(await (probes.get(stored[name]) ?? probe(stored[name], late)))]));
|
|
200
208
|
console.log(columns(rows));
|
|
201
209
|
}
|
|
202
210
|
|
|
203
|
-
export async function
|
|
211
|
+
export async function add(context) {
|
|
204
212
|
const device = parseURL(context.args[0]);
|
|
205
213
|
context.target.url = device.url;
|
|
206
214
|
const info = await open(device).system.getInfo({}, { signal: context.signal });
|
|
@@ -212,10 +220,10 @@ export async function connect(context) {
|
|
|
212
220
|
}
|
|
213
221
|
stored[name] = context.args[0];
|
|
214
222
|
save(stored);
|
|
215
|
-
console.log("
|
|
223
|
+
console.log("added", name);
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
export function
|
|
226
|
+
export function remove({ args }) {
|
|
219
227
|
const stored = load();
|
|
220
228
|
const name = storedName(stored, label(args[0]));
|
|
221
229
|
if (name === undefined) {
|
|
@@ -223,5 +231,5 @@ export function disconnect({ args }) {
|
|
|
223
231
|
}
|
|
224
232
|
delete stored[name];
|
|
225
233
|
save(stored);
|
|
226
|
-
console.log("
|
|
234
|
+
console.log("removed", name);
|
|
227
235
|
}
|
package/device.mjs
CHANGED
|
@@ -13,9 +13,8 @@ import { attach } from "./connection.mjs";
|
|
|
13
13
|
import { Ignorer, relativePath } from "./ignore.mjs";
|
|
14
14
|
import { columns, dateTime, enumText, printLog, printScriptError, serviceText } from "./output.mjs";
|
|
15
15
|
import { buildProject, loadProject } from "./project.mjs";
|
|
16
|
-
import {
|
|
16
|
+
import { LogLevel } from "./proto/common_pb.js";
|
|
17
17
|
import { ServiceStatus, ServiceStatusSchema } from "./proto/swiplets_pb.js";
|
|
18
|
-
import { sources } from "./sourcemap.mjs";
|
|
19
18
|
|
|
20
19
|
const uploadChunk = 256 << 10;
|
|
21
20
|
const rebuildDelay = 300;
|
|
@@ -45,27 +44,6 @@ export function printVersion() {
|
|
|
45
44
|
console.log("swipt", version, `node ${process.versions.node}`, `${process.platform}/${process.arch}`);
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
export const info = context =>
|
|
49
|
-
session(context, async client => {
|
|
50
|
-
const found = await client.system.getInfo({}, { signal: context.signal });
|
|
51
|
-
const connection = found.deviceConnection;
|
|
52
|
-
let state = enumText(DeviceConnectionStateSchema, connection?.state ?? 0, "DEVICE_CONNECTION_STATE_");
|
|
53
|
-
if (connection && connection.problem !== DeviceConnectionProblem.UNSPECIFIED) {
|
|
54
|
-
state += `, ${enumText(DeviceConnectionProblemSchema, connection.problem, "DEVICE_CONNECTION_PROBLEM_")}`;
|
|
55
|
-
}
|
|
56
|
-
if (connection?.message) {
|
|
57
|
-
state += `: ${connection.message}`;
|
|
58
|
-
}
|
|
59
|
-
console.log(
|
|
60
|
-
columns([
|
|
61
|
-
["device:", found.deviceName],
|
|
62
|
-
["udid:", found.udid],
|
|
63
|
-
["server:", found.serverVersion],
|
|
64
|
-
["connection:", state],
|
|
65
|
-
]),
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
47
|
export const logs = context =>
|
|
70
48
|
session(context, async client => {
|
|
71
49
|
const { options } = context;
|
|
@@ -111,25 +89,27 @@ const serviceStatus = state => (state.status === ServiceStatus.UNSPECIFIED ? "st
|
|
|
111
89
|
|
|
112
90
|
const notInstalled = id => new Error(`there is no swiplet ${JSON.stringify(id)}`);
|
|
113
91
|
|
|
114
|
-
export const
|
|
92
|
+
export const list = context =>
|
|
115
93
|
session(context, async client => {
|
|
116
94
|
const { swiplets } = await client.swiplets.listSwiplets({}, { signal: context.signal });
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
status += `: ${swiplet.service.error.name}: ${swiplet.service.error.message}`;
|
|
126
|
-
}
|
|
95
|
+
const rows = swiplets.map(swiplet => {
|
|
96
|
+
let service = "";
|
|
97
|
+
let status = "";
|
|
98
|
+
if (swiplet.service) {
|
|
99
|
+
service = swiplet.service.enabled ? "enabled" : "disabled";
|
|
100
|
+
status = serviceStatus(swiplet.service);
|
|
101
|
+
if (swiplet.service.error) {
|
|
102
|
+
status += `: ${swiplet.service.error.name}: ${swiplet.service.error.message}`;
|
|
127
103
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
104
|
+
}
|
|
105
|
+
return [swiplet.id, swiplet.title, swiplet.version, swiplet.actions.map(action => action.name).join(","), service, status];
|
|
106
|
+
});
|
|
107
|
+
console.log(columns([["ID", "TITLE", "VERSION", "ACTIONS", "SERVICE", "STATUS"], ...rows]));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const show = context =>
|
|
111
|
+
session(context, async client => {
|
|
112
|
+
const { swiplets } = await client.swiplets.listSwiplets({}, { signal: context.signal });
|
|
133
113
|
const swiplet = swiplets.find(it => it.id === context.args[0]);
|
|
134
114
|
if (!swiplet) {
|
|
135
115
|
throw notInstalled(context.args[0]);
|
|
@@ -139,7 +119,7 @@ export const swiplets = context =>
|
|
|
139
119
|
console.log("ui: yes");
|
|
140
120
|
}
|
|
141
121
|
if (swiplet.service) {
|
|
142
|
-
console.log(`service: ${serviceText(swiplet.id, swiplet.service
|
|
122
|
+
console.log(`service: ${serviceText(swiplet.id, swiplet.service)}`);
|
|
143
123
|
}
|
|
144
124
|
for (const action of swiplet.actions) {
|
|
145
125
|
console.log();
|
|
@@ -173,14 +153,14 @@ export const call = context =>
|
|
|
173
153
|
return;
|
|
174
154
|
}
|
|
175
155
|
if (result?.outcome.case === "error") {
|
|
176
|
-
throw printScriptError(result.outcome.value
|
|
156
|
+
throw printScriptError(result.outcome.value);
|
|
177
157
|
}
|
|
178
158
|
throw new Error("the server sent no result");
|
|
179
159
|
});
|
|
180
160
|
|
|
181
161
|
async function watchServices(client, id, signal) {
|
|
182
162
|
for await (const response of client.swiplets.watchServices({ swipletId: id }, { signal })) {
|
|
183
|
-
console.log(`${response.swipletId}: ${serviceText(response.swipletId, response.service
|
|
163
|
+
console.log(`${response.swipletId}: ${serviceText(response.swipletId, response.service)}`);
|
|
184
164
|
}
|
|
185
165
|
}
|
|
186
166
|
|
|
@@ -192,7 +172,7 @@ export const setService = enabled => context =>
|
|
|
192
172
|
await watchServices(client, id, context.signal);
|
|
193
173
|
return;
|
|
194
174
|
}
|
|
195
|
-
console.log(`${id}: ${serviceText(id, response.service
|
|
175
|
+
console.log(`${id}: ${serviceText(id, response.service)}`);
|
|
196
176
|
});
|
|
197
177
|
|
|
198
178
|
export const serviceWatch = context => session(context, client => watchServices(client, context.args[0] ?? "", context.signal));
|
|
@@ -203,16 +183,6 @@ export const remove = context =>
|
|
|
203
183
|
console.log("removed", context.args[0]);
|
|
204
184
|
});
|
|
205
185
|
|
|
206
|
-
export const filesList = context =>
|
|
207
|
-
session(context, async client => {
|
|
208
|
-
const [id, directory = ""] = context.args;
|
|
209
|
-
const { files } = await client.swiplets.listDataFiles({ swipletId: id, path: directory, recursive: context.options.recursive }, { signal: context.signal });
|
|
210
|
-
const rows = files.map(file => [file.directory ? "-" : String(file.size), dateTime(file.modified), file.directory ? `${file.path}/` : file.path]);
|
|
211
|
-
if (rows.length > 0) {
|
|
212
|
-
console.log(columns(rows));
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
|
|
216
186
|
const isLocal = name => {
|
|
217
187
|
const normalized = path.posix.normalize(name);
|
|
218
188
|
return !path.posix.isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith("../");
|
|
@@ -265,97 +235,131 @@ class FileSink {
|
|
|
265
235
|
|
|
266
236
|
const printSaved = (target, count) => console.error(`saved ${count} file(s) to ${target}`);
|
|
267
237
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
238
|
+
const dataCommands = target => ({
|
|
239
|
+
list: context =>
|
|
240
|
+
session(context, async client => {
|
|
241
|
+
const {
|
|
242
|
+
id,
|
|
243
|
+
args: [directory = ""],
|
|
244
|
+
} = target(context.args);
|
|
245
|
+
const { files } = await client.swiplets.listDataFiles({ swipletId: id, path: directory, recursive: context.options.recursive }, { signal: context.signal });
|
|
246
|
+
const rows = files.map(file => [file.directory ? "-" : String(file.size), dateTime(file.modified), file.directory ? `${file.path}/` : file.path]);
|
|
247
|
+
if (rows.length > 0) {
|
|
248
|
+
console.log(columns(rows));
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
read: context =>
|
|
252
|
+
session(context, async client => {
|
|
253
|
+
const {
|
|
254
|
+
id,
|
|
255
|
+
where,
|
|
256
|
+
args: [given],
|
|
257
|
+
} = target(context.args);
|
|
258
|
+
const output = context.options.output ?? "";
|
|
259
|
+
const requested = path.posix.normalize(`/${given}`).replace(/^\/+|\/+$/g, "");
|
|
260
|
+
const printing = output === "" || output === "-";
|
|
261
|
+
const directory = () =>
|
|
262
|
+
new UsageError(
|
|
263
|
+
requested === ""
|
|
264
|
+
? `${JSON.stringify(given)} names ${where}; save it with --output DIRECTORY`
|
|
265
|
+
: `${JSON.stringify(requested)} is a directory in ${where}; save it with --output DIRECTORY`,
|
|
266
|
+
);
|
|
267
|
+
const relativeTo = name => name.slice(requested.length).replace(/^\//, "");
|
|
268
|
+
let sink;
|
|
269
|
+
let single;
|
|
270
|
+
const contents = [];
|
|
271
|
+
try {
|
|
272
|
+
for await (const response of client.swiplets.readDataFile({ swipletId: id, path: given }, { signal: context.signal })) {
|
|
273
|
+
if (response.part.case === "file") {
|
|
274
|
+
const file = response.part.value;
|
|
275
|
+
if (file.path === requested && !file.directory) {
|
|
276
|
+
single = file;
|
|
277
|
+
continue;
|
|
295
278
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
279
|
+
if (!sink) {
|
|
280
|
+
if (printing) {
|
|
281
|
+
throw directory();
|
|
282
|
+
}
|
|
283
|
+
sink = new FileSink(output);
|
|
284
|
+
}
|
|
285
|
+
sink.begin(relativeTo(file.path), file.directory);
|
|
286
|
+
} else if (response.part.case === "chunk") {
|
|
287
|
+
const chunk = response.part.value;
|
|
288
|
+
if (single && chunk.path === single.path) {
|
|
289
|
+
contents.push(chunk.data);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!sink) {
|
|
293
|
+
throw new Error(`the server sent data for ${chunk.path} before announcing it`);
|
|
294
|
+
}
|
|
295
|
+
sink.write(relativeTo(chunk.path), chunk.offset, chunk.data);
|
|
307
296
|
}
|
|
308
|
-
sink.write(relativeTo(chunk.path), chunk.offset, chunk.data);
|
|
309
297
|
}
|
|
298
|
+
} finally {
|
|
299
|
+
sink?.close();
|
|
310
300
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
301
|
+
if (sink) {
|
|
302
|
+
printSaved(sink.root, sink.saved);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (!single) {
|
|
306
|
+
if (printing) {
|
|
307
|
+
throw directory();
|
|
308
|
+
}
|
|
309
|
+
fs.mkdirSync(output, { recursive: true });
|
|
310
|
+
printSaved(output, 0);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const data = Buffer.concat(contents);
|
|
319
314
|
if (printing) {
|
|
320
|
-
|
|
315
|
+
process.stdout.write(data);
|
|
316
|
+
return;
|
|
321
317
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
318
|
+
let destination = output;
|
|
319
|
+
if (fs.existsSync(destination) && fs.statSync(destination).isDirectory()) {
|
|
320
|
+
destination = path.join(destination, path.posix.basename(single.path));
|
|
321
|
+
}
|
|
322
|
+
fs.writeFileSync(destination, data);
|
|
323
|
+
printSaved(destination, 1);
|
|
324
|
+
}),
|
|
325
|
+
write: context =>
|
|
326
|
+
session(context, async client => {
|
|
327
|
+
const {
|
|
328
|
+
id,
|
|
329
|
+
args: [name, file],
|
|
330
|
+
} = target(context.args);
|
|
331
|
+
const data = file === undefined || file === "-" ? readStdin() : fs.readFileSync(file);
|
|
332
|
+
await client.swiplets.writeDataFile(
|
|
333
|
+
(async function* () {
|
|
334
|
+
yield { part: { case: "header", value: { swipletId: id, path: name } } };
|
|
335
|
+
for (let offset = 0; offset < data.length; offset += uploadChunk) {
|
|
336
|
+
yield { part: { case: "data", value: data.subarray(offset, offset + uploadChunk) } };
|
|
337
|
+
}
|
|
338
|
+
})(),
|
|
339
|
+
{ signal: context.signal },
|
|
340
|
+
);
|
|
341
|
+
}),
|
|
342
|
+
remove: context =>
|
|
343
|
+
session(context, async client => {
|
|
344
|
+
const {
|
|
345
|
+
id,
|
|
346
|
+
args: [name],
|
|
347
|
+
} = target(context.args);
|
|
348
|
+
await client.swiplets.removeDataFile({ swipletId: id, path: name, recursive: context.options.recursive }, { signal: context.signal });
|
|
349
|
+
}),
|
|
350
|
+
clear: context =>
|
|
351
|
+
session(context, async client => {
|
|
352
|
+
const { id } = target(context.args);
|
|
353
|
+
const { files } = await client.swiplets.listDataFiles({ swipletId: id }, { signal: context.signal });
|
|
354
|
+
for (const file of files) {
|
|
355
|
+
await client.swiplets.removeDataFile({ swipletId: id, path: file.path, recursive: true }, { signal: context.signal });
|
|
356
|
+
}
|
|
357
|
+
}),
|
|
358
|
+
});
|
|
338
359
|
|
|
339
|
-
export const
|
|
340
|
-
session(context, async client => {
|
|
341
|
-
const [id, name, file] = context.args;
|
|
342
|
-
const data = file === undefined || file === "-" ? readStdin() : fs.readFileSync(file);
|
|
343
|
-
await client.swiplets.writeDataFile(
|
|
344
|
-
(async function* () {
|
|
345
|
-
yield { part: { case: "header", value: { swipletId: id, path: name } } };
|
|
346
|
-
for (let offset = 0; offset < data.length; offset += uploadChunk) {
|
|
347
|
-
yield { part: { case: "data", value: data.subarray(offset, offset + uploadChunk) } };
|
|
348
|
-
}
|
|
349
|
-
})(),
|
|
350
|
-
{ signal: context.signal },
|
|
351
|
-
);
|
|
352
|
-
});
|
|
360
|
+
export const swipletData = dataCommands(([id, ...args]) => ({ id, where: `the data directory of ${id}`, args }));
|
|
353
361
|
|
|
354
|
-
export const
|
|
355
|
-
session(context, async client => {
|
|
356
|
-
const [id, name] = context.args;
|
|
357
|
-
await client.swiplets.removeDataFile({ swipletId: id, path: name, recursive: context.options.recursive }, { signal: context.signal });
|
|
358
|
-
});
|
|
362
|
+
export const replData = dataCommands(args => ({ id: "@repl", where: "the data directory of the REPL", args }));
|
|
359
363
|
|
|
360
364
|
async function deploy(client, archive, signal) {
|
|
361
365
|
const data = fs.readFileSync(archive);
|
|
@@ -371,20 +375,19 @@ async function deploy(client, archive, signal) {
|
|
|
371
375
|
|
|
372
376
|
const printDeployed = (deployed, toStdout) => (toStdout ? console.log : console.error)(deployed.replaced ? "replaced" : "installed", deployed.swiplet.id, deployed.swiplet.version);
|
|
373
377
|
|
|
374
|
-
export async function
|
|
375
|
-
const [
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (!fs.existsSync(given)) {
|
|
379
|
-
throw new Error(`there is no archive at ${given}`);
|
|
380
|
-
}
|
|
381
|
-
archive = given;
|
|
382
|
-
} else {
|
|
383
|
-
({ archive } = await buildProject(path.resolve(given ?? ".")));
|
|
378
|
+
export async function install(context) {
|
|
379
|
+
const [archive] = context.args;
|
|
380
|
+
if (!fs.existsSync(archive)) {
|
|
381
|
+
throw new Error(`there is no archive at ${archive}`);
|
|
384
382
|
}
|
|
385
383
|
await session(context, async client => printDeployed(await deploy(client, archive, context.signal), true));
|
|
386
384
|
}
|
|
387
385
|
|
|
386
|
+
export async function deployProject(context) {
|
|
387
|
+
const { archive } = await buildProject(path.resolve(context.args[0] ?? "."));
|
|
388
|
+
await session(context, async client => printDeployed(await deploy(client, archive, context.signal), true));
|
|
389
|
+
}
|
|
390
|
+
|
|
388
391
|
async function follow(signal, fatal, run) {
|
|
389
392
|
while (!signal.aborted) {
|
|
390
393
|
try {
|
|
@@ -407,7 +410,6 @@ export async function develop(context) {
|
|
|
407
410
|
const { signal, printError } = context;
|
|
408
411
|
const project = loadProject(path.resolve(context.args[0] ?? "."));
|
|
409
412
|
const root = project.root;
|
|
410
|
-
sources.root = root;
|
|
411
413
|
const client = await attach(context);
|
|
412
414
|
const controller = new AbortController();
|
|
413
415
|
const inner = AbortSignal.any([signal, controller.signal]);
|
|
@@ -418,7 +420,7 @@ export async function develop(context) {
|
|
|
418
420
|
follow(inner, fatalReject, async () => {
|
|
419
421
|
for await (const response of client.swiplets.watchServices({}, { signal: inner })) {
|
|
420
422
|
if (response.swipletId === project.config.id && response.service?.status === ServiceStatus.FAILED) {
|
|
421
|
-
console.error(`${response.swipletId}: ${serviceText(response.swipletId, response.service
|
|
423
|
+
console.error(`${response.swipletId}: ${serviceText(response.swipletId, response.service)}`);
|
|
422
424
|
}
|
|
423
425
|
}
|
|
424
426
|
});
|
|
@@ -441,7 +443,6 @@ export async function develop(context) {
|
|
|
441
443
|
}
|
|
442
444
|
return;
|
|
443
445
|
}
|
|
444
|
-
sources.forget(built.id);
|
|
445
446
|
if (built.digest === deployed) {
|
|
446
447
|
console.error(`built in ${elapsed(started)}; it came out as deployed, so nothing was deployed`);
|
|
447
448
|
return;
|
|
@@ -506,20 +507,27 @@ export async function develop(context) {
|
|
|
506
507
|
}
|
|
507
508
|
}
|
|
508
509
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
510
|
+
const isFile = name => {
|
|
511
|
+
try {
|
|
512
|
+
return fs.statSync(name).isFile();
|
|
513
|
+
} catch {
|
|
514
|
+
return false;
|
|
512
515
|
}
|
|
513
|
-
|
|
514
|
-
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
export const isProgram = first => first === "-" || isFile(first);
|
|
519
|
+
|
|
520
|
+
function readScript([first, ...args]) {
|
|
521
|
+
if (first === undefined || first === "-") {
|
|
522
|
+
return { code: readStdin().toString(), args, program: true, file: "input.js" };
|
|
515
523
|
}
|
|
516
|
-
if (
|
|
517
|
-
return { code: args
|
|
524
|
+
if (isFile(first)) {
|
|
525
|
+
return { code: fs.readFileSync(first, "utf8"), args, program: true, file: first };
|
|
518
526
|
}
|
|
519
|
-
if (args.length
|
|
520
|
-
|
|
527
|
+
if (args.length > 0) {
|
|
528
|
+
throw new UsageError(`${JSON.stringify(first)} is not a file, and CODE takes no ARG`);
|
|
521
529
|
}
|
|
522
|
-
return { code:
|
|
530
|
+
return { code: first, args, program: false, file: "input.js" };
|
|
523
531
|
}
|
|
524
532
|
|
|
525
533
|
class ImageSink {
|
|
@@ -535,7 +543,7 @@ class ImageSink {
|
|
|
535
543
|
this.directory = path.resolve(this.output);
|
|
536
544
|
fs.mkdirSync(this.directory, { recursive: true });
|
|
537
545
|
} else {
|
|
538
|
-
this.directory = fs.mkdtempSync(path.join(os.tmpdir(), "swipt-
|
|
546
|
+
this.directory = fs.mkdtempSync(path.join(os.tmpdir(), "swipt-repl-"));
|
|
539
547
|
}
|
|
540
548
|
}
|
|
541
549
|
const name = path.join(this.directory, `image-${crypto.randomBytes(5).toString("hex")}.png`);
|
|
@@ -544,29 +552,20 @@ class ImageSink {
|
|
|
544
552
|
}
|
|
545
553
|
}
|
|
546
554
|
|
|
547
|
-
export
|
|
555
|
+
export const api = context =>
|
|
556
|
+
session(context, async client => {
|
|
557
|
+
process.stdout.write((await client.repl.getDeclarations({}, { signal: context.signal })).typescript);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
export const reset = context => session(context, client => client.repl.reset({}, { signal: context.signal }));
|
|
561
|
+
|
|
562
|
+
export async function run(context) {
|
|
548
563
|
const { args, options, signal } = context;
|
|
549
|
-
|
|
550
|
-
if (args.length > 0 || options.file !== undefined || options.reset) {
|
|
551
|
-
throw new UsageError("--api takes no script and no --reset");
|
|
552
|
-
}
|
|
553
|
-
await session(context, async client => {
|
|
554
|
-
process.stdout.write((await client.repl.getDeclarations({}, { signal: context.signal })).typescript);
|
|
555
|
-
});
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const { code, run } = readScript(args, options);
|
|
564
|
+
const script = readScript(args);
|
|
559
565
|
await session(context, async client => {
|
|
560
|
-
if (options.reset) {
|
|
561
|
-
await client.repl.reset({}, { signal });
|
|
562
|
-
}
|
|
563
|
-
if (!run) {
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
const script = options.file ?? "input.js";
|
|
567
566
|
const images = new ImageSink(options.output);
|
|
568
567
|
let failure;
|
|
569
|
-
for await (const response of client.repl.execute({ code }, { signal })) {
|
|
568
|
+
for await (const response of client.repl.execute({ code: script.code, args: script.args, program: script.program }, { signal })) {
|
|
570
569
|
switch (response.part.case) {
|
|
571
570
|
case "console": {
|
|
572
571
|
const output = response.part.value;
|
|
@@ -585,7 +584,7 @@ export async function js(context) {
|
|
|
585
584
|
}
|
|
586
585
|
}
|
|
587
586
|
if (failure) {
|
|
588
|
-
throw printScriptError(failure, frame => (frame.file === "input.js" ? { ...frame, file: script } : frame));
|
|
587
|
+
throw printScriptError({ ...failure, stack: failure.stack.map(frame => (frame.file === "input.js" ? { ...frame, file: script.file } : frame)) });
|
|
589
588
|
}
|
|
590
589
|
});
|
|
591
590
|
}
|
package/output.mjs
CHANGED
|
@@ -36,10 +36,10 @@ const frameText = frame => {
|
|
|
36
36
|
return frame.function === "" ? position : `${frame.function} (${position})`;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export const failureText =
|
|
39
|
+
export const failureText = failure => [`${failure.name}: ${failure.message}`, ...failure.stack.map(frame => ` at ${frameText(frame)}`)].join("\n");
|
|
40
40
|
|
|
41
|
-
export function printScriptError(failure
|
|
42
|
-
console.error(`error: ${failureText(failure
|
|
41
|
+
export function printScriptError(failure) {
|
|
42
|
+
console.error(`error: ${failureText(failure)}`);
|
|
43
43
|
return new FalseResult();
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -48,14 +48,14 @@ export function printLog(entry, toStdout) {
|
|
|
48
48
|
(toStdout ? console.log : console.error)(clockTime(entry.time), levelName(entry.level).toUpperCase(), message);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function serviceText(id, state
|
|
51
|
+
export function serviceText(id, state) {
|
|
52
52
|
let text = state?.enabled ? "enabled" : "disabled";
|
|
53
53
|
text += `, ${state && state.status !== ServiceStatus.UNSPECIFIED ? enumText(ServiceStatusSchema, state.status, "SERVICE_STATUS_") : "stopped"}`;
|
|
54
54
|
if (state?.since) {
|
|
55
55
|
text += ` since ${clockTime(state.since)}`;
|
|
56
56
|
}
|
|
57
57
|
if (state?.error) {
|
|
58
|
-
text += `: ${failureText(state.error
|
|
58
|
+
text += `: ${failureText(state.error)}`;
|
|
59
59
|
}
|
|
60
60
|
return text;
|
|
61
61
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swipt/cli",
|
|
3
|
-
"version": "0.1.0-nightly.
|
|
3
|
+
"version": "0.1.0-nightly.40",
|
|
4
4
|
"license": "GPL-3.0-or-later",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"@babel/runtime": "^7.29.10",
|
|
24
24
|
"@bufbuild/protobuf": "^2.16.0",
|
|
25
25
|
"@expo/env": "^2.5.3",
|
|
26
|
-
"@jridgewell/trace-mapping": "^0.3.31",
|
|
27
26
|
"babel-preset-expo": "~58.0.22",
|
|
28
27
|
"bonjour-service": "^1.4.4",
|
|
29
28
|
"esbuild": "^0.28.2",
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
"react-native-worklets": "0.13.0"
|
|
32
31
|
},
|
|
33
32
|
"peerDependencies": {
|
|
34
|
-
"@swipt/api": "0.1.0-nightly.
|
|
33
|
+
"@swipt/api": "0.1.0-nightly.40"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
36
|
"@bufbuild/protoc-gen-es": "^2.16.0"
|
package/project.mjs
CHANGED
|
@@ -84,9 +84,10 @@ async function bundle(project) {
|
|
|
84
84
|
const out = { app: "", extension: "", extensionMap: "", assets: new Map() };
|
|
85
85
|
loadEnvironment(project.root);
|
|
86
86
|
if (project.extension) {
|
|
87
|
+
const outfile = path.join(project.root, "dist", project.config.id, "src", "extension.js");
|
|
87
88
|
const result = await build({
|
|
88
89
|
entryPoints: [project.extension],
|
|
89
|
-
outfile
|
|
90
|
+
outfile,
|
|
90
91
|
bundle: true,
|
|
91
92
|
format: "cjs",
|
|
92
93
|
platform: "neutral",
|
|
@@ -100,7 +101,9 @@ async function bundle(project) {
|
|
|
100
101
|
plugins: [metro(project.root, { sourcemap: true })],
|
|
101
102
|
});
|
|
102
103
|
out.extension = result.outputFiles.find(file => file.path.endsWith(".js")).text;
|
|
103
|
-
|
|
104
|
+
const map = JSON.parse(result.outputFiles.find(file => file.path.endsWith(".map")).text);
|
|
105
|
+
const sources = map.sources.map(source => path.relative(project.root, path.resolve(path.dirname(outfile), map.sourceRoot ?? "", source)).split(path.sep).join("/"));
|
|
106
|
+
out.extensionMap = JSON.stringify({ ...map, sourceRoot: undefined, sources });
|
|
104
107
|
}
|
|
105
108
|
if (project.app) {
|
|
106
109
|
const result = await build({
|
|
@@ -273,7 +276,7 @@ export async function buildProject(root) {
|
|
|
273
276
|
const archive = path.join(dist, `${manifest.id}.swiplet`);
|
|
274
277
|
fs.rmSync(archive, { force: true });
|
|
275
278
|
fs.rmSync(path.join(dist, `${manifest.id}.zip`), { force: true });
|
|
276
|
-
execFileSync("zip", ["-qr", "-n", storedSuffixes, archive, ...fs.readdirSync(folder)
|
|
279
|
+
execFileSync("zip", ["-qr", "-n", storedSuffixes, archive, ...fs.readdirSync(folder)], { cwd: folder });
|
|
277
280
|
return { id: manifest.id, archive, digest: folderDigest(folder) };
|
|
278
281
|
}
|
|
279
282
|
|
|
@@ -284,7 +287,7 @@ function folderDigest(folder) {
|
|
|
284
287
|
const name = path.join(directory, entry.name);
|
|
285
288
|
if (entry.isDirectory()) {
|
|
286
289
|
walk(name);
|
|
287
|
-
} else
|
|
290
|
+
} else {
|
|
288
291
|
hash.update(`${path.relative(folder, name)}\0`);
|
|
289
292
|
hash.update(fs.readFileSync(name));
|
|
290
293
|
hash.update("\0");
|
|
@@ -414,7 +417,7 @@ const templates = {
|
|
|
414
417
|
app: "./src/App.tsx",
|
|
415
418
|
extension: "./src/extension.ts",
|
|
416
419
|
},
|
|
417
|
-
scripts: { build: "swipt build", check: "tsc" },
|
|
420
|
+
scripts: { build: "swipt project build", check: "tsc" },
|
|
418
421
|
dependencies: { zod: "^4.6.7" },
|
|
419
422
|
devDependencies: {
|
|
420
423
|
"@swipt/api": version,
|
package/proto/repl_pb.js
CHANGED
|
@@ -10,7 +10,7 @@ import { file_common } from "./common_pb.js";
|
|
|
10
10
|
* Describes the file repl.proto.
|
|
11
11
|
*/
|
|
12
12
|
export const file_repl = /*@__PURE__*/
|
|
13
|
-
fileDesc("
|
|
13
|
+
fileDesc("CgpyZXBsLnByb3RvEgVzd2lwdCJRCg5FeGVjdXRlUmVxdWVzdBIMCgRjb2RlGAEgASgJEhIKCnRpbWVvdXRfbXMYAiABKA0SDAoEYXJncxgDIAMoCRIPCgdwcm9ncmFtGAQgASgIIj0KDUNvbnNvbGVPdXRwdXQSHgoFbGV2ZWwYASABKA4yDy5zd2lwdC5Mb2dMZXZlbBIMCgR0ZXh0GAIgASgJIikKC1NjcmlwdFZhbHVlEgwKBGpzb24YASABKAkSDAoEdGV4dBgCIAEoCSKdAQoPRXhlY3V0ZVJlc3BvbnNlEicKB2NvbnNvbGUYASABKAsyFC5zd2lwdC5Db25zb2xlT3V0cHV0SAASIwoFdmFsdWUYAiABKAsyEi5zd2lwdC5TY3JpcHRWYWx1ZUgAEg8KBWltYWdlGAMgASgMSAASIwoFZXJyb3IYBCABKAsyEi5zd2lwdC5TY3JpcHRFcnJvckgAQgYKBHBhcnQiLQoXR2V0RGVjbGFyYXRpb25zUmVzcG9uc2USEgoKdHlwZXNjcmlwdBgBIAEoCTLNAQoLUkVQTFNlcnZpY2USOgoHRXhlY3V0ZRIVLnN3aXB0LkV4ZWN1dGVSZXF1ZXN0GhYuc3dpcHQuRXhlY3V0ZVJlc3BvbnNlMAESNwoFUmVzZXQSFi5nb29nbGUucHJvdG9idWYuRW1wdHkaFi5nb29nbGUucHJvdG9idWYuRW1wdHkSSQoPR2V0RGVjbGFyYXRpb25zEhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5Gh4uc3dpcHQuR2V0RGVjbGFyYXRpb25zUmVzcG9uc2VCMlowZ2l0aHViLmNvbS9zd2lwdC1hdXRvbWF0aW9uL3N3aXB0L2ludGVybmFsL3Byb3RvYgZwcm90bzM", [file_google_protobuf_empty, file_common]);
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Describes the message swipt.ExecuteRequest.
|
package/proto/system_pb.js
CHANGED
|
@@ -10,7 +10,7 @@ import { file_common } from "./common_pb.js";
|
|
|
10
10
|
* Describes the file system.proto.
|
|
11
11
|
*/
|
|
12
12
|
export const file_system = /*@__PURE__*/
|
|
13
|
-
fileDesc("
|
|
13
|
+
fileDesc("CgxzeXN0ZW0ucHJvdG8SBXN3aXB0IqgBCg9HZXRJbmZvUmVzcG9uc2USFgoOc2VydmVyX3ZlcnNpb24YASABKAkSEwoLZGV2aWNlX25hbWUYAiABKAkSDAoEdWRpZBgDIAEoCRIyChFkZXZpY2VfY29ubmVjdGlvbhgEIAEoCzIXLnN3aXB0LkRldmljZUNvbm5lY3Rpb24SEwoLYXBwX3ZlcnNpb24YBSABKAkSEQoJYXBwX2J1aWxkGAYgASgJMkoKDVN5c3RlbVNlcnZpY2USOQoHR2V0SW5mbxIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRoWLnN3aXB0LkdldEluZm9SZXNwb25zZUIyWjBnaXRodWIuY29tL3N3aXB0LWF1dG9tYXRpb24vc3dpcHQvaW50ZXJuYWwvcHJvdG9iBnByb3RvMw", [file_google_protobuf_empty, file_common]);
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Describes the message swipt.GetInfoResponse.
|
package/swipt.mjs
CHANGED
|
@@ -7,14 +7,12 @@ import * as connection from "./connection.mjs";
|
|
|
7
7
|
import * as device from "./device.mjs";
|
|
8
8
|
import { FalseResult } from "./output.mjs";
|
|
9
9
|
import { buildProject, createCommand, sourceCommand } from "./project.mjs";
|
|
10
|
-
import { sources } from "./sourcemap.mjs";
|
|
11
10
|
|
|
12
11
|
const globalOptions = {
|
|
13
|
-
device: { type: "string", short: "d", value: "NAME|URL", help: "the device to use (SWIPT_DEVICE)" },
|
|
14
12
|
help: { type: "boolean", short: "h", help: "help" },
|
|
15
13
|
};
|
|
16
14
|
|
|
17
|
-
const
|
|
15
|
+
const deviceOption = { device: { type: "string", short: "d", value: "NAME|URL", help: "the device to use (SWIPT_DEVICE)" } };
|
|
18
16
|
|
|
19
17
|
const durationUnits = { ns: 1e-6, us: 1e-3, "µs": 1e-3, ms: 1, s: 1000, m: 60_000, h: 3_600_000 };
|
|
20
18
|
|
|
@@ -48,101 +46,143 @@ function parseCount(text, option) {
|
|
|
48
46
|
return Number(text);
|
|
49
47
|
}
|
|
50
48
|
|
|
49
|
+
const dataCommands = (operand, files) => {
|
|
50
|
+
const before = operand === "" ? 0 : 1;
|
|
51
|
+
return {
|
|
52
|
+
name: "data",
|
|
53
|
+
short: `Manage the files in the data directory of ${operand === "" ? "the REPL" : "a swiplet"}`,
|
|
54
|
+
commands: [
|
|
55
|
+
{ name: "list", use: `list ${operand}[PATH]`, short: "List data files", args: [before, before + 1], options: { recursive: { type: "boolean", help: "include subdirectories" } }, run: files.list },
|
|
56
|
+
{ name: "read", use: `read ${operand}PATH`, short: "Read a data file or directory", args: [before + 1, before + 1], options: { output: { type: "string", value: "PATH", help: "destination" } }, run: files.read },
|
|
57
|
+
{ name: "write", use: `write ${operand}PATH [FILE | -]`, short: "Write a data file", args: [before + 1, before + 2], run: files.write },
|
|
58
|
+
{ name: "remove", use: `remove ${operand}PATH`, short: "Remove a data file", args: [before + 1, before + 1], options: { recursive: { type: "boolean", help: "include directory contents" } }, run: files.remove },
|
|
59
|
+
{ name: "clear", use: `clear ${operand}`.trimEnd(), short: "Remove every data file, keeping the data directory", args: [before, before], run: files.clear },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
51
64
|
const commands = [
|
|
52
65
|
{
|
|
53
|
-
name: "
|
|
54
|
-
|
|
55
|
-
short: "Make a swiplet project in a new or empty directory",
|
|
56
|
-
args: [1, 1],
|
|
57
|
-
run: ({ args }) => createCommand(args[0]),
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "build",
|
|
61
|
-
short: "Bundle the project in this directory into dist/<swiplet.id>.swiplet",
|
|
62
|
-
args: [0, 0],
|
|
63
|
-
run: async () => {
|
|
64
|
-
const { archive } = await buildProject(process.cwd());
|
|
65
|
-
console.log(path.relative(process.cwd(), archive));
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "source",
|
|
70
|
-
use: "source [SWIPLET...]",
|
|
71
|
-
short: 'Gather the built swiplets of this directory\'s workspaces, or the given ones, into dist/ with a swiplets.json described by "source" in package.json',
|
|
72
|
-
args: [0, Infinity],
|
|
73
|
-
run: ({ args }) => sourceCommand(process.cwd(), args),
|
|
74
|
-
},
|
|
75
|
-
{ name: "devices", short: "List the devices, adding the ones found on the local network", args: [0, 0], run: connection.devices },
|
|
76
|
-
{ name: "connect", use: "connect URL", short: "Add a device by its URL; needed when it is remote or requires a token", args: [1, 1], run: connection.connect },
|
|
77
|
-
{ name: "disconnect", use: "disconnect NAME", short: "Forget a device", args: [1, 1], run: connection.disconnect },
|
|
78
|
-
{ name: "info", short: "Show the device and its connection", args: [0, 0], run: device.info },
|
|
79
|
-
{ name: "version", short: "Print the version", args: [0, 0], run: device.printVersion },
|
|
80
|
-
{ name: "swiplets", use: "swiplets [ID]", short: "List the swiplets or show one", args: [0, 1], options: projectOption, run: device.swiplets },
|
|
81
|
-
{
|
|
82
|
-
name: "call",
|
|
83
|
-
use: "call ID ACTION",
|
|
84
|
-
short: "Call a swiplet action",
|
|
85
|
-
args: [2, 2],
|
|
86
|
-
options: { input: { type: "string", value: "JSON", help: "input JSON, @FILE or -" }, ...projectOption },
|
|
87
|
-
run: device.call,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: "service",
|
|
91
|
-
short: "Manage swiplet services",
|
|
92
|
-
options: projectOption,
|
|
66
|
+
name: "project",
|
|
67
|
+
short: "Make, build and deploy swiplet projects",
|
|
93
68
|
commands: [
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
69
|
+
{
|
|
70
|
+
name: "create",
|
|
71
|
+
use: "create DIRECTORY",
|
|
72
|
+
short: "Make a swiplet project in a new or empty directory",
|
|
73
|
+
args: [1, 1],
|
|
74
|
+
run: ({ args }) => createCommand(args[0]),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "build",
|
|
78
|
+
use: "build [PROJECT]",
|
|
79
|
+
short: "Bundle the project into dist/<swiplet.id>.swiplet",
|
|
80
|
+
args: [0, 1],
|
|
81
|
+
run: async ({ args }) => {
|
|
82
|
+
const { archive } = await buildProject(path.resolve(args[0] ?? "."));
|
|
83
|
+
console.log(path.relative(process.cwd(), archive));
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "source",
|
|
88
|
+
use: "source [SWIPLET...]",
|
|
89
|
+
short: 'Gather the built swiplets of this directory\'s workspaces, or the given ones, into dist/ with a swiplets.json described by "source" in package.json',
|
|
90
|
+
args: [0, Infinity],
|
|
91
|
+
run: ({ args }) => sourceCommand(process.cwd(), args),
|
|
92
|
+
},
|
|
93
|
+
{ name: "deploy", use: "deploy [PROJECT]", short: "Build the project and install it on the device", args: [0, 1], options: deviceOption, run: device.deployProject },
|
|
94
|
+
{
|
|
95
|
+
name: "develop",
|
|
96
|
+
use: "develop [PROJECT]",
|
|
97
|
+
short: "Build, install and follow a swiplet project as it changes",
|
|
98
|
+
args: [0, 1],
|
|
99
|
+
options: { ...deviceOption, "no-logs": { type: "boolean", help: "without logs" } },
|
|
100
|
+
run: device.develop,
|
|
101
|
+
},
|
|
97
102
|
],
|
|
98
103
|
},
|
|
99
|
-
{ name: "remove", use: "remove ID", short: "Remove a swiplet", args: [1, 1], run: device.remove },
|
|
100
104
|
{
|
|
101
|
-
name: "
|
|
102
|
-
short: "Manage
|
|
105
|
+
name: "device",
|
|
106
|
+
short: "Manage the devices and read their logs",
|
|
103
107
|
commands: [
|
|
104
|
-
{ name: "list",
|
|
105
|
-
{ name: "
|
|
106
|
-
{ name: "
|
|
107
|
-
{
|
|
108
|
+
{ name: "list", short: "List the devices, adding the ones found on the local network", args: [0, 0], run: connection.list },
|
|
109
|
+
{ name: "add", use: "add URL", short: "Add a device by its URL; needed when it is remote or requires a token", args: [1, 1], run: connection.add },
|
|
110
|
+
{ name: "remove", use: "remove NAME", short: "Forget a device", args: [1, 1], run: connection.remove },
|
|
111
|
+
{
|
|
112
|
+
name: "logs",
|
|
113
|
+
short: "Print the logs",
|
|
114
|
+
args: [0, 0],
|
|
115
|
+
options: {
|
|
116
|
+
...deviceOption,
|
|
117
|
+
follow: { type: "boolean", help: "follow new entries" },
|
|
118
|
+
tail: { type: "string", value: "COUNT", help: "number of past entries (default 100)" },
|
|
119
|
+
since: { type: "string", value: "DURATION", help: "maximum age" },
|
|
120
|
+
level: { type: "string", value: "LEVEL", help: "minimum level (default info)" },
|
|
121
|
+
swiplet: { type: "string", value: "ID", help: "swiplet ID" },
|
|
122
|
+
},
|
|
123
|
+
parse: options => ({ ...options, tail: parseCount(options.tail, "--tail"), since: parseDuration(options.since, "--since"), level: options.level ?? "info" }),
|
|
124
|
+
run: device.logs,
|
|
125
|
+
},
|
|
108
126
|
],
|
|
109
127
|
},
|
|
110
|
-
{ name: "deploy", use: "deploy [ARCHIVE | PROJECT]", short: "Build and install a swiplet", args: [0, 1], run: device.deployCommand },
|
|
111
|
-
{
|
|
112
|
-
name: "develop",
|
|
113
|
-
use: "develop [PROJECT]",
|
|
114
|
-
short: "Build, install and follow a swiplet project as it changes",
|
|
115
|
-
args: [0, 1],
|
|
116
|
-
options: { "no-logs": { type: "boolean", help: "without logs" } },
|
|
117
|
-
run: device.develop,
|
|
118
|
-
},
|
|
119
128
|
{
|
|
120
|
-
name: "
|
|
121
|
-
short: "
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
name: "swiplet",
|
|
130
|
+
short: "Manage the swiplets on the device",
|
|
131
|
+
options: deviceOption,
|
|
132
|
+
commands: [
|
|
133
|
+
{ name: "list", short: "List the swiplets", args: [0, 0], run: device.list },
|
|
134
|
+
{ name: "show", use: "show ID", short: "Show a swiplet", args: [1, 1], run: device.show },
|
|
135
|
+
{ name: "install", use: "install ARCHIVE", short: "Install a swiplet archive", args: [1, 1], run: device.install },
|
|
136
|
+
{ name: "remove", use: "remove ID", short: "Remove a swiplet", args: [1, 1], run: device.remove },
|
|
137
|
+
{
|
|
138
|
+
name: "call",
|
|
139
|
+
use: "call ID ACTION",
|
|
140
|
+
short: "Call a swiplet action",
|
|
141
|
+
args: [2, 2],
|
|
142
|
+
options: { input: { type: "string", value: "JSON", help: "input JSON, @FILE or -" } },
|
|
143
|
+
run: device.call,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "service",
|
|
147
|
+
short: "Manage swiplet services",
|
|
148
|
+
commands: [
|
|
149
|
+
{ name: "enable", use: "enable ID", short: "Turn on a swiplet service", args: [1, 1], options: { watch: { type: "boolean", help: "watch the state" } }, run: device.setService(true) },
|
|
150
|
+
{ name: "disable", use: "disable ID", short: "Turn off a swiplet service", args: [1, 1], options: { watch: { type: "boolean", help: "watch the state" } }, run: device.setService(false) },
|
|
151
|
+
{ name: "watch", use: "watch [ID]", short: "Watch swiplet services", args: [0, 1], run: device.serviceWatch },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
dataCommands("ID ", device.swipletData),
|
|
155
|
+
],
|
|
132
156
|
},
|
|
133
157
|
{
|
|
134
|
-
name: "
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
name: "repl",
|
|
159
|
+
short: "Run JavaScript in the REPL session on the device",
|
|
160
|
+
options: deviceOption,
|
|
161
|
+
commands: [
|
|
162
|
+
{
|
|
163
|
+
name: "run",
|
|
164
|
+
use: "run [CODE | FILE [ARG...] | - [ARG...]]",
|
|
165
|
+
short: "Run a JavaScript expression or program on the device",
|
|
166
|
+
description: [
|
|
167
|
+
"The first argument is FILE when it names an existing file and CODE otherwise; options go before FILE or -, and every argument after it is an ARG, verbatim.",
|
|
168
|
+
"CODE is an expression; its completion value, awaited if it is a promise, is printed in inspect format: strings quoted, and values with their own toString, which include accessibility snapshots and screen recordings, as that string.",
|
|
169
|
+
"FILE, or standard input given as - or no argument, is a program; only its console output and thrown error are printed, and the global args holds its ARGs.",
|
|
170
|
+
'Top-level await is available, and top-level bindings persist across runs until "swipt repl reset".',
|
|
171
|
+
"An image given to show(), or as the completion value of CODE, is saved as a PNG file and printed as its path.",
|
|
172
|
+
'There is no module system, so import is unavailable; the globals are accessibility, input, device, screen, image, dialog, dataDirectory, show and sleep, declared by "swipt repl api".',
|
|
173
|
+
'dataDirectory is the directory that "swipt repl data" manages.',
|
|
174
|
+
],
|
|
175
|
+
args: [0, Infinity],
|
|
176
|
+
passthrough: device.isProgram,
|
|
177
|
+
options: { output: { type: "string", value: "DIRECTORY", help: "image directory (default: a new temporary directory)" } },
|
|
178
|
+
run: device.run,
|
|
179
|
+
},
|
|
180
|
+
{ name: "reset", short: "Reset the session", args: [0, 0], run: device.reset },
|
|
181
|
+
{ name: "api", short: "Print the TypeScript declarations of the globals", args: [0, 0], run: device.api },
|
|
182
|
+
dataCommands("", device.replData),
|
|
183
|
+
],
|
|
145
184
|
},
|
|
185
|
+
{ name: "version", short: "Print the version", args: [0, 0], run: device.printVersion },
|
|
146
186
|
];
|
|
147
187
|
|
|
148
188
|
const root = { name: "swipt", short: "Operate a device through Swipt", commands };
|
|
@@ -167,6 +207,9 @@ function usage(trail) {
|
|
|
167
207
|
} else {
|
|
168
208
|
lines.push(`usage: ${trail.slice(0, -1).map(it => it.name).join(" ")} ${command.use ?? command.name}`.replace(/ +/g, " "));
|
|
169
209
|
}
|
|
210
|
+
if (command.description) {
|
|
211
|
+
lines.push("", ...command.description);
|
|
212
|
+
}
|
|
170
213
|
const options = { ...trail.reduce((found, it) => ({ ...found, ...it.options }), {}), ...globalOptions };
|
|
171
214
|
lines.push("", "options:");
|
|
172
215
|
lines.push(...columns(Object.entries(options).map(([option, it]) => [` ${it.short ? `-${it.short}, ` : ""}--${option}${it.value ? ` ${it.value}` : ""}`, it.help])).split("\n"));
|
|
@@ -179,18 +222,19 @@ function columns(rows) {
|
|
|
179
222
|
}
|
|
180
223
|
|
|
181
224
|
function resolve(argv) {
|
|
182
|
-
const loose = parseArgs({ args: argv, options: { ...parseOptions(globalOptions), ...allOptions(root) }, allowPositionals: true, strict: false });
|
|
225
|
+
const loose = parseArgs({ args: argv, options: { ...parseOptions(globalOptions), ...allOptions(root) }, allowPositionals: true, strict: false, tokens: true });
|
|
226
|
+
const positionals = loose.tokens.filter(token => token.kind === "positional");
|
|
183
227
|
const trail = [root];
|
|
184
228
|
let consumed = 0;
|
|
185
|
-
let help =
|
|
229
|
+
let help = positionals[0]?.value === "help";
|
|
186
230
|
if (help) {
|
|
187
231
|
consumed++;
|
|
188
232
|
}
|
|
189
233
|
while (trail.at(-1).commands) {
|
|
190
234
|
const current = trail.at(-1);
|
|
191
|
-
const word =
|
|
235
|
+
const word = positionals[consumed]?.value;
|
|
192
236
|
if (word === undefined) {
|
|
193
|
-
if (help || current !== root || argv.length === 0) {
|
|
237
|
+
if (help || loose.values.help || current !== root || argv.length === 0) {
|
|
194
238
|
return { trail, help: true };
|
|
195
239
|
}
|
|
196
240
|
throw new UsageError("a command is missing");
|
|
@@ -202,18 +246,21 @@ function resolve(argv) {
|
|
|
202
246
|
trail.push(found);
|
|
203
247
|
consumed++;
|
|
204
248
|
}
|
|
249
|
+
const command = trail.at(-1);
|
|
205
250
|
const options = trail.reduce((found, it) => ({ ...found, ...it.options }), {});
|
|
251
|
+
const first = positionals[consumed];
|
|
252
|
+
const verbatim = first !== undefined && command.passthrough?.(first.value) ? first.index + 1 : argv.length;
|
|
206
253
|
let parsed;
|
|
207
254
|
try {
|
|
208
|
-
parsed = parseArgs({ args: argv, options: { ...parseOptions(globalOptions), ...parseOptions(options) }, allowPositionals: true, strict: true });
|
|
255
|
+
parsed = parseArgs({ args: argv.slice(0, verbatim), options: { ...parseOptions(globalOptions), ...parseOptions(options) }, allowPositionals: true, strict: true });
|
|
209
256
|
} catch (error) {
|
|
210
|
-
|
|
257
|
+
const option = error.message.match(/^Unknown option '(-[^']*)'/)?.[1];
|
|
258
|
+
throw new UsageError(option === undefined ? error.message : `${option} is not an option of ${JSON.stringify(trail.map(it => it.name).join(" "))}`);
|
|
211
259
|
}
|
|
212
260
|
if (parsed.values.help) {
|
|
213
261
|
help = true;
|
|
214
262
|
}
|
|
215
|
-
const args = parsed.positionals.slice(consumed);
|
|
216
|
-
const command = trail.at(-1);
|
|
263
|
+
const args = [...parsed.positionals.slice(consumed), ...argv.slice(verbatim)];
|
|
217
264
|
if (!help && (args.length < command.args[0] || args.length > command.args[1])) {
|
|
218
265
|
const [least, most] = command.args;
|
|
219
266
|
const expected = least === most ? `${least} arg(s)` : most === Infinity ? `at least ${least} arg(s)` : `between ${least} and ${most} arg(s)`;
|
|
@@ -234,7 +281,7 @@ function printError(error, target, operation) {
|
|
|
234
281
|
message = `Swipt at ${target.url} is unreachable: ${message}`;
|
|
235
282
|
break;
|
|
236
283
|
case Code.Unauthenticated:
|
|
237
|
-
message += `; connect with the URL from Swipt › Developer: swipt
|
|
284
|
+
message += `; connect with the URL from Swipt › Developer: swipt device add ${connection.withToken(target.url)}`;
|
|
238
285
|
break;
|
|
239
286
|
case Code.Unimplemented:
|
|
240
287
|
message = `Swipt at ${target.url} does not have this command; update Swipt`;
|
|
@@ -262,9 +309,6 @@ async function main() {
|
|
|
262
309
|
}
|
|
263
310
|
const command = resolved.trail.at(-1);
|
|
264
311
|
const values = command.parse ? command.parse(resolved.values) : resolved.values;
|
|
265
|
-
if (values.project !== undefined) {
|
|
266
|
-
sources.root = path.resolve(values.project);
|
|
267
|
-
}
|
|
268
312
|
await command.run({ args: resolved.args, options: values, target, signal: interrupt.signal, printError: (error, operation) => printError(error, target, operation) });
|
|
269
313
|
} catch (error) {
|
|
270
314
|
if (error instanceof FalseResult) {
|
package/sourcemap.mjs
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
5
|
-
|
|
6
|
-
import { loadProject } from "./project.mjs";
|
|
7
|
-
|
|
8
|
-
const suffix = ".map";
|
|
9
|
-
|
|
10
|
-
function loadSourceMap(name) {
|
|
11
|
-
const raw = JSON.parse(fs.readFileSync(name, "utf8"));
|
|
12
|
-
const directory = path.dirname(name);
|
|
13
|
-
const working = process.cwd();
|
|
14
|
-
const resolve = source => {
|
|
15
|
-
const resolved = path.join(directory, raw.sourceRoot ?? "", source);
|
|
16
|
-
const relative = path.relative(working, resolved);
|
|
17
|
-
return relative.startsWith("..") || path.isAbsolute(relative) ? resolved : relative;
|
|
18
|
-
};
|
|
19
|
-
const map = new TraceMap({ ...raw, sourceRoot: undefined });
|
|
20
|
-
return (line, column) => {
|
|
21
|
-
const found = originalPositionFor(map, { line, column: column - 1 });
|
|
22
|
-
return found.source === null ? undefined : { file: resolve(found.source), line: found.line, column: found.column + 1 };
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class SourceMaps {
|
|
27
|
-
root = "";
|
|
28
|
-
loaded = new Map();
|
|
29
|
-
|
|
30
|
-
forSwiplet(id) {
|
|
31
|
-
if (this.root === "") {
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
34
|
-
if (this.loaded.has(id)) {
|
|
35
|
-
return this.loaded.get(id);
|
|
36
|
-
}
|
|
37
|
-
const found = {};
|
|
38
|
-
let project;
|
|
39
|
-
try {
|
|
40
|
-
project = loadProject(this.root);
|
|
41
|
-
} catch {
|
|
42
|
-
project = undefined;
|
|
43
|
-
}
|
|
44
|
-
if (project?.config.id === id) {
|
|
45
|
-
const directory = path.join(project.root, "dist", id, "src");
|
|
46
|
-
let names = [];
|
|
47
|
-
try {
|
|
48
|
-
names = fs.readdirSync(directory).filter(name => name.endsWith(suffix));
|
|
49
|
-
} catch {
|
|
50
|
-
names = [];
|
|
51
|
-
}
|
|
52
|
-
for (const name of names) {
|
|
53
|
-
try {
|
|
54
|
-
found[name.slice(0, -suffix.length)] = loadSourceMap(path.join(directory, name));
|
|
55
|
-
} catch {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
this.loaded.set(id, found);
|
|
61
|
-
return found;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
forget(id) {
|
|
65
|
-
this.loaded.delete(id);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
frame(id) {
|
|
69
|
-
return frame => {
|
|
70
|
-
const lookup = this.forSwiplet(id)[frame.file];
|
|
71
|
-
const mapped = lookup?.(frame.line, frame.column);
|
|
72
|
-
return mapped ? { ...frame, ...mapped } : frame;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const sources = new SourceMaps();
|