@getxflow/cli 0.3.1 → 0.3.2
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/functions.js +7 -0
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +36 -7
|
@@ -8,6 +8,12 @@ const config_1 = require("../config");
|
|
|
8
8
|
const errors_1 = require("../errors");
|
|
9
9
|
const session_1 = require("../session");
|
|
10
10
|
const ui_1 = require("../ui");
|
|
11
|
+
/** Who can call the function: inside the app only, or an outside service holding a key. */
|
|
12
|
+
function accessLabel(fn) {
|
|
13
|
+
if (!fn.external_keys)
|
|
14
|
+
return 'in-app only';
|
|
15
|
+
return `external (${fn.external_keys} key${fn.external_keys > 1 ? 's' : ''})`;
|
|
16
|
+
}
|
|
11
17
|
const FUNCTIONS_DIR = 'functions';
|
|
12
18
|
async function functionsList() {
|
|
13
19
|
const { config } = (0, config_1.requireProject)();
|
|
@@ -20,6 +26,7 @@ async function functionsList() {
|
|
|
20
26
|
(0, ui_1.table)(data.functions.map((fn) => [
|
|
21
27
|
fn.name,
|
|
22
28
|
fn.status === 'deployed' ? 'deployed' : fn.status === 'failed' ? 'failed' : fn.status,
|
|
29
|
+
accessLabel(fn),
|
|
23
30
|
fn.last_deployed_at ? (0, ui_1.formatAge)(fn.last_deployed_at) : '-',
|
|
24
31
|
fn.error_message ?? '',
|
|
25
32
|
]));
|
package/package.json
CHANGED
package/skills/xflow/SKILL.md
CHANGED
|
@@ -180,20 +180,27 @@ exports.minRole = 'admin' // 'member' | 'developer' | 'admin' | 'owner'
|
|
|
180
180
|
The wrapper refuses anything below that role before your code runs. Without the line every
|
|
181
181
|
member of the project can call the function, including the ones who may only look at apps.
|
|
182
182
|
|
|
183
|
-
Losing access closes the function within
|
|
183
|
+
Losing access closes the function within five minutes, so a removed member cannot keep calling it.
|
|
184
184
|
Opening the deployed address directly does not work either: there is no pass outside the
|
|
185
185
|
platform.
|
|
186
186
|
|
|
187
|
-
Calling a function from another function is a server call
|
|
188
|
-
|
|
187
|
+
Calling a function from another function is a server call. Send two headers, both from the
|
|
188
|
+
environment the platform fills in: `X-Project-Token` with `process.env.XFLOW_PROJECT_TOKEN`
|
|
189
|
+
and `X-Server-Key` with `process.env.XFLOW_SERVER_KEY`. The token is the ticket into the
|
|
190
|
+
project and the key is the identity; the wrapper checks the ticket first, so the key alone
|
|
191
|
+
answers 401.
|
|
189
192
|
|
|
190
193
|
An outside service (a webhook from a payment provider, a bot, a CRM) has no person behind it
|
|
191
194
|
and needs a key of that one function. Keys are not issued by default and the CLI cannot
|
|
192
|
-
create one: a human
|
|
193
|
-
|
|
195
|
+
create one: a human issues it in the project settings: «Облачные функции» → the function →
|
|
196
|
+
«Настройки». Ask the user to do that and to paste the address back to you — never invent
|
|
194
197
|
another way in. A function holds at most two keys, and the second one exists to replace the
|
|
195
198
|
first without downtime, not to serve a second consumer.
|
|
196
199
|
|
|
200
|
+
`xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
|
|
201
|
+
only inside the application) or `external (N keys)` (a human issued external access). Key
|
|
202
|
+
values are never shown there.
|
|
203
|
+
|
|
197
204
|
Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
|
|
198
205
|
writes one, `xflow env` lists the names, `xflow env check` tells you which variables your
|
|
199
206
|
functions read but the platform does not have. Values never come back out — the only place
|
|
@@ -259,8 +266,30 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
|
|
|
259
266
|
}).then((r) => r.json())
|
|
260
267
|
```
|
|
261
268
|
|
|
262
|
-
`confirm` takes the same fields plus the returned `s3Key` and
|
|
263
|
-
|
|
269
|
+
`confirm` takes the same fields plus the returned `s3Key` and answers with the file and its
|
|
270
|
+
public address; `delete` takes the file `url`. Never pipe the bytes through the function itself.
|
|
271
|
+
|
|
272
|
+
There is no endpoint that lists the files back, so the address that `confirm` returns is the
|
|
273
|
+
only copy you get: write it into a table of your own in the same call, and the application
|
|
274
|
+
reads its files from there.
|
|
275
|
+
|
|
276
|
+
Four things bite an upload that otherwise looks right, and none of them is obvious from the
|
|
277
|
+
answers you get:
|
|
278
|
+
|
|
279
|
+
- **The content type can quietly split in two.** Nothing checks that the `Content-Type` the
|
|
280
|
+
browser sends on the PUT matches the one you named in `upload-url`: both calls answer 200.
|
|
281
|
+
But storage keeps the header the browser sent and serves the file under it, while the
|
|
282
|
+
record keeps the one you named, so a card can say `image/png` about a file every browser
|
|
283
|
+
treats as HTML. Send the same string in both calls and they cannot drift.
|
|
284
|
+
- **The same name in the same folder is refused.** Names are unique per folder, so a second
|
|
285
|
+
`avatar.png` fails instead of replacing the first. Give the name a suffix of your own, or
|
|
286
|
+
delete the old file before confirming the new one.
|
|
287
|
+
- **The limits are 200 MB per file and the storage quota of the organization.** The quota is
|
|
288
|
+
checked again on `confirm`, by the real size, which means a refusal can land after the
|
|
289
|
+
bytes are already up; the platform then removes the object and your table stays clean.
|
|
290
|
+
- **Confirm only after the PUT has finished.** The platform looks the object up in storage
|
|
291
|
+
and takes its real size from there, not from what you declared, so an early `confirm`
|
|
292
|
+
answers that the file is not there.
|
|
264
293
|
|
|
265
294
|
What the app may do with files is decided inside that function, because the page in the
|
|
266
295
|
browser can be edited by whoever opened it. Never write the key into the sources and never
|