@getxflow/cli 0.10.1 → 0.10.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/args.js +4 -4
- package/dist/bin.js +7 -1
- package/dist/commands/env.js +4 -1
- package/dist/commands/functions.js +6 -3
- package/dist/commands/schedules.js +3 -2
- package/dist/commands/skills.js +13 -4
- package/dist/flags.js +129 -0
- package/dist/help.js +18 -3
- package/dist/json-arg.js +69 -0
- package/dist/state.js +11 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +18 -6
package/dist/args.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/** Argument parsing, dependency-free. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.BOOLEAN_FLAGS = void 0;
|
|
4
5
|
exports.parseArgs = parseArgs;
|
|
5
6
|
exports.flagString = flagString;
|
|
6
7
|
exports.flagBool = flagBool;
|
|
7
8
|
exports.flagNumber = flagNumber;
|
|
8
9
|
/** Flags that never take a value, so `--force ./dir` keeps the path as a word. */
|
|
9
|
-
|
|
10
|
+
exports.BOOLEAN_FLAGS = new Set([
|
|
10
11
|
'force',
|
|
11
12
|
'yes',
|
|
12
13
|
'json',
|
|
14
|
+
'replace',
|
|
13
15
|
'help',
|
|
14
16
|
'version',
|
|
15
17
|
'global',
|
|
16
18
|
'refresh',
|
|
17
|
-
'live',
|
|
18
19
|
'no-push',
|
|
19
|
-
'skip-build',
|
|
20
20
|
'all',
|
|
21
21
|
'dry-run',
|
|
22
22
|
'allow-destructive',
|
|
@@ -40,7 +40,7 @@ function parseArgs(argv) {
|
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
const next = argv[i + 1];
|
|
43
|
-
if (BOOLEAN_FLAGS.has(body) || next === undefined || next.startsWith('-')) {
|
|
43
|
+
if (exports.BOOLEAN_FLAGS.has(body) || next === undefined || next.startsWith('-')) {
|
|
44
44
|
flags[body] = true;
|
|
45
45
|
}
|
|
46
46
|
else {
|
package/dist/bin.js
CHANGED
|
@@ -6,6 +6,7 @@ const args_1 = require("./args");
|
|
|
6
6
|
const config_1 = require("./config");
|
|
7
7
|
const credentials_1 = require("./credentials");
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
|
+
const flags_1 = require("./flags");
|
|
9
10
|
const help_1 = require("./help");
|
|
10
11
|
const limits_1 = require("./limits");
|
|
11
12
|
const state_1 = require("./state");
|
|
@@ -228,8 +229,12 @@ function nudge() {
|
|
|
228
229
|
*/
|
|
229
230
|
function nudgeSkill() {
|
|
230
231
|
const stale = (0, skills_1.staleSkillCopy)(process.cwd());
|
|
231
|
-
if (stale === null)
|
|
232
|
+
if (stale === null) {
|
|
233
|
+
// Everything matches, so the day-long silence has nothing left to protect: drop it,
|
|
234
|
+
// and a copy that drifts after a repair is news again instead of yesterday's answer.
|
|
235
|
+
(0, state_1.clearSkillNudges)();
|
|
232
236
|
return;
|
|
237
|
+
}
|
|
233
238
|
const key = `${stale} ${version_1.CLI_VERSION}`;
|
|
234
239
|
if (!(0, state_1.shouldNudgeSkill)(key))
|
|
235
240
|
return;
|
|
@@ -251,6 +256,7 @@ async function main() {
|
|
|
251
256
|
// carries the same advice in its hint.
|
|
252
257
|
let quiet = args.words[0] === 'update';
|
|
253
258
|
try {
|
|
259
|
+
(0, flags_1.checkFlags)(args);
|
|
254
260
|
await run(args);
|
|
255
261
|
return 0;
|
|
256
262
|
}
|
package/dist/commands/env.js
CHANGED
|
@@ -164,7 +164,10 @@ async function envSet(args) {
|
|
|
164
164
|
const value = pair.slice(pair.indexOf('=') + 1);
|
|
165
165
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/env`, {
|
|
166
166
|
method: 'POST',
|
|
167
|
-
|
|
167
|
+
// The word goes to the platform as typed: it knows the two scopes and
|
|
168
|
+
// refuses a third. Guessing here would have made --scope proj mean the
|
|
169
|
+
// whole organization, which is the opposite of what such a typo means.
|
|
170
|
+
body: { name, value, scope: (0, args_1.flagString)(args, 'scope') },
|
|
168
171
|
});
|
|
169
172
|
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} stored (${result.scope === 'project' ? 'this project only' : 'the whole organization'})`);
|
|
170
173
|
// Values reach a function on its next deploy.
|
|
@@ -6,6 +6,7 @@ const api_1 = require("../api");
|
|
|
6
6
|
const args_1 = require("../args");
|
|
7
7
|
const config_1 = require("../config");
|
|
8
8
|
const errors_1 = require("../errors");
|
|
9
|
+
const json_arg_1 = require("../json-arg");
|
|
9
10
|
const session_1 = require("../session");
|
|
10
11
|
const ui_1 = require("../ui");
|
|
11
12
|
/** Who can call the function: inside the app only, or an outside service holding a key. */
|
|
@@ -46,6 +47,11 @@ async function functionsInvoke(args) {
|
|
|
46
47
|
const name = args.words[1];
|
|
47
48
|
if (!name)
|
|
48
49
|
throw new errors_1.CliError('A function name is required', 'What is deployed: xflow functions list');
|
|
50
|
+
// The body is judged before anything goes over the network: a broken one is the
|
|
51
|
+
// user's own line, and it costs nothing to say so at once.
|
|
52
|
+
const data = (0, json_arg_1.jsonArg)(args, 'data');
|
|
53
|
+
const method = ((0, args_1.flagString)(args, 'method') ?? (data ? 'POST' : 'GET')).toUpperCase();
|
|
54
|
+
const sendsBody = method !== 'GET' && method !== 'HEAD';
|
|
49
55
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}`);
|
|
50
56
|
const fn = card.functions.find((item) => item.name === name);
|
|
51
57
|
if (!fn || !fn.invoke_url) {
|
|
@@ -53,9 +59,6 @@ async function functionsInvoke(args) {
|
|
|
53
59
|
? `Deployed: ${card.functions.map((item) => item.name).join(', ')}`
|
|
54
60
|
: 'Functions ship with the build: xflow deploy');
|
|
55
61
|
}
|
|
56
|
-
const data = (0, args_1.flagString)(args, 'data');
|
|
57
|
-
const method = ((0, args_1.flagString)(args, 'method') ?? (data ? 'POST' : 'GET')).toUpperCase();
|
|
58
|
-
const sendsBody = method !== 'GET' && method !== 'HEAD';
|
|
59
62
|
// A pass for the person who owns the key: the project token alone is not an
|
|
60
63
|
// identity, and a project built with the current template refuses without one.
|
|
61
64
|
// A read-only key cannot get a pass, so the call still goes out without it.
|
|
@@ -4,9 +4,9 @@ exports.schedulesList = schedulesList;
|
|
|
4
4
|
exports.schedulesSet = schedulesSet;
|
|
5
5
|
exports.schedulesRemove = schedulesRemove;
|
|
6
6
|
const api_1 = require("../api");
|
|
7
|
-
const args_1 = require("../args");
|
|
8
7
|
const config_1 = require("../config");
|
|
9
8
|
const errors_1 = require("../errors");
|
|
9
|
+
const json_arg_1 = require("../json-arg");
|
|
10
10
|
const session_1 = require("../session");
|
|
11
11
|
const ui_1 = require("../ui");
|
|
12
12
|
async function schedulesList() {
|
|
@@ -33,9 +33,10 @@ async function schedulesSet(args) {
|
|
|
33
33
|
if (!functionName || !cron) {
|
|
34
34
|
throw new errors_1.CliError('A function name and a schedule are required', 'For example: xflow schedules set nightly-report "0 3 ? * * *" runs every day at 03:00 UTC');
|
|
35
35
|
}
|
|
36
|
+
const payload = (0, json_arg_1.jsonArg)(args, 'payload') ?? null;
|
|
36
37
|
const row = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/schedules`, {
|
|
37
38
|
method: 'POST',
|
|
38
|
-
body: { function: functionName, cron, payload
|
|
39
|
+
body: { function: functionName, cron, payload },
|
|
39
40
|
});
|
|
40
41
|
(0, ui_1.ok)(`${(0, ui_1.bold)(row.function)} runs ${row.description}`);
|
|
41
42
|
(0, ui_1.note)((0, ui_1.dim)(` The time is UTC. To check by hand: xflow functions invoke ${row.function}`));
|
package/dist/commands/skills.js
CHANGED
|
@@ -167,12 +167,21 @@ function copyPaths(base) {
|
|
|
167
167
|
return paths;
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
170
|
+
* The stamp names the CLI version, so it moves on every release even when not a word of
|
|
171
|
+
* the instructions changed. Comparing it would call every copy on the machine stale after
|
|
172
|
+
* such a release, which is exactly what comparing content instead of versions avoids.
|
|
173
|
+
*/
|
|
174
|
+
const STAMP = /^These instructions ship with xflow CLI .*$/m;
|
|
175
|
+
/**
|
|
176
|
+
* Line endings are not a difference either. An editor that saved a copy as CRLF would
|
|
177
|
+
* otherwise leave the machine announcing a stale skill for ever, and the refresh that
|
|
178
|
+
* "fixes" it would change nothing visible. Same normalisation the template gate uses.
|
|
173
179
|
*/
|
|
174
180
|
function copyIsStale(expected, actual) {
|
|
175
|
-
return expected
|
|
181
|
+
return compared(expected) !== compared(actual);
|
|
182
|
+
}
|
|
183
|
+
function compared(text) {
|
|
184
|
+
return text.replace(/\r\n/g, '\n').replace(STAMP, '');
|
|
176
185
|
}
|
|
177
186
|
/**
|
|
178
187
|
* The first installed copy whose text is not what this CLI ships, or null when every
|
package/dist/flags.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkFlags = checkFlags;
|
|
4
|
+
const args_1 = require("./args");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const help_1 = require("./help");
|
|
7
|
+
/**
|
|
8
|
+
* Which flags each command answers to.
|
|
9
|
+
*
|
|
10
|
+
* An unknown flag used to pass in silence, and one written before a word ate that
|
|
11
|
+
* word: `functions logs --nosuch flows` answered for every function instead of the
|
|
12
|
+
* one asked for, and the answer looked truthful. A mistyped switch is worse still:
|
|
13
|
+
* `db migrate --dryrun` applied the migrations for real.
|
|
14
|
+
*
|
|
15
|
+
* Keys are the command as it is typed. A command that is not here is not checked,
|
|
16
|
+
* so a new one keeps working until it is added, and the list also drives the hint:
|
|
17
|
+
* whoever is at the keyboard gets the flags of this very command back.
|
|
18
|
+
*/
|
|
19
|
+
const KNOWN = {
|
|
20
|
+
help: [],
|
|
21
|
+
login: [],
|
|
22
|
+
logout: ['all'],
|
|
23
|
+
whoami: [],
|
|
24
|
+
org: [],
|
|
25
|
+
'org list': [],
|
|
26
|
+
'org switch': [],
|
|
27
|
+
templates: [],
|
|
28
|
+
init: ['name', 'template', 'database'],
|
|
29
|
+
link: [],
|
|
30
|
+
projects: [],
|
|
31
|
+
'projects list': [],
|
|
32
|
+
'projects get': [],
|
|
33
|
+
skills: ['refresh', 'global', 'agent', 'yes'],
|
|
34
|
+
mcp: ['show-token'],
|
|
35
|
+
'mcp install': ['show-token'],
|
|
36
|
+
functions: [],
|
|
37
|
+
'functions list': [],
|
|
38
|
+
'functions invoke': ['data', 'data-file', 'method'],
|
|
39
|
+
'functions logs': ['limit'],
|
|
40
|
+
logs: ['limit'],
|
|
41
|
+
env: [],
|
|
42
|
+
'env list': [],
|
|
43
|
+
'env set': ['scope'],
|
|
44
|
+
'env rm': [],
|
|
45
|
+
'env remove': [],
|
|
46
|
+
'env check': [],
|
|
47
|
+
connections: [],
|
|
48
|
+
'connections list': [],
|
|
49
|
+
'connections link': ['as'],
|
|
50
|
+
'connections unlink': ['force'],
|
|
51
|
+
schedules: [],
|
|
52
|
+
'schedules list': [],
|
|
53
|
+
'schedules set': ['payload', 'payload-file'],
|
|
54
|
+
'schedules rm': [],
|
|
55
|
+
'schedules remove': [],
|
|
56
|
+
db: [],
|
|
57
|
+
'db status': [],
|
|
58
|
+
'db schema': [],
|
|
59
|
+
'db query': ['limit'],
|
|
60
|
+
'db migrate': ['dry-run', 'allow-destructive'],
|
|
61
|
+
storage: ['json'],
|
|
62
|
+
'storage ls': ['json'],
|
|
63
|
+
'storage list': ['json'],
|
|
64
|
+
'storage push': ['to', 'replace', 'json'],
|
|
65
|
+
'storage rm': ['folder', 'yes'],
|
|
66
|
+
'storage remove': ['folder', 'yes'],
|
|
67
|
+
status: [],
|
|
68
|
+
pull: ['into', 'force', 'revision'],
|
|
69
|
+
deploy: ['no-push', 'force', 'allow-removals'],
|
|
70
|
+
publish: [],
|
|
71
|
+
rollback: [],
|
|
72
|
+
deployments: [],
|
|
73
|
+
update: [],
|
|
74
|
+
};
|
|
75
|
+
/** Answered everywhere: both are handled before the command runs. */
|
|
76
|
+
const GLOBAL = new Set(['help', 'version']);
|
|
77
|
+
/**
|
|
78
|
+
* Commands whose second word is a subcommand rather than a value. An unknown one
|
|
79
|
+
* belongs to the dispatcher, which names it and lists the real ones: complaining
|
|
80
|
+
* about a flag of a command that does not exist would send the reader the wrong way.
|
|
81
|
+
*/
|
|
82
|
+
const NAMESPACES = new Set([
|
|
83
|
+
'org',
|
|
84
|
+
'projects',
|
|
85
|
+
'functions',
|
|
86
|
+
'env',
|
|
87
|
+
'connections',
|
|
88
|
+
'schedules',
|
|
89
|
+
'db',
|
|
90
|
+
'storage',
|
|
91
|
+
'mcp',
|
|
92
|
+
]);
|
|
93
|
+
/** The command these words name, as far as the table knows it. */
|
|
94
|
+
function commandOf(words) {
|
|
95
|
+
const [first, second] = words;
|
|
96
|
+
if (!first)
|
|
97
|
+
return null;
|
|
98
|
+
if (second !== undefined) {
|
|
99
|
+
if (Object.hasOwn(KNOWN, `${first} ${second}`))
|
|
100
|
+
return `${first} ${second}`;
|
|
101
|
+
if (NAMESPACES.has(first))
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return Object.hasOwn(KNOWN, first) ? first : null;
|
|
105
|
+
}
|
|
106
|
+
function checkFlags(args) {
|
|
107
|
+
const command = commandOf(args.words);
|
|
108
|
+
if (command === null)
|
|
109
|
+
return;
|
|
110
|
+
const allowed = KNOWN[command];
|
|
111
|
+
const topic = command.split(' ')[0];
|
|
112
|
+
// Not every command has a page, and sending the reader to "No page for login"
|
|
113
|
+
// would be a second dead end in the same breath as the first.
|
|
114
|
+
const page = (0, help_1.hasHelpPage)(topic) ? `xflow help ${topic}` : 'xflow help';
|
|
115
|
+
for (const [name, value] of Object.entries(args.flags)) {
|
|
116
|
+
if (GLOBAL.has(name))
|
|
117
|
+
continue;
|
|
118
|
+
if (!allowed.includes(name)) {
|
|
119
|
+
throw new errors_1.CliError(`xflow ${command} has no flag --${name}`, allowed.length > 0
|
|
120
|
+
? `It takes ${allowed.map((flag) => `--${flag}`).join(', ')}. What each one does: ${page}`
|
|
121
|
+
: `It takes no flags. What it does: ${page}`);
|
|
122
|
+
}
|
|
123
|
+
// The value went missing, so the flag arrived as a bare switch. Left alone it
|
|
124
|
+
// would mean the default, which is never what the person typing it wanted.
|
|
125
|
+
if (value === true && !args_1.BOOLEAN_FLAGS.has(name)) {
|
|
126
|
+
throw new errors_1.CliError(`--${name} needs a value`, `Put it after the flag, or write --${name}=value when the value starts with a dash`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/dist/help.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hasHelpPage = hasHelpPage;
|
|
3
4
|
exports.help = help;
|
|
4
5
|
const ui_1 = require("./ui");
|
|
5
6
|
const version_1 = require("./version");
|
|
@@ -14,6 +15,10 @@ const ALIASES = {
|
|
|
14
15
|
deployments: 'rollback',
|
|
15
16
|
};
|
|
16
17
|
/** Help text. For agents this is the reference documentation. */
|
|
18
|
+
/** Whether `xflow help <topic>` has a page: a hint that sends nowhere is a second dead end. */
|
|
19
|
+
function hasHelpPage(topic) {
|
|
20
|
+
return Object.hasOwn(TOPICS, ALIASES[topic] ?? topic);
|
|
21
|
+
}
|
|
17
22
|
function help(topic) {
|
|
18
23
|
if (topic) {
|
|
19
24
|
const page = TOPICS[ALIASES[topic] ?? topic];
|
|
@@ -259,7 +264,11 @@ the function. So keep your own copy wherever you got it from.
|
|
|
259
264
|
xflow env set SMTP_PASSWORD=… store
|
|
260
265
|
xflow env rm SMTP_PASSWORD delete
|
|
261
266
|
|
|
262
|
-
--scope project visible to this project only
|
|
267
|
+
--scope project visible to this project only
|
|
268
|
+
--scope organization visible to every project of the organization (the default)
|
|
269
|
+
|
|
270
|
+
Those two words are the whole list: a third is refused rather than read as the default,
|
|
271
|
+
because a value meant for one project would have gone out to all of them.
|
|
263
272
|
|
|
264
273
|
${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
|
|
265
274
|
references. The same rule applies on deploy: a function receives only the variables it
|
|
@@ -326,6 +335,7 @@ the day-of-month and day-of-week fields has to be ${(0, ui_1.bold)('?')}, which
|
|
|
326
335
|
how Yandex works. ${(0, ui_1.bold)('The time is always UTC')}, local time is not understood.
|
|
327
336
|
|
|
328
337
|
--payload '{"mode":"full"}' the body the function will receive
|
|
338
|
+
--payload-file body.json the same body from a file
|
|
329
339
|
|
|
330
340
|
A scheduled run arrives at the handler as a POST with no headers, and the project
|
|
331
341
|
token is not checked on it: such an event cannot be forged from outside. The function
|
|
@@ -339,8 +349,13 @@ Calls it exactly the way the application does: the project token plus a visitor
|
|
|
339
349
|
for the person who owns the key, so the function sees a real caller and its role.
|
|
340
350
|
Both are taken from the platform, no local .env is needed.
|
|
341
351
|
|
|
342
|
-
--data '{"a":1}'
|
|
343
|
-
--
|
|
352
|
+
--data '{"a":1}' request body (the method becomes POST by default)
|
|
353
|
+
--data-file body.json the same body from a file
|
|
354
|
+
--method GET a different method
|
|
355
|
+
|
|
356
|
+
The body has to be JSON, and it is checked before the call goes out: PowerShell
|
|
357
|
+
strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
|
|
358
|
+
(${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
|
|
344
359
|
|
|
345
360
|
Prints the status, the response time and the body. A non-zero exit code on 4xx and
|
|
346
361
|
5xx: in CI such a call has to fail the step. The cause of a crash is shown by
|
package/dist/json-arg.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsonProblem = jsonProblem;
|
|
4
|
+
exports.jsonHint = jsonHint;
|
|
5
|
+
exports.jsonArg = jsonArg;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const args_1 = require("./args");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
/**
|
|
10
|
+
* A JSON body typed on the command line: --data for a call, --payload for a
|
|
11
|
+
* schedule. Checked here, before it leaves: PowerShell strips the double quotes
|
|
12
|
+
* out of a native call, and an unchecked body arrives at the function as garbage,
|
|
13
|
+
* so a shell problem looks like a crash in the project's own code.
|
|
14
|
+
*/
|
|
15
|
+
/** No quote left where JSON needs them: a shell ate them, this is not a typo. */
|
|
16
|
+
function quotesStripped(raw) {
|
|
17
|
+
return !raw.includes('"') && /^[{[]/.test(raw);
|
|
18
|
+
}
|
|
19
|
+
/** What the parser says about the text, or null when it is JSON. */
|
|
20
|
+
function jsonProblem(raw) {
|
|
21
|
+
const text = raw.trim();
|
|
22
|
+
if (!text)
|
|
23
|
+
return 'the value is empty';
|
|
24
|
+
try {
|
|
25
|
+
JSON.parse(text);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
return e instanceof Error ? e.message : String(e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Where to look, and on a stripped value the shell is the first suspect. */
|
|
33
|
+
function jsonHint(flag, raw) {
|
|
34
|
+
const file = `--${flag}-file body.json`;
|
|
35
|
+
if (quotesStripped(raw.trim())) {
|
|
36
|
+
return (`Not one double quote survived: the shell removed them, PowerShell does this to ` +
|
|
37
|
+
`native calls. Escape them: --${flag} '{\\"key\\":\\"value\\"}', or keep the body ` +
|
|
38
|
+
`in a file: ${file}`);
|
|
39
|
+
}
|
|
40
|
+
return `A single JSON value is expected, or keep the body in a file: ${file}`;
|
|
41
|
+
}
|
|
42
|
+
/** The body from --<flag> or --<flag>-file, already known to be JSON. */
|
|
43
|
+
function jsonArg(args, flag) {
|
|
44
|
+
const fileFlag = `${flag}-file`;
|
|
45
|
+
const inline = args.flags[flag];
|
|
46
|
+
const path = (0, args_1.flagString)(args, fileFlag);
|
|
47
|
+
if (inline !== undefined && path !== undefined) {
|
|
48
|
+
throw new errors_1.CliError(`--${flag} and --${fileFlag} cannot be used together`, 'Give the body once');
|
|
49
|
+
}
|
|
50
|
+
if (path !== undefined) {
|
|
51
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
52
|
+
throw new errors_1.CliError(`No file ${path}`, `--${fileFlag} takes the path to a file with one JSON value`);
|
|
53
|
+
}
|
|
54
|
+
const text = (0, node_fs_1.readFileSync)(path, 'utf-8').trim();
|
|
55
|
+
const problem = jsonProblem(text);
|
|
56
|
+
if (problem)
|
|
57
|
+
throw new errors_1.CliError(`${path} is not valid JSON: ${problem}`, 'The file has to hold one JSON value');
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
if (inline === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (inline === true)
|
|
63
|
+
throw new errors_1.CliError(`--${flag} needs a value`, `For example: --${flag} '{"mode":"full"}'`);
|
|
64
|
+
const raw = String(inline).trim();
|
|
65
|
+
const problem = jsonProblem(raw);
|
|
66
|
+
if (problem)
|
|
67
|
+
throw new errors_1.CliError(`--${flag} is not valid JSON: ${raw}`, jsonHint(flag, raw));
|
|
68
|
+
return raw;
|
|
69
|
+
}
|
package/dist/state.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.shouldNudge = shouldNudge;
|
|
4
4
|
exports.shouldNudgeSkill = shouldNudgeSkill;
|
|
5
5
|
exports.markSkillNudged = markSkillNudged;
|
|
6
|
+
exports.clearSkillNudges = clearSkillNudges;
|
|
6
7
|
exports.markNudged = markNudged;
|
|
7
8
|
const node_fs_1 = require("node:fs");
|
|
8
9
|
const node_os_1 = require("node:os");
|
|
@@ -64,6 +65,16 @@ function markSkillNudged(key) {
|
|
|
64
65
|
kept[key] = new Date(now).toISOString();
|
|
65
66
|
write({ skillNudgedAt: kept });
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Forget what was said once there is nothing to repair. The window is for a copy nobody
|
|
70
|
+
* fixed, not for the memory of one that is fine now: without this, "warned, refreshed,
|
|
71
|
+
* drifted again" stays silent all day, because the key is the path and it is already marked.
|
|
72
|
+
*/
|
|
73
|
+
function clearSkillNudges() {
|
|
74
|
+
if (Object.keys(read().skillNudgedAt ?? {}).length === 0)
|
|
75
|
+
return;
|
|
76
|
+
write({ skillNudgedAt: {} });
|
|
77
|
+
}
|
|
67
78
|
function markNudged(version) {
|
|
68
79
|
write({ nudgedVersion: version, nudgedAt: new Date().toISOString() });
|
|
69
80
|
}
|
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.10.
|
|
5
|
+
exports.CLI_VERSION = '0.10.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
|
@@ -24,7 +24,7 @@ contains the fix.
|
|
|
24
24
|
|
|
25
25
|
## Keeping these instructions current
|
|
26
26
|
|
|
27
|
-
These instructions ship with xflow CLI 0.10.
|
|
27
|
+
These instructions ship with xflow CLI 0.10.3. They travel inside the package, so the copy
|
|
28
28
|
you are reading can be older than the CLI answering your commands, and nothing about that
|
|
29
29
|
is visible in the text itself.
|
|
30
30
|
|
|
@@ -234,10 +234,15 @@ you to delete. Put the directories back instead, and if the removal really is in
|
|
|
234
234
|
which functions are about to go and let the user answer.
|
|
235
235
|
|
|
236
236
|
Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it
|
|
237
|
-
the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body
|
|
238
|
-
|
|
239
|
-
output of that call. Only failed calls are
|
|
240
|
-
never crashed, not that logging is broken.
|
|
237
|
+
the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body,
|
|
238
|
+
`--data-file body.json` sends the same from a file), and `xflow functions logs <name>` shows
|
|
239
|
+
the failures, each with its stack and the console output of that call. Only failed calls are
|
|
240
|
+
logged, so an empty output means the function never crashed, not that logging is broken.
|
|
241
|
+
|
|
242
|
+
The body has to be JSON and the CLI checks it before calling. In PowerShell the double
|
|
243
|
+
quotes never reach the CLI: `--data '{"a":1}'` arrives as `{a:1}`, so escape them
|
|
244
|
+
(`--data '{\"a\":1}'`) or pass a file. This is a shell habit, not a bug in the function, and
|
|
245
|
+
it bites `--payload` of a schedule the same way.
|
|
241
246
|
|
|
242
247
|
From the app, call a function through `src/lib/xflow.ts`:
|
|
243
248
|
`await xflow.functions.invoke('send-mail', { body: { to } })`. It carries the credentials
|
|
@@ -303,6 +308,11 @@ writes one, `xflow env` lists the names, `xflow env check` tells you which varia
|
|
|
303
308
|
functions read but the platform does not have. Values never come back out — the only place
|
|
304
309
|
they exist is inside the running function.
|
|
305
310
|
|
|
311
|
+
A variable goes to the whole organization unless you say otherwise: `--scope project` keeps it
|
|
312
|
+
to this project alone, `--scope organization` is the default, and a third word is refused
|
|
313
|
+
rather than read as the default. Pick the narrow one for anything that belongs to one
|
|
314
|
+
application.
|
|
315
|
+
|
|
306
316
|
These commands see only what this project can see: variables shared across the organization
|
|
307
317
|
and the ones bound to this project. A variable bound to another project is invisible here, so
|
|
308
318
|
`env rm` reports it as missing even though names are unique within the organization.
|
|
@@ -342,7 +352,9 @@ grant it to itself. Connecting a new account and switching one off stay with a p
|
|
|
342
352
|
`xflow schedules set report "0 3 ? * * *"` runs a function daily at 03:00. Six fields, UTC,
|
|
343
353
|
and exactly one of day-of-month / day-of-week must be `?` — that is how Yandex wants it.
|
|
344
354
|
A scheduled run reaches the handler as a POST with no headers, and `--payload '{"mode":"full"}'`
|
|
345
|
-
is how it gets a body.
|
|
355
|
+
(or `--payload-file body.json`) is how it gets a body. The payload has to be JSON: a broken one
|
|
356
|
+
is refused when the schedule is set, not at three in the morning with nobody watching. How often
|
|
357
|
+
a schedule may run is a plan limit, see **Plan limits**.
|
|
346
358
|
|
|
347
359
|
The pieces line up in one pass. From a new function to a verified schedule:
|
|
348
360
|
|