@getxflow/cli 0.30.0 → 0.32.0
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 +9 -5
- package/dist/commands/projects.js +22 -11
- package/dist/help.js +9 -8
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +36 -15
|
@@ -9,11 +9,12 @@ const errors_1 = require("../errors");
|
|
|
9
9
|
const json_arg_1 = require("../json-arg");
|
|
10
10
|
const session_1 = require("../session");
|
|
11
11
|
const ui_1 = require("../ui");
|
|
12
|
-
/** Who can call the function:
|
|
12
|
+
/** Who can call the function: anyone, an outside service holding a key, or the app alone. */
|
|
13
13
|
function accessLabel(fn) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const keys = fn.external_keys ? `external (${fn.external_keys} key${fn.external_keys > 1 ? 's' : ''})` : '';
|
|
15
|
+
if (fn.is_public)
|
|
16
|
+
return keys ? `public, ${keys}` : 'public';
|
|
17
|
+
return keys || 'in-app only';
|
|
17
18
|
}
|
|
18
19
|
const FUNCTIONS_DIR = 'functions';
|
|
19
20
|
async function functionsList(args) {
|
|
@@ -28,6 +29,9 @@ async function functionsList(args) {
|
|
|
28
29
|
fn.status === 'deployed' ? 'deployed' : fn.status === 'failed' ? 'failed' : fn.status,
|
|
29
30
|
accessLabel(fn),
|
|
30
31
|
fn.last_deployed_at ? (0, ui_1.formatAge)(fn.last_deployed_at) : '-',
|
|
32
|
+
// The address is not a secret (the app bundle carries it): a page or a
|
|
33
|
+
// webhook needs it, and asking the user to copy it out of the UI was a detour.
|
|
34
|
+
fn.invoke_url ?? '',
|
|
31
35
|
fn.error_message ?? '',
|
|
32
36
|
]));
|
|
33
37
|
}
|
|
@@ -50,7 +54,7 @@ function prettyBody(text) {
|
|
|
50
54
|
* this package ships without dependencies and cannot reach into the platform. One test
|
|
51
55
|
* holds the two halves together.
|
|
52
56
|
*/
|
|
53
|
-
const WRAPPER_REASONS = ['no_pass', 'invalid', 'expired', 'no_access', 'unavailable', 'role'];
|
|
57
|
+
const WRAPPER_REASONS = ['no_pass', 'invalid', 'expired', 'no_access', 'unavailable', 'role', 'access'];
|
|
54
58
|
/** What to look at next, when the function did not answer with a success. */
|
|
55
59
|
function failureHint(name, status, body) {
|
|
56
60
|
let reason;
|
|
@@ -49,27 +49,38 @@ async function templates() {
|
|
|
49
49
|
}
|
|
50
50
|
(0, ui_1.table)(list.map((t) => [t.id, t.name, t.is_default ? 'default' : '', t.description ?? '']));
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* letters, digits and dashes, and the folder it lives in gives it its name.
|
|
55
|
-
*/
|
|
56
|
-
function functionNameFrom(name) {
|
|
57
|
-
const slug = name
|
|
52
|
+
function slugify(name) {
|
|
53
|
+
return name
|
|
58
54
|
.toLowerCase()
|
|
59
55
|
.replace(/[^a-z0-9-]+/g, '-')
|
|
60
56
|
.replace(/^-+|-+$/g, '')
|
|
61
57
|
.slice(0, 41);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A function name out of the project name: the platform takes lowercase latin
|
|
61
|
+
* letters, digits and dashes, and the folder it lives in gives it its name.
|
|
62
|
+
*
|
|
63
|
+
* The folder name is the fallback, so a project called in Russian still gets a
|
|
64
|
+
* function: the name a person reads and the name the cloud takes are different
|
|
65
|
+
* things, and the folder already holds a usable one.
|
|
66
|
+
*/
|
|
67
|
+
function functionNameFrom(name, folder) {
|
|
68
|
+
const usable = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
69
|
+
const slug = slugify(name);
|
|
70
|
+
if (usable.test(slug))
|
|
71
|
+
return slug;
|
|
72
|
+
const fromFolder = folder ? slugify(folder) : '';
|
|
73
|
+
if (usable.test(fromFolder))
|
|
74
|
+
return fromFolder;
|
|
75
|
+
throw new errors_1.CliError(`Cannot make a function name out of "${name}"`, 'The name comes from the project name, and the folder is the fallback: name either of them in latin ' +
|
|
76
|
+
'letters, digits and dashes (xflow init my-fn --function)');
|
|
66
77
|
}
|
|
67
78
|
/**
|
|
68
79
|
* Create a standalone function: a shared backend of the organization, a project
|
|
69
80
|
* without a site. No template is fetched — there is no frontend to fetch.
|
|
70
81
|
*/
|
|
71
82
|
async function initFunction(args, client, target, name) {
|
|
72
|
-
const functionName = functionNameFrom(name);
|
|
83
|
+
const functionName = functionNameFrom(name, (0, node_path_1.basename)(target));
|
|
73
84
|
(0, ui_1.step)('Creating the function on the platform');
|
|
74
85
|
const project = await (0, api_1.apiJson)(client, '/api/v1/projects', {
|
|
75
86
|
method: 'POST',
|
package/dist/help.js
CHANGED
|
@@ -64,7 +64,7 @@ ${(0, ui_1.bold)('Releasing')}
|
|
|
64
64
|
xflow deployments version history
|
|
65
65
|
|
|
66
66
|
${(0, ui_1.bold)('Functions')}
|
|
67
|
-
xflow functions list what is deployed
|
|
67
|
+
xflow functions list what is deployed, who may call it, the address
|
|
68
68
|
xflow functions invoke <name> [--data '{"a":1}']
|
|
69
69
|
call a function and print the answer
|
|
70
70
|
xflow functions logs [name] function crashes: stack and console output
|
|
@@ -261,13 +261,14 @@ build it and upload what the build writes. Dot entries are skipped. At most 5 MB
|
|
|
261
261
|
unpacked and 1000 files.
|
|
262
262
|
|
|
263
263
|
The page is public, so a file holding a secret key is refused whole: every visitor would read
|
|
264
|
-
it. A
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
and deleting it are for a person, in the
|
|
264
|
+
it. A page that needs a backend (a form, a lookup) calls a cloud function that declares
|
|
265
|
+
${(0, ui_1.bold)("exports.access = 'public'")}: such a function answers without a pass and without a
|
|
266
|
+
key, and the page calls its address directly (${(0, ui_1.bold)('xflow functions list')} prints it). No
|
|
267
|
+
key is baked into the page. Search engines are asked not to index pages, so do not promise SEO.
|
|
268
|
+
|
|
269
|
+
A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
|
|
270
|
+
scoped to projects is refused. Switching a page off and deleting it are for a person, in the
|
|
271
|
+
platform interface.`,
|
|
271
272
|
update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
|
|
272
273
|
|
|
273
274
|
Installs the published version and, if anything changed, rewrites the platform
|
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.
|
|
5
|
+
exports.CLI_VERSION = '0.32.0';
|
|
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
|
@@ -22,7 +22,7 @@ contains the fix.
|
|
|
22
22
|
|
|
23
23
|
## Keeping these instructions current
|
|
24
24
|
|
|
25
|
-
These instructions ship with xflow CLI 0.
|
|
25
|
+
These instructions ship with xflow CLI 0.32.0, so the copy you are reading can be older than
|
|
26
26
|
the CLI answering your commands.
|
|
27
27
|
|
|
28
28
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
@@ -223,7 +223,8 @@ application, in that order. `xflow functions list` shows what is live. The handl
|
|
|
223
223
|
`{ statusCode, body }` where `body` is a JSON string.
|
|
224
224
|
|
|
225
225
|
A function that belongs to no single application is created on its own: `xflow init <dir>
|
|
226
|
-
--function` makes a project without a site, with one handler and no template.
|
|
226
|
+
--function` makes a project without a site, with one handler and no template. The function name
|
|
227
|
+
comes from the project name, or from the folder when the project name is not latin. It is a shared
|
|
227
228
|
backend of the organization, and any application of that organization calls it; who is let in
|
|
228
229
|
is decided by `minRole` inside the function. Deploy builds and releases it the same way, but it
|
|
229
230
|
has no pages, so `xflow publish` and `xflow rollback` do not apply; an earlier version comes back
|
|
@@ -287,7 +288,8 @@ with the platform on every call, and the handler receives the answer in `event.x
|
|
|
287
288
|
exports.handler = async (event) => {
|
|
288
289
|
const { caller, user } = event.xflow
|
|
289
290
|
// caller: 'visitor' (a person), 'service' (another function of this project),
|
|
290
|
-
// 'external' (an outside service with a key), 'schedule' (a timer run)
|
|
291
|
+
// 'external' (an outside service with a key), 'schedule' (a timer run),
|
|
292
|
+
// 'public' (anyone, see below)
|
|
291
293
|
// user: { id, role } for a visitor, null for everything else
|
|
292
294
|
}
|
|
293
295
|
```
|
|
@@ -320,13 +322,30 @@ An outside service (a payment webhook, a bot, a CRM) has no person behind it and
|
|
|
320
322
|
that one function. Keys are not issued by default and the CLI cannot create one: a human issues
|
|
321
323
|
it in the project settings, «Облачные функции» → the function → «Настройки»; a standalone
|
|
322
324
|
function has a screen of its own, «Функции» → the function → «Настройки». Ask the user to do
|
|
323
|
-
that
|
|
324
|
-
`
|
|
325
|
-
without downtime, not to serve a second consumer.
|
|
325
|
+
that — never invent another way in. The service sends the key in the `X-Function-Key` header
|
|
326
|
+
to the address `xflow functions list` prints. A function holds at most two keys, the second
|
|
327
|
+
one to replace the first without downtime, not to serve a second consumer.
|
|
326
328
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
A function behind a public page (a form on a landing, a lookup, a calculator on
|
|
330
|
+
`xflowpage.ru`) has neither a person nor a key: every visitor calls it. Declare that in the
|
|
331
|
+
code and nothing else is needed, no key, no setting, nobody to ask:
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
exports.access = 'public'
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The wrapper then lets a call without any credential through, with `caller: 'public'` and
|
|
338
|
+
`user: null`, and never asks the platform. The page calls the address from `xflow functions
|
|
339
|
+
list` directly (CORS is open) and bakes no key into its files. Everything else stays as it
|
|
340
|
+
is: a call that does carry a pass or a key is still checked. Use it only for a function whose
|
|
341
|
+
work is fine for anyone on the internet to trigger: it cannot hold `minRole`, and it must not
|
|
342
|
+
write or delete data on the caller's word, there is no caller to trust. The platform marks
|
|
343
|
+
such a function «Открыта всем»
|
|
344
|
+
in the interface, and removing the line and deploying closes it again.
|
|
345
|
+
|
|
346
|
+
`xflow functions list` shows who can reach each function: `in-app only` (answers only inside
|
|
347
|
+
the application), `external (N keys)` (a human issued external access), `public` (the code
|
|
348
|
+
opened it), and the address of every deployed function. Key values are never shown there.
|
|
330
349
|
|
|
331
350
|
## Secrets and connected accounts
|
|
332
351
|
|
|
@@ -604,15 +623,17 @@ platform: pick a page when the audience is outside the organization.
|
|
|
604
623
|
- Static files only: HTML, CSS, JS, images, fonts, PDF, audio, video. No server code, database
|
|
605
624
|
or functions on a page. Upload what the build writes (`dist`), never a folder with
|
|
606
625
|
`package.json`. At most 5 MB zipped, 25 MB unpacked, 1000 files.
|
|
626
|
+
- A page that needs a backend (a form, a lookup) calls a cloud function of a project that
|
|
627
|
+
declares `exports.access = 'public'` (see «Who is calling»): the page fetches the address
|
|
628
|
+
from `xflow functions list` directly, with no key and no header. The function lives in a
|
|
629
|
+
project, the page does not: deploy the function first, then upload the page.
|
|
607
630
|
- Everything in a page is readable by every visitor. Never put a key, token or password into
|
|
608
|
-
it: a known key format gets the upload refused, anything else simply leaks.
|
|
609
|
-
the exception: a person issues it in the settings of the function, and a form on a page may
|
|
610
|
-
call that function with it. So can every visitor.
|
|
631
|
+
it: a known key format gets the upload refused, anything else simply leaks.
|
|
611
632
|
- Search engines are asked not to index pages: do not promise SEO.
|
|
612
633
|
- The MCP `pages` tool uploads one self-contained HTML document up to 150 KB.
|
|
613
|
-
- Pages take the key of the whole organization, the one `xflow login` issues
|
|
614
|
-
|
|
615
|
-
|
|
634
|
+
- Pages take the key of the whole organization, the one `xflow login` issues; a key scoped to
|
|
635
|
+
projects is refused, and that refusal is not yours to fix. Switching a page off and deleting it
|
|
636
|
+
are for a person, in the platform interface.
|
|
616
637
|
|
|
617
638
|
## Organizations and keys
|
|
618
639
|
|