@getxflow/cli 1.8.1 → 1.10.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/args.js +1 -0
- package/dist/bin.js +9 -1
- package/dist/commands/db.js +108 -6
- package/dist/flags.js +6 -3
- package/dist/help.js +32 -3
- package/dist/limits.js +2 -1
- package/dist/moment.js +32 -0
- package/dist/version.js +1 -1
- package/package.json +24 -24
- package/skills/xflow/SKILL.md +704 -689
package/dist/args.js
CHANGED
package/dist/bin.js
CHANGED
|
@@ -183,11 +183,19 @@ async function run(args) {
|
|
|
183
183
|
await (0, logs_1.dbLogs)(args);
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
|
+
if (second === 'branch' || second === 'branches') {
|
|
187
|
+
await (0, db_1.dbBranch)({ ...args, words: args.words.slice(2) });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (second === 'restore') {
|
|
191
|
+
await (0, db_1.dbRestore)(args);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
186
194
|
if (second === undefined || second === 'status') {
|
|
187
195
|
await (0, db_1.dbStatus)({ ...args, words: args.words.slice(2) });
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
|
-
throw new errors_1.CliError(`Unknown command: db ${second}`, 'Available: status, list, schema, query, exec and
|
|
198
|
+
throw new errors_1.CliError(`Unknown command: db ${second}`, 'Available: status, list, schema, query, exec, migrate, branch and restore');
|
|
191
199
|
case 'storage':
|
|
192
200
|
if (second === 'push') {
|
|
193
201
|
await (0, storage_1.storagePush)(rest);
|
package/dist/commands/db.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.dbQuery = dbQuery;
|
|
|
7
7
|
exports.dbExec = dbExec;
|
|
8
8
|
exports.dbMigrate = dbMigrate;
|
|
9
9
|
exports.migrateBeforeDeploy = migrateBeforeDeploy;
|
|
10
|
+
exports.dbBranch = dbBranch;
|
|
11
|
+
exports.dbRestore = dbRestore;
|
|
10
12
|
const node_fs_1 = require("node:fs");
|
|
11
13
|
const node_path_1 = require("node:path");
|
|
12
14
|
const api_1 = require("../api");
|
|
@@ -14,6 +16,7 @@ const args_1 = require("../args");
|
|
|
14
16
|
const config_1 = require("../config");
|
|
15
17
|
const errors_1 = require("../errors");
|
|
16
18
|
const session_1 = require("../session");
|
|
19
|
+
const moment_1 = require("../moment");
|
|
17
20
|
const text_file_1 = require("../text-file");
|
|
18
21
|
const ui_1 = require("../ui");
|
|
19
22
|
const sources_1 = require("./sources");
|
|
@@ -171,7 +174,7 @@ async function dbQuery(args) {
|
|
|
171
174
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
172
175
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/query`, {
|
|
173
176
|
method: 'POST',
|
|
174
|
-
body: { sql },
|
|
177
|
+
body: { sql, branch: (0, args_1.flagString)(args, 'branch') },
|
|
175
178
|
timeoutMs: 60_000,
|
|
176
179
|
});
|
|
177
180
|
// The whole values, machine-readable, and the only thing printed: --json silences
|
|
@@ -217,7 +220,7 @@ async function dbExec(args) {
|
|
|
217
220
|
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
218
221
|
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/exec`, {
|
|
219
222
|
method: 'POST',
|
|
220
|
-
body: { sql, allow_destructive: (0, args_1.flagBool)(args, 'allow-destructive') },
|
|
223
|
+
body: { sql, allow_destructive: (0, args_1.flagBool)(args, 'allow-destructive'), branch: (0, args_1.flagString)(args, 'branch') },
|
|
221
224
|
timeoutMs: 120_000,
|
|
222
225
|
});
|
|
223
226
|
if ((0, args_1.flagBool)(args, 'json')) {
|
|
@@ -251,11 +254,13 @@ async function dbMigrate(args) {
|
|
|
251
254
|
const client = await (0, session_1.connectProject)(config.projectId, root, config);
|
|
252
255
|
const migrations = requireMigrations(root);
|
|
253
256
|
const dryRun = (0, args_1.flagBool)(args, 'dry-run');
|
|
257
|
+
const branch = (0, args_1.flagString)(args, 'branch');
|
|
254
258
|
// Migrations live in the revisions of the project root: without this a project
|
|
255
259
|
// without functions would lose them from its history, and a clone would miss them.
|
|
256
|
-
|
|
260
|
+
// A try on a branch is not history yet.
|
|
261
|
+
if (!dryRun && !branch)
|
|
257
262
|
await (0, sources_1.pushSources)({ kind: 'project', root, projectId: config.projectId }, client, { force: false });
|
|
258
|
-
await migrate(client, config.projectId, migrations, dryRun, (0, args_1.flagBool)(args, 'allow-destructive'));
|
|
263
|
+
await migrate(client, config.projectId, migrations, dryRun, (0, args_1.flagBool)(args, 'allow-destructive'), branch);
|
|
259
264
|
}
|
|
260
265
|
/** The functions deploy runs this first: new code must not meet an old schema. */
|
|
261
266
|
async function migrateBeforeDeploy(client, root, projectId) {
|
|
@@ -271,14 +276,16 @@ async function migrateBeforeDeploy(client, root, projectId) {
|
|
|
271
276
|
throw e;
|
|
272
277
|
}
|
|
273
278
|
}
|
|
274
|
-
async function migrate(client, projectId, migrations, dryRun, allowDestructive) {
|
|
275
|
-
|
|
279
|
+
async function migrate(client, projectId, migrations, dryRun, allowDestructive, branch) {
|
|
280
|
+
const where = branch ? ` to branch ${branch}` : '';
|
|
281
|
+
(0, ui_1.step)(dryRun ? `Checking the migrations${where} (${migrations.length} in the directory)` : `Applying the migrations${where} (${migrations.length} in the directory)`);
|
|
276
282
|
const result = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/migrate`, {
|
|
277
283
|
method: 'POST',
|
|
278
284
|
body: {
|
|
279
285
|
migrations,
|
|
280
286
|
dry_run: dryRun,
|
|
281
287
|
allow_destructive: allowDestructive,
|
|
288
|
+
branch,
|
|
282
289
|
},
|
|
283
290
|
timeoutMs: 300_000,
|
|
284
291
|
});
|
|
@@ -314,3 +321,98 @@ async function migrate(client, projectId, migrations, dryRun, allowDestructive)
|
|
|
314
321
|
: 'The database is unchanged: the migration rolled back as a whole');
|
|
315
322
|
}
|
|
316
323
|
}
|
|
324
|
+
/** Time a branch has left, `5h` or `3d`. */
|
|
325
|
+
function left(iso) {
|
|
326
|
+
const h = Math.max(0, Math.round((Date.parse(iso) - Date.now()) / 3_600_000));
|
|
327
|
+
return h < 48 ? `${h}h left` : `${Math.round(h / 24)}d left`;
|
|
328
|
+
}
|
|
329
|
+
/** Branches of the project database: list, create, delete. */
|
|
330
|
+
async function dbBranch(args) {
|
|
331
|
+
const [sub, name] = args.words;
|
|
332
|
+
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
333
|
+
const path = `/api/v1/projects/${projectId}/db/branches`;
|
|
334
|
+
if (sub === undefined || sub === 'list') {
|
|
335
|
+
const data = await (0, api_1.apiJson)(client, path);
|
|
336
|
+
if ((0, args_1.flagBool)(args, 'json')) {
|
|
337
|
+
(0, ui_1.out)(JSON.stringify(data, null, 2));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (data.branches.length === 0) {
|
|
341
|
+
(0, ui_1.note)('No branches');
|
|
342
|
+
(0, ui_1.note)((0, ui_1.dim)(' A copy to try things on: xflow db branch create <name> [--at 2h]'));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
(0, ui_1.table)(data.branches.map((b) => [b.name, b.at ? `as of ${new Date(b.at).toLocaleString()}` : 'as made', (0, ui_1.formatAge)(b.created_at), left(b.expires_at)]));
|
|
346
|
+
(0, ui_1.note)((0, ui_1.dim)(` ${data.branches.length} of ${data.plan.branches}; a branch lives up to ${data.plan.ttl_hours / 24}d on this plan (xflow db branch extend <name>)`));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (!name)
|
|
350
|
+
throw new errors_1.CliError(`xflow db branch ${sub} needs a name`, `For example: xflow db branch ${sub} fix-orders`);
|
|
351
|
+
if (sub === 'create') {
|
|
352
|
+
const at = (0, args_1.flagString)(args, 'at');
|
|
353
|
+
const ttl = (0, args_1.flagString)(args, 'ttl');
|
|
354
|
+
const data = await (0, api_1.apiJson)(client, path, {
|
|
355
|
+
method: 'POST',
|
|
356
|
+
body: { name, at: at ? (0, moment_1.parseMoment)(at) : undefined, ttl_hours: ttl ? (0, moment_1.parseTtl)(ttl) : undefined },
|
|
357
|
+
timeoutMs: 180_000,
|
|
358
|
+
});
|
|
359
|
+
if ((0, args_1.flagBool)(args, 'json')) {
|
|
360
|
+
(0, ui_1.out)(JSON.stringify(data.branch, null, 2));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
(0, ui_1.ok)(`Branch ${name}: the project database ${data.branch.at ? `as of ${new Date(data.branch.at).toLocaleString()}` : 'as it is now'}`);
|
|
364
|
+
(0, ui_1.note)((0, ui_1.dim)(` Work in it: xflow db query --branch ${name} '...', db exec and db migrate take --branch too`));
|
|
365
|
+
(0, ui_1.note)((0, ui_1.dim)(` Rows back into the project database: xflow db restore --from ${name} --table <table>`));
|
|
366
|
+
(0, ui_1.note)((0, ui_1.dim)(` Deleted on its own in ${left(data.branch.expires_at)}; more time: xflow db branch extend ${name}`));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (sub === 'extend') {
|
|
370
|
+
const ttl = (0, args_1.flagString)(args, 'ttl');
|
|
371
|
+
const data = await (0, api_1.apiJson)(client, `${path}/${encodeURIComponent(name)}`, {
|
|
372
|
+
method: 'PATCH',
|
|
373
|
+
body: { ttl_hours: ttl ? (0, moment_1.parseTtl)(ttl) : undefined },
|
|
374
|
+
});
|
|
375
|
+
if ((0, args_1.flagBool)(args, 'json')) {
|
|
376
|
+
(0, ui_1.out)(JSON.stringify(data.branch, null, 2));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
(0, ui_1.ok)(`Branch ${name}: ${left(data.branch.expires_at)}, until ${new Date(data.branch.expires_at).toLocaleString()}`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (sub === 'delete' || sub === 'rm') {
|
|
383
|
+
await (0, api_1.apiJson)(client, `${path}/${encodeURIComponent(name)}`, { method: 'DELETE' });
|
|
384
|
+
(0, ui_1.ok)(`Branch ${name} deleted; the project database is unchanged`);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
throw new errors_1.CliError(`Unknown command: db branch ${sub}`, 'Available: list, create, extend and delete');
|
|
388
|
+
}
|
|
389
|
+
/** Rows of a table from a branch back into the project database, by primary key. */
|
|
390
|
+
async function dbRestore(args) {
|
|
391
|
+
const from = (0, args_1.flagString)(args, 'from');
|
|
392
|
+
const tableName = (0, args_1.flagString)(args, 'table');
|
|
393
|
+
if (!from || !tableName) {
|
|
394
|
+
throw new errors_1.CliError('Name the branch and the table', "For example: xflow db restore --from before-import --table orders --where 'id in (7, 9)'");
|
|
395
|
+
}
|
|
396
|
+
const overwrite = (0, args_1.flagBool)(args, 'overwrite');
|
|
397
|
+
const { projectId, client } = await (0, session_1.projectTarget)(args);
|
|
398
|
+
const data = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}/db/restore`, {
|
|
399
|
+
method: 'POST',
|
|
400
|
+
body: { branch: from, table: tableName, where: (0, args_1.flagString)(args, 'where'), overwrite },
|
|
401
|
+
timeoutMs: 120_000,
|
|
402
|
+
});
|
|
403
|
+
if ((0, args_1.flagBool)(args, 'json')) {
|
|
404
|
+
(0, ui_1.out)(JSON.stringify(data, null, 2));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (data.read === 0) {
|
|
408
|
+
(0, ui_1.note)(`No rows of ${tableName} in branch ${from} match`);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
(0, ui_1.ok)(`${data.restored} of ${data.read} rows of ${tableName} from branch ${from} ${overwrite ? 'written' : 'put back'}`);
|
|
412
|
+
for (const dump of data.dumps ?? []) {
|
|
413
|
+
(0, ui_1.note)((0, ui_1.dim)(` Copy of ${dump.table} before the change: ${dump.rows} rows, kept for ${data.dump_retention_days} days`));
|
|
414
|
+
}
|
|
415
|
+
if (!overwrite && data.restored < data.read) {
|
|
416
|
+
(0, ui_1.note)((0, ui_1.dim)(` The other ${data.read - data.restored} are in the database already and kept as they are: --overwrite replaces them`));
|
|
417
|
+
}
|
|
418
|
+
}
|
package/dist/flags.js
CHANGED
|
@@ -78,10 +78,13 @@ exports.KNOWN = {
|
|
|
78
78
|
'db status': ['project'],
|
|
79
79
|
'db list': [],
|
|
80
80
|
'db schema': ['project'],
|
|
81
|
-
'db query': ['json', 'project'],
|
|
82
|
-
'db exec': ['allow-destructive', 'json', 'project'],
|
|
81
|
+
'db query': ['json', 'project', 'branch'],
|
|
82
|
+
'db exec': ['allow-destructive', 'json', 'project', 'branch'],
|
|
83
83
|
'db logs': ['limit', 'project', 'cursor'],
|
|
84
|
-
'db migrate': ['dry-run', 'allow-destructive'],
|
|
84
|
+
'db migrate': ['dry-run', 'allow-destructive', 'branch'],
|
|
85
|
+
'db branch': ['at', 'json', 'project', 'ttl'],
|
|
86
|
+
'db branches': ['at', 'json', 'project', 'ttl'],
|
|
87
|
+
'db restore': ['from', 'table', 'where', 'overwrite', 'json', 'project'],
|
|
85
88
|
storage: ['json', 'project'],
|
|
86
89
|
'storage ls': ['json', 'project'],
|
|
87
90
|
'storage list': ['json', 'project'],
|
package/dist/help.js
CHANGED
|
@@ -98,6 +98,10 @@ ${(0, ui_1.bold)('Database')}
|
|
|
98
98
|
xflow db logs errors the database raised, newest first
|
|
99
99
|
xflow db migrate [--dry-run] [--allow-destructive]
|
|
100
100
|
apply the migrations from migrations/*.sql
|
|
101
|
+
xflow db branch create <name> [--at 2h]
|
|
102
|
+
a copy of the database to try things on, now or as it was
|
|
103
|
+
xflow db restore --from <branch> --table <table>
|
|
104
|
+
put rows back from a branch, newer data kept
|
|
101
105
|
|
|
102
106
|
${(0, ui_1.bold)('Reference')}
|
|
103
107
|
xflow logs [--limit N] browser errors of the published apps
|
|
@@ -290,6 +294,14 @@ will work.`,
|
|
|
290
294
|
xflow db logs errors the database raised: failed and timed-out queries
|
|
291
295
|
with their SQL (cut to 2 KB), dropped connections; 7 days,
|
|
292
296
|
1 on the Free plan (--limit, --cursor for older)
|
|
297
|
+
xflow db branch [list] branches of the database (--json)
|
|
298
|
+
xflow db branch create <name> a branch as the database is now; --at 2h, --at 14:05 or
|
|
299
|
+
--at 2026-10-30T14:05+03:00 for a moment of its history;
|
|
300
|
+
--ttl 12h or --ttl 3d to delete it sooner than the plan does
|
|
301
|
+
xflow db branch extend <name> more time from now: the plan's longest, or --ttl
|
|
302
|
+
xflow db branch delete <name> the branch goes, the database is unchanged
|
|
303
|
+
xflow db restore --from <branch> --table <table> [--where '...'] [--overwrite]
|
|
304
|
+
rows of a table from a branch back into the database
|
|
293
305
|
|
|
294
306
|
Every project has exactly one database of its own, shared by all of its apps and functions,
|
|
295
307
|
and every command here works from any folder of the project. ${(0, ui_1.bold)('list')} is about the
|
|
@@ -315,6 +327,23 @@ is saved; deleting also needs the right to destroy data on the key, as in migrat
|
|
|
315
327
|
below). The rows it holds are locked while it runs, so a statement that waits on a running
|
|
316
328
|
app is cancelled after a few seconds and changes nothing. There is no undo.
|
|
317
329
|
|
|
330
|
+
${(0, ui_1.bold)('branch')}: a database of its own, as the project database is now or was at a moment of its
|
|
331
|
+
history, made in seconds without copying: try a migration or a fix there (${(0, ui_1.bold)('--branch <name>')} on
|
|
332
|
+
${(0, ui_1.bold)('query')}, ${(0, ui_1.bold)('exec')} and ${(0, ui_1.bold)('migrate')}), or read the data as it was. Nothing done there
|
|
333
|
+
reaches the project database. A branch sleeps after a quiet minute, so its first query takes a
|
|
334
|
+
second or two; it has no functions and no history of its own, and a branch of a branch is not
|
|
335
|
+
made. Branches per project: 2 on Free, 5 on Start, 15 on Business; delete one you no longer need.
|
|
336
|
+
A branch is deleted on its own when its lifetime ends: at most 1 day on Free, 7 days on Start,
|
|
337
|
+
30 days on Business, and ${(0, ui_1.bold)('extend')} gives it more from now within that. The history reaches
|
|
338
|
+
back 6 hours on Free, 3 days on Start, 7 days on Business.
|
|
339
|
+
|
|
340
|
+
${(0, ui_1.bold)('restore')} puts rows back after a mistake without rolling the whole database back: make a
|
|
341
|
+
branch as of a moment before it, check the rows there with ${(0, ui_1.bold)('query --branch')}, then restore
|
|
342
|
+
them. Rows are told apart by the primary key: the missing ones go back in, the ones still there
|
|
343
|
+
stay as they are, and ${(0, ui_1.bold)('--overwrite')} replaces them with the branch version (it needs the
|
|
344
|
+
right to destroy data on the key). Everything the database got after that moment is kept. Up
|
|
345
|
+
to 10 000 rows at a time: narrow with ${(0, ui_1.bold)('--where')}.
|
|
346
|
+
|
|
318
347
|
${(0, ui_1.bold)('migrate')}: the files live in the project root (${(0, ui_1.bold)('migrations/0001_init.sql')},
|
|
319
348
|
${(0, ui_1.bold)('migrations/0002_orders.sql')} and so on). The order comes from the file name, which
|
|
320
349
|
is why the leading number is required. The command first stores the project root as a
|
|
@@ -325,13 +354,13 @@ itself, and anything already applied is not run again.
|
|
|
325
354
|
--dry-run report what would be applied without touching the database
|
|
326
355
|
--allow-destructive allow operations that destroy data
|
|
327
356
|
|
|
328
|
-
About destructive ones.
|
|
329
|
-
so ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and deleting rows (judged by what
|
|
357
|
+
About destructive ones. ${(0, ui_1.bold)('DROP TABLE')}, ${(0, ui_1.bold)('DROP COLUMN')}, ${(0, ui_1.bold)('TRUNCATE')} and deleting rows (judged by what
|
|
330
358
|
the database did, not by the text) need two things at once: this flag, and the right to destroy data on the
|
|
331
359
|
access key. That right is off by default, and only its owner turns it on, in the
|
|
332
360
|
platform settings, where the key permissions live: a flag is something an agent adds by itself, a
|
|
333
361
|
right is not. With both in place, the contents of the affected tables are dumped and
|
|
334
|
-
kept for 7 days
|
|
362
|
+
kept for 7 days. Past that, a branch as of a moment before the migration holds the data
|
|
363
|
+
while the history reaches that far (see ${(0, ui_1.bold)('branch')} above).
|
|
335
364
|
${(0, ui_1.bold)('DROP DATABASE')} is never allowed.
|
|
336
365
|
|
|
337
366
|
Editing an already applied file achieves nothing: ${(0, ui_1.bold)('migrate')} refuses a name that is
|
package/dist/limits.js
CHANGED
|
@@ -15,6 +15,7 @@ const DENIAL_LABELS = {
|
|
|
15
15
|
limit_seats: 'people in the organization',
|
|
16
16
|
limit_schedule_interval: 'schedule frequency',
|
|
17
17
|
limit_schedules: 'schedules in the organization',
|
|
18
|
+
limit_branches: 'database branches in the project',
|
|
18
19
|
db_write_locked: 'database volume, writes are off',
|
|
19
20
|
plan_no_schedules: 'schedules',
|
|
20
21
|
subscription_blocked: 'the subscription is unpaid',
|
|
@@ -53,7 +54,7 @@ const QUOTA_LABELS = {
|
|
|
53
54
|
people: 'People',
|
|
54
55
|
space_mb: 'Space (databases and files together)',
|
|
55
56
|
function_minutes: 'Function minutes',
|
|
56
|
-
db_hours: 'Database hours',
|
|
57
|
+
db_hours: 'Database GB-hours',
|
|
57
58
|
builds: 'Builds this month',
|
|
58
59
|
};
|
|
59
60
|
/** Decimal units, 1000 MB per GB: matches how volumes are sold. */
|
package/dist/moment.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMoment = parseMoment;
|
|
4
|
+
exports.parseTtl = parseTtl;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const UNIT_MS = { m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
7
|
+
/** `30m`/`2h`/`1d` ago, `14:05` local (yesterday if still ahead), or an ISO date. */
|
|
8
|
+
function parseMoment(text, now = new Date()) {
|
|
9
|
+
const ago = /^(\d{1,4})([mhd])$/.exec(text);
|
|
10
|
+
if (ago)
|
|
11
|
+
return new Date(now.getTime() - Number(ago[1]) * UNIT_MS[ago[2]]).toISOString();
|
|
12
|
+
const clock = /^(\d{1,2}):(\d{2})$/.exec(text);
|
|
13
|
+
if (clock && Number(clock[1]) < 24 && Number(clock[2]) < 60) {
|
|
14
|
+
const at = new Date(now);
|
|
15
|
+
at.setHours(Number(clock[1]), Number(clock[2]), 0, 0);
|
|
16
|
+
if (at > now)
|
|
17
|
+
at.setDate(at.getDate() - 1);
|
|
18
|
+
return at.toISOString();
|
|
19
|
+
}
|
|
20
|
+
const at = new Date(text);
|
|
21
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(text) || Number.isNaN(at.getTime())) {
|
|
22
|
+
throw new errors_1.CliError(`--at ${text} is not a moment`, 'For example: --at 2h (two hours ago), --at 14:05 (today), --at 2026-10-30T14:05+03:00');
|
|
23
|
+
}
|
|
24
|
+
return at.toISOString();
|
|
25
|
+
}
|
|
26
|
+
/** A branch lifetime in whole hours: `12h` or `3d`. */
|
|
27
|
+
function parseTtl(text) {
|
|
28
|
+
const m = /^(\d{1,4})([hd])$/.exec(text);
|
|
29
|
+
if (!m || Number(m[1]) < 1)
|
|
30
|
+
throw new errors_1.CliError(`--ttl ${text} is not a lifetime`, 'For example: --ttl 12h or --ttl 3d');
|
|
31
|
+
return Number(m[1]) * (m[2] === 'd' ? 24 : 1);
|
|
32
|
+
}
|
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.10.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.
|
|
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": ">=22"
|
|
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.10.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": ">=22"
|
|
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
|
+
}
|