@getxflow/cli 0.15.0 → 0.17.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 +4 -12
- package/dist/commands/functions.js +1 -1
- package/dist/commands/projects.js +4 -7
- package/dist/commands/sources.js +18 -20
- package/dist/config.js +3 -1
- package/dist/flags.js +1 -1
- package/dist/help.js +4 -3
- package/dist/template.js +10 -13
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +8 -6
package/dist/commands/deploy.js
CHANGED
|
@@ -4,8 +4,6 @@ exports.deploy = deploy;
|
|
|
4
4
|
exports.publish = publish;
|
|
5
5
|
exports.rollback = rollback;
|
|
6
6
|
exports.deployments = deployments;
|
|
7
|
-
const node_fs_1 = require("node:fs");
|
|
8
|
-
const node_path_1 = require("node:path");
|
|
9
7
|
const api_1 = require("../api");
|
|
10
8
|
const args_1 = require("../args");
|
|
11
9
|
const config_1 = require("../config");
|
|
@@ -47,21 +45,15 @@ async function waitForBuild(client, projectId, started) {
|
|
|
47
45
|
throw new errors_1.CliError('The build is taking longer than expected', `To check the state: xflow deployments. Version ${started.deploy_id}`);
|
|
48
46
|
}
|
|
49
47
|
/**
|
|
50
|
-
*
|
|
48
|
+
* Point the local .env at this project, function addresses included.
|
|
51
49
|
*
|
|
52
50
|
* The build bakes its own copy of the map into the bundle, this one is for
|
|
53
51
|
* `npm run dev`: without it the local run calls a function that it has no
|
|
54
52
|
* address for.
|
|
55
53
|
*/
|
|
56
|
-
async function
|
|
54
|
+
async function refreshPlatformEnv(root, client, projectId) {
|
|
57
55
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
58
|
-
|
|
59
|
-
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(root, '.env')) && card.project_token) {
|
|
60
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, '.env'), (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions), 'utf-8');
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
(0, template_1.writeEnvValue)(root, template_1.FUNCTIONS_ENV_KEY, (0, template_1.functionsEnvValue)(card.functions));
|
|
64
|
-
}
|
|
56
|
+
(0, template_1.writePlatformEnv)(root, card.project_token, client.apiUrl, card.functions);
|
|
65
57
|
return card.functions.filter((fn) => fn.invoke_url).map((fn) => fn.name);
|
|
66
58
|
}
|
|
67
59
|
/**
|
|
@@ -180,7 +172,7 @@ async function deploy(args) {
|
|
|
180
172
|
// The functions were shipped by the build itself, so their addresses are known
|
|
181
173
|
// only now. A failure here does not undo a finished build: warn and stop there.
|
|
182
174
|
try {
|
|
183
|
-
const functions = await
|
|
175
|
+
const functions = await refreshPlatformEnv(root, client, config.projectId);
|
|
184
176
|
if (functions.length > 0) {
|
|
185
177
|
(0, ui_1.note)((0, ui_1.dim)(` Functions in .env: ${functions.join(', ')}`));
|
|
186
178
|
}
|
|
@@ -93,7 +93,7 @@ async function functionsInvoke(args) {
|
|
|
93
93
|
const text = await response.text();
|
|
94
94
|
(0, ui_1.note)((0, ui_1.dim)(`${response.status} ${response.statusText} in ${elapsed} ms`));
|
|
95
95
|
if (text)
|
|
96
|
-
(0, ui_1.out)(prettyBody(text));
|
|
96
|
+
(0, ui_1.out)((0, args_1.flagBool)(args, 'json') ? text : prettyBody(text));
|
|
97
97
|
if (!response.ok) {
|
|
98
98
|
throw new errors_1.CliError(`The function answered ${response.status}`, `The stack and console output: xflow functions logs ${name}`);
|
|
99
99
|
}
|
|
@@ -77,7 +77,7 @@ async function init(args) {
|
|
|
77
77
|
write(target, file.path, titleFromProject(file.path, file.content, name));
|
|
78
78
|
for (const file of (0, template_1.scaffoldFiles)(name))
|
|
79
79
|
write(target, file.path, file.content);
|
|
80
|
-
|
|
80
|
+
(0, template_1.writePlatformEnv)(target, project.project_token, client.apiUrl);
|
|
81
81
|
const config = {
|
|
82
82
|
projectId: project.id,
|
|
83
83
|
build: { command: 'npm run build', dir: 'dist' },
|
|
@@ -159,11 +159,8 @@ async function link(args) {
|
|
|
159
159
|
(0, config_1.ignoreStateInGit)(dir);
|
|
160
160
|
if (client.organizationId)
|
|
161
161
|
(0, config_1.writeState)(dir, { organizationId: client.organizationId });
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
write(dir, '.env', (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions));
|
|
165
|
-
(0, ui_1.note)((0, ui_1.dim)(' Created .env with the project token and the function addresses'));
|
|
166
|
-
}
|
|
162
|
+
(0, template_1.writePlatformEnv)(dir, card.project_token, client.apiUrl, card.functions);
|
|
163
|
+
(0, ui_1.note)((0, ui_1.dim)(' .env now points at this project'));
|
|
167
164
|
const skillReady = (0, skills_1.installSkillQuietly)(dir);
|
|
168
165
|
(0, ui_1.ok)(`The folder is linked to the project "${card.name}"`);
|
|
169
166
|
(0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
|
|
@@ -176,7 +173,7 @@ async function link(args) {
|
|
|
176
173
|
// compare against and stops.
|
|
177
174
|
try {
|
|
178
175
|
const { info, files } = await (0, sources_1.downloadRevision)(client, card.id, dir);
|
|
179
|
-
(0, config_1.writeState)(dir, {
|
|
176
|
+
(0, config_1.writeState)(dir, { treeHash: info.tree_hash });
|
|
180
177
|
(0, ui_1.ok)(`Revision ${info.revision}: ${files} files`);
|
|
181
178
|
}
|
|
182
179
|
catch (e) {
|
package/dist/commands/sources.js
CHANGED
|
@@ -74,33 +74,32 @@ async function confirmForce(client, projectId, server, local) {
|
|
|
74
74
|
async function pushSources(root, config, client, options) {
|
|
75
75
|
const tree = prepareTree(root, config);
|
|
76
76
|
const state = (0, config_1.readState)(root);
|
|
77
|
-
(0, ui_1.step)(`Sending ${tree.files.length} files (${(0, ui_1.formatBytes)(tree.archive.length)})`);
|
|
78
77
|
const server = await latestRevision(client, config.projectId);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
throw new errors_1.CliError(`The server holds revision ${server.revision} while this folder does not remember which version it worked from`, 'Fetch the server copy alongside and compare: xflow pull --into ./server-copy');
|
|
78
|
+
if (server && server.tree_hash === tree.hash) {
|
|
79
|
+
(0, config_1.writeState)(root, {
|
|
80
|
+
treeHash: server.tree_hash,
|
|
81
|
+
organizationId: client.organizationId ?? undefined,
|
|
82
|
+
});
|
|
83
|
+
(0, ui_1.ok)(`No changes, revision ${server.revision}`);
|
|
84
|
+
return { revision: server.revision, status: 'unchanged' };
|
|
85
|
+
}
|
|
86
|
+
// Asked before the upload, so a doomed archive never leaves the machine.
|
|
87
|
+
if (server && server.tree_hash !== state.treeHash && !options.force) {
|
|
88
|
+
throw new errors_1.CliError(`The server holds revision ${server.revision}, and this folder does not stand on it`, 'Someone deployed before you, or this folder worked on a different project. ' +
|
|
89
|
+
'Fetch the server copy alongside and compare: xflow pull --into ./server-copy');
|
|
92
90
|
}
|
|
93
91
|
if (options.force && server && server.tree_hash !== tree.hash) {
|
|
94
92
|
await confirmForce(client, config.projectId, server, tree.files);
|
|
95
93
|
}
|
|
94
|
+
(0, ui_1.step)(`Sending ${tree.files.length} files (${(0, ui_1.formatBytes)(tree.archive.length)})`);
|
|
96
95
|
const headers = {};
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// Just read from this project: the header only guards the gap until the upload lands.
|
|
97
|
+
if (server)
|
|
98
|
+
headers['X-Base-Revision'] = String(server.revision);
|
|
99
99
|
if (options.force)
|
|
100
100
|
headers['X-Force'] = 'true';
|
|
101
101
|
const result = await (0, api_1.apiUpload)(client, `/api/v1/projects/${config.projectId}/push`, tree.archive, headers);
|
|
102
102
|
(0, config_1.writeState)(root, {
|
|
103
|
-
revision: result.revision,
|
|
104
103
|
treeHash: result.tree_hash,
|
|
105
104
|
organizationId: client.organizationId ?? undefined,
|
|
106
105
|
});
|
|
@@ -165,7 +164,6 @@ async function pull(args) {
|
|
|
165
164
|
// Only the working folder tracks revisions; a side copy (--into) must not reset them.
|
|
166
165
|
if (!into) {
|
|
167
166
|
(0, config_1.writeState)(root, {
|
|
168
|
-
revision: info.revision,
|
|
169
167
|
treeHash: info.tree_hash,
|
|
170
168
|
organizationId: client.organizationId ?? undefined,
|
|
171
169
|
});
|
|
@@ -196,8 +194,8 @@ async function status() {
|
|
|
196
194
|
if (server.tree_hash === tree.hash) {
|
|
197
195
|
(0, ui_1.out)('State: matches the server');
|
|
198
196
|
}
|
|
199
|
-
else if (
|
|
200
|
-
(0, ui_1.out)(`State: ${(0, ui_1.bold)('diverged')}, the server
|
|
197
|
+
else if (server.tree_hash !== state.treeHash) {
|
|
198
|
+
(0, ui_1.out)(`State: ${(0, ui_1.bold)('diverged')}, the server holds a version this folder has not seen`);
|
|
201
199
|
(0, ui_1.note)((0, ui_1.dim)(' Fetch the server copy alongside: xflow pull --into ./server-copy'));
|
|
202
200
|
}
|
|
203
201
|
else {
|
package/dist/config.js
CHANGED
|
@@ -82,7 +82,9 @@ function readState(root) {
|
|
|
82
82
|
if (!(0, node_fs_1.existsSync)(path))
|
|
83
83
|
return {};
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
// Read by field, so that keys older versions wrote leave the file on the next write.
|
|
86
|
+
const { treeHash, organizationId } = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf-8'));
|
|
87
|
+
return { treeHash, organizationId };
|
|
86
88
|
}
|
|
87
89
|
catch {
|
|
88
90
|
return {};
|
package/dist/flags.js
CHANGED
|
@@ -50,7 +50,7 @@ exports.KNOWN = {
|
|
|
50
50
|
'mcp install': ['show-token'],
|
|
51
51
|
functions: ['project'],
|
|
52
52
|
'functions list': ['project'],
|
|
53
|
-
'functions invoke': ['data', 'data-file', 'method', 'project'],
|
|
53
|
+
'functions invoke': ['data', 'data-file', 'json', 'method', 'project'],
|
|
54
54
|
'functions logs': ['limit', 'project'],
|
|
55
55
|
logs: ['limit', 'project'],
|
|
56
56
|
env: ['project'],
|
package/dist/help.js
CHANGED
|
@@ -472,14 +472,15 @@ Both are taken from the platform, no local .env is needed.
|
|
|
472
472
|
--data '{"a":1}' request body (the method becomes POST by default)
|
|
473
473
|
--data-file body.json the same body from a file
|
|
474
474
|
--method GET a different method
|
|
475
|
+
--json the answer of the function alone, exactly as it came
|
|
475
476
|
|
|
476
477
|
The body has to be JSON, and it is checked before the call goes out: PowerShell
|
|
477
478
|
strips double quotes from a native call, so ${(0, ui_1.bold)('--data')} needs them escaped there
|
|
478
479
|
(${(0, ui_1.bold)('--data \'{\\"a\\":1}\'')}) and a file needs nothing.
|
|
479
480
|
|
|
480
|
-
Prints the status, the response time and the body
|
|
481
|
-
5xx: in CI such a call has to fail the step.
|
|
482
|
-
${(0, ui_1.bold)('xflow functions logs <name>')}.`,
|
|
481
|
+
Prints the status, the response time and the body; under ${(0, ui_1.bold)('--json')} the body
|
|
482
|
+
alone. A non-zero exit code on 4xx and 5xx: in CI such a call has to fail the step.
|
|
483
|
+
The cause of a crash is shown by ${(0, ui_1.bold)('xflow functions logs <name>')}.`,
|
|
483
484
|
mcp: `${(0, ui_1.bold)('xflow mcp install')}: connect the agent to the platform directly
|
|
484
485
|
|
|
485
486
|
Writes the MCP server into the configuration of a client that has its own connect
|
package/dist/template.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.functionsEnvValue = functionsEnvValue;
|
|
5
|
-
exports.envFile = envFile;
|
|
6
|
-
exports.writeEnvValue = writeEnvValue;
|
|
3
|
+
exports.writePlatformEnv = writePlatformEnv;
|
|
7
4
|
exports.scaffoldFiles = scaffoldFiles;
|
|
8
5
|
const node_fs_1 = require("node:fs");
|
|
9
6
|
const node_path_1 = require("node:path");
|
|
@@ -32,7 +29,7 @@ tokens from \`src/index.css\` break that consistency.
|
|
|
32
29
|
Useful: \`xflow status\` for what is on the server, \`xflow deployments\` for version history.
|
|
33
30
|
`;
|
|
34
31
|
/** Function name → URL map, one JSON line. */
|
|
35
|
-
|
|
32
|
+
const FUNCTIONS_ENV_KEY = 'VITE_XFLOW_FUNCTIONS';
|
|
36
33
|
function functionsEnvValue(functions) {
|
|
37
34
|
const map = {};
|
|
38
35
|
for (const fn of functions) {
|
|
@@ -41,14 +38,14 @@ function functionsEnvValue(functions) {
|
|
|
41
38
|
}
|
|
42
39
|
return JSON.stringify(map);
|
|
43
40
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Point .env at the current project: written on every link and deploy, so that `npm run dev`
|
|
43
|
+
* never keeps talking to the previous one. Variables of your own are left alone.
|
|
44
|
+
*/
|
|
45
|
+
function writePlatformEnv(root, projectToken, apiUrl, functions = []) {
|
|
46
|
+
writeEnvValue(root, 'VITE_XFLOW_PROJECT_TOKEN', projectToken ?? '');
|
|
47
|
+
writeEnvValue(root, 'VITE_XFLOW_API_URL', apiUrl.replace(/^https?:\/\//, ''));
|
|
48
|
+
writeEnvValue(root, FUNCTIONS_ENV_KEY, functionsEnvValue(functions));
|
|
52
49
|
}
|
|
53
50
|
/** Update one variable in .env without touching the rest of the file. */
|
|
54
51
|
function writeEnvValue(root, key, value) {
|
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.17.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
|
@@ -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.
|
|
27
|
+
These instructions ship with xflow CLI 0.17.0. 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
|
|
|
@@ -122,9 +122,10 @@ the very first version of a project: it publishes automatically, since there is
|
|
|
122
122
|
live version to protect yet.
|
|
123
123
|
|
|
124
124
|
`xflow status` says how the local copy differs from the server revision. When a deploy is
|
|
125
|
-
rejected before the build starts, the server
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
rejected before the build starts, the server holds a version this folder has never seen:
|
|
126
|
+
somebody deployed first, or the folder was linked to a different project along the way.
|
|
127
|
+
Fetch the server work next to yours (`xflow pull --into ./server-copy`), merge it in git,
|
|
128
|
+
deploy again. `--force` destroys that work, a last resort rather than a way around the error.
|
|
128
129
|
|
|
129
130
|
**Only pages are versioned.** Cloud functions and the database are one per project: they
|
|
130
131
|
are not versioned, and dev and live share them. So `xflow deploy` changes the running
|
|
@@ -256,8 +257,9 @@ which functions are about to go and let the user answer.
|
|
|
256
257
|
|
|
257
258
|
Debugging a deployed function is two commands: `xflow functions invoke <name>` calls it
|
|
258
259
|
the way the app does and prints status, timing and body (`--data '{"a":1}'` sends a body,
|
|
259
|
-
`--data-file body.json` sends the same from a file
|
|
260
|
-
the failures, each with its stack and the console
|
|
260
|
+
`--data-file body.json` sends the same from a file, `--json` prints the body alone), and
|
|
261
|
+
`xflow functions logs <name>` shows the failures, each with its stack and the console
|
|
262
|
+
output of that call. Only failed calls are
|
|
261
263
|
logged, so an empty output means the function never crashed, not that logging is broken.
|
|
262
264
|
|
|
263
265
|
The body has to be JSON and the CLI checks it before calling. In PowerShell the double
|