@getxflow/cli 0.23.0 → 0.24.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/deploy.js +3 -1
- package/dist/commands/dev.js +8 -1
- package/dist/commands/functions.js +33 -1
- package/dist/commands/schedules.js +21 -6
- package/dist/help.js +10 -3
- package/dist/template.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +4 -2
package/dist/commands/deploy.js
CHANGED
|
@@ -26,7 +26,9 @@ function reportIssues(e) {
|
|
|
26
26
|
(0, ui_1.out)(` ${(0, ui_1.bold)(issue.code)} ${where}`);
|
|
27
27
|
(0, ui_1.note)((0, ui_1.dim)(` ${issue.message}`));
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// Not the platform hint: it names its own JSON field, and a terminal has no field,
|
|
30
|
+
// it has the list printed right above.
|
|
31
|
+
throw new errors_1.CliError(e.message, 'Fix what is listed above and retry: the build did not start');
|
|
30
32
|
}
|
|
31
33
|
/** Poll the build until it finishes, printing phases. */
|
|
32
34
|
async function waitForBuild(client, projectId, started) {
|
package/dist/commands/dev.js
CHANGED
|
@@ -38,7 +38,14 @@ async function dev() {
|
|
|
38
38
|
// Ctrl+C reaches the server on its own; staying alive until it is gone is what
|
|
39
39
|
// hands its exit code back instead of leaving it to print over the prompt.
|
|
40
40
|
process.on('SIGINT', () => { });
|
|
41
|
-
|
|
41
|
+
// Appended last, so it beats the --host of the dev script: the server holds the key
|
|
42
|
+
// of the project and answers this machine alone. By number, not by name: proxies of
|
|
43
|
+
// older templates cut the Host at ':' and so never match [::1].
|
|
44
|
+
const result = (0, spawn_1.run)('npm', ['run', 'dev', '--', '--host', '127.0.0.1'], {
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
cwd: root,
|
|
47
|
+
env,
|
|
48
|
+
});
|
|
42
49
|
if (result.error) {
|
|
43
50
|
throw new errors_1.CliError(`Could not start npm run dev: ${result.error.message}`, 'npm ships with Node.js: check that npm works in this terminal');
|
|
44
51
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.functionsList = functionsList;
|
|
4
|
+
exports.failureHint = failureHint;
|
|
4
5
|
exports.functionsInvoke = functionsInvoke;
|
|
5
6
|
const api_1 = require("../api");
|
|
6
7
|
const args_1 = require("../args");
|
|
@@ -38,6 +39,37 @@ function prettyBody(text) {
|
|
|
38
39
|
return text;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Refusals the wrapper writes itself, before the handler runs. Nothing about them
|
|
44
|
+
* reaches the logs, and the answer already names the cause.
|
|
45
|
+
*
|
|
46
|
+
* Matched by value and not by the presence of the field, because a handler is free to
|
|
47
|
+
* put a reason of its own into the body it returns, and that one did run.
|
|
48
|
+
*
|
|
49
|
+
* Keep in sync with lib/yandex-functions/call-outcome.ts, which the MCP tool calls:
|
|
50
|
+
* this package ships without dependencies and cannot reach into the platform. One test
|
|
51
|
+
* holds the two halves together.
|
|
52
|
+
*/
|
|
53
|
+
const WRAPPER_REASONS = ['no_pass', 'invalid', 'expired', 'no_access', 'unavailable', 'role'];
|
|
54
|
+
/** What to look at next, when the function did not answer with a success. */
|
|
55
|
+
function failureHint(name, status, body) {
|
|
56
|
+
let reason;
|
|
57
|
+
try {
|
|
58
|
+
reason = JSON.parse(body).reason;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
reason = undefined;
|
|
62
|
+
}
|
|
63
|
+
const logs = `The stack and console output: xflow functions logs ${name}`;
|
|
64
|
+
if (typeof reason !== 'string' || !WRAPPER_REASONS.includes(reason))
|
|
65
|
+
return logs;
|
|
66
|
+
if (reason !== 'role' || status < 500)
|
|
67
|
+
return undefined;
|
|
68
|
+
// Two different answers wear this one: a threshold the platform cannot read, refused
|
|
69
|
+
// before the handler and so absent from the logs, and a handler that answered 5xx with
|
|
70
|
+
// a reason of that name itself, which is logged. Emptiness there tells them apart.
|
|
71
|
+
return `${logs}. Nothing there means the declared minRole is the problem: fix functions/${name}/index.ts`;
|
|
72
|
+
}
|
|
41
73
|
/** Call a function the same way the application does. */
|
|
42
74
|
async function functionsInvoke(args) {
|
|
43
75
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
@@ -95,6 +127,6 @@ async function functionsInvoke(args) {
|
|
|
95
127
|
if (text)
|
|
96
128
|
(0, ui_1.out)((0, args_1.flagBool)(args, 'json') ? text : prettyBody(text));
|
|
97
129
|
if (!response.ok) {
|
|
98
|
-
throw new errors_1.CliError(`The function answered ${response.status}`,
|
|
130
|
+
throw new errors_1.CliError(`The function answered ${response.status}`, failureHint(name, response.status, text));
|
|
99
131
|
}
|
|
100
132
|
}
|
|
@@ -8,6 +8,13 @@ const errors_1 = require("../errors");
|
|
|
8
8
|
const json_arg_1 = require("../json-arg");
|
|
9
9
|
const session_1 = require("../session");
|
|
10
10
|
const ui_1 = require("../ui");
|
|
11
|
+
/**
|
|
12
|
+
* The payload is kept as it was written, and `--payload-file` takes a file a person
|
|
13
|
+
* formatted: its newlines would break a row of a table into several.
|
|
14
|
+
*/
|
|
15
|
+
function oneLine(payload) {
|
|
16
|
+
return (payload ?? '').replace(/\s+/g, ' ').trim();
|
|
17
|
+
}
|
|
11
18
|
async function schedulesList(args) {
|
|
12
19
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
13
20
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/schedules`);
|
|
@@ -16,12 +23,18 @@ async function schedulesList(args) {
|
|
|
16
23
|
(0, ui_1.note)((0, ui_1.dim)(' To run a function on a timer: xflow schedules set <function> "0 3 ? * * *"'));
|
|
17
24
|
return;
|
|
18
25
|
}
|
|
19
|
-
(0, ui_1.table)(data.schedules.map((row) =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
(0, ui_1.table)(data.schedules.map((row) => {
|
|
27
|
+
// The body the timer sends. Nothing else printed it, so a schedule said when a
|
|
28
|
+
// function runs and never what it gets.
|
|
29
|
+
const payload = oneLine(row.payload);
|
|
30
|
+
return [
|
|
31
|
+
row.function,
|
|
32
|
+
row.cron,
|
|
33
|
+
row.description === row.cron ? '' : row.description,
|
|
34
|
+
payload.length > 40 ? `${payload.slice(0, 39)}…` : payload,
|
|
35
|
+
row.status === 'active' ? '' : (row.error ?? row.status),
|
|
36
|
+
];
|
|
37
|
+
}));
|
|
25
38
|
}
|
|
26
39
|
async function schedulesSet(args) {
|
|
27
40
|
const functionName = args.words[1];
|
|
@@ -36,6 +49,8 @@ async function schedulesSet(args) {
|
|
|
36
49
|
body: { function: functionName, cron, payload },
|
|
37
50
|
});
|
|
38
51
|
(0, ui_1.ok)(`${(0, ui_1.bold)(row.function)} runs ${row.description}`);
|
|
52
|
+
if (row.payload)
|
|
53
|
+
(0, ui_1.note)((0, ui_1.dim)(` The timer sends: ${oneLine(row.payload)}`));
|
|
39
54
|
(0, ui_1.note)((0, ui_1.dim)(` The time is UTC. To check by hand: xflow functions invoke ${row.function}`));
|
|
40
55
|
}
|
|
41
56
|
async function schedulesRemove(args) {
|
package/dist/help.js
CHANGED
|
@@ -40,7 +40,7 @@ ${(0, ui_1.bold)('Getting started')}
|
|
|
40
40
|
xflow init [dir] new project from the platform template
|
|
41
41
|
xflow templates which templates are available
|
|
42
42
|
xflow link <id> link this folder to a project, an empty one also gets the sources
|
|
43
|
-
xflow dev run the app on
|
|
43
|
+
xflow dev run the app on 127.0.0.1:3000, cloud functions included
|
|
44
44
|
xflow skills pick the agents that get the platform instructions
|
|
45
45
|
xflow mcp install give the agent platform access without a terminal
|
|
46
46
|
|
|
@@ -465,6 +465,9 @@ how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not
|
|
|
465
465
|
--payload '{"mode":"full"}' the body the function will receive
|
|
466
466
|
--payload-file body.json the same body from a file
|
|
467
467
|
|
|
468
|
+
${(0, ui_1.bold)('list')} prints that body next to the schedule, so what a timer sends can be read
|
|
469
|
+
back without setting it again.
|
|
470
|
+
|
|
468
471
|
A scheduled run arrives at the handler as a POST with no headers, and the project
|
|
469
472
|
token is not checked on it: such an event cannot be forged from outside. The function
|
|
470
473
|
has to be deployed already, since the schedule points at it and not the other way round.
|
|
@@ -538,7 +541,7 @@ is always read by the agent, which is why the pointer is appended there, at the
|
|
|
538
541
|
the file and once. ${(0, ui_1.bold)('CLAUDE.md')} is left alone: Claude Code reads .claude/skills anyway.`,
|
|
539
542
|
dev: `${(0, ui_1.bold)('xflow dev')}: run the application on this machine
|
|
540
543
|
|
|
541
|
-
Runs ${(0, ui_1.bold)('npm run dev')} in the project folder (
|
|
544
|
+
Runs ${(0, ui_1.bold)('npm run dev')} in the project folder (127.0.0.1:3000) with the access key of the
|
|
542
545
|
organization that holds the project. The dev server trades that key for a visitor pass,
|
|
543
546
|
so the cloud functions answer here the way they do on the platform. The key is picked
|
|
544
547
|
as for every other command (${(0, ui_1.bold)('xflow help org')}): with several organizations signed
|
|
@@ -551,7 +554,11 @@ The platform address comes from the CLI too, not from .env, so the key goes to t
|
|
|
551
554
|
platform that issued it and nowhere else. It never reaches the browser: the page gets
|
|
552
555
|
only the ${(0, ui_1.bold)('VITE_')} variables. The project token and the function addresses are
|
|
553
556
|
fetched on every start as well: a function someone else shipped answers here without a
|
|
554
|
-
deploy of yours
|
|
557
|
+
deploy of yours.
|
|
558
|
+
|
|
559
|
+
The server listens on 127.0.0.1 and nowhere else, because it holds that key: on a
|
|
560
|
+
shared network, anything else would let a neighbour call the functions of the project
|
|
561
|
+
as you. A phone on the same Wi-Fi therefore cannot open it either.`,
|
|
555
562
|
pull: `${(0, ui_1.bold)('xflow pull')}: fetch the sources
|
|
556
563
|
|
|
557
564
|
By default it fetches the latest revision into the project folder and refuses to write
|
package/dist/template.js
CHANGED
|
@@ -18,7 +18,7 @@ An application on XFlow.
|
|
|
18
18
|
|
|
19
19
|
\`\`\`bash
|
|
20
20
|
npm install # dependencies
|
|
21
|
-
xflow dev # develop on
|
|
21
|
+
xflow dev # develop on 127.0.0.1:3000
|
|
22
22
|
xflow deploy # send the code and build it on the platform
|
|
23
23
|
xflow publish # show the dev version to visitors
|
|
24
24
|
\`\`\`
|
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.24.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.24.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
|
|
@@ -602,7 +602,9 @@ own devtools.
|
|
|
602
602
|
|
|
603
603
|
Run the app locally with `xflow dev`, not a bare `npm run dev`: it hands the dev server
|
|
604
604
|
the key of the project's organization, which cloud functions need to answer, and their
|
|
605
|
-
current addresses, including functions someone else deployed.
|
|
605
|
+
current addresses, including functions someone else deployed. The server answers
|
|
606
|
+
`127.0.0.1` alone, because it holds that key: a phone on the same network cannot open it,
|
|
607
|
+
and neither can anybody else on that network.
|
|
606
608
|
|
|
607
609
|
## Environment
|
|
608
610
|
|