@getxflow/cli 1.1.0 → 1.1.1
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 +1 -1
- package/dist/commands/pages.js +0 -1
- package/dist/help.js +11 -12
- package/dist/template.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +23 -24
|
@@ -24,7 +24,7 @@ async function functionsList(args) {
|
|
|
24
24
|
fn.last_called_at ? `called ${(0, ui_1.formatAge)(fn.last_called_at)}` : 'never called',
|
|
25
25
|
fn.last_error_at ? `failed ${(0, ui_1.formatAge)(fn.last_error_at)}` : '',
|
|
26
26
|
]));
|
|
27
|
-
(0, ui_1.note)((0, ui_1.dim)(' callers: the app folders
|
|
27
|
+
(0, ui_1.note)((0, ui_1.dim)(' callers: the app folders allowed to call it (functions in the root xflow.json)'));
|
|
28
28
|
(0, ui_1.note)((0, ui_1.dim)(' open: a caller is public, so anyone on the internet can call it; no callers: a workflow'));
|
|
29
29
|
(0, ui_1.note)((0, ui_1.dim)(` Webhooks and other functions call ${functions[0].url.replace(/\/[^/]+$/, '/<name>')} with a key`));
|
|
30
30
|
}
|
package/dist/commands/pages.js
CHANGED
|
@@ -99,7 +99,6 @@ async function pagesCreate(args) {
|
|
|
99
99
|
(0, ui_1.ok)(`Page "${result.page.title}" is live: ${result.file_count} file(s), ${(0, ui_1.formatBytes)(result.size_bytes)}`);
|
|
100
100
|
(0, ui_1.out)(result.page.url);
|
|
101
101
|
(0, ui_1.note)((0, ui_1.dim)(` Anyone with the address sees it within seconds. The next version: xflow pages deploy ${result.page.slug} <path>`));
|
|
102
|
-
(0, ui_1.note)((0, ui_1.dim)(` A backend for it: name "${result.page.slug}" in functions.<name>.callers of a project, xflow deploy functions, then fetch /_fn/<name>`));
|
|
103
102
|
}
|
|
104
103
|
function contentTypeOf(upload) {
|
|
105
104
|
return upload.kind === 'zip' ? 'application/zip' : 'text/html';
|
package/dist/help.js
CHANGED
|
@@ -301,11 +301,10 @@ build it and upload what the build writes. Dot entries are skipped. At most 5 MB
|
|
|
301
301
|
unpacked and 1000 files.
|
|
302
302
|
|
|
303
303
|
The page is public, so a file holding a secret key is refused whole: every visitor would read
|
|
304
|
-
it. A page
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
so do not promise SEO.
|
|
304
|
+
it. A page has no backend: a form or a lookup belongs in an app with access "anyone". The one
|
|
305
|
+
key a page may hold is a function key, and it opens that function to every visitor: an HTML
|
|
306
|
+
form can post to its webhook address with ${(0, ui_1.bold)('?key=')}, while fetch cannot read the answer
|
|
307
|
+
(no CORS). Search engines are asked not to index pages, so do not promise SEO.
|
|
309
308
|
|
|
310
309
|
${(0, ui_1.bold)('Deleting is final.')} The address goes empty and every version of the page is gone: there
|
|
311
310
|
is no undo and no copy on the platform side. The name stays reserved for the organization, so
|
|
@@ -618,14 +617,14 @@ of the project runs the new code the moment it finishes; there is no publish ste
|
|
|
618
617
|
|
|
619
618
|
Who may call a function is its callers, in the root xflow.json:
|
|
620
619
|
|
|
621
|
-
{ "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"
|
|
620
|
+
{ "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"] } } }
|
|
622
621
|
|
|
623
|
-
A caller is an app folder of this project (apps/zapis is "zapis")
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
622
|
+
A caller is an app folder of this project (apps/zapis is "zapis"); pages are static and call
|
|
623
|
+
nothing. A function nobody lists has no callers: a workflow, woken by a schedule, by a service
|
|
624
|
+
holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a caller is an app
|
|
625
|
+
anyone with the link opens, so anyone on the internet can call it. The address in the note
|
|
626
|
+
under the table is for webhooks and other functions, with a key; apps call ${(0, ui_1.bold)('/_fn/<name>')} on
|
|
627
|
+
their own address.
|
|
629
628
|
|
|
630
629
|
${(0, ui_1.bold)('invoke')} calls it as the person who owns the key: the platform checks the role, gives
|
|
631
630
|
the function a one-minute ticket and calls it itself, so the function sees
|
package/dist/template.js
CHANGED
|
@@ -13,7 +13,7 @@ const README = `# %NAME%
|
|
|
13
13
|
A project on XFlow: one database, cloud functions and the apps that use them.
|
|
14
14
|
|
|
15
15
|
\`\`\`
|
|
16
|
-
xflow.json projectId, and which apps
|
|
16
|
+
xflow.json projectId, and which apps may call each function
|
|
17
17
|
package.json dependencies of the functions
|
|
18
18
|
functions/<name>/ cloud functions, shared by every app
|
|
19
19
|
migrations/ the database of the project
|
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 = '1.1.
|
|
5
|
+
exports.CLI_VERSION = '1.1.1';
|
|
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
|
@@ -23,7 +23,7 @@ contains the fix.
|
|
|
23
23
|
|
|
24
24
|
## Keeping these instructions current
|
|
25
25
|
|
|
26
|
-
These instructions ship with xflow CLI 1.1.
|
|
26
|
+
These instructions ship with xflow CLI 1.1.1, so the copy you are reading can be older than
|
|
27
27
|
the CLI answering your commands.
|
|
28
28
|
|
|
29
29
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
@@ -48,10 +48,10 @@ the error points away from the cause. The sections below carry the details.
|
|
|
48
48
|
3. Never delete a `functions/<name>/` directory unless the user asked for that
|
|
49
49
|
function to go. The next deploy removes it from the cloud together with its keys and
|
|
50
50
|
schedules, and that is final.
|
|
51
|
-
4. A function an app
|
|
51
|
+
4. A function an app calls needs that app in its `callers` in the root
|
|
52
52
|
`xflow.json`. Without it the call answers 404 `not_bound`, and a new function is not
|
|
53
53
|
reachable from anywhere.
|
|
54
|
-
5. Every function
|
|
54
|
+
5. Every function called by an app open to anyone can be called by
|
|
55
55
|
anybody on the internet with curl. Write it as such, see **Your function is open to the
|
|
56
56
|
whole internet**.
|
|
57
57
|
6. An already applied migration is never re-run, and `xflow db migrate` refuses a
|
|
@@ -128,6 +128,9 @@ halfway through the work into a sentence said before it starts.
|
|
|
128
128
|
- The cap on how long one function call may run is written into the function when it is
|
|
129
129
|
deployed, so a plan change reaches the cloud only with the next `xflow deploy functions` of
|
|
130
130
|
each project, in both directions.
|
|
131
|
+
- One function runs at most a plan-set number of calls at once: Free 5, Start 15, Business 35
|
|
132
|
+
per cloud zone (the cloud has three, the spread is not promised). A call over it gets 429 at
|
|
133
|
+
once: fan out in batches of that size, never all items of a list in parallel.
|
|
131
134
|
|
|
132
135
|
A limit refusal is not a bad request. The CLI prints a line starting with `Plan limit:`, the
|
|
133
136
|
API answers `code: "forbidden"` with a `limit` object (`code`, `used`, `limit`), MCP tools
|
|
@@ -210,8 +213,7 @@ An app:
|
|
|
210
213
|
|
|
211
214
|
The project root: function names are lowercase latin letters, digits and hyphens, up to 41
|
|
212
215
|
characters (`send-mail`, not `sendMail`); every name under `functions` in `xflow.json` has its
|
|
213
|
-
`functions/<name>/index.ts`; every caller is an app folder of this project
|
|
214
|
-
of the organization (`unknown-caller`).
|
|
216
|
+
`functions/<name>/index.ts`; every caller is an app folder of this project (`unknown-caller`).
|
|
215
217
|
|
|
216
218
|
Passes with a line `Unfinished:` instead of blocking: the template's page title, no
|
|
217
219
|
`<link rel="icon">` in `index.html`, an `exports.access` left in a function (it does nothing).
|
|
@@ -263,14 +265,14 @@ The root `xflow.json` names the callers of each function:
|
|
|
263
265
|
"projectId": "…",
|
|
264
266
|
"functions": {
|
|
265
267
|
"list-slots": { "callers": ["zapis", "priem"] },
|
|
266
|
-
"book-slot": { "callers": ["zapis"
|
|
268
|
+
"book-slot": { "callers": ["zapis"] }
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
- A caller is an app folder of this project (`apps/zapis` is `"zapis"`)
|
|
271
|
-
a
|
|
272
|
-
|
|
273
|
-
- A function nobody lists has no callers: no app
|
|
272
|
+
- A caller is an app folder of this project (`apps/zapis` is `"zapis"`); a page is never
|
|
273
|
+
a caller. A function without callers is left out of `functions` or given `"callers": []`,
|
|
274
|
+
the same thing.
|
|
275
|
+
- A function nobody lists has no callers: no app reaches it. That is a workflow,
|
|
274
276
|
run by a schedule, a key or `xflow functions invoke`.
|
|
275
277
|
- Callers ship with `xflow deploy functions` and come back with its `--revision N`.
|
|
276
278
|
- The app calls `xflow.functions.invoke('book-slot', { body })` from `src/lib/xflow.ts`. The call
|
|
@@ -281,15 +283,13 @@ The root `xflow.json` names the callers of each function:
|
|
|
281
283
|
`invalid_credential` (401, the visitor has to sign in again), `no_access`, `role_too_low`,
|
|
282
284
|
`functions_stopped` (403, minutes ran out or the plan is unpaid: a person settles it),
|
|
283
285
|
`rate_limited` (429), `unavailable` (503, retry later). Branch on `code`, never on the message.
|
|
284
|
-
- A page has to exist before a function can name it: create the page, then deploy functions.
|
|
285
286
|
|
|
286
287
|
## Your function is open to the whole internet
|
|
287
288
|
|
|
288
|
-
A function is open when
|
|
289
|
-
with the link". `xflow deploy functions` says so in a line of its own, `xflow functions list`
|
|
289
|
+
A function is open when an app at "Anyone with the link" is among its callers. `xflow deploy functions` says so in a line of its own, `xflow functions list`
|
|
290
290
|
prints `open`, the web interface marks it.
|
|
291
291
|
|
|
292
|
-
Open means anybody can call it with curl, without your
|
|
292
|
+
Open means anybody can call it with curl, without your app: the browser is not a gate.
|
|
293
293
|
Hiding the address, checking `Origin` or `Referer`, a token baked into the bundle: all of it is
|
|
294
294
|
readable or forgeable and protects nothing. Write every open function as if the request came
|
|
295
295
|
from a stranger, because it may:
|
|
@@ -346,14 +346,13 @@ At the closed levels do none of this: the platform already knows the person.
|
|
|
346
346
|
is uploaded, no build, no publish step, no database. A landing page, a presentation, an invoice.
|
|
347
347
|
- An app is source the platform builds: versions and publish, access levels, a database through
|
|
348
348
|
functions. Anything people sign in to, enter data into or come back to.
|
|
349
|
-
- A form open to strangers on a plan without "Anyone with the link" is a page: pages are public
|
|
350
|
-
on every plan.
|
|
351
349
|
- Keep the sources of a page outside the project folder, or list them in `.xflowignore`: the
|
|
352
350
|
root travels with `deploy functions`.
|
|
353
|
-
- A page
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
351
|
+
- A page has no backend. A form or a lookup for strangers is an app at "Anyone with the link"
|
|
352
|
+
(a paid plan): the platform checks every call to its functions.
|
|
353
|
+
- A function key on a page is allowed, and it opens that function to every visitor: an HTML
|
|
354
|
+
form posts to `https://fn.xflowpage.ru/<projectId>/<name>?key=...`, fetch cannot read the
|
|
355
|
+
answer (no CORS). Nothing guards it but the function's own checks: say so to the user.
|
|
357
356
|
|
|
358
357
|
## Cloud functions
|
|
359
358
|
|
|
@@ -397,12 +396,12 @@ The platform checks every call before your code runs and hands the answer over i
|
|
|
397
396
|
```js
|
|
398
397
|
exports.handler = async (event) => {
|
|
399
398
|
const { caller, slug, user, ip } = event.xflow
|
|
400
|
-
// caller: 'app' (an app of this project), '
|
|
399
|
+
// caller: 'app' (an app of this project), 'service' (another function of this
|
|
401
400
|
// project), 'external' (an outside service with a key), 'schedule',
|
|
402
401
|
// 'platform' (a person through xflow functions invoke or the web interface)
|
|
403
|
-
// slug: the address name of the calling app (<id>--<org>)
|
|
402
|
+
// slug: the address name of the calling app (<id>--<org>), null otherwise
|
|
404
403
|
// user: { id, role, name, access } of a signed-in person, null for anonymous
|
|
405
|
-
// visitors
|
|
404
|
+
// visitors and keys. access: 'view' | 'edit' on this project
|
|
406
405
|
// ip: the visitor's address, null for a schedule
|
|
407
406
|
}
|
|
408
407
|
```
|
|
@@ -429,7 +428,7 @@ exports.minRole = 'developer' // 'member' | 'developer' | 'owner'
|
|
|
429
428
|
```
|
|
430
429
|
|
|
431
430
|
The platform refuses anything below that role before your code runs; it applies to people
|
|
432
|
-
(apps,
|
|
431
|
+
(apps, `functions invoke`), not to keys and schedules. `member` is the people who only
|
|
433
432
|
use the apps (clinic employees, managers), `developer` and `owner` build them. In the web
|
|
434
433
|
interface these roles are called «Просмотр» (`member`), «Редактор» (`developer`) and «Админ»
|
|
435
434
|
(`owner`). Without the line
|