@getxflow/cli 0.3.1 → 0.3.3

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/dist/version.js CHANGED
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
4
4
  /** Keep in sync with cli/package.json. */
5
- exports.CLI_VERSION = '0.3.1';
5
+ exports.CLI_VERSION = '0.3.3';
6
6
  /** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
7
7
  exports.DEFAULT_API_URL = 'https://app.getxflow.com';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -6,8 +6,9 @@ description: Build, deploy and publish apps on the XFlow platform with the xflow
6
6
  # XFlow
7
7
 
8
8
  Hosting for web apps. The code lives in an ordinary repository on the developer
9
- machine and is built locally; the platform takes the finished build and serves it.
10
- A platform project is recognized by the `xflow.json` file in its root.
9
+ machine, `xflow deploy` sends the sources, and the platform builds them in a clean
10
+ sandbox and serves the result as static files. A platform project is recognized by
11
+ the `xflow.json` file in its root.
11
12
 
12
13
  ## First rule
13
14
 
@@ -134,6 +135,12 @@ separate deploy command: `xflow deploy` ships the functions and then builds the
134
135
  in that order. List what is live with `xflow functions list`. The handler returns
135
136
  `{ statusCode, body }` where `body` is a JSON string.
136
137
 
138
+ Keep one shape inside that string across the whole project: `{ success: true, data }`
139
+ when it worked, `{ success: false, error: { message, code } }` when it did not. Nothing
140
+ enforces this, but a project where every function answers its own way costs an adapter
141
+ on every call. Branch the frontend on `error.code`, never on `error.message`: wording
142
+ gets rewritten on any edit, a code does not.
143
+
137
144
  The sources are the whole truth about which functions exist. Delete the directory and the
138
145
  next deploy deletes the function from the cloud, schedules included, and that cannot be
139
146
  undone: a function created again later gets a different address. So never remove a function
@@ -180,20 +187,27 @@ exports.minRole = 'admin' // 'member' | 'developer' | 'admin' | 'owner'
180
187
  The wrapper refuses anything below that role before your code runs. Without the line every
181
188
  member of the project can call the function, including the ones who may only look at apps.
182
189
 
183
- Losing access closes the function within a minute, so a removed member cannot keep calling it.
190
+ Losing access closes the function within five minutes, so a removed member cannot keep calling it.
184
191
  Opening the deployed address directly does not work either: there is no pass outside the
185
192
  platform.
186
193
 
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.
194
+ Calling a function from another function is a server call. Send two headers, both from the
195
+ environment the platform fills in: `X-Project-Token` with `process.env.XFLOW_PROJECT_TOKEN`
196
+ and `X-Server-Key` with `process.env.XFLOW_SERVER_KEY`. The token is the ticket into the
197
+ project and the key is the identity; the wrapper checks the ticket first, so the key alone
198
+ answers 401.
189
199
 
190
200
  An outside service (a webhook from a payment provider, a bot, a CRM) has no person behind it
191
201
  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
202
+ create one: a human issues it in the project settings: «Облачные функции» the function →
203
+ «Настройки». Ask the user to do that and to paste the address back to you — never invent
194
204
  another way in. A function holds at most two keys, and the second one exists to replace the
195
205
  first without downtime, not to serve a second consumer.
196
206
 
207
+ `xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
208
+ only inside the application) or `external (N keys)` (a human issued external access). Key
209
+ values are never shown there.
210
+
197
211
  Keys and passwords live on the platform, not in the repository: `xflow env set SMTP_PASSWORD=…`
198
212
  writes one, `xflow env` lists the names, `xflow env check` tells you which variables your
199
213
  functions read but the platform does not have. Values never come back out — the only place
@@ -259,8 +273,34 @@ const link = await fetch(`${process.env.XFLOW_API_URL}/api/storage/project/uploa
259
273
  }).then((r) => r.json())
260
274
  ```
261
275
 
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.
276
+ `confirm` takes the same fields plus the returned `s3Key` and answers with the file and its
277
+ public address; `delete` takes the file `url`. Never pipe the bytes through the function itself.
278
+
279
+ There is no endpoint that lists the files back, so the address that `confirm` returns is the
280
+ only copy you get: write it into a table of your own in the same call, and the application
281
+ reads its files from there.
282
+
283
+ Four things bite an upload that otherwise looks right, and none of them is obvious from the
284
+ answers you get:
285
+
286
+ - **The content type can quietly split in two.** Nothing checks that the `Content-Type` the
287
+ browser sends on the PUT matches the one you named in `upload-url`: both calls answer 200.
288
+ But storage keeps the header the browser sent and serves the file under it, while the
289
+ record keeps the one you named, so a card can say `image/png` about a file every browser
290
+ treats as HTML. Send the same string in both calls and they cannot drift.
291
+ - **The same name in the same folder is refused.** Names are unique per folder, so a second
292
+ `avatar.png` fails instead of replacing the first. Give the name a suffix of your own, or
293
+ delete the old file before confirming the new one.
294
+ - **The limits are 200 MB per file and the storage quota of the organization.** The quota is
295
+ checked again on `confirm`, by the real size, which means a refusal can land after the
296
+ bytes are already up; the platform then removes the object and your table stays clean.
297
+ - **Confirm only after the PUT has finished.** The platform looks the object up in storage
298
+ and takes its real size from there, not from what you declared, so an early `confirm`
299
+ answers that the file is not there.
300
+
301
+ A refusal comes back as `{ error, code }`. Branch on `code` (`invalid_name`, `file_too_large`,
302
+ `quota_exceeded`, `not_uploaded`, `duplicate_name`, `not_found`, …) and never on the text:
303
+ the wording is free to change, the code is not.
264
304
 
265
305
  What the app may do with files is decided inside that function, because the page in the
266
306
  browser can be edited by whoever opened it. Never write the key into the sources and never