@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.
- package/dist/commands/functions.js +7 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +49 -9
|
@@ -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.
|
|
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
package/skills/xflow/SKILL.md
CHANGED
|
@@ -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
|
|
10
|
-
|
|
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
|
|
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
|
|
188
|
-
|
|
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
|
|
193
|
-
|
|
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
|
|
263
|
-
|
|
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
|