avo-cli 4.2.0.beta.3 → 4.2.0.beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +38 -267
- data/lib/avo/cli/version.rb +1 -1
- data/payload/README.md +38 -267
- data/payload/dist/commands/list.js +1 -1
- data/payload/dist/commands/logout.js +1 -1
- data/payload/dist/commands/schema.js +9 -3
- data/payload/dist/payload.js +8 -14
- data/payload/dist/table.js +11 -6
- data/payload/oclif.manifest.json +3 -3
- data/payload/package.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66d0e9e6a0168a500f44a92e621a3f49f3a388a8eaf063233d55784b86ebf7a2
|
|
4
|
+
data.tar.gz: f8a276ec085a14b64f9ee1ab48e3ddcf2ef958dfab0f9462fbd71d763daed8b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a013c30323a74482001cbc1b013f4b680ed3645e9938cbcf085338816753a5c1c438e5d63f7f04f7d29e8db9c5f3ec8596f3b9fc45fc448d5bacf41e106ee55
|
|
7
|
+
data.tar.gz: 117498f71159878438130a4fd033572d94c08501bfbd29b0c7472907249c33f6044e6ff8f2285930f770532c5022f3d88bb79e771b185bce247b6818a7845ec1
|
data/README.md
CHANGED
|
@@ -1,263 +1,65 @@
|
|
|
1
1
|
# avo-cli
|
|
2
2
|
|
|
3
3
|
Command line client for the [Avo REST API](https://docs.avohq.io/4.0/rest-api.html).
|
|
4
|
-
It talks to any Rails app running `avo-api
|
|
4
|
+
It talks to any Rails app running `avo-api` over HTTP; nothing is installed in
|
|
5
|
+
the app. A table per command for a person, the raw JSON body under
|
|
6
|
+
`--format json` for a script or an agent.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
site describes them. It ships as a Ruby gem that runs the built client on the
|
|
8
|
-
machine's Node ([docs/install.md](docs/install.md)); the gem builds and installs
|
|
9
|
-
locally and has not been released yet. What is left is in
|
|
10
|
-
[docs/plans/v1-todo.md](docs/plans/v1-todo.md).
|
|
8
|
+
The full guide is at [docs.avohq.io/4.0/cli](https://docs.avohq.io/4.0/cli.html).
|
|
11
9
|
|
|
12
10
|
## Requirements
|
|
13
11
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
(
|
|
19
|
-
|
|
12
|
+
- Ruby 3.0 or newer, for the `avo` executable.
|
|
13
|
+
- Node 18.12.1 or newer on your `PATH`, or named by `AVO_CLI_NODE`. Node is
|
|
14
|
+
not bundled.
|
|
15
|
+
- An app running `avo-api`, reachable over HTTP, and an
|
|
16
|
+
[API token](https://docs.avohq.io/4.0/rest-api.html#create-a-token) created
|
|
17
|
+
in that app.
|
|
20
18
|
|
|
21
|
-
## Install
|
|
19
|
+
## Install
|
|
22
20
|
|
|
23
21
|
```sh
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
./bin/dev.js --version
|
|
22
|
+
gem install avo-cli
|
|
23
|
+
avo --version
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
`./bin/run.js` runs the compiled `dist/` after `yarn build`. The installed gem's
|
|
31
|
-
`avo` runs that same `run.js`, from the `payload/` the gem carries. To build the
|
|
32
|
-
gem and install it locally, see [docs/distribution.md](docs/distribution.md) §6.
|
|
33
|
-
|
|
34
|
-
## Configuration
|
|
26
|
+
## Connect
|
|
35
27
|
|
|
36
28
|
Log in once and every later command knows the app:
|
|
37
29
|
|
|
38
30
|
```sh
|
|
39
|
-
avo login # asks for the
|
|
40
|
-
avo schema #
|
|
31
|
+
avo login # asks for the API base URL, the token and the API version, checks them, saves them
|
|
32
|
+
avo schema # what the token may reach
|
|
41
33
|
avo logout # deletes what was saved
|
|
42
34
|
```
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
The host is the API base URL: the address `avo-api` answers on, mount path
|
|
49
|
-
included, exactly what you would paste into Postman before `/resources/...`.
|
|
50
|
-
An app that mounts it the usual way is `http://localhost:3000/api`; one that
|
|
51
|
-
mounts it elsewhere, say `mount_avo_api at: "avo"`, is
|
|
52
|
-
`http://localhost:3000/avo`. The CLI appends only `/resources/<version>/<path>`
|
|
53
|
-
and guesses no mount, so the address that works in a browser works here.
|
|
54
|
-
|
|
55
|
-
For CI and scripts, skip `login` and set the variables. A flag wins over its
|
|
56
|
-
variable, and either wins over what was saved. The saved host and token are
|
|
57
|
-
used only together: name a host or a token yourself and the saved pair is left
|
|
58
|
-
out, so a saved token never reaches another host. The saved API version goes
|
|
59
|
-
with them: `avo login --api-version v2` once, and later commands use `v2`.
|
|
36
|
+
The API base URL is the address `avo-api` answers on, mount path included,
|
|
37
|
+
e.g. `http://localhost:3000/api`. The token is saved as plain text in
|
|
38
|
+
`~/.config/avo/config.json`, so use one you can revoke.
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| `AVO_API_TOKEN` | `--token` | none | API token secret, sent as `Authorization: Bearer` |
|
|
65
|
-
| `AVO_API_VERSION` | `--api-version` | `v1` | API version segment of the URL, saved by `login` |
|
|
40
|
+
For CI and scripts, skip `login` and set `AVO_API_HOST`, `AVO_API_TOKEN` and
|
|
41
|
+
`AVO_API_VERSION`, or pass `--host`, `--token` and `--api-version`. See
|
|
42
|
+
[Connect without logging in](https://docs.avohq.io/4.0/cli.html#connect-without-logging-in).
|
|
66
43
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
The gem is not released yet, so there is no `avo` on anyone's PATH: every
|
|
70
|
-
example here runs the source through `./bin/dev.js`. The generated reference
|
|
71
|
-
further down prints `avo`, the released name. These examples are swept to it
|
|
72
|
-
when the gem ships.
|
|
73
|
-
|
|
74
|
-
```sh
|
|
75
|
-
./bin/dev.js schema # what the token may reach, as a table
|
|
76
|
-
./bin/dev.js schema --format json # the _schema body, byte for byte
|
|
77
|
-
./bin/dev.js schema users # the fields a create sends, with required
|
|
78
|
-
./bin/dev.js schema users --view show # the fields a record reads back
|
|
79
|
-
./bin/dev.js list users # first page, one row per record
|
|
80
|
-
./bin/dev.js list users --page 2 --per-page 10 # paging
|
|
81
|
-
./bin/dev.js list users --sort name --dir desc # sorting, server side
|
|
82
|
-
./bin/dev.js list users --fields id,name,email # pick the columns
|
|
83
|
-
./bin/dev.js get users 5 # one record, field by field
|
|
84
|
-
./bin/dev.js get users 5 --format json # the body, byte for byte
|
|
85
|
-
./bin/dev.js get users 5 --verbose # request and response lines on stderr
|
|
86
|
-
./bin/dev.js create users --data '{"name": "Ada", "email": "ada@example.com"}'
|
|
87
|
-
./bin/dev.js update users 5 --data '{"name": "Ada King", "bio": null}' # null clears
|
|
88
|
-
./bin/dev.js create users --data @user.json # the object from a file
|
|
89
|
-
./bin/dev.js update users 5 --data - < user.json # the object from stdin
|
|
90
|
-
./bin/dev.js delete users 5 # no confirmation, see below
|
|
91
|
-
./bin/dev.js login # save the host, token and API version
|
|
92
|
-
./bin/dev.js logout # delete what login saved
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Grammar
|
|
44
|
+
## Usage
|
|
96
45
|
|
|
97
46
|
Every line has the same shape:
|
|
98
47
|
|
|
99
48
|
```
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
There are six verbs: `schema`, `list`, `get`, `create`, `update` and `delete`.
|
|
104
|
-
The resource is always an argument, never a command, because route keys come
|
|
105
|
-
from the app being talked to and only after an HTTP call. `login` and `logout`
|
|
106
|
-
are the two commands outside the grammar: they take no resource
|
|
107
|
-
([Configuration](#configuration)).
|
|
108
|
-
|
|
109
|
-
`./bin/dev.js --help` lists the six verbs, with `login`, `logout` and `help`
|
|
110
|
-
beside them, and `./bin/dev.js get --help` shows one. A line that names no
|
|
111
|
-
command prints the usage above, with a did-you-mean when the typo is close to
|
|
112
|
-
something real.
|
|
113
|
-
|
|
114
|
-
Each verb has a one-letter shortcut — `s l g c u d`, in the order listed above
|
|
115
|
-
— in the same slot, so `./bin/dev.js l users` is `list users`. They are hidden
|
|
116
|
-
aliases: help, the reference below and the error messages all keep to the full
|
|
117
|
-
spelling, and nothing else about the grammar bends. `./bin/dev.js users l` is
|
|
118
|
-
refused like any other line that puts the resource first.
|
|
119
|
-
|
|
120
|
-
### Output
|
|
121
|
-
|
|
122
|
-
A table is plain text, two spaces between columns. `--fields` picks the
|
|
123
|
-
columns and lays a table out in the flag's order; a single `get` reads down
|
|
124
|
-
the page in the record's own order. A cell is one line: `-` for null and
|
|
125
|
-
empty, a list joined with `, `, an object — an association included — as its
|
|
126
|
-
JSON, and anything past 40 characters cut with `…`.
|
|
127
|
-
|
|
128
|
-
`--format json` prints the body exactly as the server sent it and ignores
|
|
129
|
-
`--fields`; `table` is the default. `--verbose` logs each request and
|
|
130
|
-
response line to stderr, so `--format json --verbose | jq` still works.
|
|
131
|
-
|
|
132
|
-
### Writing fields
|
|
133
|
-
|
|
134
|
-
Fields are one JSON object, given through `--data` (`-d`): the JSON itself,
|
|
135
|
-
`@path` to read it from a file, or `-` to read stdin. Every key is a field as
|
|
136
|
-
`schema <resource>` lists them. JSON carries its own types, so nothing is
|
|
137
|
-
converted — a value is sent as written. Before sending, the CLI reads the
|
|
138
|
-
resource's form view: for the key the body nests under, for the `file` refusal
|
|
139
|
-
below, and for the two values the wire wants in another form. What each field
|
|
140
|
-
takes:
|
|
141
|
-
|
|
142
|
-
| The view says | You write | Sent |
|
|
143
|
-
| ------------------------------------------------- | ----------------------------------------------- | --------------------------------------- |
|
|
144
|
-
| `shape: array` | `"roles": ["editor", "reviewer"]` | as written; `null` sends `[]` |
|
|
145
|
-
| `shape: hash`, `keys: [latitude, longitude]` | `"coordinates": {"latitude": 44.4, …}` | as written; `null` sends every key null |
|
|
146
|
-
| polymorphic `belongs_to`, two scalars | `"reactable_type": "Post", "reactable_id": 7` | as written |
|
|
147
|
-
| `type: key_value` or `type: code` | `"settings": {"theme": "dark"}` | the object as a JSON string |
|
|
148
|
-
| `type: tags` | `"skills": "ruby,rails"` | the string; the field splits it |
|
|
149
|
-
| anything else | `"active": false`, `"birth_date": "1985-03-04"` | as written; the app casts |
|
|
150
|
-
|
|
151
|
-
Refused with exit `2`, before any request: `--data` that is not valid JSON or
|
|
152
|
-
not one object, and an `@path` that cannot be read. Refused once the view is
|
|
153
|
-
read, before the write: a `file` or `files` field, which needs a multipart
|
|
154
|
-
upload the CLI does not send. Nothing else is checked against the view: the API
|
|
155
|
-
validates a write, so an empty object, a field the view does not list or a
|
|
156
|
-
value outside a field's `options` goes out as written and the server answers.
|
|
157
|
-
An empty object is a `400`. A value outside a field's `options` is written as
|
|
158
|
-
given, or a `500` where a Rails enum raises on it, and a misspelled field name
|
|
159
|
-
or a value of the wrong shape is mostly dropped by the app's strong params
|
|
160
|
-
without a word — the write exits `0` with that field unchanged — until
|
|
161
|
-
[AVO-1804](https://linear.app/avo-hq/issue/AVO-1804) makes the API answer those
|
|
162
|
-
too. Read the record back when it matters.
|
|
163
|
-
[docs/field-types.md](docs/field-types.md) is the full table, type by type.
|
|
164
|
-
|
|
165
|
-
Quote the object in single quotes: the shell reads the line first, and an
|
|
166
|
-
unquoted `{"a":1,"b":2}` is a brace expansion that silently splits in two.
|
|
167
|
-
When the quoting gets in the way — a value holding a quote, or an object too
|
|
168
|
-
long for one line — write the JSON to a file and pass `--data @user.json`, or
|
|
169
|
-
pipe it in with `--data -`.
|
|
170
|
-
|
|
171
|
-
A `code` field takes its JSON as a string. Whether the field parses that string
|
|
172
|
-
on the way in is a write transform the view does not publish — it describes the
|
|
173
|
-
value a write sends, not what the field does with it afterwards — so over a
|
|
174
|
-
JSON column the string lands double-encoded unless the resource declares
|
|
175
|
-
`pretty_generated: true`. Setting such a field replaces the whole column;
|
|
176
|
-
nothing is merged.
|
|
177
|
-
|
|
178
|
-
`schema users` lists the fields a create may send, each with every key the
|
|
179
|
-
view publishes for it: its `type`, whether it is `required`, its `shape`, and
|
|
180
|
-
`options` or `keys` where it has them. `--view update` does the same for an
|
|
181
|
-
update, and `--view index` or `--view show` list what a record comes back
|
|
182
|
-
with, as `id` and `type`. The views are named by the API action they describe,
|
|
183
|
-
the same words `schema` shows under `entitlements`. The answer is always for
|
|
184
|
-
one view, never a merge of several, and each view needs the entitlement of the
|
|
185
|
-
request it describes: a read-only token gets `index` and `show`, and on
|
|
186
|
-
`create` and `update` a `403` the CLI relays like any other. A view the API
|
|
187
|
-
does not serve is refused by the API, which names the ones it has. A
|
|
188
|
-
`belongs_to` is listed by the key the body sets it through, `team_id`.
|
|
189
|
-
|
|
190
|
-
`delete` has no body, so it reads no view and sends straight away. The server
|
|
191
|
-
answers every request, and a refusal comes back as a `403` whose `reason` says
|
|
192
|
-
which side to widen, `token_entitlement` or `policy`. The CLI names the request
|
|
193
|
-
it made and relays the reason as it came:
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
› Error: Forbidden: DELETE /api/resources/v1/users/5 was refused (reason: token_entitlement).
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
`delete` asks for no confirmation: an agent or a CI job has no terminal to
|
|
200
|
-
answer a prompt on, and the line already names the record. A confirmation
|
|
201
|
-
step is parked for after v1.
|
|
202
|
-
|
|
203
|
-
### Errors
|
|
204
|
-
|
|
205
|
-
Every error is one sentence naming what to change, on stderr. A `404` is the
|
|
206
|
-
one message whatever the cause — a mistyped resource, a missing id, an app
|
|
207
|
-
without `avo-api` mounted or licensed — because the API does not yet say which,
|
|
208
|
-
and the CLI does not guess. `schema` lists the names this token may reach:
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
› Error: Not found: http://localhost:3030/api/resources/v1/user.
|
|
212
|
-
› Check the resource name and the API version, and that the app has avo-api mounted and licensed.
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
A line that names no command is answered with the one grammar there is, and a
|
|
216
|
-
correction above it when the typo is close to something real:
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
› Error: command lst users not found
|
|
220
|
-
› Did you mean: avo list?
|
|
221
|
-
› Usage: avo <verb> <resource> [id] [flags]
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
A `422` lists what the app refused, one line per field:
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
› Error: Failed to create User
|
|
228
|
-
› name: can't be blank
|
|
229
|
-
› email: has already been taken
|
|
49
|
+
avo <verb> <resource> [id] [flags]
|
|
230
50
|
```
|
|
231
51
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
› Error: http://app.example.com redirected to https://app.example.com/api/resources/v1/_schema. Use the address it points to as the host.
|
|
52
|
+
```sh
|
|
53
|
+
avo schema users # the fields a create sends
|
|
54
|
+
avo list users --sort name --dir desc # a page of records
|
|
55
|
+
avo get users 5 --format json # one record, as the server sent it
|
|
56
|
+
avo create users --data '{"name": "Ada", "email": "ada@example.com"}'
|
|
57
|
+
avo update users 5 --data '{"name": "Ada King"}'
|
|
58
|
+
avo delete users 5 # no confirmation
|
|
241
59
|
```
|
|
242
60
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| Code | Meaning |
|
|
246
|
-
| ----- | -------------------------------------------------------------------------------------------- |
|
|
247
|
-
| `0` | Success |
|
|
248
|
-
| `1` | The request failed: refused (400, 401, 403, 404, 422, 5xx), redirected, unreachable, timeout |
|
|
249
|
-
| `2` | Usage: the line or the setup was wrong, and nothing was written |
|
|
250
|
-
| `130` | Interrupted: Ctrl-C at an `avo login` prompt, with nothing saved |
|
|
251
|
-
|
|
252
|
-
Exit `1` also covers an answer that is not avo-api's: a `200` without the
|
|
253
|
-
`records`, `record` or schema the endpoint promises. Exit `2` is an unknown
|
|
254
|
-
command, a bad flag, `--view` with no resource, an empty id, a missing host or
|
|
255
|
-
token, a host without `http://` or `https://` or with a username and password
|
|
256
|
-
in it, a token holding a character a header cannot carry, `--data` that is not
|
|
257
|
-
one JSON object or names a file that cannot be read, and a `file` field. It is
|
|
258
|
-
also a saved login that cannot be read, written or parsed, `avo login` with a
|
|
259
|
-
value left to ask for and no terminal to ask on, and the installed gem finding
|
|
260
|
-
no Node, or one older than 18.12.1.
|
|
61
|
+
Field formats, errors and exit codes are in the
|
|
62
|
+
[docs](https://docs.avohq.io/4.0/cli.html#send-the-right-value-for-a-field).
|
|
261
63
|
|
|
262
64
|
<!-- commands -->
|
|
263
65
|
* [`avo create RESOURCE`](#avo-create-resource)
|
|
@@ -478,14 +280,15 @@ EXAMPLES
|
|
|
478
280
|
|
|
479
281
|
## `avo logout`
|
|
480
282
|
|
|
481
|
-
Delete the saved host and token. AVO_API_HOST and
|
|
283
|
+
Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep working
|
|
482
284
|
|
|
483
285
|
```
|
|
484
286
|
USAGE
|
|
485
287
|
$ avo logout
|
|
486
288
|
|
|
487
289
|
DESCRIPTION
|
|
488
|
-
Delete the saved host and token. AVO_API_HOST and
|
|
290
|
+
Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep
|
|
291
|
+
working
|
|
489
292
|
|
|
490
293
|
EXAMPLES
|
|
491
294
|
$ avo logout
|
|
@@ -504,8 +307,8 @@ ARGUMENTS
|
|
|
504
307
|
[RESOURCE] A resource by its route key; with none, the resources the API serves are listed
|
|
505
308
|
|
|
506
309
|
FLAGS
|
|
507
|
-
--view=<value> Which view's fields: create/update list what a write may send, with
|
|
508
|
-
reads back (default: create)
|
|
310
|
+
--view=<value> Which view's fields: create/update list what a write may send, with their field_options; index/show
|
|
311
|
+
what a record reads back (default: create)
|
|
509
312
|
|
|
510
313
|
CONNECTION FLAGS
|
|
511
314
|
--api-version=<value> [env: AVO_API_VERSION] API version segment of the URL; defaults to the one `avo login` saved,
|
|
@@ -578,35 +381,3 @@ EXAMPLES
|
|
|
578
381
|
$ avo update users 5 --data '{"name": "Ada"}' --format json
|
|
579
382
|
```
|
|
580
383
|
<!-- commandsstop -->
|
|
581
|
-
|
|
582
|
-
## Development
|
|
583
|
-
|
|
584
|
-
```sh
|
|
585
|
-
cd gems/avo-cli
|
|
586
|
-
node --version # 22.13 or newer, per `.node-version`
|
|
587
|
-
|
|
588
|
-
yarn build # compile src/ to dist/
|
|
589
|
-
yarn test # mocha, then typecheck and eslint (posttest)
|
|
590
|
-
yarn typecheck # tsc over src/ and test/, writing nothing
|
|
591
|
-
yarn lint
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
Narrower runs, when the whole suite is more than you need:
|
|
595
|
-
|
|
596
|
-
```sh
|
|
597
|
-
yarn mocha test/commands/list.test.ts # one file
|
|
598
|
-
yarn mocha "test/**/*.test.ts" --grep "404" # every test whose name matches
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Tests never reach the network: HTTP is stubbed with undici's `MockAgent`,
|
|
602
|
-
which Node's built-in `fetch` honours (see `test/http.test.ts`).
|
|
603
|
-
|
|
604
|
-
The command reference above is generated by `oclif readme` and regenerated on
|
|
605
|
-
`yarn version` and on pack. After changing a command's description, flags or
|
|
606
|
-
examples, run `yarn build && yarn oclif readme --no-source-links` and commit
|
|
607
|
-
the result. The flag matters: without it the reference links into the private
|
|
608
|
-
workspace repository, which `test/readme.test.ts` refuses.
|
|
609
|
-
|
|
610
|
-
To run the CLI against a real app, command by command, with the expected
|
|
611
|
-
output and every way a line can fail, follow
|
|
612
|
-
[docs/manual-testing.md](docs/manual-testing.md).
|
data/lib/avo/cli/version.rb
CHANGED
data/payload/README.md
CHANGED
|
@@ -1,263 +1,65 @@
|
|
|
1
1
|
# avo-cli
|
|
2
2
|
|
|
3
3
|
Command line client for the [Avo REST API](https://docs.avohq.io/4.0/rest-api.html).
|
|
4
|
-
It talks to any Rails app running `avo-api
|
|
4
|
+
It talks to any Rails app running `avo-api` over HTTP; nothing is installed in
|
|
5
|
+
the app. A table per command for a person, the raw JSON body under
|
|
6
|
+
`--format json` for a script or an agent.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
site describes them. It ships as a Ruby gem that runs the built client on the
|
|
8
|
-
machine's Node ([docs/install.md](docs/install.md)); the gem builds and installs
|
|
9
|
-
locally and has not been released yet. What is left is in
|
|
10
|
-
[docs/plans/v1-todo.md](docs/plans/v1-todo.md).
|
|
8
|
+
The full guide is at [docs.avohq.io/4.0/cli](https://docs.avohq.io/4.0/cli.html).
|
|
11
9
|
|
|
12
10
|
## Requirements
|
|
13
11
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
(
|
|
19
|
-
|
|
12
|
+
- Ruby 3.0 or newer, for the `avo` executable.
|
|
13
|
+
- Node 18.12.1 or newer on your `PATH`, or named by `AVO_CLI_NODE`. Node is
|
|
14
|
+
not bundled.
|
|
15
|
+
- An app running `avo-api`, reachable over HTTP, and an
|
|
16
|
+
[API token](https://docs.avohq.io/4.0/rest-api.html#create-a-token) created
|
|
17
|
+
in that app.
|
|
20
18
|
|
|
21
|
-
## Install
|
|
19
|
+
## Install
|
|
22
20
|
|
|
23
21
|
```sh
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
./bin/dev.js --version
|
|
22
|
+
gem install avo-cli
|
|
23
|
+
avo --version
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
`./bin/run.js` runs the compiled `dist/` after `yarn build`. The installed gem's
|
|
31
|
-
`avo` runs that same `run.js`, from the `payload/` the gem carries. To build the
|
|
32
|
-
gem and install it locally, see [docs/distribution.md](docs/distribution.md) §6.
|
|
33
|
-
|
|
34
|
-
## Configuration
|
|
26
|
+
## Connect
|
|
35
27
|
|
|
36
28
|
Log in once and every later command knows the app:
|
|
37
29
|
|
|
38
30
|
```sh
|
|
39
|
-
avo login # asks for the
|
|
40
|
-
avo schema #
|
|
31
|
+
avo login # asks for the API base URL, the token and the API version, checks them, saves them
|
|
32
|
+
avo schema # what the token may reach
|
|
41
33
|
avo logout # deletes what was saved
|
|
42
34
|
```
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
The host is the API base URL: the address `avo-api` answers on, mount path
|
|
49
|
-
included, exactly what you would paste into Postman before `/resources/...`.
|
|
50
|
-
An app that mounts it the usual way is `http://localhost:3000/api`; one that
|
|
51
|
-
mounts it elsewhere, say `mount_avo_api at: "avo"`, is
|
|
52
|
-
`http://localhost:3000/avo`. The CLI appends only `/resources/<version>/<path>`
|
|
53
|
-
and guesses no mount, so the address that works in a browser works here.
|
|
54
|
-
|
|
55
|
-
For CI and scripts, skip `login` and set the variables. A flag wins over its
|
|
56
|
-
variable, and either wins over what was saved. The saved host and token are
|
|
57
|
-
used only together: name a host or a token yourself and the saved pair is left
|
|
58
|
-
out, so a saved token never reaches another host. The saved API version goes
|
|
59
|
-
with them: `avo login --api-version v2` once, and later commands use `v2`.
|
|
36
|
+
The API base URL is the address `avo-api` answers on, mount path included,
|
|
37
|
+
e.g. `http://localhost:3000/api`. The token is saved as plain text in
|
|
38
|
+
`~/.config/avo/config.json`, so use one you can revoke.
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| `AVO_API_TOKEN` | `--token` | none | API token secret, sent as `Authorization: Bearer` |
|
|
65
|
-
| `AVO_API_VERSION` | `--api-version` | `v1` | API version segment of the URL, saved by `login` |
|
|
40
|
+
For CI and scripts, skip `login` and set `AVO_API_HOST`, `AVO_API_TOKEN` and
|
|
41
|
+
`AVO_API_VERSION`, or pass `--host`, `--token` and `--api-version`. See
|
|
42
|
+
[Connect without logging in](https://docs.avohq.io/4.0/cli.html#connect-without-logging-in).
|
|
66
43
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
The gem is not released yet, so there is no `avo` on anyone's PATH: every
|
|
70
|
-
example here runs the source through `./bin/dev.js`. The generated reference
|
|
71
|
-
further down prints `avo`, the released name. These examples are swept to it
|
|
72
|
-
when the gem ships.
|
|
73
|
-
|
|
74
|
-
```sh
|
|
75
|
-
./bin/dev.js schema # what the token may reach, as a table
|
|
76
|
-
./bin/dev.js schema --format json # the _schema body, byte for byte
|
|
77
|
-
./bin/dev.js schema users # the fields a create sends, with required
|
|
78
|
-
./bin/dev.js schema users --view show # the fields a record reads back
|
|
79
|
-
./bin/dev.js list users # first page, one row per record
|
|
80
|
-
./bin/dev.js list users --page 2 --per-page 10 # paging
|
|
81
|
-
./bin/dev.js list users --sort name --dir desc # sorting, server side
|
|
82
|
-
./bin/dev.js list users --fields id,name,email # pick the columns
|
|
83
|
-
./bin/dev.js get users 5 # one record, field by field
|
|
84
|
-
./bin/dev.js get users 5 --format json # the body, byte for byte
|
|
85
|
-
./bin/dev.js get users 5 --verbose # request and response lines on stderr
|
|
86
|
-
./bin/dev.js create users --data '{"name": "Ada", "email": "ada@example.com"}'
|
|
87
|
-
./bin/dev.js update users 5 --data '{"name": "Ada King", "bio": null}' # null clears
|
|
88
|
-
./bin/dev.js create users --data @user.json # the object from a file
|
|
89
|
-
./bin/dev.js update users 5 --data - < user.json # the object from stdin
|
|
90
|
-
./bin/dev.js delete users 5 # no confirmation, see below
|
|
91
|
-
./bin/dev.js login # save the host, token and API version
|
|
92
|
-
./bin/dev.js logout # delete what login saved
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Grammar
|
|
44
|
+
## Usage
|
|
96
45
|
|
|
97
46
|
Every line has the same shape:
|
|
98
47
|
|
|
99
48
|
```
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
There are six verbs: `schema`, `list`, `get`, `create`, `update` and `delete`.
|
|
104
|
-
The resource is always an argument, never a command, because route keys come
|
|
105
|
-
from the app being talked to and only after an HTTP call. `login` and `logout`
|
|
106
|
-
are the two commands outside the grammar: they take no resource
|
|
107
|
-
([Configuration](#configuration)).
|
|
108
|
-
|
|
109
|
-
`./bin/dev.js --help` lists the six verbs, with `login`, `logout` and `help`
|
|
110
|
-
beside them, and `./bin/dev.js get --help` shows one. A line that names no
|
|
111
|
-
command prints the usage above, with a did-you-mean when the typo is close to
|
|
112
|
-
something real.
|
|
113
|
-
|
|
114
|
-
Each verb has a one-letter shortcut — `s l g c u d`, in the order listed above
|
|
115
|
-
— in the same slot, so `./bin/dev.js l users` is `list users`. They are hidden
|
|
116
|
-
aliases: help, the reference below and the error messages all keep to the full
|
|
117
|
-
spelling, and nothing else about the grammar bends. `./bin/dev.js users l` is
|
|
118
|
-
refused like any other line that puts the resource first.
|
|
119
|
-
|
|
120
|
-
### Output
|
|
121
|
-
|
|
122
|
-
A table is plain text, two spaces between columns. `--fields` picks the
|
|
123
|
-
columns and lays a table out in the flag's order; a single `get` reads down
|
|
124
|
-
the page in the record's own order. A cell is one line: `-` for null and
|
|
125
|
-
empty, a list joined with `, `, an object — an association included — as its
|
|
126
|
-
JSON, and anything past 40 characters cut with `…`.
|
|
127
|
-
|
|
128
|
-
`--format json` prints the body exactly as the server sent it and ignores
|
|
129
|
-
`--fields`; `table` is the default. `--verbose` logs each request and
|
|
130
|
-
response line to stderr, so `--format json --verbose | jq` still works.
|
|
131
|
-
|
|
132
|
-
### Writing fields
|
|
133
|
-
|
|
134
|
-
Fields are one JSON object, given through `--data` (`-d`): the JSON itself,
|
|
135
|
-
`@path` to read it from a file, or `-` to read stdin. Every key is a field as
|
|
136
|
-
`schema <resource>` lists them. JSON carries its own types, so nothing is
|
|
137
|
-
converted — a value is sent as written. Before sending, the CLI reads the
|
|
138
|
-
resource's form view: for the key the body nests under, for the `file` refusal
|
|
139
|
-
below, and for the two values the wire wants in another form. What each field
|
|
140
|
-
takes:
|
|
141
|
-
|
|
142
|
-
| The view says | You write | Sent |
|
|
143
|
-
| ------------------------------------------------- | ----------------------------------------------- | --------------------------------------- |
|
|
144
|
-
| `shape: array` | `"roles": ["editor", "reviewer"]` | as written; `null` sends `[]` |
|
|
145
|
-
| `shape: hash`, `keys: [latitude, longitude]` | `"coordinates": {"latitude": 44.4, …}` | as written; `null` sends every key null |
|
|
146
|
-
| polymorphic `belongs_to`, two scalars | `"reactable_type": "Post", "reactable_id": 7` | as written |
|
|
147
|
-
| `type: key_value` or `type: code` | `"settings": {"theme": "dark"}` | the object as a JSON string |
|
|
148
|
-
| `type: tags` | `"skills": "ruby,rails"` | the string; the field splits it |
|
|
149
|
-
| anything else | `"active": false`, `"birth_date": "1985-03-04"` | as written; the app casts |
|
|
150
|
-
|
|
151
|
-
Refused with exit `2`, before any request: `--data` that is not valid JSON or
|
|
152
|
-
not one object, and an `@path` that cannot be read. Refused once the view is
|
|
153
|
-
read, before the write: a `file` or `files` field, which needs a multipart
|
|
154
|
-
upload the CLI does not send. Nothing else is checked against the view: the API
|
|
155
|
-
validates a write, so an empty object, a field the view does not list or a
|
|
156
|
-
value outside a field's `options` goes out as written and the server answers.
|
|
157
|
-
An empty object is a `400`. A value outside a field's `options` is written as
|
|
158
|
-
given, or a `500` where a Rails enum raises on it, and a misspelled field name
|
|
159
|
-
or a value of the wrong shape is mostly dropped by the app's strong params
|
|
160
|
-
without a word — the write exits `0` with that field unchanged — until
|
|
161
|
-
[AVO-1804](https://linear.app/avo-hq/issue/AVO-1804) makes the API answer those
|
|
162
|
-
too. Read the record back when it matters.
|
|
163
|
-
[docs/field-types.md](docs/field-types.md) is the full table, type by type.
|
|
164
|
-
|
|
165
|
-
Quote the object in single quotes: the shell reads the line first, and an
|
|
166
|
-
unquoted `{"a":1,"b":2}` is a brace expansion that silently splits in two.
|
|
167
|
-
When the quoting gets in the way — a value holding a quote, or an object too
|
|
168
|
-
long for one line — write the JSON to a file and pass `--data @user.json`, or
|
|
169
|
-
pipe it in with `--data -`.
|
|
170
|
-
|
|
171
|
-
A `code` field takes its JSON as a string. Whether the field parses that string
|
|
172
|
-
on the way in is a write transform the view does not publish — it describes the
|
|
173
|
-
value a write sends, not what the field does with it afterwards — so over a
|
|
174
|
-
JSON column the string lands double-encoded unless the resource declares
|
|
175
|
-
`pretty_generated: true`. Setting such a field replaces the whole column;
|
|
176
|
-
nothing is merged.
|
|
177
|
-
|
|
178
|
-
`schema users` lists the fields a create may send, each with every key the
|
|
179
|
-
view publishes for it: its `type`, whether it is `required`, its `shape`, and
|
|
180
|
-
`options` or `keys` where it has them. `--view update` does the same for an
|
|
181
|
-
update, and `--view index` or `--view show` list what a record comes back
|
|
182
|
-
with, as `id` and `type`. The views are named by the API action they describe,
|
|
183
|
-
the same words `schema` shows under `entitlements`. The answer is always for
|
|
184
|
-
one view, never a merge of several, and each view needs the entitlement of the
|
|
185
|
-
request it describes: a read-only token gets `index` and `show`, and on
|
|
186
|
-
`create` and `update` a `403` the CLI relays like any other. A view the API
|
|
187
|
-
does not serve is refused by the API, which names the ones it has. A
|
|
188
|
-
`belongs_to` is listed by the key the body sets it through, `team_id`.
|
|
189
|
-
|
|
190
|
-
`delete` has no body, so it reads no view and sends straight away. The server
|
|
191
|
-
answers every request, and a refusal comes back as a `403` whose `reason` says
|
|
192
|
-
which side to widen, `token_entitlement` or `policy`. The CLI names the request
|
|
193
|
-
it made and relays the reason as it came:
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
› Error: Forbidden: DELETE /api/resources/v1/users/5 was refused (reason: token_entitlement).
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
`delete` asks for no confirmation: an agent or a CI job has no terminal to
|
|
200
|
-
answer a prompt on, and the line already names the record. A confirmation
|
|
201
|
-
step is parked for after v1.
|
|
202
|
-
|
|
203
|
-
### Errors
|
|
204
|
-
|
|
205
|
-
Every error is one sentence naming what to change, on stderr. A `404` is the
|
|
206
|
-
one message whatever the cause — a mistyped resource, a missing id, an app
|
|
207
|
-
without `avo-api` mounted or licensed — because the API does not yet say which,
|
|
208
|
-
and the CLI does not guess. `schema` lists the names this token may reach:
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
› Error: Not found: http://localhost:3030/api/resources/v1/user.
|
|
212
|
-
› Check the resource name and the API version, and that the app has avo-api mounted and licensed.
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
A line that names no command is answered with the one grammar there is, and a
|
|
216
|
-
correction above it when the typo is close to something real:
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
› Error: command lst users not found
|
|
220
|
-
› Did you mean: avo list?
|
|
221
|
-
› Usage: avo <verb> <resource> [id] [flags]
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
A `422` lists what the app refused, one line per field:
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
› Error: Failed to create User
|
|
228
|
-
› name: can't be blank
|
|
229
|
-
› email: has already been taken
|
|
49
|
+
avo <verb> <resource> [id] [flags]
|
|
230
50
|
```
|
|
231
51
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
› Error: http://app.example.com redirected to https://app.example.com/api/resources/v1/_schema. Use the address it points to as the host.
|
|
52
|
+
```sh
|
|
53
|
+
avo schema users # the fields a create sends
|
|
54
|
+
avo list users --sort name --dir desc # a page of records
|
|
55
|
+
avo get users 5 --format json # one record, as the server sent it
|
|
56
|
+
avo create users --data '{"name": "Ada", "email": "ada@example.com"}'
|
|
57
|
+
avo update users 5 --data '{"name": "Ada King"}'
|
|
58
|
+
avo delete users 5 # no confirmation
|
|
241
59
|
```
|
|
242
60
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
| Code | Meaning |
|
|
246
|
-
| ----- | -------------------------------------------------------------------------------------------- |
|
|
247
|
-
| `0` | Success |
|
|
248
|
-
| `1` | The request failed: refused (400, 401, 403, 404, 422, 5xx), redirected, unreachable, timeout |
|
|
249
|
-
| `2` | Usage: the line or the setup was wrong, and nothing was written |
|
|
250
|
-
| `130` | Interrupted: Ctrl-C at an `avo login` prompt, with nothing saved |
|
|
251
|
-
|
|
252
|
-
Exit `1` also covers an answer that is not avo-api's: a `200` without the
|
|
253
|
-
`records`, `record` or schema the endpoint promises. Exit `2` is an unknown
|
|
254
|
-
command, a bad flag, `--view` with no resource, an empty id, a missing host or
|
|
255
|
-
token, a host without `http://` or `https://` or with a username and password
|
|
256
|
-
in it, a token holding a character a header cannot carry, `--data` that is not
|
|
257
|
-
one JSON object or names a file that cannot be read, and a `file` field. It is
|
|
258
|
-
also a saved login that cannot be read, written or parsed, `avo login` with a
|
|
259
|
-
value left to ask for and no terminal to ask on, and the installed gem finding
|
|
260
|
-
no Node, or one older than 18.12.1.
|
|
61
|
+
Field formats, errors and exit codes are in the
|
|
62
|
+
[docs](https://docs.avohq.io/4.0/cli.html#send-the-right-value-for-a-field).
|
|
261
63
|
|
|
262
64
|
<!-- commands -->
|
|
263
65
|
* [`avo create RESOURCE`](#avo-create-resource)
|
|
@@ -478,14 +280,15 @@ EXAMPLES
|
|
|
478
280
|
|
|
479
281
|
## `avo logout`
|
|
480
282
|
|
|
481
|
-
Delete the saved host and token. AVO_API_HOST and
|
|
283
|
+
Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep working
|
|
482
284
|
|
|
483
285
|
```
|
|
484
286
|
USAGE
|
|
485
287
|
$ avo logout
|
|
486
288
|
|
|
487
289
|
DESCRIPTION
|
|
488
|
-
Delete the saved host and token. AVO_API_HOST and
|
|
290
|
+
Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep
|
|
291
|
+
working
|
|
489
292
|
|
|
490
293
|
EXAMPLES
|
|
491
294
|
$ avo logout
|
|
@@ -504,8 +307,8 @@ ARGUMENTS
|
|
|
504
307
|
[RESOURCE] A resource by its route key; with none, the resources the API serves are listed
|
|
505
308
|
|
|
506
309
|
FLAGS
|
|
507
|
-
--view=<value> Which view's fields: create/update list what a write may send, with
|
|
508
|
-
reads back (default: create)
|
|
310
|
+
--view=<value> Which view's fields: create/update list what a write may send, with their field_options; index/show
|
|
311
|
+
what a record reads back (default: create)
|
|
509
312
|
|
|
510
313
|
CONNECTION FLAGS
|
|
511
314
|
--api-version=<value> [env: AVO_API_VERSION] API version segment of the URL; defaults to the one `avo login` saved,
|
|
@@ -578,35 +381,3 @@ EXAMPLES
|
|
|
578
381
|
$ avo update users 5 --data '{"name": "Ada"}' --format json
|
|
579
382
|
```
|
|
580
383
|
<!-- commandsstop -->
|
|
581
|
-
|
|
582
|
-
## Development
|
|
583
|
-
|
|
584
|
-
```sh
|
|
585
|
-
cd gems/avo-cli
|
|
586
|
-
node --version # 22.13 or newer, per `.node-version`
|
|
587
|
-
|
|
588
|
-
yarn build # compile src/ to dist/
|
|
589
|
-
yarn test # mocha, then typecheck and eslint (posttest)
|
|
590
|
-
yarn typecheck # tsc over src/ and test/, writing nothing
|
|
591
|
-
yarn lint
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
Narrower runs, when the whole suite is more than you need:
|
|
595
|
-
|
|
596
|
-
```sh
|
|
597
|
-
yarn mocha test/commands/list.test.ts # one file
|
|
598
|
-
yarn mocha "test/**/*.test.ts" --grep "404" # every test whose name matches
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Tests never reach the network: HTTP is stubbed with undici's `MockAgent`,
|
|
602
|
-
which Node's built-in `fetch` honours (see `test/http.test.ts`).
|
|
603
|
-
|
|
604
|
-
The command reference above is generated by `oclif readme` and regenerated on
|
|
605
|
-
`yarn version` and on pack. After changing a command's description, flags or
|
|
606
|
-
examples, run `yarn build && yarn oclif readme --no-source-links` and commit
|
|
607
|
-
the result. The flag matters: without it the reference links into the private
|
|
608
|
-
workspace repository, which `test/readme.test.ts` refuses.
|
|
609
|
-
|
|
610
|
-
To run the CLI against a real app, command by command, with the expected
|
|
611
|
-
output and every way a line can fail, follow
|
|
612
|
-
[docs/manual-testing.md](docs/manual-testing.md).
|
|
@@ -33,7 +33,7 @@ export default class List extends RecordCommand {
|
|
|
33
33
|
if (this.printedJson(flags, response))
|
|
34
34
|
return;
|
|
35
35
|
const { pagination, records } = parseRecords(response);
|
|
36
|
-
this.log(records.length === 0 ? `No ${args.resource} records.` : renderTable(records, flags.fields));
|
|
36
|
+
this.log(records.length === 0 ? `No ${args.resource} records.` : renderTable(records, { columns: flags.fields }));
|
|
37
37
|
if (typeof pagination?.page === 'number') {
|
|
38
38
|
this.log(`Page ${pagination.page} of ${pagination.total_pages} (${pagination.total_count} records)`);
|
|
39
39
|
}
|
|
@@ -2,7 +2,7 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { fileIn, remove } from '../store.js';
|
|
3
3
|
// Not a `BaseCommand`: it makes no request, so the connection flags would be on `--help` doing nothing.
|
|
4
4
|
export default class Logout extends Command {
|
|
5
|
-
static description = 'Delete the saved host and token. AVO_API_HOST and
|
|
5
|
+
static description = 'Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep working';
|
|
6
6
|
static examples = ['<%= config.bin %> logout'];
|
|
7
7
|
async run() {
|
|
8
8
|
// Nothing to read from it: parsing is what refuses a flag this command does not have, instead of ignoring it.
|
|
@@ -22,7 +22,7 @@ export default class Schema extends BaseCommand {
|
|
|
22
22
|
static flags = {
|
|
23
23
|
// No `options`: the view names are the API's to know, not this flag's.
|
|
24
24
|
view: Flags.string({
|
|
25
|
-
description: `Which view's fields: create/update list what a write may send, with
|
|
25
|
+
description: `Which view's fields: create/update list what a write may send, with their field_options; index/show what a record reads back (default: ${DEFAULT_VIEW})`,
|
|
26
26
|
}),
|
|
27
27
|
};
|
|
28
28
|
static hiddenAliases = ['s'];
|
|
@@ -43,7 +43,13 @@ export default class Schema extends BaseCommand {
|
|
|
43
43
|
if (this.printedJson(flags, response))
|
|
44
44
|
return;
|
|
45
45
|
const { fields } = schema;
|
|
46
|
-
|
|
46
|
+
// Headed `field id`, `field type`, `field options`, as a person reads them; `--format json` keeps the keys.
|
|
47
|
+
// Never cut: the CLI checks nothing against the view (decisions.md), so a truncated cell is the only place a
|
|
48
|
+
// reader could have seen the `options` before sending.
|
|
49
|
+
const rows = fields.map((field) => Object.fromEntries(Object.entries(field).map(([key, value]) => [key.replaceAll('_', ' '), value])));
|
|
50
|
+
this.log(fields.length === 0
|
|
51
|
+
? `No fields on the ${asked} view of ${resource}.`
|
|
52
|
+
: renderTable(rows, { maxCellWidth: Infinity }));
|
|
47
53
|
}
|
|
48
54
|
async listResources(client, flags) {
|
|
49
55
|
const { response, schema } = await loadSchema(client);
|
|
@@ -53,6 +59,6 @@ export default class Schema extends BaseCommand {
|
|
|
53
59
|
this.log(`No resources: nothing API ${schema.api_version} serves is within this token's entitlements.`);
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
56
|
-
this.log(renderTable(schema.resources, ['route_key', 'name', 'entitlements']));
|
|
62
|
+
this.log(renderTable(schema.resources, { columns: ['route_key', 'name', 'entitlements'] }));
|
|
57
63
|
}
|
|
58
64
|
}
|
data/payload/dist/payload.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CliError } from './errors.js';
|
|
2
2
|
// A value is sent as given and the server validates it: a second copy of the API's rules here would refuse a write a
|
|
3
|
-
// newer server accepts. The
|
|
3
|
+
// newer server accepts. The view's `field_type` is read only for a file, and for the two types that take their JSON
|
|
4
|
+
// as a string.
|
|
4
5
|
export function buildPayload(data, fields) {
|
|
5
6
|
const payload = {};
|
|
6
|
-
const byId = new Map(fields.map((field) => [field.
|
|
7
|
+
const byId = new Map(fields.map((field) => [field.field_id, field]));
|
|
7
8
|
for (const [id, value] of Object.entries(data)) {
|
|
8
9
|
payload[id] = onTheWire(byId.get(id), value);
|
|
9
10
|
}
|
|
@@ -12,24 +13,17 @@ export function buildPayload(data, fields) {
|
|
|
12
13
|
// `key_value` and `code` take their JSON as a string and parse it themselves; sent as an object, a `key_value` column
|
|
13
14
|
// is wiped.
|
|
14
15
|
const JSON_AS_STRING = new Set(['code', 'key_value']);
|
|
15
|
-
// null
|
|
16
|
-
//
|
|
16
|
+
// null is sent as given: the API reads it as "clear" on every field, a list and a two-column location included
|
|
17
|
+
// (held by avo-api's `field_types_update_test.rb`).
|
|
17
18
|
function onTheWire(field, value) {
|
|
18
19
|
if (!field)
|
|
19
20
|
return value;
|
|
20
|
-
const { id,
|
|
21
|
-
//
|
|
21
|
+
const { field_id: id, field_type: type } = field;
|
|
22
|
+
// A file is bytes the JSON body cannot carry.
|
|
22
23
|
if (type === 'file' || type === 'files') {
|
|
23
24
|
throw CliError.usage(`${id} is a ${type} field, and a file needs a multipart upload the CLI does not send.`);
|
|
24
25
|
}
|
|
25
|
-
if (value ===
|
|
26
|
-
if (shape === 'array')
|
|
27
|
-
return [];
|
|
28
|
-
if (shape === 'hash')
|
|
29
|
-
return Object.fromEntries(keys.map((key) => [key, null]));
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
if (typeof value === 'object' && JSON_AS_STRING.has(type))
|
|
26
|
+
if (value !== null && typeof value === 'object' && JSON_AS_STRING.has(type))
|
|
33
27
|
return JSON.stringify(value);
|
|
34
28
|
return value;
|
|
35
29
|
}
|
data/payload/dist/table.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// Wide enough for an email or a title, narrow enough that five columns still fit a terminal.
|
|
1
|
+
// Wide enough for an email or a title, narrow enough that five columns still fit a terminal. A caller whose cells
|
|
2
|
+
// are the answer (`schema`, where a cut `field_options` loses the `options`) passes its own width.
|
|
2
3
|
const MAX_CELL_WIDTH = 40;
|
|
3
4
|
// No box drawing and no colour: it has to read the same in a terminal, in a CI log, and to an agent.
|
|
4
|
-
export function renderTable(rows,
|
|
5
|
-
const
|
|
5
|
+
export function renderTable(rows, options = {}) {
|
|
6
|
+
const { columns = collectColumns(rows), maxCellWidth = MAX_CELL_WIDTH } = options;
|
|
7
|
+
const cells = rows.map((row) => columns.map((column) => truncate(formatCell(row[column]), maxCellWidth)));
|
|
6
8
|
// Widened a cell at a time: `Math.max(...column)` can exceed the argument limit on a long page.
|
|
7
9
|
const widths = columns.map((column) => column.length);
|
|
8
10
|
for (const row of cells) {
|
|
@@ -11,7 +13,10 @@ export function renderTable(rows, columns = collectColumns(rows)) {
|
|
|
11
13
|
widths[index] = cell.length;
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
const line = (values) => values
|
|
16
|
+
const line = (values) => values
|
|
17
|
+
.map((value, index) => value.padEnd(widths[index]))
|
|
18
|
+
.join(' ')
|
|
19
|
+
.trimEnd();
|
|
15
20
|
return [line(columns), ...cells.map((cell) => line(cell))].join('\n');
|
|
16
21
|
}
|
|
17
22
|
// Nothing becomes `-`, so an empty cell is visibly empty. An object becomes its JSON: the wire carries no type, so an
|
|
@@ -35,6 +40,6 @@ function collectColumns(rows) {
|
|
|
35
40
|
}
|
|
36
41
|
return [...columns];
|
|
37
42
|
}
|
|
38
|
-
function truncate(text) {
|
|
39
|
-
return text.length >
|
|
43
|
+
function truncate(text, maxCellWidth) {
|
|
44
|
+
return text.length > maxCellWidth ? `${text.slice(0, maxCellWidth - 1)}…` : text;
|
|
40
45
|
}
|
data/payload/oclif.manifest.json
CHANGED
|
@@ -551,7 +551,7 @@
|
|
|
551
551
|
"logout": {
|
|
552
552
|
"aliases": [],
|
|
553
553
|
"args": {},
|
|
554
|
-
"description": "Delete the saved host and token. AVO_API_HOST and
|
|
554
|
+
"description": "Delete the saved host and token. AVO_API_HOST, AVO_API_TOKEN and AVO_API_VERSION exported in the environment keep working",
|
|
555
555
|
"examples": [
|
|
556
556
|
"<%= config.bin %> logout"
|
|
557
557
|
],
|
|
@@ -636,7 +636,7 @@
|
|
|
636
636
|
"type": "option"
|
|
637
637
|
},
|
|
638
638
|
"view": {
|
|
639
|
-
"description": "Which view's fields: create/update list what a write may send, with
|
|
639
|
+
"description": "Which view's fields: create/update list what a write may send, with their field_options; index/show what a record reads back (default: create)",
|
|
640
640
|
"name": "view",
|
|
641
641
|
"hasDynamicHelp": false,
|
|
642
642
|
"multiple": false,
|
|
@@ -789,5 +789,5 @@
|
|
|
789
789
|
]
|
|
790
790
|
}
|
|
791
791
|
},
|
|
792
|
-
"version": "4.2.0.beta.
|
|
792
|
+
"version": "4.2.0.beta.4"
|
|
793
793
|
}
|
data/payload/package.json
CHANGED