@getxflow/cli 1.0.8 → 1.1.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/bin.js +2 -1
- package/dist/commands/db.js +23 -6
- package/dist/commands/deploy.js +4 -1
- package/dist/commands/functions.js +1 -2
- package/dist/flags.js +2 -2
- package/dist/help.js +11 -13
- package/dist/version.js +1 -1
- package/package.json +24 -24
- package/skills/xflow/SKILL.md +729 -727
package/dist/bin.js
CHANGED
|
@@ -309,7 +309,8 @@ async function main() {
|
|
|
309
309
|
const args = (0, args_1.parseArgs)(process.argv.slice(2));
|
|
310
310
|
// Decided here rather than in each command: the header of --project, the nudges and
|
|
311
311
|
// every progress line are written on the way to a payload that has to stand alone.
|
|
312
|
-
|
|
312
|
+
// An invoke is always such a payload: its answer goes straight into a pipe.
|
|
313
|
+
(0, ui_1.setQuiet)((0, args_1.flagBool)(args, 'json') || (args.words[0] === 'functions' && args.words[1] === 'invoke'));
|
|
313
314
|
if ((0, args_1.flagBool)(args, 'version')) {
|
|
314
315
|
(0, ui_1.out)(version_1.CLI_VERSION);
|
|
315
316
|
return 0;
|
package/dist/commands/db.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.dbSchema = dbSchema;
|
|
|
6
6
|
exports.dbQuery = dbQuery;
|
|
7
7
|
exports.dbMove = dbMove;
|
|
8
8
|
exports.dbMigrate = dbMigrate;
|
|
9
|
+
exports.migrateBeforeDeploy = migrateBeforeDeploy;
|
|
9
10
|
const node_fs_1 = require("node:fs");
|
|
10
11
|
const node_path_1 = require("node:path");
|
|
11
12
|
const api_1 = require("../api");
|
|
@@ -244,7 +245,7 @@ async function dbQuery(args) {
|
|
|
244
245
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
245
246
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/query`, {
|
|
246
247
|
method: 'POST',
|
|
247
|
-
body: { sql
|
|
248
|
+
body: { sql },
|
|
248
249
|
timeoutMs: 60_000,
|
|
249
250
|
});
|
|
250
251
|
// The whole values, machine-readable, and the only thing printed: --json silences
|
|
@@ -272,11 +273,10 @@ async function dbQuery(args) {
|
|
|
272
273
|
}
|
|
273
274
|
if (data.truncated) {
|
|
274
275
|
// No exact total here: the platform stops reading at limit + 1 rows, so all
|
|
275
|
-
// it knows is that more exist.
|
|
276
|
-
// the flag still has room below the ceiling.
|
|
276
|
+
// it knows is that more exist.
|
|
277
277
|
const room = data.limit !== undefined && data.limit_max !== undefined && data.limit < data.limit_max;
|
|
278
278
|
(0, ui_1.note)((0, ui_1.dim)(room
|
|
279
|
-
? ` Shown the first ${data.rows.length} rows, more exist:
|
|
279
|
+
? ` Shown the first ${data.rows.length} rows, more exist: end the query with a larger limit (up to ${data.limit_max}), or narrow it`
|
|
280
280
|
: ` Shown the first ${data.rows.length} rows, more exist: narrow the query, with a where clause or an offset`));
|
|
281
281
|
}
|
|
282
282
|
}
|
|
@@ -345,13 +345,30 @@ async function dbMigrate(args) {
|
|
|
345
345
|
// without functions would lose them from its history, and a clone would miss them.
|
|
346
346
|
if (!dryRun)
|
|
347
347
|
await (0, sources_1.pushSources)({ kind: 'project', root, projectId: config.projectId }, client, { force: false });
|
|
348
|
+
await migrate(client, config.projectId, migrations, dryRun, (0, args_1.flagBool)(args, 'allow-destructive'));
|
|
349
|
+
}
|
|
350
|
+
/** The functions deploy runs this first: new code must not meet an old schema. */
|
|
351
|
+
async function migrateBeforeDeploy(client, root, projectId) {
|
|
352
|
+
const migrations = readMigrations(root);
|
|
353
|
+
if (migrations.length === 0)
|
|
354
|
+
return;
|
|
355
|
+
try {
|
|
356
|
+
await migrate(client, projectId, migrations, false, false);
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
(0, ui_1.warn)('The functions were not deployed: the migrations stopped first');
|
|
360
|
+
(0, ui_1.note)((0, ui_1.dim)(' Once xflow db migrate goes through, deploy again'));
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function migrate(client, projectId, migrations, dryRun, allowDestructive) {
|
|
348
365
|
(0, ui_1.step)(dryRun ? `Checking the migrations (${migrations.length} in the directory)` : `Applying the migrations (${migrations.length} in the directory)`);
|
|
349
|
-
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${
|
|
366
|
+
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/migrate`, {
|
|
350
367
|
method: 'POST',
|
|
351
368
|
body: {
|
|
352
369
|
migrations,
|
|
353
370
|
dry_run: dryRun,
|
|
354
|
-
allow_destructive:
|
|
371
|
+
allow_destructive: allowDestructive,
|
|
355
372
|
},
|
|
356
373
|
timeoutMs: 300_000,
|
|
357
374
|
});
|
package/dist/commands/deploy.js
CHANGED
|
@@ -14,6 +14,7 @@ const prompt_1 = require("../prompt");
|
|
|
14
14
|
const session_1 = require("../session");
|
|
15
15
|
const tree_1 = require("../tree");
|
|
16
16
|
const ui_1 = require("../ui");
|
|
17
|
+
const db_1 = require("./db");
|
|
17
18
|
const sources_1 = require("./sources");
|
|
18
19
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
20
|
/** Slightly longer than the platform's own build timeout. */
|
|
@@ -166,7 +167,7 @@ async function warnUndeployedRoot(client, projectRoot, projectId) {
|
|
|
166
167
|
const relation = server ? (0, tree_1.syncState)(tree.hash, server.tree_hash, (0, config_1.readState)(projectRoot).treeHash) : 'local-changes';
|
|
167
168
|
if (relation !== 'matches') {
|
|
168
169
|
(0, ui_1.warn)('The project root holds changes nobody deployed (functions, migrations or callers)');
|
|
169
|
-
(0, ui_1.note)((0, ui_1.dim)(' If this app needs them: xflow
|
|
170
|
+
(0, ui_1.note)((0, ui_1.dim)(' If this app needs them: xflow deploy functions first, it applies the migrations too'));
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
catch {
|
|
@@ -199,6 +200,8 @@ async function deployTarget(target, args, comment, revision) {
|
|
|
199
200
|
: { kind: 'project', root: target.root, projectId: target.projectId };
|
|
200
201
|
revision = (await (0, sources_1.pushSources)(stream, client, { force: (0, args_1.flagBool)(args, 'force') })).revision;
|
|
201
202
|
}
|
|
203
|
+
if (target.kind === 'project')
|
|
204
|
+
await (0, db_1.migrateBeforeDeploy)(client, target.root, target.projectId);
|
|
202
205
|
}
|
|
203
206
|
(0, ui_1.step)(target.kind === 'app' ? `Building the app from revision ${revision}` : `Deploying the functions from revision ${revision}`);
|
|
204
207
|
const started = await startBuild(client, base, revision, (0, args_1.flagBool)(args, 'allow-removals'), comment);
|
|
@@ -50,9 +50,8 @@ async function functionsInvoke(args) {
|
|
|
50
50
|
body: { method, ...(data !== undefined && method !== 'GET' && method !== 'HEAD' ? { body: data } : {}) },
|
|
51
51
|
timeoutMs: 15 * 60_000,
|
|
52
52
|
});
|
|
53
|
-
(0, ui_1.note)((0, ui_1.dim)(`${answer.status} in ${answer.elapsedMs} ms`));
|
|
54
53
|
if (answer.body)
|
|
55
|
-
(0, ui_1.out)(
|
|
54
|
+
(0, ui_1.out)(prettyBody(answer.body));
|
|
56
55
|
if (answer.status >= 400)
|
|
57
56
|
throw new errors_1.CliError(`The function answered ${answer.status}`, answer.hint);
|
|
58
57
|
}
|
package/dist/flags.js
CHANGED
|
@@ -57,7 +57,7 @@ exports.KNOWN = {
|
|
|
57
57
|
'mcp install': ['show-token'],
|
|
58
58
|
functions: ['project'],
|
|
59
59
|
'functions list': ['project'],
|
|
60
|
-
'functions invoke': ['data', 'data-file', '
|
|
60
|
+
'functions invoke': ['data', 'data-file', 'method', 'project'],
|
|
61
61
|
'functions logs': ['limit', 'project'],
|
|
62
62
|
logs: ['limit', 'project', 'app'],
|
|
63
63
|
env: ['project'],
|
|
@@ -80,7 +80,7 @@ exports.KNOWN = {
|
|
|
80
80
|
'db status': ['project'],
|
|
81
81
|
'db list': [],
|
|
82
82
|
'db schema': ['project'],
|
|
83
|
-
'db query': ['
|
|
83
|
+
'db query': ['json', 'project'],
|
|
84
84
|
'db migrate': ['dry-run', 'allow-destructive'],
|
|
85
85
|
'db move': ['to', 'confirm', 'project'],
|
|
86
86
|
storage: ['json', 'project'],
|
package/dist/help.js
CHANGED
|
@@ -343,8 +343,8 @@ will work.`,
|
|
|
343
343
|
xflow db status what is applied, what is waiting, the server
|
|
344
344
|
xflow db list databases of the projects, the servers of the organization
|
|
345
345
|
xflow db schema [table] tables of the project, or the columns of one
|
|
346
|
-
xflow db query 'select ...' read data (
|
|
347
|
-
--json prints the values whole)
|
|
346
|
+
xflow db query 'select ...' read data (rows: the query's own limit, 50 without
|
|
347
|
+
one, 300 at most; --json prints the values whole)
|
|
348
348
|
xflow db query <file>.sql the same, the query read from a file
|
|
349
349
|
xflow db migrate store the project root, then apply migrations/*.sql
|
|
350
350
|
xflow db move --to <server> --confirm <project name>
|
|
@@ -634,14 +634,13 @@ ${(0, ui_1.bold)("event.xflow.caller === 'platform'")} and the person in ${(0, u
|
|
|
634
634
|
--data '{"a":1}' request body (the method becomes POST by default)
|
|
635
635
|
--data-file body.json the same body from a file
|
|
636
636
|
--method GET a different method
|
|
637
|
-
--json the answer of the function alone, exactly as it came
|
|
638
637
|
|
|
639
638
|
The body has to be JSON, and it is checked before the call goes out: PowerShell
|
|
640
639
|
strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
|
|
641
640
|
(${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
|
|
642
641
|
|
|
643
|
-
Prints the
|
|
644
|
-
|
|
642
|
+
Prints the body of the answer and nothing else, so it goes straight into a pipe
|
|
643
|
+
(${(0, ui_1.bold)('| jq')}, ${(0, ui_1.bold)('| python')}). A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step. A
|
|
645
644
|
refusal of the guard names its reason in the body (${(0, ui_1.bold)('not_bound')}, ${(0, ui_1.bold)('no_access')},
|
|
646
645
|
${(0, ui_1.bold)('functions_stopped')}); for a crash the hint points at ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
|
|
647
646
|
mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
|
|
@@ -719,8 +718,8 @@ git's job.
|
|
|
719
718
|
--force overwrite the folder completely`,
|
|
720
719
|
deploy: `${(0, ui_1.bold)('xflow deploy')}: deploy the functions or build an app, then publish
|
|
721
720
|
|
|
722
|
-
xflow deploy functions -m "…" send the project root, check it,
|
|
723
|
-
and their callers
|
|
721
|
+
xflow deploy functions -m "…" send the project root, check it, apply the new
|
|
722
|
+
migrations, ship the functions and their callers
|
|
724
723
|
xflow deploy apps/<folder> -m "…" send one app and build it; inside its folder a plain
|
|
725
724
|
xflow deploy does the same
|
|
726
725
|
xflow publish [build] point the app address at the newest build, or a named one
|
|
@@ -761,11 +760,13 @@ keys in the code, alert instead of a dialog) is printed as a list and nothing st
|
|
|
761
760
|
merely unfinished (the template page title, no icon) goes through and prints a line
|
|
762
761
|
${(0, ui_1.bold)('Unfinished:')}. Fix it before telling the person the app is done.
|
|
763
762
|
|
|
764
|
-
${(0, ui_1.bold)('deploy functions')}
|
|
763
|
+
${(0, ui_1.bold)('deploy functions')} first applies the new migrations of ${(0, ui_1.bold)('migrations/')}: if one fails or
|
|
764
|
+
would destroy data, nothing ships, and ${(0, ui_1.bold)('xflow db migrate')} is where that gets settled. Then it
|
|
765
|
+
bundles everything in ${(0, ui_1.bold)('functions/<name>/index.ts')}. A function whose
|
|
765
766
|
code and variables did not change is left alone, and one that fails to ship fails the whole
|
|
766
767
|
deploy. It then records the callers and prints which functions are open to the whole
|
|
767
|
-
internet now. A change that touches data and screens goes in this order: the
|
|
768
|
-
the
|
|
768
|
+
internet now. A change that touches data and screens goes in this order: the functions
|
|
769
|
+
(with the migration), then every app that shows it; an app deploy warns when the root holds
|
|
769
770
|
changes nobody deployed.
|
|
770
771
|
|
|
771
772
|
A function gone from the sources would be deleted from the cloud along with its keys and
|
|
@@ -775,9 +776,6 @@ to agree. If the sources hold no functions at all while the cloud holds several,
|
|
|
775
776
|
is deleted and nothing is asked: that looks like a directory which never made it (a
|
|
776
777
|
${(0, ui_1.bold)('functions/')} line in .xflowignore) rather than a decision.
|
|
777
778
|
|
|
778
|
-
The database is not part of this: migrations change data in ways nothing can undo, so
|
|
779
|
-
they stay their own command (${(0, ui_1.bold)('xflow db migrate')}).
|
|
780
|
-
|
|
781
779
|
A build of an app is not shown to visitors until ${(0, ui_1.bold)('xflow publish')}: publishing points the
|
|
782
780
|
app address at a build and takes a second, and an older kept build is the rollback. Only
|
|
783
781
|
the app moves: the functions and the database are one per project and stay as they are.
|
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.0
|
|
5
|
+
exports.CLI_VERSION = '1.1.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,24 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@getxflow/cli",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "CLI for the XFlow platform: projects with apps, functions and a database; source sync, deployment and publishing",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"engines": {
|
|
7
|
-
"node": ">=20"
|
|
8
|
-
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"xflow": "dist/bin.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"skills"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
18
|
-
"build": "npm run clean && tsc -p tsconfig.json",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@getxflow/cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "CLI for the XFlow platform: projects with apps, functions and a database; source sync, deployment and publishing",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"xflow": "dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"skills"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
18
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|