Skip to content

Commit 209c3fe

Browse files
committed
feat(cli): Async deploy with status updates
1 parent 32f9de3 commit 209c3fe

File tree

5 files changed

+121
-96
lines changed

5 files changed

+121
-96
lines changed

src/SDK/Language/CLI.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,8 @@ public function getFiles(): array
134134
],
135135
[
136136
'scope' => 'default',
137-
'destination' => 'lib/formatters.js',
138-
'template' => 'cli/lib/formatters.js.twig',
139-
],
140-
[
141-
'scope' => 'default',
142-
'destination' => 'lib/updateTable.js',
143-
'template' => 'cli/lib/updateTable.js.twig',
137+
'destination' => 'lib/updater.js',
138+
'template' => 'cli/lib/updater.js.twig',
144139
],
145140
[
146141
'scope' => 'default',

templates/cli/lib/commands/deploy.js.twig

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const JSONbig = require("json-bigint")({ storeAsString: false });
33
const { Command } = require("commander");
44
const { localConfig } = require("../config");
55
const { loaderInterval, clearLoaderInterval, } = require('../utils');
6-
const { formatterFunction } = require('../formatters');
6+
const { Updater, SPINNER_ARC, SPINNER_DOTS } = require('../updater');
77
const { paginate } = require('../paginate');
88
const { questionsDeployBuckets, questionsDeployTeams, questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollections, questionsConfirmDeployCollections } = require("../questions");
99
const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
@@ -271,19 +271,18 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
271271
return func;
272272
});
273273

274+
log('Pushing functions\n');
274275

275-
276-
log('Deploying functions\n');
277-
276+
Updater.start(false,)
278277
let successfullyDeployed = 0;
279278

280279
await Promise.all(functions.map(async (func) => {
281280
const ignore = func.ignore ? 'appwrite.json' : '.gitignore';
282281
let functionExists = false;
283282

284-
const bar = updatesBar.create(100, 0, { status: 'Deploying', function: func.name, id: func['$id'], ignore })
285-
bar.update({ status: 'Getting' });
286-
const updatingInterval = loaderInterval(bar, 8, 1, 80);
283+
const updaterRow = new Updater({ status: '', resource: func.name, id: func['$id'], end: `Ignoring using: ${ignore}` });
284+
285+
updaterRow.update({ status: 'Getting' }).startSpinner(SPINNER_DOTS);
287286

288287
try {
289288
response = await functionsGet({
@@ -292,11 +291,11 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
292291
});
293292
functionExists = true;
294293
if (response.runtime !== func.runtime) {
295-
bar.update({ status: 'Error', errorMessage: `Runtime missmatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json` })
296-
clearLoaderInterval(bar, updatingInterval);
294+
updaterRow.fail({ errorMessage: `Runtime mismatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json` })
297295
return;
298296
}
299-
bar.update({ status: 'Updating' });
297+
298+
updaterRow.update({ status: 'Updating' }).replaceSpinner(SPINNER_ARC);
300299

301300
response = await functionsUpdate({
302301
functionId: func['$id'],
@@ -317,17 +316,13 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
317316
if (e.code == 404) {
318317
functionExists = false;
319318
} else {
320-
clearLoaderInterval(bar, updatingInterval)
321-
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' });
319+
updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
322320
return;
323321
}
324322
}
325323

326-
clearLoaderInterval(bar, updatingInterval)
327-
328324
if (!functionExists) {
329-
bar.update({ status: 'Creating' })
330-
const creatingInterval = loaderInterval(bar, 8, 1, 80);
325+
updaterRow.update({ status: 'Creating' }).replaceSpinner(SPINNER_DOTS);
331326

332327
try {
333328
response = await functionsCreate({
@@ -350,13 +345,9 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
350345
"$id": response['$id'],
351346
});
352347
func["$id"] = response['$id'];
353-
bar.update({ status: 'Created' });
354-
355-
clearLoaderInterval(bar, creatingInterval);
348+
updaterRow.update({ status: 'Created' });
356349
} catch (e) {
357-
clearLoaderInterval(bar, creatingInterval);
358-
359-
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' });
350+
updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' });
360351
return;
361352
}
362353
}
@@ -400,7 +391,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
400391

401392
let result = await awaitPools.wipeVariables(func['$id']);
402393
if (!result) {
403-
bar.update({ status: 'Error', errorMessage: 'Variable deletion timed out' })
394+
updaterRow.fail({ errorMessage: 'Variable deletion timed out' })
404395
return;
405396
}
406397

@@ -422,10 +413,9 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
422413
func.entrypoint = answers.entrypoint;
423414
localConfig.updateFunction(func['$id'], func);
424415
}
425-
bar.update({ status: 'Deploying' })
426-
const deployingInterval = loaderInterval(bar, 8, 1, 80);
427416

428417
try {
418+
updaterRow.update({ status: 'Pushing' }).replaceSpinner(SPINNER_ARC);
429419
response = await functionsCreateDeployment({
430420
functionId: func['$id'],
431421
entrypoint: func.entrypoint,
@@ -435,23 +425,23 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
435425
parseOutput: false
436426
})
437427

438-
bar.update({ status: 'Deployed' })
428+
updaterRow.update({ status: 'Pushed' })
439429
successfullyDeployed++;
440430

441431
} catch (e) {
442432
switch (e.code) {
443433
case 'ENOENT':
444-
bar.update({ status: 'Error', errorMessage: 'Not found in the current directory. Skipping...' })
434+
updaterRow.fail({ errorMessage: 'Not found in the current directory. Skipping...' })
445435
break;
446436
default:
447-
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' })
437+
updaterRow.fail({ errorMessage: e.message ?? 'General error occurs please try again' })
448438
}
449439
}
450-
clearLoaderInterval(bar, deployingInterval);
440+
updaterRow.stopSpinner();
451441
}))
442+
Updater.stop();
452443

453-
updatesBar.stop()
454-
success(`Deployed ${successfullyDeployed} functions`);
444+
success(`Pushed ${successfullyDeployed} functions`);
455445
}
456446

457447
const createAttribute = async (databaseId, collectionId, attribute) => {

templates/cli/lib/formatters.js.twig

Lines changed: 0 additions & 35 deletions
This file was deleted.

templates/cli/lib/updateTable.js.twig

Lines changed: 0 additions & 23 deletions
This file was deleted.

templates/cli/lib/updater.js.twig

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const progress = require('cli-progress');
2+
const chalk = require('chalk');
3+
4+
const SPINNER_ARC = 'arc';
5+
const SPINNER_DOTS = 'dots';
6+
7+
const spinners = {
8+
[SPINNER_ARC]: {
9+
"interval": 100,
10+
"frames": ["◜", "◠", "◝", "◞", "◡", "◟"]
11+
},
12+
[SPINNER_DOTS]: {
13+
"interval": 80,
14+
"frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
15+
}
16+
}
17+
18+
class Updater {
19+
static start(clearOnComplete = true, hideCursor = true) {
20+
Updater.updatesBar = new progress.MultiBar({
21+
format: this.#formatter,
22+
hideCursor,
23+
clearOnComplete,
24+
stopOnComplete: true,
25+
noTTYOutput: true
26+
});
27+
}
28+
29+
static stop() {
30+
Updater.updatesBar.stop();
31+
}
32+
33+
static #formatter(options, params, payload) {
34+
const status = payload.status.padEnd(12);
35+
const middle = `${payload.resource} (${payload.id})`.padEnd(40);
36+
37+
let prefix = chalk.cyan(payload.prefix ?? '⧗');
38+
let start = chalk.cyan(status);
39+
let end = chalk.yellow(payload.end);
40+
41+
if (status.toLowerCase().trim() === 'pushed') {
42+
start = chalk.green.bold(status);
43+
prefix = chalk.green.bold('✓');
44+
end = '';
45+
} else if (status.toLowerCase().trim() === 'error') {
46+
start = chalk.red.bold(status);
47+
prefix = chalk.red.bold('✗');
48+
end = chalk.red(payload.errorMessage);
49+
}
50+
51+
return Updater.#line(prefix, start, middle, end);
52+
}
53+
54+
static #line(prefix, start, middle, end, separator = '•') {
55+
return `${prefix} ${start} ${separator} ${middle} ${separator} ${end}`;
56+
57+
}
58+
59+
constructor(payload, total = 100, startValue = 0) {
60+
this.bar = Updater.updatesBar.create(total, startValue, payload)
61+
}
62+
63+
update(payload) {
64+
this.bar.update(payload);
65+
return this;
66+
}
67+
68+
fail(payload) {
69+
this.stopSpinner();
70+
this.update({ status: 'Error', ...payload });
71+
}
72+
73+
startSpinner(name) {
74+
let spinnerFrame = 1;
75+
const spinner = spinners[name] ?? spinners['dots'];
76+
77+
this.spinnerInterval = setInterval(() => {
78+
if (spinnerFrame === spinner.frames.length) spinnerFrame = 1;
79+
this.bar.update({ prefix: spinner.frames[spinnerFrame++] });
80+
}, spinner.interval);
81+
}
82+
83+
stopSpinner() {
84+
clearInterval(this.spinnerInterval);
85+
}
86+
87+
replaceSpinner(name) {
88+
this.stopSpinner();
89+
this.startSpinner(name);
90+
}
91+
}
92+
93+
94+
module.exports = {
95+
Updater,
96+
SPINNER_ARC,
97+
SPINNER_DOTS
98+
}

0 commit comments

Comments
 (0)