@getxflow/cli 1.4.0 → 1.6.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 -33
- package/dist/commands/db.js +11 -151
- package/dist/commands/deploy.js +1 -2
- package/dist/commands/env.js +4 -9
- package/dist/commands/projects.js +2 -4
- package/dist/commands/storage.js +1 -2
- package/dist/flags.js +2 -12
- package/dist/help.js +7 -89
- package/dist/limits.js +3 -5
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/xflow/SKILL.md +681 -735
- package/dist/commands/pages.js +0 -257
package/dist/bin.js
CHANGED
|
@@ -25,7 +25,6 @@ const schedules_1 = require("./commands/schedules");
|
|
|
25
25
|
const skills_1 = require("./commands/skills");
|
|
26
26
|
const sources_1 = require("./commands/sources");
|
|
27
27
|
const storage_1 = require("./commands/storage");
|
|
28
|
-
const pages_1 = require("./commands/pages");
|
|
29
28
|
const deploy_1 = require("./commands/deploy");
|
|
30
29
|
const dev_1 = require("./commands/dev");
|
|
31
30
|
const update_1 = require("./commands/update");
|
|
@@ -180,15 +179,11 @@ async function run(args) {
|
|
|
180
179
|
await (0, db_1.dbExec)({ ...args, words: args.words.slice(2) });
|
|
181
180
|
return;
|
|
182
181
|
}
|
|
183
|
-
if (second === 'move') {
|
|
184
|
-
await (0, db_1.dbMove)(rest);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
182
|
if (second === undefined || second === 'status') {
|
|
188
183
|
await (0, db_1.dbStatus)({ ...args, words: args.words.slice(2) });
|
|
189
184
|
return;
|
|
190
185
|
}
|
|
191
|
-
throw new errors_1.CliError(`Unknown command: db ${second}`, 'Available: status, list, schema, query, exec
|
|
186
|
+
throw new errors_1.CliError(`Unknown command: db ${second}`, 'Available: status, list, schema, query, exec and migrate');
|
|
192
187
|
case 'storage':
|
|
193
188
|
if (second === 'push') {
|
|
194
189
|
await (0, storage_1.storagePush)(rest);
|
|
@@ -203,32 +198,6 @@ async function run(args) {
|
|
|
203
198
|
return;
|
|
204
199
|
}
|
|
205
200
|
throw new errors_1.CliError(`Unknown command: storage ${second}`, 'Available: ls, push and rm');
|
|
206
|
-
case 'pages':
|
|
207
|
-
if (second === 'create') {
|
|
208
|
-
await (0, pages_1.pagesCreate)(rest);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (second === 'deploy') {
|
|
212
|
-
await (0, pages_1.pagesDeploy)(rest);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (second === 'status') {
|
|
216
|
-
await (0, pages_1.pagesStatus)(rest);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (second === 'rollback') {
|
|
220
|
-
await (0, pages_1.pagesRollback)(rest);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if (second === 'rm' || second === 'remove') {
|
|
224
|
-
await (0, pages_1.pagesRemove)(rest);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (second === undefined || second === 'list') {
|
|
228
|
-
await (0, pages_1.pagesList)(rest);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
throw new errors_1.CliError(`Unknown command: pages ${second}`, 'Available: list, create, deploy, status, rollback and rm');
|
|
232
201
|
case 'status':
|
|
233
202
|
await (0, sources_1.status)();
|
|
234
203
|
return;
|
|
@@ -338,7 +307,7 @@ async function main() {
|
|
|
338
307
|
// XFLOW_TOKEN is taken as given and no organization is looked for, so a
|
|
339
308
|
// project of another one answers "not found" with nothing else to say.
|
|
340
309
|
if (process.env.XFLOW_TOKEN?.trim()) {
|
|
341
|
-
(0, ui_1.fatalNote)((0, ui_1.dim)(' XFLOW_TOKEN is set: it is the key of one organization and is used as it comes, so a project
|
|
310
|
+
(0, ui_1.fatalNote)((0, ui_1.dim)(' XFLOW_TOKEN is set: it is the key of one organization and is used as it comes, so a project of another one is not found'));
|
|
342
311
|
}
|
|
343
312
|
else if (e.apiUrl && (0, credentials_1.listOrgs)(e.apiUrl).length > 1) {
|
|
344
313
|
(0, ui_1.fatalNote)((0, ui_1.dim)(' Several organizations are signed in: the answer may live in another one, see xflow org'));
|
package/dist/commands/db.js
CHANGED
|
@@ -5,7 +5,6 @@ exports.dbList = dbList;
|
|
|
5
5
|
exports.dbSchema = dbSchema;
|
|
6
6
|
exports.dbQuery = dbQuery;
|
|
7
7
|
exports.dbExec = dbExec;
|
|
8
|
-
exports.dbMove = dbMove;
|
|
9
8
|
exports.dbMigrate = dbMigrate;
|
|
10
9
|
exports.migrateBeforeDeploy = migrateBeforeDeploy;
|
|
11
10
|
const node_fs_1 = require("node:fs");
|
|
@@ -20,41 +19,6 @@ const ui_1 = require("../ui");
|
|
|
20
19
|
const sources_1 = require("./sources");
|
|
21
20
|
/** Migration files live in the repo; the applied history lives in the database. */
|
|
22
21
|
const MIGRATIONS_DIR = 'migrations';
|
|
23
|
-
function serverLabel(server) {
|
|
24
|
-
if (server === undefined)
|
|
25
|
-
return '-';
|
|
26
|
-
if (server === null)
|
|
27
|
-
return 'platform';
|
|
28
|
-
return server.status === 'active' ? `own "${server.name}"` : `own "${server.name}", ${server.status}`;
|
|
29
|
-
}
|
|
30
|
-
const moving = (move) => move?.status === 'copying' || move?.status === 'switching';
|
|
31
|
-
/** Asked apart from the history: during a move the database does not answer, this does. */
|
|
32
|
-
async function projectDatabase(client, projectId) {
|
|
33
|
-
try {
|
|
34
|
-
return await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/move`);
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
if (e instanceof api_1.ApiError && e.code === 'not_deployed')
|
|
38
|
-
return null;
|
|
39
|
-
throw e;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function printPlace(history, place) {
|
|
43
|
-
(0, ui_1.out)(`Schema: ${(0, ui_1.bold)(history.schema)}`);
|
|
44
|
-
if (history.server !== undefined)
|
|
45
|
-
(0, ui_1.out)(`Server: ${serverLabel(history.server)}`);
|
|
46
|
-
if (place?.move)
|
|
47
|
-
(0, ui_1.out)(`Move: ${moveLine(place.move)}`);
|
|
48
|
-
(0, ui_1.out)('');
|
|
49
|
-
}
|
|
50
|
-
/** Mid-move the history cannot be read, so the answer is the move itself. */
|
|
51
|
-
function printMoving(place) {
|
|
52
|
-
if (place.database)
|
|
53
|
-
(0, ui_1.out)(`Database: ${(0, ui_1.bold)(place.database.name)}`);
|
|
54
|
-
(0, ui_1.out)(`Server: ${serverLabel(place.server)}`);
|
|
55
|
-
(0, ui_1.out)(`Move: ${place.move?.note ?? ''}`);
|
|
56
|
-
(0, ui_1.note)((0, ui_1.dim)(' Migrations and queries wait until it is over; the platform finishes it on its own'));
|
|
57
|
-
}
|
|
58
22
|
const STATE_LABEL = {
|
|
59
23
|
pending: 'waiting',
|
|
60
24
|
applied: 'applied',
|
|
@@ -86,11 +50,9 @@ async function dbStatus(args) {
|
|
|
86
50
|
// is the applied history alone, which does not depend on any working copy.
|
|
87
51
|
if ((0, args_1.flagString)(args, 'project') !== undefined) {
|
|
88
52
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
89
|
-
const place = await projectDatabase(client, projectId);
|
|
90
|
-
if (place && moving(place.move))
|
|
91
|
-
return printMoving(place);
|
|
92
53
|
const history = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/migrate`);
|
|
93
|
-
|
|
54
|
+
(0, ui_1.out)(`Schema: ${(0, ui_1.bold)(history.schema)}`);
|
|
55
|
+
(0, ui_1.out)('');
|
|
94
56
|
if (history.applied.length === 0) {
|
|
95
57
|
(0, ui_1.note)('No migrations have been applied yet');
|
|
96
58
|
return;
|
|
@@ -102,15 +64,13 @@ async function dbStatus(args) {
|
|
|
102
64
|
const { root, config } = (0, config_1.requireProject)();
|
|
103
65
|
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
104
66
|
const local = readMigrations(root);
|
|
105
|
-
const place = await projectDatabase(client, config.projectId);
|
|
106
|
-
if (place && moving(place.move))
|
|
107
|
-
return printMoving(place);
|
|
108
67
|
const status = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/db/status`, {
|
|
109
68
|
method: 'POST',
|
|
110
69
|
body: { migrations: local },
|
|
111
70
|
timeoutMs: 60_000,
|
|
112
71
|
});
|
|
113
|
-
|
|
72
|
+
(0, ui_1.out)(`Schema: ${(0, ui_1.bold)(status.schema)}`);
|
|
73
|
+
(0, ui_1.out)('');
|
|
114
74
|
if (status.migrations.length === 0) {
|
|
115
75
|
(0, ui_1.note)(`No migrations. The first one: ${MIGRATIONS_DIR}/0001_init.sql`);
|
|
116
76
|
return;
|
|
@@ -121,65 +81,23 @@ async function dbStatus(args) {
|
|
|
121
81
|
(0, ui_1.warn)('A changed file is never applied again and db migrate refuses it: write a new migration');
|
|
122
82
|
}
|
|
123
83
|
}
|
|
124
|
-
|
|
125
|
-
const room = server.used_bytes !== null && server.space_bytes !== null
|
|
126
|
-
? `${(0, ui_1.formatBytes)(server.used_bytes)} of ${(0, ui_1.formatBytes)(server.space_bytes)} used`
|
|
127
|
-
: '';
|
|
128
|
-
return [
|
|
129
|
-
server.id === null ? 'platform' : `own "${server.name}"`,
|
|
130
|
-
server.id === null ? 'plan space' : server.tier ?? '',
|
|
131
|
-
server.status === 'active' ? '' : server.status,
|
|
132
|
-
room,
|
|
133
|
-
`${server.databases} ${server.databases === 1 ? 'database' : 'databases'}`,
|
|
134
|
-
];
|
|
135
|
-
}
|
|
136
|
-
/** The databases of the projects of the organization, and the servers they can live on. */
|
|
84
|
+
/** The databases of the projects of the organization, largest first. */
|
|
137
85
|
async function dbList() {
|
|
138
86
|
const client = (0, session_1.connect)((0, config_1.localConfig)());
|
|
139
87
|
const data = await (0, api_1.apiJson)(client, '/api/v1/org/databases');
|
|
140
88
|
if (data.databases.length === 0) {
|
|
141
89
|
(0, ui_1.note)('The organization has no databases');
|
|
142
90
|
(0, ui_1.note)((0, ui_1.dim)(' One is created with the first project: xflow init'));
|
|
91
|
+
return;
|
|
143
92
|
}
|
|
144
|
-
|
|
145
|
-
printDatabases(data.databases, data.total, data.truncated);
|
|
146
|
-
}
|
|
147
|
-
printServers(data.servers);
|
|
148
|
-
}
|
|
149
|
-
function moveLine(move) {
|
|
150
|
-
return move.finished_at ? `${move.note} (${(0, ui_1.formatAge)(move.finished_at)})` : move.note;
|
|
151
|
-
}
|
|
152
|
-
function printDatabases(databases, total, truncated) {
|
|
153
|
-
(0, ui_1.table)(databases.map((database) => [
|
|
93
|
+
(0, ui_1.table)(data.databases.map((database) => [
|
|
154
94
|
database.name,
|
|
155
95
|
database.id,
|
|
156
|
-
database.
|
|
157
|
-
|
|
158
|
-
typeof database.bytes === 'number' ? (0, ui_1.formatBytes)(database.bytes) : '',
|
|
96
|
+
database.schema_name,
|
|
97
|
+
database.bytes === null ? '' : (0, ui_1.formatBytes)(database.bytes),
|
|
159
98
|
]));
|
|
160
|
-
if (truncated)
|
|
161
|
-
(0, ui_1.note)((0, ui_1.dim)(` Shown ${databases.length} of ${total}`));
|
|
162
|
-
for (const database of databases) {
|
|
163
|
-
if (database.move)
|
|
164
|
-
(0, ui_1.note)((0, ui_1.dim)(` ${database.name}: ${moveLine(database.move)}`));
|
|
165
|
-
}
|
|
166
|
-
const down = new Map();
|
|
167
|
-
for (const { server } of databases)
|
|
168
|
-
if (server && server.status !== 'active')
|
|
169
|
-
down.set(server.id, server);
|
|
170
|
-
for (const server of down.values()) {
|
|
171
|
-
(0, ui_1.note)((0, ui_1.dim)(` Server "${server.name}" is not running (${server.status}): its databases do not answer until it is back. ` +
|
|
172
|
-
'Not fixable from the code: tell the person, the server state is in the «Базы данных» (Databases) section of the web interface'));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/** Where a new or a moved database can go, with the room on each. Only the platform: no choice to show. */
|
|
176
|
-
function printServers(servers) {
|
|
177
|
-
if (!servers || servers.length < 2)
|
|
178
|
-
return;
|
|
179
|
-
(0, ui_1.out)('');
|
|
180
|
-
(0, ui_1.out)((0, ui_1.bold)('Servers'));
|
|
181
|
-
(0, ui_1.table)(servers.map(serverRow));
|
|
182
|
-
(0, ui_1.note)((0, ui_1.dim)(' Without --server a new project keeps its database on the platform server. To put it on another: xflow init --server <name>, or xflow db move --to <name> for one that already exists'));
|
|
99
|
+
if (data.truncated)
|
|
100
|
+
(0, ui_1.note)((0, ui_1.dim)(` Shown ${data.databases.length} of ${data.total}`));
|
|
183
101
|
}
|
|
184
102
|
/** Long values would tear the columns apart; the cut is marked, never silent. */
|
|
185
103
|
const CELL_LIMIT = 60;
|
|
@@ -328,64 +246,6 @@ async function dbExec(args) {
|
|
|
328
246
|
(0, ui_1.note)((0, ui_1.dim)(` The first ${data.rows.length} returned rows: the rest are saved but not sent back`));
|
|
329
247
|
}
|
|
330
248
|
}
|
|
331
|
-
const MOVE_POLL_MS = 3_000;
|
|
332
|
-
/** Past this the command lets go: the move carries on, db status follows it. */
|
|
333
|
-
const MOVE_FOLLOW_MS = 10 * 60_000;
|
|
334
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
335
|
-
/**
|
|
336
|
-
* Moving the project database to another server of the organization. The platform
|
|
337
|
-
* runs the move by itself; this command starts it and follows it to the end.
|
|
338
|
-
*/
|
|
339
|
-
async function dbMove(args) {
|
|
340
|
-
const to = (0, args_1.flagString)(args, 'to');
|
|
341
|
-
if (!to) {
|
|
342
|
-
throw new errors_1.CliError('Name the server: --to <name>', 'The servers and their names: xflow db list ("platform" is the common one)');
|
|
343
|
-
}
|
|
344
|
-
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
345
|
-
const path = `/api/v1/projects/${projectId}/db/move`;
|
|
346
|
-
let place;
|
|
347
|
-
// The platform takes the project name as the yes; --yes looks it up.
|
|
348
|
-
const confirm = (0, args_1.flagBool)(args, 'yes')
|
|
349
|
-
? (await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`)).name
|
|
350
|
-
: undefined;
|
|
351
|
-
try {
|
|
352
|
-
place = await (0, api_1.apiJson)(client, path, {
|
|
353
|
-
method: 'POST',
|
|
354
|
-
body: { to, confirm },
|
|
355
|
-
timeoutMs: 60_000,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
catch (e) {
|
|
359
|
-
// Said in the words of this command: the flag, and the name that goes in it.
|
|
360
|
-
if (e instanceof api_1.ApiError && e.code === 'confirm_required') {
|
|
361
|
-
throw new errors_1.CliError(e.message, `To move it: xflow db move --to "${to}" --yes`);
|
|
362
|
-
}
|
|
363
|
-
throw e;
|
|
364
|
-
}
|
|
365
|
-
(0, ui_1.note)((0, ui_1.dim)(' The platform carries the move through by itself: stopping this command does not stop it'));
|
|
366
|
-
const deadline = Date.now() + MOVE_FOLLOW_MS;
|
|
367
|
-
let shown = '';
|
|
368
|
-
while (moving(place.move)) {
|
|
369
|
-
const line = place.move?.note ?? '';
|
|
370
|
-
if (line !== shown)
|
|
371
|
-
(0, ui_1.step)(line);
|
|
372
|
-
shown = line;
|
|
373
|
-
if (Date.now() > deadline) {
|
|
374
|
-
(0, ui_1.note)('Still going: xflow db status follows it');
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
await sleep(MOVE_POLL_MS);
|
|
378
|
-
place = await (0, api_1.apiJson)(client, path);
|
|
379
|
-
}
|
|
380
|
-
const { move } = place;
|
|
381
|
-
if (move?.status === 'failed')
|
|
382
|
-
throw new errors_1.CliError(move.note);
|
|
383
|
-
if (move && (move.functions_done ?? 0) < (move.functions_total ?? 0)) {
|
|
384
|
-
(0, ui_1.warn)(move.note);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
(0, ui_1.ok)(move?.note ?? `The database is on ${serverLabel(place.server)}`);
|
|
388
|
-
}
|
|
389
249
|
async function dbMigrate(args) {
|
|
390
250
|
const { root, config } = (0, config_1.requireProject)();
|
|
391
251
|
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.versionComment = versionComment;
|
|
4
3
|
exports.statedRevision = statedRevision;
|
|
5
4
|
exports.deploy = deploy;
|
|
6
5
|
exports.publish = publish;
|
|
@@ -70,7 +69,7 @@ async function startBuild(client, base, revision, allowRemovals, comment) {
|
|
|
70
69
|
const MAX_COMMENT_LENGTH = 200;
|
|
71
70
|
/** Control and bidi-override characters: they spoof terminals, not comments. */
|
|
72
71
|
const COMMENT_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
|
|
73
|
-
/** The version comment as typed after -m, cleaned; empty when there is none.
|
|
72
|
+
/** The version comment as typed after -m, cleaned; empty when there is none. */
|
|
74
73
|
function versionComment(raw) {
|
|
75
74
|
const comment = (raw ?? '').replace(/\s+/g, ' ').replace(COMMENT_CONTROL_CHARS, '').trim();
|
|
76
75
|
if (comment.length > MAX_COMMENT_LENGTH) {
|
package/dist/commands/env.js
CHANGED
|
@@ -8,14 +8,12 @@ exports.envRemove = envRemove;
|
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = require("node:path");
|
|
10
10
|
const api_1 = require("../api");
|
|
11
|
-
const args_1 = require("../args");
|
|
12
11
|
const config_1 = require("../config");
|
|
13
12
|
const errors_1 = require("../errors");
|
|
14
13
|
const session_1 = require("../session");
|
|
15
14
|
const ui_1 = require("../ui");
|
|
16
15
|
const SCOPE_LABEL = {
|
|
17
|
-
|
|
18
|
-
project: 'this project only',
|
|
16
|
+
project: 'this project',
|
|
19
17
|
connection: 'from a connected service, read-only',
|
|
20
18
|
};
|
|
21
19
|
const FUNCTIONS_DIR = 'functions';
|
|
@@ -101,7 +99,7 @@ async function envList(args) {
|
|
|
101
99
|
(0, ui_1.note)((0, ui_1.dim)(' To store one: xflow env set SMTP_PASSWORD=secret'));
|
|
102
100
|
return;
|
|
103
101
|
}
|
|
104
|
-
(0, ui_1.table)(rows.map((row) => [row.name, SCOPE_LABEL[row.scope]
|
|
102
|
+
(0, ui_1.table)(rows.map((row) => [row.name, SCOPE_LABEL[row.scope]]));
|
|
105
103
|
(0, ui_1.note)((0, ui_1.dim)(' The platform never returns values: they are visible only inside the function'));
|
|
106
104
|
}
|
|
107
105
|
/** Check that every referenced variable is stored. */
|
|
@@ -172,12 +170,9 @@ async function envSet(args) {
|
|
|
172
170
|
const value = pair.slice(pair.indexOf('=') + 1);
|
|
173
171
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}/env`, {
|
|
174
172
|
method: 'POST',
|
|
175
|
-
|
|
176
|
-
// refuses a third. Guessing here would have made --scope proj mean the
|
|
177
|
-
// whole organization, which is the opposite of what such a typo means.
|
|
178
|
-
body: { name, value, scope: (0, args_1.flagString)(args, 'scope') },
|
|
173
|
+
body: { name, value },
|
|
179
174
|
});
|
|
180
|
-
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} stored
|
|
175
|
+
(0, ui_1.ok)(`${(0, ui_1.bold)(result.name)} stored`);
|
|
181
176
|
// Values reach a function on its next deploy.
|
|
182
177
|
const users = referencedByFunctions(root).needed.get(name);
|
|
183
178
|
if (users && users.length > 0) {
|
|
@@ -83,7 +83,7 @@ async function init(args) {
|
|
|
83
83
|
(0, ui_1.step)(orgName ? `Creating the project in "${orgName}"` : 'Creating the project on the platform');
|
|
84
84
|
const project = await (0, api_1.apiJson)(client, '/api/v1/projects', {
|
|
85
85
|
method: 'POST',
|
|
86
|
-
body: { name, app: folder ? { name } : null
|
|
86
|
+
body: { name, app: folder ? { name } : null },
|
|
87
87
|
timeoutMs: 60_000,
|
|
88
88
|
});
|
|
89
89
|
try {
|
|
@@ -105,8 +105,6 @@ async function init(args) {
|
|
|
105
105
|
}
|
|
106
106
|
(0, skills_1.installSkillQuietly)(target);
|
|
107
107
|
(0, ui_1.ok)(`Project "${project.name}" created`);
|
|
108
|
-
if (project.server)
|
|
109
|
-
(0, ui_1.note)((0, ui_1.dim)(` Its database goes to the own server "${project.server.name}"`));
|
|
110
108
|
(0, ui_1.out)('');
|
|
111
109
|
(0, ui_1.out)(` ${(0, ui_1.bold)('Next:')}`);
|
|
112
110
|
(0, ui_1.out)(` cd ${(0, node_path_1.basename)(target)}`);
|
|
@@ -265,7 +263,7 @@ async function get(args) {
|
|
|
265
263
|
if (card.description)
|
|
266
264
|
(0, ui_1.out)(card.description);
|
|
267
265
|
(0, ui_1.out)('');
|
|
268
|
-
(0, ui_1.out)(`Database: ${card.database.schema}
|
|
266
|
+
(0, ui_1.out)(`Database: ${card.database.schema}`);
|
|
269
267
|
(0, ui_1.out)('');
|
|
270
268
|
(0, ui_1.out)('Apps:');
|
|
271
269
|
if (card.apps.length === 0)
|
package/dist/commands/storage.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.contentTypeFor = contentTypeFor;
|
|
4
|
-
exports.collectMedia = collectMedia;
|
|
5
4
|
exports.planBatches = planBatches;
|
|
6
5
|
exports.caseCollisions = caseCollisions;
|
|
7
6
|
exports.storageList = storageList;
|
|
@@ -75,7 +74,7 @@ function contentTypeFor(name) {
|
|
|
75
74
|
* what one is asked to put there so it stays out of the 10 MB archive.
|
|
76
75
|
*
|
|
77
76
|
* Dot entries and symlinks are skipped: pointing push at a working copy should
|
|
78
|
-
* not upload .git, and a link is not ours to follow.
|
|
77
|
+
* not upload .git, and a link is not ours to follow.
|
|
79
78
|
*/
|
|
80
79
|
function collectMedia(root, prefix = '') {
|
|
81
80
|
const found = [];
|
package/dist/flags.js
CHANGED
|
@@ -44,7 +44,7 @@ exports.KNOWN = {
|
|
|
44
44
|
'org list': [],
|
|
45
45
|
'org members': [],
|
|
46
46
|
'org switch': [],
|
|
47
|
-
init: ['name', 'app', 'no-app', '
|
|
47
|
+
init: ['name', 'app', 'no-app', 'org'],
|
|
48
48
|
link: [],
|
|
49
49
|
projects: [],
|
|
50
50
|
'projects list': [],
|
|
@@ -60,7 +60,7 @@ exports.KNOWN = {
|
|
|
60
60
|
logs: ['limit', 'project', 'app'],
|
|
61
61
|
env: ['project'],
|
|
62
62
|
'env list': ['project'],
|
|
63
|
-
'env set': [
|
|
63
|
+
'env set': [],
|
|
64
64
|
'env rm': ['project'],
|
|
65
65
|
'env remove': ['project'],
|
|
66
66
|
'env check': [],
|
|
@@ -81,21 +81,12 @@ exports.KNOWN = {
|
|
|
81
81
|
'db query': ['json', 'project'],
|
|
82
82
|
'db exec': ['allow-destructive', 'json', 'project'],
|
|
83
83
|
'db migrate': ['dry-run', 'allow-destructive'],
|
|
84
|
-
'db move': ['to', 'yes', 'project'],
|
|
85
84
|
storage: ['json', 'project'],
|
|
86
85
|
'storage ls': ['json', 'project'],
|
|
87
86
|
'storage list': ['json', 'project'],
|
|
88
87
|
'storage push': ['to', 'replace', 'json', 'project'],
|
|
89
88
|
'storage rm': ['folder', 'yes', 'project'],
|
|
90
89
|
'storage remove': ['folder', 'yes', 'project'],
|
|
91
|
-
pages: ['json'],
|
|
92
|
-
'pages list': ['json'],
|
|
93
|
-
'pages create': ['slug'],
|
|
94
|
-
'pages deploy': ['message'],
|
|
95
|
-
'pages status': ['json'],
|
|
96
|
-
'pages rollback': [],
|
|
97
|
-
'pages rm': ['yes'],
|
|
98
|
-
'pages remove': ['yes'],
|
|
99
90
|
status: [],
|
|
100
91
|
dev: [],
|
|
101
92
|
pull: ['into', 'force', 'revision'],
|
|
@@ -145,7 +136,6 @@ const NAMESPACES = new Set([
|
|
|
145
136
|
'schedules',
|
|
146
137
|
'db',
|
|
147
138
|
'storage',
|
|
148
|
-
'pages',
|
|
149
139
|
]);
|
|
150
140
|
/** The command these words name, as far as the table knows it. */
|
|
151
141
|
function commandOf(words) {
|
package/dist/help.js
CHANGED
|
@@ -91,24 +91,12 @@ ${(0, ui_1.bold)('Functions')}
|
|
|
91
91
|
|
|
92
92
|
${(0, ui_1.bold)('Database')}
|
|
93
93
|
xflow db status which migrations are applied and which are waiting
|
|
94
|
-
xflow db list databases of the projects
|
|
94
|
+
xflow db list databases of the projects of the organization, with sizes
|
|
95
95
|
xflow db schema [table] tables of the project, or the columns of one
|
|
96
96
|
xflow db query 'select ...' read data, in a read-only transaction
|
|
97
97
|
xflow db exec 'update ...' a one-off data change, outside the migration history
|
|
98
98
|
xflow db migrate [--dry-run] [--allow-destructive]
|
|
99
99
|
apply the migrations from migrations/*.sql
|
|
100
|
-
xflow db move --to <server> --yes
|
|
101
|
-
move the project database to another server
|
|
102
|
-
|
|
103
|
-
${(0, ui_1.bold)('Pages')}
|
|
104
|
-
xflow pages public sites of the organization at <name>.xflowpage.ru
|
|
105
|
-
xflow pages create <title> <folder|.zip|.html> [--slug name]
|
|
106
|
-
a new page with the site on it, live at once
|
|
107
|
-
xflow pages deploy <page> <folder|.zip|.html> [-m "what changed"]
|
|
108
|
-
upload the site: live at once, there is no publish step
|
|
109
|
-
xflow pages status <page> the page and its versions
|
|
110
|
-
xflow pages rollback <page> <N> put an earlier version back on the address
|
|
111
|
-
xflow pages rm <page> --yes delete the page for good: the address empties
|
|
112
100
|
|
|
113
101
|
${(0, ui_1.bold)('Reference')}
|
|
114
102
|
xflow logs [--limit N] browser errors of the published apps
|
|
@@ -173,7 +161,7 @@ no working copy of its own:
|
|
|
173
161
|
xflow db query --project 3f2a… 'select id, title from tasks where done = false'
|
|
174
162
|
xflow functions invoke kanban --project 3f2a… --data-file card.json
|
|
175
163
|
|
|
176
|
-
Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db exec')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('
|
|
164
|
+
Which commands: ${(0, ui_1.bold)('db status')}, ${(0, ui_1.bold)('db query')}, ${(0, ui_1.bold)('db exec')}, ${(0, ui_1.bold)('db schema')}, ${(0, ui_1.bold)('functions list')},
|
|
177
165
|
${(0, ui_1.bold)('functions invoke')}, ${(0, ui_1.bold)('functions logs')}, ${(0, ui_1.bold)('logs')}, ${(0, ui_1.bold)('env')}, ${(0, ui_1.bold)('env rm')},
|
|
178
166
|
${(0, ui_1.bold)('connections')}, ${(0, ui_1.bold)('connections link')}, ${(0, ui_1.bold)('connections unlink')}, ${(0, ui_1.bold)('schedules')},
|
|
179
167
|
${(0, ui_1.bold)('schedules set')}, ${(0, ui_1.bold)('schedules rm')}, ${(0, ui_1.bold)('storage ls')}, ${(0, ui_1.bold)('storage push')},
|
|
@@ -264,53 +252,6 @@ how they differ, ${(0, ui_1.bold)('xflow pull --into ./server-copy')} fetches th
|
|
|
264
252
|
|
|
265
253
|
In a folder that is already linked, the command points xflow.json at the other project and
|
|
266
254
|
keeps the rest of the file. A new project instead of an existing one: ${(0, ui_1.bold)('xflow init')}.`,
|
|
267
|
-
pages: `${(0, ui_1.bold)('xflow pages')}: public sites of the organization
|
|
268
|
-
|
|
269
|
-
A page is a static site at https://<name>.xflowpage.ru: a landing page, a presentation, an
|
|
270
|
-
invoice for people outside the organization. It opens by its address for anyone, without
|
|
271
|
-
signing in, and has no build, no publish step and no database. An app is the opposite: code
|
|
272
|
-
the platform builds, with versions, access levels and a database through functions.
|
|
273
|
-
|
|
274
|
-
xflow pages the pages, their state and address
|
|
275
|
-
xflow pages create <title> <path> a new page with its first version: there are no
|
|
276
|
-
empty pages
|
|
277
|
-
--slug <name> the name in the address; without it the name is made
|
|
278
|
-
from the title, with a random tail
|
|
279
|
-
xflow pages deploy <page> <path> upload a new version
|
|
280
|
-
-m <text> what changed, one short line: it names the version
|
|
281
|
-
xflow pages status <page> the page and its versions
|
|
282
|
-
xflow pages rollback <page> <N> put version N back on the address
|
|
283
|
-
xflow pages rm <page> delete the page
|
|
284
|
-
--yes without it the command names what goes and stops
|
|
285
|
-
|
|
286
|
-
${(0, ui_1.bold)('<path>')} is a folder with index.html at the top, its .zip, or one .html file.
|
|
287
|
-
${(0, ui_1.bold)('<page>')} is the name from the address or the id. ${(0, ui_1.bold)('--json')} on list and status
|
|
288
|
-
prints the answer as JSON. A page belongs to the organization, not to a project, so there is
|
|
289
|
-
no ${(0, ui_1.bold)('--project')}: the key is picked as for every other command (${(0, ui_1.bold)('xflow help org')}).
|
|
290
|
-
|
|
291
|
-
${(0, ui_1.bold)('An upload is live at once.')} There is no draft and no publish step: anyone with the
|
|
292
|
-
address sees the new version within seconds. Uploading the same files again changes nothing.
|
|
293
|
-
The files of the last 5 versions are kept, so a rollback takes seconds and needs no upload.
|
|
294
|
-
|
|
295
|
-
What goes up is the built site: HTML, CSS, JS, images, fonts, PDF, audio and video. No code
|
|
296
|
-
runs on the platform side, and a folder with package.json is refused as the sources of a site:
|
|
297
|
-
build it and upload what the build writes. Dot entries are skipped. At most 5 MB zipped, 25 MB
|
|
298
|
-
unpacked and 1000 files.
|
|
299
|
-
|
|
300
|
-
The page is public, so a file holding a secret key is refused whole: every visitor would read
|
|
301
|
-
it. A page has no backend: a form or a lookup belongs in an app with access "anyone". The one
|
|
302
|
-
key a page may hold is a function key, and it opens that function to every visitor: an HTML
|
|
303
|
-
form can post to its webhook address with ${(0, ui_1.bold)('?key=')}, while fetch cannot read the answer
|
|
304
|
-
(no CORS). Search engines are asked not to index pages, so do not promise SEO.
|
|
305
|
-
|
|
306
|
-
${(0, ui_1.bold)('Deleting is final.')} The address goes empty and every version of the page is gone: there
|
|
307
|
-
is no undo and no copy on the platform side. The name stays reserved for the organization, so
|
|
308
|
-
the same ${(0, ui_1.bold)('--slug')} is free for the next page of yours and taken for everyone else. A
|
|
309
|
-
throwaway page put up to try something is worth deleting the same way it was made.
|
|
310
|
-
|
|
311
|
-
A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
|
|
312
|
-
scoped to projects is refused. A page is deleted by whoever made it, or by the owner of the
|
|
313
|
-
organization. Switching a page off for a while is for a person, in the platform interface.`,
|
|
314
255
|
update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
|
|
315
256
|
|
|
316
257
|
Installs the published version and, if anything changed, rewrites the platform
|
|
@@ -336,8 +277,8 @@ is a hard gate on a changed contract, and until you run this command nothing els
|
|
|
336
277
|
will work.`,
|
|
337
278
|
db: `${(0, ui_1.bold)('xflow db')}: schema, data, migrations
|
|
338
279
|
|
|
339
|
-
xflow db status what is applied, what is waiting
|
|
340
|
-
xflow db list databases of the projects
|
|
280
|
+
xflow db status what is applied, what is waiting
|
|
281
|
+
xflow db list databases of the projects of the organization, with sizes
|
|
341
282
|
xflow db schema [table] tables of the project, or the columns of one
|
|
342
283
|
xflow db query 'select ...' read data (rows: the query's own limit, 50 without
|
|
343
284
|
one, 300 at most; --json prints the values whole)
|
|
@@ -345,26 +286,10 @@ will work.`,
|
|
|
345
286
|
xflow db exec 'update ...' a one-off data change: one INSERT, UPDATE, DELETE or MERGE
|
|
346
287
|
(or <file>.sql; --allow-destructive, --json)
|
|
347
288
|
xflow db migrate store the project root, then apply migrations/*.sql
|
|
348
|
-
xflow db move --to <server> --yes
|
|
349
|
-
move the project database to another server
|
|
350
289
|
|
|
351
290
|
Every project has exactly one database of its own, shared by all of its apps and functions,
|
|
352
291
|
and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
|
|
353
|
-
organization: one database per project,
|
|
354
|
-
server, ${(0, ui_1.bold)('own "<name>"')} an own server of the organization. While an own server is not
|
|
355
|
-
running, its databases do not answer, and that is not fixable from the code. An organization
|
|
356
|
-
with own servers also gets them listed, with the room used on each: that is the choice for
|
|
357
|
-
${(0, ui_1.bold)('xflow init --server')} and ${(0, ui_1.bold)('db move')}.
|
|
358
|
-
|
|
359
|
-
${(0, ui_1.bold)('move')} takes the database of this project to another server of the organization:
|
|
360
|
-
${(0, ui_1.bold)('platform')} or an own server by name (or id, when two share a name). The platform copies
|
|
361
|
-
the tables, switches the functions of the project and removes the old copy; nothing needs a
|
|
362
|
-
deploy. While it copies, the apps read data but cannot save anything new, usually for a
|
|
363
|
-
couple of minutes, which is why it asks for ${(0, ui_1.bold)('--yes')}. It also needs the
|
|
364
|
-
right to move databases on the key: off by default, only its owner turns it on, in the
|
|
365
|
-
platform settings where the key permissions live. The command follows the move to the end;
|
|
366
|
-
stopping it does not stop the move, and ${(0, ui_1.bold)('xflow db status')} shows where it stands. A move
|
|
367
|
-
that fails is rolled back. Buying, paying for and deleting servers stay with the person.
|
|
292
|
+
organization: one database per project, largest first, to see which project takes the room.
|
|
368
293
|
|
|
369
294
|
${(0, ui_1.bold)('schema')} answers a different question than the ${(0, ui_1.bold)('migrations/')} directory: the files say
|
|
370
295
|
what you did, the schema says what is actually in there. ${(0, ui_1.bold)('query')} runs inside a READ ONLY transaction, so
|
|
@@ -481,11 +406,7 @@ the function. So keep your own copy wherever you got it from.
|
|
|
481
406
|
xflow env set SMTP_PASSWORD=… store
|
|
482
407
|
xflow env rm SMTP_PASSWORD delete
|
|
483
408
|
|
|
484
|
-
|
|
485
|
-
--scope organization visible to every project of the organization (the default)
|
|
486
|
-
|
|
487
|
-
Those two words are the whole list: a third is refused rather than read as the default,
|
|
488
|
-
because a value meant for one project would have gone out to all of them.
|
|
409
|
+
A variable belongs to this project: functions of other projects never see it.
|
|
489
410
|
|
|
490
411
|
${(0, ui_1.bold)('check')} reads the sources in ${(0, ui_1.bold)('functions/')} and looks for ${(0, ui_1.bold)('process.env.NAME')}
|
|
491
412
|
references. The same rule applies on deploy: a function receives only the variables it
|
|
@@ -627,8 +548,7 @@ Who may call a function is its callers, in the root xflow.json:
|
|
|
627
548
|
|
|
628
549
|
{ "projectId": "…", "functions": { "book-slot": { "callers": ["zapis"] } } }
|
|
629
550
|
|
|
630
|
-
A caller is an app folder of this project (apps/zapis is "zapis")
|
|
631
|
-
nothing. A function nobody lists has no callers: a workflow, woken by a schedule, by a service
|
|
551
|
+
A caller is an app folder of this project (apps/zapis is "zapis"). A function nobody lists has no callers: a workflow, woken by a schedule, by a service
|
|
632
552
|
holding a key a person issued, or by ${(0, ui_1.bold)('invoke')}. ${(0, ui_1.bold)('open')} in the list means a caller is an app
|
|
633
553
|
anyone with the link opens, so anyone on the internet can call it. The address in the note
|
|
634
554
|
under the table is for webhooks and other functions, with a key; apps call ${(0, ui_1.bold)('/_fn/<name>')} on
|
|
@@ -776,8 +696,6 @@ change them, restyle them or delete what you do not use.
|
|
|
776
696
|
--name <name> project name (the folder name by default)
|
|
777
697
|
--app <folder> the folder of the first app (made from the name by default)
|
|
778
698
|
--no-app a project without apps: its functions are workflows
|
|
779
|
-
--server <name> the database of the project, right away on that server:
|
|
780
|
-
platform, or an own server of the organization (xflow db list)
|
|
781
699
|
--org <name> the organization that gets the project; required when you are
|
|
782
700
|
signed in to several (xflow org)
|
|
783
701
|
|
package/dist/limits.js
CHANGED
|
@@ -4,12 +4,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.limitLine = limitLine;
|
|
5
5
|
exports.quotaRows = quotaRows;
|
|
6
6
|
const DENIAL_LABELS = {
|
|
7
|
-
limit_projects: 'projects, the platform cap',
|
|
7
|
+
limit_projects: 'new projects today, the platform cap (resets at 00:00 UTC)',
|
|
8
8
|
limit_apps: 'apps',
|
|
9
9
|
plan_app_access: 'access levels of apps',
|
|
10
10
|
functions_stopped: 'function minutes, the functions of the organization are stopped',
|
|
11
11
|
limit_connections: 'active connector connections',
|
|
12
|
-
limit_pages: 'public pages',
|
|
13
12
|
limit_functions: 'cloud functions',
|
|
14
13
|
limit_builds: 'builds this month',
|
|
15
14
|
limit_builds_concurrent: 'builds running at once',
|
|
@@ -32,12 +31,12 @@ function limitLine(detail) {
|
|
|
32
31
|
: detail.code === 'limit_schedule_interval'
|
|
33
32
|
? `, every ${detail.used} min against a ${detail.limit} min minimum`
|
|
34
33
|
: `, ${detail.used} of ${detail.limit} used`;
|
|
35
|
-
|
|
34
|
+
const tail = detail.code === 'limit_projects' ? 'Repeating it before then will not help.' : 'Repeating the same command will not help.';
|
|
35
|
+
return `Plan limit: ${label}${numbers}. ${tail}`;
|
|
36
36
|
}
|
|
37
37
|
const QUOTA_ORDER = [
|
|
38
38
|
'apps',
|
|
39
39
|
'connections',
|
|
40
|
-
'pages',
|
|
41
40
|
'functions',
|
|
42
41
|
'schedules',
|
|
43
42
|
'people',
|
|
@@ -49,7 +48,6 @@ const QUOTA_ORDER = [
|
|
|
49
48
|
const QUOTA_LABELS = {
|
|
50
49
|
apps: 'Apps',
|
|
51
50
|
connections: 'Active connections',
|
|
52
|
-
pages: 'Public pages',
|
|
53
51
|
functions: 'Cloud functions',
|
|
54
52
|
schedules: 'Schedules',
|
|
55
53
|
people: 'People',
|
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.
|
|
5
|
+
exports.CLI_VERSION = '1.6.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