@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.
@@ -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: inside the app only, or an outside service holding a key. */
12
+ /** Who can call the function: anyone, an outside service holding a key, or the app alone. */
13
13
  function accessLabel(fn) {
14
- if (!fn.external_keys)
15
- return 'in-app only';
16
- return `external (${fn.external_keys} key${fn.external_keys > 1 ? 's' : ''})`;
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
- * A function name out of the project name: the platform takes lowercase latin
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
- if (!/^[a-z0-9][a-z0-9-]{0,40}$/.test(slug)) {
63
- throw new errors_1.CliError(`Cannot make a function name out of "${name}"`, 'Name it in latin letters, digits and dashes: xflow init my-fn --function');
64
- }
65
- return slug;
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 (they ship with xflow deploy)
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 function key is the exception: it opens that one function to every visitor, which is
265
- what a form on a landing page needs. Search engines are asked not to index pages, so do not
266
- promise SEO.
267
-
268
- A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues. The
269
- section is in beta: XFlow support switches it on for an organization. Switching a page off
270
- and deleting it are for a person, in the platform interface.`,
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.30.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getxflow/cli",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "CLI for the XFlow platform: source sync, deployment and publishing of applications",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -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.30.0, so the copy you are reading can be older than
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. It is a shared
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 and to paste the address back — never invent another way in. Send it in the
324
- `X-Function-Key` header. A function holds at most two keys, the second one to replace the first
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
- `xflow functions list` shows who can reach each function: `in-app only` (no keys, answers
328
- only inside the application) or `external (N keys)` (a human issued external access). Key
329
- values are never shown there.
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. A function key is
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. The section is in
614
- beta, switched on per organization by XFlow support: that refusal is not yours to fix. Switching
615
- a page off and deleting it are for a person, in the platform interface.
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