@getxflow/cli 0.16.0 → 0.18.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/connections.js +12 -0
- package/dist/commands/deploy.js +4 -12
- package/dist/commands/projects.js +4 -7
- package/dist/commands/sources.js +18 -20
- package/dist/config.js +3 -1
- package/dist/help.js +10 -7
- package/dist/template.js +10 -13
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +10 -8
|
@@ -32,6 +32,14 @@ function state(row) {
|
|
|
32
32
|
return 'linked';
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The names of a connection as they are written in a call, braces and all. Ready to paste:
|
|
37
|
+
* guessing one costs a real request to somebody's live account, since an unknown name is
|
|
38
|
+
* left in the text and the request still goes out.
|
|
39
|
+
*/
|
|
40
|
+
function placeholders(row) {
|
|
41
|
+
return (row.substitutes ?? []).map((name) => `{${name}}`).join(' ');
|
|
42
|
+
}
|
|
35
43
|
function endpoint(projectId) {
|
|
36
44
|
return `/api/v1/projects/${projectId}/connections`;
|
|
37
45
|
}
|
|
@@ -69,6 +77,7 @@ async function connectionsList(args) {
|
|
|
69
77
|
row.label,
|
|
70
78
|
row.connector_key ?? '-',
|
|
71
79
|
row.alias ?? '-',
|
|
80
|
+
placeholders(row) || '-',
|
|
72
81
|
state(row),
|
|
73
82
|
]));
|
|
74
83
|
const variables = data.connections.flatMap((row) => row.env);
|
|
@@ -76,6 +85,9 @@ async function connectionsList(args) {
|
|
|
76
85
|
(0, ui_1.note)((0, ui_1.dim)(` The functions get: ${variables.join(', ')}`));
|
|
77
86
|
(0, ui_1.note)((0, ui_1.dim)(' A change reaches a function on its next deploy: xflow deploy'));
|
|
78
87
|
}
|
|
88
|
+
if (data.connections.some((row) => placeholders(row))) {
|
|
89
|
+
(0, ui_1.note)((0, ui_1.dim)(' The fourth column is what a call fills in: xflow connections call <name> <path>'));
|
|
90
|
+
}
|
|
79
91
|
if (data.connections.some((row) => !row.alias)) {
|
|
80
92
|
(0, ui_1.note)((0, ui_1.dim)(' To link one: xflow connections link <name> --as ALIAS'));
|
|
81
93
|
}
|
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
|
}
|
|
@@ -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/help.js
CHANGED
|
@@ -372,9 +372,10 @@ the platform into the function.
|
|
|
372
372
|
xflow connections call <name> <path>
|
|
373
373
|
ask that API something, once, without deploying
|
|
374
374
|
|
|
375
|
-
Every row says whether the connection is linked to this project (its alias)
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
Every row says whether the connection is linked to this project (its alias), which of
|
|
376
|
+
its fields a call can fill in (${(0, ui_1.bold)('{CLIENT_ID}')} and the like) and what state the
|
|
377
|
+
access is in. ${(0, ui_1.bold)('available, not linked')} is the useful one: the account exists in
|
|
378
|
+
the organization, but this project gets nothing from it yet: link it.
|
|
378
379
|
|
|
379
380
|
Both commands take the name of the connection, the first column of the list, and both
|
|
380
381
|
also take its identifier. ${(0, ui_1.bold)('unlink')} takes the alias as well, and prefers it: the
|
|
@@ -420,10 +421,12 @@ refusal lists them. ${(0, ui_1.bold)('--method')} defaults to GET, or to POST wh
|
|
|
420
421
|
${(0, ui_1.bold)('{NAME}')} in the path or in a header value is filled in by the platform from the
|
|
421
422
|
fields of the connection. That is how the two-header APIs are called: Ozon wants
|
|
422
423
|
${(0, ui_1.bold)('Client-Id')} beside its key, Yandex Direct wants ${(0, ui_1.bold)('Client-Login')} for an
|
|
423
|
-
agency account, and you never see those values.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
agency account, and you never see those values. Which names a connection has is the fourth
|
|
425
|
+
column of ${(0, ui_1.bold)('xflow connections')}, printed in braces and the same whether the account
|
|
426
|
+
is linked or not. Do not derive them from ${(0, ui_1.bold)('xflow env')}: that list is the variables
|
|
427
|
+
of a link, and it names both more and less than this one. A name the connection does not
|
|
428
|
+
have is left in the text as you typed it and the request still goes out, so an invented
|
|
429
|
+
name reaches somebody's live account as text.
|
|
427
430
|
|
|
428
431
|
The answer is printed head first, at most a few kilobytes, and it says so when there is
|
|
429
432
|
more. Ask for the shape, not the volume: one day and ${(0, ui_1.bold)('limit=1')} tells you what the
|
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.18.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.18.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
|
|
@@ -395,10 +396,11 @@ something. To use the account in code, link it and deploy.
|
|
|
395
396
|
Two refusals do need a person, and different ones: `connections:link` right — the owner of the
|
|
396
397
|
key grants it where the key permissions live; `not granted to you` — ask whoever connected the account.
|
|
397
398
|
|
|
398
|
-
`{NAME}` in the path or in a header value is filled in from the fields of the connection
|
|
399
|
-
names
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
`{NAME}` in the path or in a header value is filled in from the fields of the connection.
|
|
400
|
+
**Take the names from `xflow connections`**, which prints them in braces for every account,
|
|
401
|
+
linked or not: an invented name is left in the text as you typed it and the request goes out
|
|
402
|
+
anyway, into somebody's live account. `xflow env` is not that list. Only the hosts of that
|
|
403
|
+
connector are reachable and a refusal lists them.
|
|
402
404
|
|
|
403
405
|
**Ask for the shape, not the volume.** One day and `limit=1` is enough to learn what the
|
|
404
406
|
fields are called; the answer is printed head first and says when there is more. The whole
|