@whatcha/cli 0.3.0 → 0.6.0
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 +120 -3
- package/dist/cli.js +18 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/diary-cover.js +54 -0
- package/dist/commands/diary-cover.js.map +1 -0
- package/dist/commands/entry.js +367 -0
- package/dist/commands/entry.js.map +1 -0
- package/dist/commands/profiles.js +125 -33
- package/dist/commands/profiles.js.map +1 -1
- package/dist/commands/read.js +7 -1
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/shared-diary.js +5 -3
- package/dist/commands/shared-diary.js.map +1 -1
- package/dist/commands/whoami.js +12 -2
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/write.js +187 -10
- package/dist/commands/write.js.map +1 -1
- package/dist/lib/append-entry.js +188 -52
- package/dist/lib/append-entry.js.map +1 -1
- package/dist/lib/entry-date.js +2 -2
- package/dist/lib/entry-date.js.map +1 -1
- package/dist/lib/entry-edit.js +75 -0
- package/dist/lib/entry-edit.js.map +1 -0
- package/dist/lib/entry-get.js +31 -0
- package/dist/lib/entry-get.js.map +1 -0
- package/dist/lib/profile-family.js +14 -0
- package/dist/lib/profile-family.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseEntryArgs = parseEntryArgs;
|
|
7
|
+
exports.runEntry = runEntry;
|
|
8
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const session_1 = require("../auth/session");
|
|
11
|
+
const errors_1 = require("../errors");
|
|
12
|
+
const append_entry_1 = require("../lib/append-entry");
|
|
13
|
+
const entry_edit_1 = require("../lib/entry-edit");
|
|
14
|
+
const entry_get_1 = require("../lib/entry-get");
|
|
15
|
+
const image_upload_1 = require("../lib/image-upload");
|
|
16
|
+
const output_1 = require("../lib/output");
|
|
17
|
+
const profile_family_1 = require("../lib/profile-family");
|
|
18
|
+
const write_1 = require("./write");
|
|
19
|
+
/**
|
|
20
|
+
* `whatcha entry update | delete` -- change or take back an entry that is
|
|
21
|
+
* already there (#117), by its id (`whatcha write` and `whatcha read
|
|
22
|
+
* --json` print it as `entryId`). The app's own calls:
|
|
23
|
+
*
|
|
24
|
+
* update PATCH /entries/{id}, optimistic-locked on the head commit
|
|
25
|
+
* delete DELETE /entries/{id} (a soft delete)
|
|
26
|
+
*
|
|
27
|
+
* Who may do either is the API's call -- the diary's `entry` update/delete
|
|
28
|
+
* permission, printed as the API's own error when refused. What this file
|
|
29
|
+
* adds is the CLI's one promise, nothing somebody else wrote is removed:
|
|
30
|
+
* replacing the text of an entry other people wrote in, or deleting it,
|
|
31
|
+
* needs `--all`, and `--mine` replaces only your own lines (lib/entry-edit.ts).
|
|
32
|
+
*
|
|
33
|
+
* Both run with no terminal at all: everything is a flag, and `delete`
|
|
34
|
+
* without `--yes` asks only when there is a terminal to ask at.
|
|
35
|
+
*/
|
|
36
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
37
|
+
const MAX_MESSAGE = 500;
|
|
38
|
+
const MAX_MEDIA = 50;
|
|
39
|
+
const UPDATE_USAGE = 'whatcha entry update <entry-id> [--text "..." | --text-file <path|->] [--mine | --all] [--title "..."] [--tag <tag>]... ' +
|
|
40
|
+
'[--image <file>]... [--remove-image <media-id>]... [--cover <file>] [--mood <mood>] [--location "..."] ' +
|
|
41
|
+
'[--lat <deg> --lng <deg>] [--message "..."] [--json]';
|
|
42
|
+
const DELETE_USAGE = "whatcha entry delete <entry-id> [--yes] [--all] [--json]";
|
|
43
|
+
const USAGE = `Usage:\n ${UPDATE_USAGE}\n ${DELETE_USAGE}`;
|
|
44
|
+
const UPDATE_ONLY = ["text", "text-file", "mine", "title", "tag", "image", "remove-image", "cover", "mood", "location", "lat", "lng", "message"];
|
|
45
|
+
function parseEntryArgs(argv) {
|
|
46
|
+
const [action, ...rest] = argv;
|
|
47
|
+
let values, positionals;
|
|
48
|
+
try {
|
|
49
|
+
({ values, positionals } = (0, node_util_1.parseArgs)({
|
|
50
|
+
args: (0, write_1.negativeDegrees)(rest),
|
|
51
|
+
options: {
|
|
52
|
+
json: { type: "boolean", default: false },
|
|
53
|
+
text: { type: "string" },
|
|
54
|
+
"text-file": { type: "string" },
|
|
55
|
+
mine: { type: "boolean", default: false },
|
|
56
|
+
all: { type: "boolean", default: false },
|
|
57
|
+
title: { type: "string" },
|
|
58
|
+
tag: { type: "string", multiple: true },
|
|
59
|
+
image: { type: "string", multiple: true },
|
|
60
|
+
"remove-image": { type: "string", multiple: true },
|
|
61
|
+
cover: { type: "string" },
|
|
62
|
+
mood: { type: "string" },
|
|
63
|
+
location: { type: "string" },
|
|
64
|
+
lat: { type: "string" },
|
|
65
|
+
lng: { type: "string" },
|
|
66
|
+
message: { type: "string" },
|
|
67
|
+
yes: { type: "boolean", short: "y", default: false },
|
|
68
|
+
},
|
|
69
|
+
allowPositionals: true,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new errors_1.CliUsageError(err.message);
|
|
74
|
+
}
|
|
75
|
+
const json = values.json;
|
|
76
|
+
const entryId = () => {
|
|
77
|
+
if (positionals.length === 0)
|
|
78
|
+
throw new errors_1.CliUsageError(`Which entry? Give its id (\`whatcha read --json\` prints it as entryId).\n${USAGE}`);
|
|
79
|
+
if (positionals.length > 1)
|
|
80
|
+
throw new errors_1.CliUsageError(`Unexpected extra argument: "${positionals[1]}"`);
|
|
81
|
+
if (!UUID_RE.test(positionals[0])) {
|
|
82
|
+
throw new errors_1.CliUsageError(`An entry id is a UUID (\`whatcha read --json\` prints it as entryId), got "${positionals[0]}".`);
|
|
83
|
+
}
|
|
84
|
+
return positionals[0].toLowerCase();
|
|
85
|
+
};
|
|
86
|
+
switch (action) {
|
|
87
|
+
case "update":
|
|
88
|
+
if (values.yes)
|
|
89
|
+
throw new errors_1.CliUsageError(`--yes does not apply to \`whatcha entry update\`.\nUsage: ${UPDATE_USAGE}`);
|
|
90
|
+
return parseUpdate(values, entryId(), json);
|
|
91
|
+
case "delete":
|
|
92
|
+
for (const name of UPDATE_ONLY) {
|
|
93
|
+
const v = values[name];
|
|
94
|
+
if (v !== undefined && v !== false)
|
|
95
|
+
throw new errors_1.CliUsageError(`--${name} does not apply to \`whatcha entry delete\`.\nUsage: ${DELETE_USAGE}`);
|
|
96
|
+
}
|
|
97
|
+
return { action, json, entryId: entryId(), yes: values.yes, all: values.all };
|
|
98
|
+
default:
|
|
99
|
+
throw new errors_1.CliUsageError(action ? `Unknown entry action "${action}".\n${USAGE}` : USAGE);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function parseUpdate(values, entryId, json) {
|
|
103
|
+
const textFile = values["text-file"];
|
|
104
|
+
const text = values.text;
|
|
105
|
+
if (text !== undefined && textFile !== undefined)
|
|
106
|
+
throw new errors_1.CliUsageError("Give --text or --text-file, not both.");
|
|
107
|
+
if (textFile !== undefined && !textFile.trim())
|
|
108
|
+
throw new errors_1.CliUsageError("--text-file takes a path, or - for standard input.");
|
|
109
|
+
const mine = values.mine;
|
|
110
|
+
const all = values.all;
|
|
111
|
+
const hasText = text !== undefined || textFile !== undefined;
|
|
112
|
+
if (mine && all)
|
|
113
|
+
throw new errors_1.CliUsageError("Give --mine or --all, not both.");
|
|
114
|
+
if ((mine || all) && !hasText)
|
|
115
|
+
throw new errors_1.CliUsageError(`${mine ? "--mine" : "--all"} says which text to replace; give the new text with --text or --text-file.`);
|
|
116
|
+
// Empty text is only "remove my part" -- an entry's whole text is never blanked by accident.
|
|
117
|
+
if (text !== undefined && !text.trim() && !mine) {
|
|
118
|
+
throw new errors_1.CliUsageError("--text must not be empty (with --mine, an empty --text removes your part).");
|
|
119
|
+
}
|
|
120
|
+
const title = values.title?.trim();
|
|
121
|
+
if (title !== undefined) {
|
|
122
|
+
if (!title)
|
|
123
|
+
throw new errors_1.CliUsageError("--title must not be empty.");
|
|
124
|
+
if (title.length > append_entry_1.MAX_TITLE_LENGTH)
|
|
125
|
+
throw new errors_1.CliUsageError(`--title is at most ${append_entry_1.MAX_TITLE_LENGTH} characters.`);
|
|
126
|
+
}
|
|
127
|
+
const rawTags = values.tag;
|
|
128
|
+
for (const t of rawTags ?? []) {
|
|
129
|
+
if (!t.trim())
|
|
130
|
+
throw new errors_1.CliUsageError("--tag must not be empty.");
|
|
131
|
+
if (t.trim().length > append_entry_1.MAX_TAG_LENGTH)
|
|
132
|
+
throw new errors_1.CliUsageError(`A --tag is at most ${append_entry_1.MAX_TAG_LENGTH} characters: "${t}".`);
|
|
133
|
+
}
|
|
134
|
+
const tags = rawTags ? (0, append_entry_1.normalizeTags)(rawTags) : undefined;
|
|
135
|
+
if (tags && tags.length > append_entry_1.MAX_TAGS)
|
|
136
|
+
throw new errors_1.CliUsageError(`At most ${append_entry_1.MAX_TAGS} tags per entry.`);
|
|
137
|
+
const mood = values.mood?.trim();
|
|
138
|
+
if (mood !== undefined) {
|
|
139
|
+
if (!mood)
|
|
140
|
+
throw new errors_1.CliUsageError(`--mood must not be empty (the app's are ${write_1.APP_MOODS.join(" ")}).`);
|
|
141
|
+
if (mood.length > append_entry_1.MAX_MOOD_LENGTH)
|
|
142
|
+
throw new errors_1.CliUsageError(`--mood is at most ${append_entry_1.MAX_MOOD_LENGTH} characters.`);
|
|
143
|
+
}
|
|
144
|
+
const location = values.location?.trim();
|
|
145
|
+
if (location !== undefined) {
|
|
146
|
+
if (!location)
|
|
147
|
+
throw new errors_1.CliUsageError("--location must not be empty.");
|
|
148
|
+
if (location.length > append_entry_1.MAX_LOCATION_LENGTH)
|
|
149
|
+
throw new errors_1.CliUsageError(`--location is at most ${append_entry_1.MAX_LOCATION_LENGTH} characters.`);
|
|
150
|
+
}
|
|
151
|
+
const rawLat = values.lat;
|
|
152
|
+
const rawLng = values.lng;
|
|
153
|
+
if ((rawLat === undefined) !== (rawLng === undefined))
|
|
154
|
+
throw new errors_1.CliUsageError("Give --lat and --lng together: a point needs both.");
|
|
155
|
+
const coordinates = rawLat !== undefined && rawLng !== undefined ? { lat: (0, write_1.degrees)(rawLat, "--lat", 90), lng: (0, write_1.degrees)(rawLng, "--lng", 180) } : undefined;
|
|
156
|
+
const images = values.image ?? [];
|
|
157
|
+
if (images.some((f) => !f.trim()))
|
|
158
|
+
throw new errors_1.CliUsageError("--image takes a file path.");
|
|
159
|
+
const removeImages = (values["remove-image"] ?? []).map((id) => id.trim().toLowerCase());
|
|
160
|
+
for (const id of removeImages) {
|
|
161
|
+
if (!UUID_RE.test(id))
|
|
162
|
+
throw new errors_1.CliUsageError(`--remove-image takes an image's id (\`whatcha read --json\` lists them as images), got "${id}".`);
|
|
163
|
+
}
|
|
164
|
+
const cover = values.cover;
|
|
165
|
+
if (cover !== undefined && !cover.trim())
|
|
166
|
+
throw new errors_1.CliUsageError("--cover takes a file path.");
|
|
167
|
+
const message = values.message?.trim();
|
|
168
|
+
if (message !== undefined && message.length > MAX_MESSAGE)
|
|
169
|
+
throw new errors_1.CliUsageError(`--message is at most ${MAX_MESSAGE} characters.`);
|
|
170
|
+
const changesSomething = hasText || title !== undefined || tags !== undefined || mood !== undefined || location !== undefined ||
|
|
171
|
+
coordinates !== undefined || images.length > 0 || removeImages.length > 0 || cover !== undefined;
|
|
172
|
+
if (!changesSomething)
|
|
173
|
+
throw new errors_1.CliUsageError(`Nothing to update -- give the text or a field to change.\nUsage: ${UPDATE_USAGE}`);
|
|
174
|
+
return {
|
|
175
|
+
action: "update",
|
|
176
|
+
json,
|
|
177
|
+
entryId,
|
|
178
|
+
...(text !== undefined ? { text } : {}),
|
|
179
|
+
...(textFile !== undefined ? { textFile } : {}),
|
|
180
|
+
mine,
|
|
181
|
+
all,
|
|
182
|
+
...(title !== undefined ? { title } : {}),
|
|
183
|
+
...(tags !== undefined ? { tags } : {}),
|
|
184
|
+
...(mood !== undefined ? { mood } : {}),
|
|
185
|
+
...(location !== undefined ? { location } : {}),
|
|
186
|
+
...(coordinates ? { coordinates } : {}),
|
|
187
|
+
images,
|
|
188
|
+
removeImages,
|
|
189
|
+
...(cover !== undefined ? { cover } : {}),
|
|
190
|
+
...(message ? { message } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function runEntry(opts, deps = {}) {
|
|
194
|
+
return opts.action === "update" ? runUpdate(opts, deps) : runDelete(opts, deps);
|
|
195
|
+
}
|
|
196
|
+
function concurrentSave(err) {
|
|
197
|
+
return new errors_1.CliUsageError("Someone else saved to this entry while whatcha was updating it. Nothing was overwritten -- " +
|
|
198
|
+
`run the same command again to retry. (${err.message})`);
|
|
199
|
+
}
|
|
200
|
+
async function readableEntry(client, entryId) {
|
|
201
|
+
try {
|
|
202
|
+
return await (0, entry_get_1.getEntry)(client, entryId);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (err instanceof errors_1.ApiError && err.status === 404) {
|
|
206
|
+
throw new errors_1.CliUsageError(`No entry with id ${entryId} (it was deleted, or is not visible to you).`);
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function runUpdate(opts, deps) {
|
|
212
|
+
// Every local file is read and checked before anything is sent, so a wrong path changes nothing.
|
|
213
|
+
let text = opts.text;
|
|
214
|
+
if (opts.textFile !== undefined) {
|
|
215
|
+
text = await (0, write_1.readTextFile)(opts.textFile, deps.stdin);
|
|
216
|
+
if (!text.trim() && !opts.mine) {
|
|
217
|
+
throw new errors_1.CliUsageError(`${opts.textFile === "-" ? "Standard input" : opts.textFile} has no text.`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const images = [];
|
|
221
|
+
for (const path of opts.images)
|
|
222
|
+
images.push(await (0, image_upload_1.readImageFile)(path));
|
|
223
|
+
const coverFile = opts.cover !== undefined ? await (0, image_upload_1.readImageFile)(opts.cover) : undefined;
|
|
224
|
+
const client = deps.client ?? (await (0, session_1.authenticatedClient)()).client;
|
|
225
|
+
const entry = await readableEntry(client, opts.entryId);
|
|
226
|
+
if (entry.isLocked)
|
|
227
|
+
throw new errors_1.CliUsageError("This entry is locked (Time Capsule) and cannot be changed until it opens.");
|
|
228
|
+
let body = entry.body;
|
|
229
|
+
let textMode = null;
|
|
230
|
+
if (text !== undefined) {
|
|
231
|
+
const me = await (0, profile_family_1.currentProfile)(client);
|
|
232
|
+
const blame = await (0, entry_edit_1.getBlame)(client, entry.id, entry.headCommitId);
|
|
233
|
+
if (opts.mine) {
|
|
234
|
+
if (!(0, entry_edit_1.blameMatches)(blame, entry.body)) {
|
|
235
|
+
throw new errors_1.CliUsageError("This entry changed while whatcha was reading it. Nothing was changed -- run the command again.");
|
|
236
|
+
}
|
|
237
|
+
body = (0, entry_edit_1.replaceMyPart)(blame, me.id, text);
|
|
238
|
+
textMode = "mine";
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
const others = (0, entry_edit_1.otherWriters)(blame, me.id);
|
|
242
|
+
if (others.length > 0 && !opts.all) {
|
|
243
|
+
throw new errors_1.CliUsageError(`${others.join(", ")} also wrote in this entry. Nothing was changed. Use --mine to replace only ` +
|
|
244
|
+
"the part you wrote, or --all to replace the whole text, theirs included.");
|
|
245
|
+
}
|
|
246
|
+
body = text;
|
|
247
|
+
textMode = "all";
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Images: the entry's own, minus the ones taken off, then the new ones.
|
|
251
|
+
const current = (0, entry_get_1.entryMedia)(entry);
|
|
252
|
+
const missing = opts.removeImages.filter((id) => !current.some((m) => m.mediaAssetId.toLowerCase() === id));
|
|
253
|
+
if (missing.length > 0) {
|
|
254
|
+
throw new errors_1.CliUsageError(`This entry has no image ${missing.join(", ")} (\`whatcha read --json\` lists its images). Nothing was changed.`);
|
|
255
|
+
}
|
|
256
|
+
const kept = current.filter((m) => !opts.removeImages.includes(m.mediaAssetId.toLowerCase()));
|
|
257
|
+
if (kept.length + images.length > MAX_MEDIA)
|
|
258
|
+
throw new errors_1.CliUsageError(`An entry holds at most ${MAX_MEDIA} images. Nothing was changed.`);
|
|
259
|
+
// Into the diary's own group: an entry can only show media of its group.
|
|
260
|
+
const imageIds = [];
|
|
261
|
+
let coverId;
|
|
262
|
+
if (images.length > 0 || coverFile) {
|
|
263
|
+
const diary = await client.get(`/diaries/${entry.diaryId}`);
|
|
264
|
+
for (const image of images)
|
|
265
|
+
imageIds.push(await (0, image_upload_1.uploadImage)(client, diary.groupId, image));
|
|
266
|
+
if (coverFile)
|
|
267
|
+
coverId = await (0, image_upload_1.uploadImage)(client, diary.groupId, coverFile);
|
|
268
|
+
}
|
|
269
|
+
let header = (0, append_entry_1.headerOf)(entry);
|
|
270
|
+
if (opts.title !== undefined)
|
|
271
|
+
header = { ...header, title: opts.title };
|
|
272
|
+
if (opts.tags !== undefined)
|
|
273
|
+
header = { ...header, tags: opts.tags };
|
|
274
|
+
if (opts.mood !== undefined)
|
|
275
|
+
header = { ...header, mood: opts.mood };
|
|
276
|
+
if (opts.location !== undefined)
|
|
277
|
+
header = { ...header, location: opts.location };
|
|
278
|
+
if (opts.coordinates)
|
|
279
|
+
header = { ...header, locationLat: opts.coordinates.lat, locationLng: opts.coordinates.lng };
|
|
280
|
+
if (coverId)
|
|
281
|
+
header = { ...header, coverMediaAssetId: coverId };
|
|
282
|
+
const changesMedia = opts.removeImages.length > 0 || imageIds.length > 0;
|
|
283
|
+
const media = [...kept, ...imageIds.map((mediaAssetId) => ({ mediaAssetId }))];
|
|
284
|
+
// A `PATCH /entries` save writes the header from `content`, which has no
|
|
285
|
+
// field for the time range, so a range the entry has is saved again after.
|
|
286
|
+
const range = await (0, append_entry_1.readRange)(client, entry.diaryId, entry.id);
|
|
287
|
+
let headCommitId;
|
|
288
|
+
try {
|
|
289
|
+
const saved = await client.patch(`/entries/${entry.id}`, {
|
|
290
|
+
content: { body, ...header },
|
|
291
|
+
...(changesMedia ? { media } : {}),
|
|
292
|
+
expectedHeadCommitId: entry.headCommitId,
|
|
293
|
+
...(opts.message ? { commitMessage: opts.message } : {}),
|
|
294
|
+
});
|
|
295
|
+
headCommitId = saved.headCommitId;
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
if (err instanceof errors_1.ApiError && err.status === 409)
|
|
299
|
+
throw concurrentSave(err);
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
if (range)
|
|
303
|
+
headCommitId = await (0, append_entry_1.saveRange)(client, entry.diaryId, entry.id, range, headCommitId);
|
|
304
|
+
if (opts.json) {
|
|
305
|
+
(0, output_1.printJson)({
|
|
306
|
+
ok: true,
|
|
307
|
+
entryId: entry.id,
|
|
308
|
+
diaryId: entry.diaryId,
|
|
309
|
+
headCommitId,
|
|
310
|
+
text: textMode ?? "unchanged",
|
|
311
|
+
title: header.title,
|
|
312
|
+
tags: header.tags,
|
|
313
|
+
images: media.map((m) => m.mediaAssetId),
|
|
314
|
+
coverMediaAssetId: header.coverMediaAssetId ?? null,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const what = textMode === "mine" ? "your part of the text" : textMode === "all" ? "the text" : "the entry";
|
|
319
|
+
(0, output_1.printLine)(`Updated ${what}${imageIds.length > 0 ? ` (+${imageIds.length} image${imageIds.length === 1 ? "" : "s"})` : ""}. entryId=${entry.id}`);
|
|
320
|
+
}
|
|
321
|
+
return { exitCode: 0 };
|
|
322
|
+
}
|
|
323
|
+
function askOnTerminal(question) {
|
|
324
|
+
return new Promise((resolve) => {
|
|
325
|
+
// On stderr, so `--json` stdout stays the one object it promises.
|
|
326
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
327
|
+
rl.question(question, (answer) => {
|
|
328
|
+
rl.close();
|
|
329
|
+
resolve(answer);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async function runDelete(opts, deps) {
|
|
334
|
+
const client = deps.client ?? (await (0, session_1.authenticatedClient)()).client;
|
|
335
|
+
const entry = await readableEntry(client, opts.entryId);
|
|
336
|
+
if (!opts.all) {
|
|
337
|
+
const me = await (0, profile_family_1.currentProfile)(client);
|
|
338
|
+
const others = (0, entry_edit_1.otherWriters)(await (0, entry_edit_1.getBlame)(client, entry.id, entry.headCommitId), me.id);
|
|
339
|
+
if (others.length > 0) {
|
|
340
|
+
throw new errors_1.CliUsageError(`${others.join(", ")} also wrote in this entry, so deleting it deletes their writing too. Nothing was deleted. ` +
|
|
341
|
+
`To take back only your part: whatcha entry update ${entry.id} --mine --text "". ` +
|
|
342
|
+
"To delete the whole entry anyway: add --all.");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!opts.yes) {
|
|
346
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
347
|
+
if (!isTTY) {
|
|
348
|
+
throw new errors_1.CliUsageError("Not deleting: there is no terminal to confirm at. Add --yes to delete without asking.");
|
|
349
|
+
}
|
|
350
|
+
const label = `${entry.occurredAt.slice(0, 10)}${entry.title ? ` "${entry.title}"` : ""}`;
|
|
351
|
+
const answer = await (deps.confirm ?? askOnTerminal)(`Delete the entry ${label} (${entry.id})? [y/N] `);
|
|
352
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
353
|
+
if (opts.json)
|
|
354
|
+
(0, output_1.printJson)({ ok: true, entryId: entry.id, deleted: false });
|
|
355
|
+
else
|
|
356
|
+
(0, output_1.printErrorLine)("Not deleted.");
|
|
357
|
+
return { exitCode: 1 };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
await client.delete(`/entries/${entry.id}`);
|
|
361
|
+
if (opts.json)
|
|
362
|
+
(0, output_1.printJson)({ ok: true, entryId: entry.id, diaryId: entry.diaryId, deleted: true });
|
|
363
|
+
else
|
|
364
|
+
(0, output_1.printLine)(`Deleted the entry ${entry.id}.`);
|
|
365
|
+
return { exitCode: 0 };
|
|
366
|
+
}
|
|
367
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/commands/entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,8 @@ exports.runProfiles = runProfiles;
|
|
|
5
5
|
exports.parseRemoveArgs = parseRemoveArgs;
|
|
6
6
|
exports.runRemove = runRemove;
|
|
7
7
|
exports.parseAddArgs = parseAddArgs;
|
|
8
|
+
exports.parseAvatarArgs = parseAvatarArgs;
|
|
9
|
+
exports.runAvatar = runAvatar;
|
|
8
10
|
exports.runAdd = runAdd;
|
|
9
11
|
exports.parseSwitchArgs = parseSwitchArgs;
|
|
10
12
|
exports.runSwitch = runSwitch;
|
|
@@ -34,14 +36,15 @@ const label = (p) => p.displayName && p.displayName.length > 0 ? p.displayName :
|
|
|
34
36
|
async function runProfiles(opts, deps = {}) {
|
|
35
37
|
const resolved = deps.client ? null : await (0, session_1.authenticatedClient)();
|
|
36
38
|
const client = deps.client ?? resolved.client;
|
|
37
|
-
const session = deps.session ?? resolved.session;
|
|
38
39
|
const switcher = await client.get("/me/profiles");
|
|
40
|
+
// The server's answer, not the saved note (#58).
|
|
41
|
+
const current = (await (0, profile_family_1.currentProfile)(client)).id;
|
|
39
42
|
if (opts.json) {
|
|
40
|
-
(0, output_1.printJson)({ ok: true, ...switcher, currentProfileId:
|
|
43
|
+
(0, output_1.printJson)({ ok: true, ...switcher, currentProfileId: current });
|
|
41
44
|
return { exitCode: 0 };
|
|
42
45
|
}
|
|
43
46
|
for (const p of switcher.profiles) {
|
|
44
|
-
const here = p.id ===
|
|
47
|
+
const here = p.id === current ? "* " : " ";
|
|
45
48
|
const kind = p.parentProfileId === null ? " (account)" : "";
|
|
46
49
|
(0, output_1.printLine)(`${here}@${p.username ?? "?"} ${label(p)}${kind}`);
|
|
47
50
|
}
|
|
@@ -130,6 +133,79 @@ function parseAddArgs(argv) {
|
|
|
130
133
|
}
|
|
131
134
|
return { username, displayName, avatar: values.avatar, json: values.json };
|
|
132
135
|
}
|
|
136
|
+
/** Reads an avatar file and checks it against `POST /me/avatar`'s limits. */
|
|
137
|
+
async function readAvatarFile(path) {
|
|
138
|
+
const avatar = await (0, image_upload_1.readImageFile)(path);
|
|
139
|
+
if (avatar.bytes.length > MAX_AVATAR_BYTES) {
|
|
140
|
+
throw new errors_1.CliUsageError(`${avatar.path} is over 5 MB; an avatar is at most 5 MB.`);
|
|
141
|
+
}
|
|
142
|
+
return avatar;
|
|
143
|
+
}
|
|
144
|
+
/** `POST /me/avatar` as whoever `client` is. The new avatar's URL. */
|
|
145
|
+
async function setAvatar(client, avatar) {
|
|
146
|
+
const set = await client.post("/me/avatar", {
|
|
147
|
+
fileName: avatar.fileName,
|
|
148
|
+
contentType: avatar.contentType,
|
|
149
|
+
dataBase64: Buffer.from(avatar.bytes).toString("base64"),
|
|
150
|
+
});
|
|
151
|
+
return set.avatarUrl;
|
|
152
|
+
}
|
|
153
|
+
function parseAvatarArgs(argv) {
|
|
154
|
+
let values;
|
|
155
|
+
let positionals;
|
|
156
|
+
try {
|
|
157
|
+
({ values, positionals } = (0, node_util_1.parseArgs)({
|
|
158
|
+
args: argv,
|
|
159
|
+
options: { json: { type: "boolean", default: false } },
|
|
160
|
+
allowPositionals: true,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
throw new errors_1.CliUsageError(err.message);
|
|
165
|
+
}
|
|
166
|
+
const [target, file, extra] = positionals;
|
|
167
|
+
if (!target || !file)
|
|
168
|
+
throw new errors_1.CliUsageError("Usage: whatcha profile-avatar <@handle> <file> [--json]");
|
|
169
|
+
if (extra !== undefined)
|
|
170
|
+
throw new errors_1.CliUsageError(`Unexpected extra argument: "${extra}"`);
|
|
171
|
+
return { target, file, json: values.json };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Change an existing profile's avatar (#50) -- until now only `profile-add
|
|
175
|
+
* --avatar` could set one, at creation. The profile the CLI is signed in
|
|
176
|
+
* as sets its own; another of this login's sub-profiles is set through a
|
|
177
|
+
* session for it, used once and dropped, as `profile-add` does. The account
|
|
178
|
+
* itself cannot be reached from a sub-profile (the API refuses that on
|
|
179
|
+
* purpose), so that case says to switch back first.
|
|
180
|
+
*/
|
|
181
|
+
async function runAvatar(opts, deps = {}) {
|
|
182
|
+
const avatar = await readAvatarFile(opts.file);
|
|
183
|
+
const resolved = deps.client ? null : await (0, session_1.authenticatedClient)();
|
|
184
|
+
const client = deps.client ?? resolved.client;
|
|
185
|
+
const forToken = deps.clientForToken ?? (0, profile_family_1.bearerClients)(resolved?.session.apiBaseUrl || (0, config_1.apiBaseUrl)());
|
|
186
|
+
const switcher = await client.get("/me/profiles");
|
|
187
|
+
const target = (0, profile_family_1.findProfile)(switcher.profiles, opts.target);
|
|
188
|
+
if (!target) {
|
|
189
|
+
throw new errors_1.CliUsageError(`No profile "${opts.target}". Run \`whatcha profiles\` to see them.`);
|
|
190
|
+
}
|
|
191
|
+
// Who the credential is, asked of the server (#58): a stale saved id made
|
|
192
|
+
// this set the ACCOUNT's avatar while naming a sub-profile.
|
|
193
|
+
const current = await (0, profile_family_1.currentProfile)(client);
|
|
194
|
+
let asTarget;
|
|
195
|
+
if (target.id === current.id)
|
|
196
|
+
asTarget = client;
|
|
197
|
+
else if (target.parentProfileId === null) {
|
|
198
|
+
throw new errors_1.CliUsageError("That is the account. Run `whatcha switch --account` first, then this again.");
|
|
199
|
+
}
|
|
200
|
+
else
|
|
201
|
+
asTarget = await (0, profile_family_1.clientAsProfile)(client, target.id, forToken);
|
|
202
|
+
const avatarUrl = await setAvatar(asTarget, avatar);
|
|
203
|
+
if (opts.json)
|
|
204
|
+
(0, output_1.printJson)({ ok: true, profileId: target.id, avatarUrl });
|
|
205
|
+
else
|
|
206
|
+
(0, output_1.printLine)(`Changed the avatar of @${target.username ?? target.id}.`);
|
|
207
|
+
return { exitCode: 0 };
|
|
208
|
+
}
|
|
133
209
|
/**
|
|
134
210
|
* Create a sub-profile (#1699): `POST /me/profiles`, the call the app's
|
|
135
211
|
* "Add a profile" dialog makes. The plan's limit and a taken @handle are
|
|
@@ -144,10 +220,7 @@ function parseAddArgs(argv) {
|
|
|
144
220
|
* than undone -- the profile is real and usable, just without a picture.
|
|
145
221
|
*/
|
|
146
222
|
async function runAdd(opts, deps = {}) {
|
|
147
|
-
const avatar = opts.avatar !== undefined ? await (
|
|
148
|
-
if (avatar && avatar.bytes.length > MAX_AVATAR_BYTES) {
|
|
149
|
-
throw new errors_1.CliUsageError(`${avatar.path} is over 5 MB; an avatar is at most 5 MB.`);
|
|
150
|
-
}
|
|
223
|
+
const avatar = opts.avatar !== undefined ? await readAvatarFile(opts.avatar) : undefined;
|
|
151
224
|
const resolved = deps.client ? null : await (0, session_1.authenticatedClient)();
|
|
152
225
|
const client = deps.client ?? resolved.client;
|
|
153
226
|
const forToken = deps.clientForToken ?? (0, profile_family_1.bearerClients)(resolved?.session.apiBaseUrl || (0, config_1.apiBaseUrl)());
|
|
@@ -159,12 +232,7 @@ async function runAdd(opts, deps = {}) {
|
|
|
159
232
|
if (avatar) {
|
|
160
233
|
try {
|
|
161
234
|
const asProfile = await (0, profile_family_1.clientAsProfile)(client, profile.id, forToken);
|
|
162
|
-
|
|
163
|
-
fileName: avatar.fileName,
|
|
164
|
-
contentType: avatar.contentType,
|
|
165
|
-
dataBase64: Buffer.from(avatar.bytes).toString("base64"),
|
|
166
|
-
});
|
|
167
|
-
profile = { ...profile, avatarUrl: set.avatarUrl };
|
|
235
|
+
profile = { ...profile, avatarUrl: await setAvatar(asProfile, avatar) };
|
|
168
236
|
}
|
|
169
237
|
catch (err) {
|
|
170
238
|
if (!(err instanceof errors_1.ApiError))
|
|
@@ -178,7 +246,7 @@ async function runAdd(opts, deps = {}) {
|
|
|
178
246
|
(0, output_1.printLine)(`Created @${profile.username ?? opts.username} (${label(profile)}). Run \`whatcha switch @${profile.username ?? opts.username}\` to write as it.`);
|
|
179
247
|
if (avatarError) {
|
|
180
248
|
(0, output_1.printErrorLine)(`WARNING: the profile was created, but its avatar could not be set (${avatarError.code}: ${avatarError.message}). ` +
|
|
181
|
-
|
|
249
|
+
`Try again with \`whatcha profile-avatar @${profile.username ?? opts.username} <file>\`.`);
|
|
182
250
|
return { exitCode: 2 };
|
|
183
251
|
}
|
|
184
252
|
return { exitCode: 0 };
|
|
@@ -209,36 +277,53 @@ function parseSwitchArgs(argv) {
|
|
|
209
277
|
}
|
|
210
278
|
return { target, toAccount, json: values.json };
|
|
211
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Saves `session` with its `userId` / `email` set to who `client` is on the
|
|
282
|
+
* server (#58). The saved id is only a note beside the credential; it has
|
|
283
|
+
* to be rewritten whenever the credential changes, or every check that
|
|
284
|
+
* reads it (and 0.4.0 had several) answers for the wrong profile.
|
|
285
|
+
*/
|
|
286
|
+
async function saveAs(session, who, stores) {
|
|
287
|
+
const next = { ...session, userId: who.id, email: who.email };
|
|
288
|
+
await (0, session_1.saveSession)(next, stores);
|
|
289
|
+
return next;
|
|
290
|
+
}
|
|
212
291
|
async function runSwitch(opts, deps = {}) {
|
|
213
292
|
const stores = deps.stores ?? (0, token_store_1.candidateStores)();
|
|
293
|
+
const reopen = deps.reopen ?? (() => (0, session_1.authenticatedClient)(stores));
|
|
214
294
|
const resolved = deps.client ? null : await (0, session_1.authenticatedClient)(stores);
|
|
215
295
|
const client = deps.client ?? resolved.client;
|
|
216
296
|
const session = (deps.session ?? resolved.session);
|
|
217
297
|
if (opts.toAccount) {
|
|
218
298
|
const { accountRefreshToken: _dropped, accountKey, ...rest } = session;
|
|
299
|
+
let restored;
|
|
219
300
|
if (accountKey) {
|
|
220
301
|
// Logged in with a personal access key (#1663): the way back is the
|
|
221
302
|
// key itself, which needs no refresh.
|
|
222
|
-
|
|
223
|
-
if (opts.json)
|
|
224
|
-
(0, output_1.printJson)({ ok: true, switchedTo: "account" });
|
|
225
|
-
else
|
|
226
|
-
(0, output_1.printLine)("Back on the account.");
|
|
227
|
-
return { exitCode: 0 };
|
|
303
|
+
restored = { ...rest, accessToken: accountKey, refreshToken: "", expiresAt: 0, authKind: "key" };
|
|
228
304
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
305
|
+
else {
|
|
306
|
+
const back = session.accountRefreshToken;
|
|
307
|
+
if (!back) {
|
|
308
|
+
throw new errors_1.CliUsageError("Already on the account.");
|
|
309
|
+
}
|
|
310
|
+
// An access token cannot be derived here, so the stored one is left
|
|
311
|
+
// expired on purpose: reopening it below refreshes from the refresh
|
|
312
|
+
// token, the same path a session that sat unused overnight takes.
|
|
313
|
+
restored = { ...rest, refreshToken: back, expiresAt: 0, authKind: "session" };
|
|
232
314
|
}
|
|
233
|
-
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
|
|
315
|
+
await (0, session_1.saveSession)(restored, stores);
|
|
316
|
+
// #58: `rest` still carries the sub-profile's id and (absent) email.
|
|
317
|
+
// Reopen the account's credential now and ask the server who it is,
|
|
318
|
+
// so the saved note is the account's -- and a credential that no
|
|
319
|
+
// longer works fails here rather than on the next command.
|
|
320
|
+
const opened = await reopen();
|
|
321
|
+
const me = await (0, profile_family_1.currentProfile)(opened.client);
|
|
322
|
+
await saveAs(opened.session, me, stores);
|
|
238
323
|
if (opts.json)
|
|
239
|
-
(0, output_1.printJson)({ ok: true, switchedTo: "account" });
|
|
324
|
+
(0, output_1.printJson)({ ok: true, switchedTo: "account", profileId: me.id });
|
|
240
325
|
else
|
|
241
|
-
(0, output_1.printLine)(
|
|
326
|
+
(0, output_1.printLine)(`Back on the account (@${me.username ?? me.id}).`);
|
|
242
327
|
return { exitCode: 0 };
|
|
243
328
|
}
|
|
244
329
|
const switcher = await client.get("/me/profiles");
|
|
@@ -249,7 +334,11 @@ async function runSwitch(opts, deps = {}) {
|
|
|
249
334
|
if (target.parentProfileId === null) {
|
|
250
335
|
throw new errors_1.CliUsageError("To go back to the account, run `whatcha switch --account`.");
|
|
251
336
|
}
|
|
252
|
-
|
|
337
|
+
// "Already there" is the server's call, not the saved id's (#58).
|
|
338
|
+
const current = await (0, profile_family_1.currentProfile)(client);
|
|
339
|
+
if (target.id === current.id) {
|
|
340
|
+
if (session.userId !== current.id || session.email !== current.email)
|
|
341
|
+
await saveAs(session, current, stores);
|
|
253
342
|
if (opts.json)
|
|
254
343
|
(0, output_1.printJson)({ ok: true, switchedTo: target.id, alreadyThere: true });
|
|
255
344
|
else
|
|
@@ -257,6 +346,9 @@ async function runSwitch(opts, deps = {}) {
|
|
|
257
346
|
return { exitCode: 0 };
|
|
258
347
|
}
|
|
259
348
|
const issued = await client.post(`/me/profiles/${target.id}/session`, {});
|
|
349
|
+
if (issued.profileId && issued.profileId !== target.id) {
|
|
350
|
+
throw new errors_1.CliUsageError(`The server issued a session for ${issued.profileId}, not @${target.username ?? target.id}; nothing was changed.`);
|
|
351
|
+
}
|
|
260
352
|
// The account's credential is carried over BEFORE the session is
|
|
261
353
|
// replaced: after this save it is the only copy, and losing it would
|
|
262
354
|
// strand the CLI inside a sub-profile. A key login (#1663) has no refresh
|
|
@@ -267,7 +359,7 @@ async function runSwitch(opts, deps = {}) {
|
|
|
267
359
|
const { accountRefreshToken: _r, accountKey: _k, ...base } = session;
|
|
268
360
|
const next = {
|
|
269
361
|
...base,
|
|
270
|
-
userId:
|
|
362
|
+
userId: target.id,
|
|
271
363
|
// A sub-profile has no address of its own; the internal one the API
|
|
272
364
|
// holds for GoTrue is never handed out and must not be stored here.
|
|
273
365
|
email: null,
|
|
@@ -279,7 +371,7 @@ async function runSwitch(opts, deps = {}) {
|
|
|
279
371
|
};
|
|
280
372
|
await (0, session_1.saveSession)(next, stores);
|
|
281
373
|
if (opts.json)
|
|
282
|
-
(0, output_1.printJson)({ ok: true, switchedTo:
|
|
374
|
+
(0, output_1.printJson)({ ok: true, switchedTo: target.id });
|
|
283
375
|
else
|
|
284
376
|
(0, output_1.printLine)(`Writing as @${target.username ?? target.id}.`);
|
|
285
377
|
return { exitCode: 0 };
|