@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -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 a minute, so a removed member cannot keep calling it.
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 and uses the server key of the
188
- project: send `process.env.XFLOW_SERVER_KEY` in the `X-Server-Key` header.
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 opens the function in the web interface, section "Внешний доступ", and
193
- issues it there. Ask the user to do that and to paste the address back to you — never invent
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 makes the file visible to the
263
- app; `delete` takes the file `url`. Never pipe the bytes through the function itself.
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