@getxflow/cli 0.1.7 → 0.1.9
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/README.md +45 -44
- package/dist/api.js +10 -15
- package/dist/args.js +1 -1
- package/dist/bin.js +11 -7
- package/dist/commands/auth.js +33 -18
- package/dist/commands/db.js +18 -18
- package/dist/commands/deploy.js +18 -18
- package/dist/commands/env.js +36 -25
- package/dist/commands/functions.js +21 -21
- package/dist/commands/logs.js +6 -6
- package/dist/commands/mcp.js +14 -14
- package/dist/commands/projects.js +23 -23
- package/dist/commands/schedules.js +8 -8
- package/dist/commands/skills.js +6 -6
- package/dist/commands/sources.js +32 -32
- package/dist/config.js +5 -5
- package/dist/help.js +252 -250
- package/dist/limits.js +78 -0
- package/dist/session.js +1 -1
- package/dist/template.js +22 -22
- package/dist/ui.js +12 -12
- package/dist/version.js +1 -1
- package/dist/zip.js +7 -7
- package/package.json +22 -22
- package/skills/xflow/SKILL.md +19 -1
package/dist/commands/sources.js
CHANGED
|
@@ -23,17 +23,17 @@ function prepareTree(root, config) {
|
|
|
23
23
|
const extra = [...(config.ignore ?? []), ...(buildDir ? [`${buildDir}/`] : [])];
|
|
24
24
|
const { files, skippedLinks } = (0, tree_1.collectFiles)(root, (0, tree_1.loadIgnoreRules)(root, extra));
|
|
25
25
|
if (files.length === 0) {
|
|
26
|
-
throw new errors_1.CliError('
|
|
26
|
+
throw new errors_1.CliError('The folder has no files to send', 'Check .xflowignore: everything may be excluded');
|
|
27
27
|
}
|
|
28
28
|
for (const link of skippedLinks) {
|
|
29
|
-
(0, ui_1.warn)(
|
|
29
|
+
(0, ui_1.warn)(`Skipped the link ${link}: symbolic links are not synchronized`);
|
|
30
30
|
}
|
|
31
31
|
const archive = (0, zip_1.zipCreate)(files);
|
|
32
32
|
if (archive.length > MAX_ARCHIVE_BYTES) {
|
|
33
33
|
const top = (0, tree_1.heaviest)(files)
|
|
34
|
-
.map((f) => ` ${f.path}
|
|
34
|
+
.map((f) => ` ${f.path} ${(0, ui_1.formatBytes)(f.content.length)}`)
|
|
35
35
|
.join('\n');
|
|
36
|
-
throw new errors_1.CliError(
|
|
36
|
+
throw new errors_1.CliError(`The archive is ${(0, ui_1.formatBytes)(archive.length)}, the cap is ${(0, ui_1.formatBytes)(MAX_ARCHIVE_BYTES)}`, `The heaviest files:\n${top}\n Exclude what is not needed in .xflowignore`);
|
|
37
37
|
}
|
|
38
38
|
return { files, hash: (0, tree_1.treeHash)(files), archive };
|
|
39
39
|
}
|
|
@@ -50,36 +50,36 @@ async function latestRevision(client, projectId) {
|
|
|
50
50
|
async function confirmForce(client, projectId, server, local) {
|
|
51
51
|
const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
|
|
52
52
|
(0, ui_1.note)('');
|
|
53
|
-
(0, ui_1.warn)(
|
|
53
|
+
(0, ui_1.warn)(`The server holds revision ${server.revision} from ${(0, ui_1.formatAge)(server.created_at)}, ${server.file_count} files`);
|
|
54
54
|
try {
|
|
55
55
|
const info = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/pull`);
|
|
56
56
|
const serverPaths = new Set((0, zip_1.zipRead)(await (0, api_1.downloadUrl)(info.download_url)).map((e) => e.path));
|
|
57
57
|
const localPaths = new Set(local.map((f) => f.path));
|
|
58
58
|
const disappearing = [...serverPaths].filter((p) => !localPaths.has(p));
|
|
59
59
|
if (disappearing.length > 0) {
|
|
60
|
-
(0, ui_1.note)(`
|
|
60
|
+
(0, ui_1.note)(` Files that will disappear (${disappearing.length}):`);
|
|
61
61
|
for (const path of disappearing.slice(0, 20))
|
|
62
62
|
(0, ui_1.note)((0, ui_1.dim)(` ${path}`));
|
|
63
63
|
if (disappearing.length > 20)
|
|
64
|
-
(0, ui_1.note)((0, ui_1.dim)(`
|
|
64
|
+
(0, ui_1.note)((0, ui_1.dim)(` …and ${disappearing.length - 20} more`));
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
67
|
+
(0, ui_1.note)((0, ui_1.dim)(' No files disappear, but the contents of the server revision are replaced by yours'));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
catch {
|
|
71
|
-
(0, ui_1.warn)('
|
|
71
|
+
(0, ui_1.warn)('Could not read the server copy: the list of disappearing files is unavailable');
|
|
72
72
|
}
|
|
73
73
|
(0, ui_1.note)('');
|
|
74
|
-
const confirmed = await (0, ui_1.confirmWord)(
|
|
74
|
+
const confirmed = await (0, ui_1.confirmWord)(`This overwrites somebody else"s work with no way to recover it from the platform.`, card.name);
|
|
75
75
|
if (!confirmed)
|
|
76
|
-
throw new errors_1.CliError('
|
|
76
|
+
throw new errors_1.CliError('Cancelled');
|
|
77
77
|
}
|
|
78
78
|
/** Отправка исходников. Используется и командой push, и первым шагом deploy. */
|
|
79
79
|
async function pushSources(root, config, client, options) {
|
|
80
80
|
const tree = prepareTree(root, config);
|
|
81
81
|
const state = (0, config_1.readState)(root);
|
|
82
|
-
(0, ui_1.step)(
|
|
82
|
+
(0, ui_1.step)(`Sending ${tree.files.length} files (${(0, ui_1.formatBytes)(tree.archive.length)})`);
|
|
83
83
|
const server = await latestRevision(client, config.projectId);
|
|
84
84
|
// Ревизия сервера есть, а мы не знаем, от какой работали (свежий клон, чужая
|
|
85
85
|
// машина). Совпал хеш — просто синхронизируемся; не совпал — молча заливать
|
|
@@ -87,10 +87,10 @@ async function pushSources(root, config, client, options) {
|
|
|
87
87
|
if (server && state.revision === undefined && !options.force) {
|
|
88
88
|
if (server.tree_hash === tree.hash) {
|
|
89
89
|
(0, config_1.writeState)(root, { revision: server.revision, treeHash: server.tree_hash });
|
|
90
|
-
(0, ui_1.ok)(
|
|
90
|
+
(0, ui_1.ok)(`Already in sync, revision ${server.revision}`);
|
|
91
91
|
return { revision: server.revision, status: 'unchanged' };
|
|
92
92
|
}
|
|
93
|
-
throw new errors_1.CliError(
|
|
93
|
+
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');
|
|
94
94
|
}
|
|
95
95
|
if (options.force && server && server.tree_hash !== tree.hash) {
|
|
96
96
|
await confirmForce(client, config.projectId, server, tree.files);
|
|
@@ -103,10 +103,10 @@ async function pushSources(root, config, client, options) {
|
|
|
103
103
|
const result = await (0, api_1.apiUpload)(client, `/api/v1/projects/${config.projectId}/push`, tree.archive, headers);
|
|
104
104
|
(0, config_1.writeState)(root, { revision: result.revision, treeHash: result.tree_hash });
|
|
105
105
|
if (result.status === 'unchanged') {
|
|
106
|
-
(0, ui_1.ok)(
|
|
106
|
+
(0, ui_1.ok)(`No changes, revision ${result.revision}`);
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
(0, ui_1.ok)(
|
|
109
|
+
(0, ui_1.ok)(`Revision ${result.revision}: ${result.file_count} files, ${(0, ui_1.formatBytes)(result.size_bytes)}`);
|
|
110
110
|
}
|
|
111
111
|
return { revision: result.revision, status: result.status };
|
|
112
112
|
}
|
|
@@ -130,14 +130,14 @@ async function pull(args) {
|
|
|
130
130
|
const query = revision !== undefined ? `?revision=${revision}` : '';
|
|
131
131
|
const info = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/pull${query}`);
|
|
132
132
|
if (hasContent(target) && !(0, args_1.flagBool)(args, 'force')) {
|
|
133
|
-
throw new errors_1.CliError(
|
|
134
|
-
'xflow pull --into ./server-copy,
|
|
133
|
+
throw new errors_1.CliError(`The directory ${target} is not empty`, 'The platform cannot merge changes, that is git work. Fetch the server copy alongside: ' +
|
|
134
|
+
'xflow pull --into ./server-copy, or overwrite the folder completely: xflow pull --force');
|
|
135
135
|
}
|
|
136
|
-
(0, ui_1.step)(
|
|
136
|
+
(0, ui_1.step)(`Downloading revision ${info.revision} (${(0, ui_1.formatBytes)(info.size_bytes)})`);
|
|
137
137
|
const entries = (0, zip_1.zipRead)(await (0, api_1.downloadUrl)(info.download_url));
|
|
138
138
|
const hash = (0, tree_1.treeHash)(entries.map((e) => ({ path: e.path, content: e.content })));
|
|
139
139
|
if (hash !== info.tree_hash) {
|
|
140
|
-
(0, ui_1.warn)('
|
|
140
|
+
(0, ui_1.warn)('The hash of the downloaded tree did not match the server one: the contents may have changed in transit');
|
|
141
141
|
}
|
|
142
142
|
(0, node_fs_1.mkdirSync)(target, { recursive: true });
|
|
143
143
|
for (const entry of entries) {
|
|
@@ -150,7 +150,7 @@ async function pull(args) {
|
|
|
150
150
|
if (!into) {
|
|
151
151
|
(0, config_1.writeState)(root, { revision: info.revision, treeHash: info.tree_hash });
|
|
152
152
|
}
|
|
153
|
-
(0, ui_1.ok)(
|
|
153
|
+
(0, ui_1.ok)(`Revision ${info.revision}: ${entries.length} files in ${target}`);
|
|
154
154
|
}
|
|
155
155
|
async function status() {
|
|
156
156
|
const { root, config } = (0, config_1.requireProject)();
|
|
@@ -163,28 +163,28 @@ async function status() {
|
|
|
163
163
|
]);
|
|
164
164
|
(0, ui_1.out)(`${(0, ui_1.bold)(card.name)} ${(0, ui_1.dim)(card.id)}`);
|
|
165
165
|
(0, ui_1.out)('');
|
|
166
|
-
(0, ui_1.out)(
|
|
166
|
+
(0, ui_1.out)(`Local: ${tree.files.length} files, ${(0, ui_1.formatBytes)(tree.archive.length)} archived`);
|
|
167
167
|
if (!server) {
|
|
168
|
-
(0, ui_1.out)('
|
|
169
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
168
|
+
(0, ui_1.out)('On the server: no sources yet');
|
|
169
|
+
(0, ui_1.note)((0, ui_1.dim)(' Send them: xflow push'));
|
|
170
170
|
}
|
|
171
171
|
else {
|
|
172
|
-
(0, ui_1.out)(
|
|
172
|
+
(0, ui_1.out)(`On the server: revision ${server.revision}, ${server.file_count} files, ${(0, ui_1.formatAge)(server.created_at)}`);
|
|
173
173
|
if (server.tree_hash === tree.hash) {
|
|
174
|
-
(0, ui_1.out)('
|
|
174
|
+
(0, ui_1.out)('State: matches the server');
|
|
175
175
|
}
|
|
176
176
|
else if (state.revision !== undefined && state.revision < server.revision) {
|
|
177
|
-
(0, ui_1.out)(
|
|
178
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
177
|
+
(0, ui_1.out)(`State: ${(0, ui_1.bold)('diverged')}, the server is newer (you worked from ${state.revision})`);
|
|
178
|
+
(0, ui_1.note)((0, ui_1.dim)(' Fetch the server copy alongside: xflow pull --into ./server-copy'));
|
|
179
179
|
}
|
|
180
180
|
else {
|
|
181
|
-
(0, ui_1.out)(
|
|
182
|
-
(0, ui_1.note)((0, ui_1.dim)('
|
|
181
|
+
(0, ui_1.out)(`State: ${(0, ui_1.bold)('local changes')}`);
|
|
182
|
+
(0, ui_1.note)((0, ui_1.dim)(' Send them: xflow push'));
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
(0, ui_1.out)('');
|
|
186
|
-
(0, ui_1.out)(
|
|
187
|
-
(0, ui_1.out)(
|
|
186
|
+
(0, ui_1.out)(`Built version: ${card.dev_deploy_id ?? '(no build)'}`);
|
|
187
|
+
(0, ui_1.out)(`Visitors see: ${card.live_deploy_id ?? '(never published)'}`);
|
|
188
188
|
(0, ui_1.out)('');
|
|
189
189
|
(0, ui_1.out)(card.project_url);
|
|
190
190
|
}
|
package/dist/config.js
CHANGED
|
@@ -36,11 +36,11 @@ function readConfig(root) {
|
|
|
36
36
|
parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf-8'));
|
|
37
37
|
}
|
|
38
38
|
catch (e) {
|
|
39
|
-
throw new ConfigError(
|
|
39
|
+
throw new ConfigError(`Could not read ${exports.CONFIG_FILE}: ${e instanceof Error ? e.message : e}`);
|
|
40
40
|
}
|
|
41
41
|
const config = parsed;
|
|
42
42
|
if (!config || typeof config.projectId !== 'string' || !config.projectId) {
|
|
43
|
-
throw new ConfigError(
|
|
43
|
+
throw new ConfigError(`${exports.CONFIG_FILE} has no projectId. Link the folder to a project: xflow link <id>`);
|
|
44
44
|
}
|
|
45
45
|
return config;
|
|
46
46
|
}
|
|
@@ -51,9 +51,9 @@ function writeConfig(root, config) {
|
|
|
51
51
|
function requireProject() {
|
|
52
52
|
const root = findProjectRoot();
|
|
53
53
|
if (!root) {
|
|
54
|
-
throw new ConfigError(
|
|
55
|
-
'
|
|
56
|
-
'
|
|
54
|
+
throw new ConfigError(`The folder is not linked to a project: there is no ${exports.CONFIG_FILE} next to it.\n` +
|
|
55
|
+
' Link an existing one: xflow link <project id>\n' +
|
|
56
|
+
' Create a new one: xflow init');
|
|
57
57
|
}
|
|
58
58
|
return { root, config: readConfig(root) };
|
|
59
59
|
}
|