@zeegme/cli 0.4.0 → 0.5.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/dist/commands/bookings.js +12 -5
- package/dist/commands/util.js +13 -0
- package/dist/errors.js +5 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
1
2
|
import { unwrap } from '../api/client.js';
|
|
2
3
|
import { UsageError } from '../errors.js';
|
|
3
|
-
import { asJson, clockTime, collect, isoDate, localTimeZone, positiveInt } from './util.js';
|
|
4
|
+
import { asJson, clockTime, collect, isoDate, localTimeZone, overrideLevelOption, positiveInt } from './util.js';
|
|
4
5
|
const inviteeNames = (row) => (row.invitees ?? []).map((invitee) => invitee.fullName ?? invitee.email);
|
|
5
6
|
export function registerBookings(program, context) {
|
|
6
7
|
const bookings = program.command('bookings').description('Scheduled events (bookings)');
|
|
@@ -67,6 +68,8 @@ export function registerBookings(program, context) {
|
|
|
67
68
|
.option('--guest <email>', 'add a guest, repeatable', collect)
|
|
68
69
|
.option('--location <type>', 'location type, when the event type offers several')
|
|
69
70
|
.option('--location-key <key>', 'exact location key from the event type')
|
|
71
|
+
.option('--host <email>', 'book with this host (Round Robin event types only)')
|
|
72
|
+
.addOption(overrideLevelOption())
|
|
70
73
|
.option('-y, --yes', 'book without asking')
|
|
71
74
|
.action(async (uuid, options) => {
|
|
72
75
|
const ctx = context();
|
|
@@ -85,6 +88,8 @@ export function registerBookings(program, context) {
|
|
|
85
88
|
guests: options.guest,
|
|
86
89
|
location: options.location,
|
|
87
90
|
locationKey: options.locationKey,
|
|
91
|
+
hostEmail: options.host,
|
|
92
|
+
overrideLevel: options.overrideLevel,
|
|
88
93
|
},
|
|
89
94
|
})));
|
|
90
95
|
ctx.print.record(data, `Booked: ${data.title ?? 'meeting'} at ${data.startTime ?? `${options.date} ${options.time}`} (${data.uuid}).`);
|
|
@@ -111,13 +116,14 @@ export function registerBookings(program, context) {
|
|
|
111
116
|
.option('--tz <zone>', 'IANA time zone of --date and --time (default: the invitee’s)')
|
|
112
117
|
.option('--duration <minutes>', 'new length', positiveInt('--duration'))
|
|
113
118
|
.option('--reason <text>', 'shown to the invitees')
|
|
119
|
+
.addOption(overrideLevelOption())
|
|
114
120
|
.option('-y, --yes', 'reschedule without asking')
|
|
115
121
|
.action(async (uuid, options) => {
|
|
116
122
|
const ctx = context();
|
|
117
123
|
await ctx.confirm(`Move booking ${uuid} to ${options.date} ${options.time}? The invitees are notified.`, options.yes);
|
|
118
124
|
const data = asJson(await unwrap((await ctx.api()).PUT('/scheduled-events/{uuid}/reschedule', {
|
|
119
125
|
params: { path: { uuid } },
|
|
120
|
-
body: { date: options.date, start: options.time, timeZone: options.tz, duration: options.duration, reason: options.reason },
|
|
126
|
+
body: { date: options.date, start: options.time, timeZone: options.tz, duration: options.duration, reason: options.reason, overrideLevel: options.overrideLevel },
|
|
121
127
|
})));
|
|
122
128
|
ctx.print.record(data.resource, `Rescheduled ${uuid} to ${options.date} ${options.time}.`);
|
|
123
129
|
});
|
|
@@ -142,8 +148,9 @@ export function registerBookings(program, context) {
|
|
|
142
148
|
.command('hand-over <uuid>')
|
|
143
149
|
.description('Hand a round-robin booking to another host')
|
|
144
150
|
.option('--to <email>', 'the new host (default: Zeeg picks the next available host)')
|
|
145
|
-
.
|
|
146
|
-
.option('--fallback-to-reschedule', '
|
|
151
|
+
.addOption(new Option('--require-availability', 'accepted and ignored').hideHelp())
|
|
152
|
+
.option('--fallback-to-reschedule', 'when the booking cannot move in place, reschedule it to the new host at the same time; the booking gets a new uuid')
|
|
153
|
+
.addOption(overrideLevelOption())
|
|
147
154
|
.option('-y, --yes', 'hand over without asking')
|
|
148
155
|
.action(async (uuid, options) => {
|
|
149
156
|
const ctx = context();
|
|
@@ -152,8 +159,8 @@ export function registerBookings(program, context) {
|
|
|
152
159
|
params: { path: { uuid } },
|
|
153
160
|
body: {
|
|
154
161
|
newHostEmail: options.to,
|
|
155
|
-
requireHostAvailability: options.requireAvailability,
|
|
156
162
|
fallbackToReschedule: options.fallbackToReschedule,
|
|
163
|
+
overrideLevel: options.overrideLevel,
|
|
157
164
|
},
|
|
158
165
|
}));
|
|
159
166
|
ctx.print.record(asJson(data).resource, `Handed over ${uuid}.`);
|
package/dist/commands/util.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { Option } from 'commander';
|
|
2
3
|
import { UsageError } from '../errors.js';
|
|
3
4
|
export function asJson(value) {
|
|
4
5
|
return value !== null && typeof value === 'object' ? value : {};
|
|
@@ -12,6 +13,18 @@ export function positiveInt(name) {
|
|
|
12
13
|
return value;
|
|
13
14
|
};
|
|
14
15
|
}
|
|
16
|
+
export function overrideLevel(name) {
|
|
17
|
+
return (raw) => {
|
|
18
|
+
if (!/^[1-4]$/.test(raw)) {
|
|
19
|
+
throw new UsageError(`${name} must be 1, 2, 3 or 4, not "${raw}".`);
|
|
20
|
+
}
|
|
21
|
+
return Number(raw);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** `--override-level`, shared by every command that can book outside availability. */
|
|
25
|
+
export function overrideLevelOption() {
|
|
26
|
+
return new Option('--override-level <level>', 'book outside availability: 2 also outside working hours, 3 also over calendar entries, 4 also over other bookings (double-books the host)').argParser(overrideLevel('--override-level'));
|
|
27
|
+
}
|
|
15
28
|
export function isoDate(name) {
|
|
16
29
|
return (raw) => {
|
|
17
30
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(raw) || Number.isNaN(Date.parse(raw))) {
|
package/dist/errors.js
CHANGED
|
@@ -45,7 +45,8 @@ export function exitCodeFor(error) {
|
|
|
45
45
|
/**
|
|
46
46
|
* The API answers errors in two shapes: controllers and validation send `{ message, errors? }`,
|
|
47
47
|
* with validation messages listed per field, and the shared exception handler sends
|
|
48
|
-
* `{ error: { type, message } }` for not found, forbidden and similar. A
|
|
48
|
+
* `{ error: { type, message } }` for not found, forbidden and similar. A refused override carries
|
|
49
|
+
* `data.blockedReason`, the kind of entry that blocks the time (such as `calendar`). A 401 means the token was
|
|
49
50
|
* revoked or expired server-side, so it asks for a new login.
|
|
50
51
|
*/
|
|
51
52
|
export function apiErrorFrom(status, body) {
|
|
@@ -60,7 +61,9 @@ export function apiErrorFrom(status, body) {
|
|
|
60
61
|
? Object.values(record.errors).flat().filter((item) => typeof item === 'string')
|
|
61
62
|
: [];
|
|
62
63
|
const unique = details.filter((detail) => detail !== message);
|
|
63
|
-
|
|
64
|
+
const data = typeof record.data === 'object' && record.data !== null ? record.data : {};
|
|
65
|
+
const blocked = typeof data.blockedReason === 'string' && data.blockedReason !== '' ? `\nBlocked by: ${data.blockedReason}` : '';
|
|
66
|
+
return new ApiError((unique.length > 0 ? `${message}\n- ${unique.join('\n- ')}` : message) + blocked);
|
|
64
67
|
}
|
|
65
68
|
/** `fetch`, with a network failure reported as an ApiError that names the origin. */
|
|
66
69
|
export async function fetchFrom(fetchImpl, baseUrl, input, init) {
|
package/package.json
CHANGED