Skip to content

Commit 6dc761b

Browse files
committed
feat(cli): Deploy function in parallelism.
1 parent 0bbcafb commit 6dc761b

File tree

6 files changed

+125
-33
lines changed

6 files changed

+125
-33
lines changed

src/SDK/Language/CLI.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ public function getFiles(): array
132132
'destination' => 'lib/validations.js',
133133
'template' => 'cli/lib/validations.js.twig',
134134
],
135+
[
136+
'scope' => 'default',
137+
'destination' => 'lib/formatters.js',
138+
'template' => 'cli/lib/formatters.js.twig',
139+
],
135140
[
136141
'scope' => 'default',
137142
'destination' => 'lib/parser.js',

templates/cli/base/params.twig

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,17 @@
1111
if (!fs.lstatSync(folderPath).isDirectory()) {
1212
throw new Error('The path is not a directory.');
1313
}
14-
14+
1515
const ignorer = ignore();
1616

1717
const func = localConfig.getFunction(functionId);
1818

1919
if (func.ignore) {
2020
ignorer.add(func.ignore);
21-
log('Ignoring files using configuration from appwrite.json');
2221
} else if (fs.existsSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore'))) {
2322
ignorer.add(fs.readFileSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore')).toString());
24-
log('Ignoring files in .gitignore');
2523
}
26-
24+
2725
const files = getAllFiles({{ parameter.name | caseCamel | escapeKeyword }}).map((file) => pathLib.relative({{ parameter.name | caseCamel | escapeKeyword }}, file)).filter((file) => !ignorer.ignores(file));
2826

2927
await tar
@@ -81,4 +79,4 @@
8179
payload['key'] = globalConfig.getKey();
8280
const queryParams = new URLSearchParams(payload);
8381
apiPath = `${globalConfig.getEndpoint()}${apiPath}?${queryParams.toString()}`;
84-
{% endif %}
82+
{% endif %}

templates/cli/base/requests/file.twig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% for parameter in method.parameters.all %}
22
{% if parameter.type == 'file' %}
33
const size = {{ parameter.name | caseCamel | escapeKeyword }}.size;
4-
4+
55
const apiHeaders = {
66
{% for parameter in method.parameters.header %}
77
'{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }},
@@ -45,7 +45,7 @@
4545
}
4646

4747
let uploadableChunkTrimmed;
48-
48+
4949
if(currentPosition + 1 >= client.CHUNK_SIZE) {
5050
uploadableChunkTrimmed = uploadableChunk;
5151
} else {
@@ -99,17 +99,17 @@
9999
}
100100

101101
{% if method.packaging %}
102-
fs.unlinkSync(filePath);
102+
await fs.unlink(filePath,()=>{});
103103
{% endif %}
104104
{% if method.type == 'location' %}
105105
fs.writeFileSync(destination, response);
106106
{% endif %}
107-
107+
108108
if (parseOutput) {
109109
parse(response)
110110
success()
111111
}
112112

113113
return response;
114114
{% endif %}
115-
{% endfor %}
115+
{% endfor %}

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

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const inquirer = require("inquirer");
33
const JSONbig = require("json-bigint")({ storeAsString: false });
44
const { Command } = require("commander");
55
const { localConfig } = require("../config");
6+
const { loaderInterval, clearLoaderInterval, } = require('../utils');
7+
const { formatterFunction } = require('../formatters');
68
const { paginate } = require('../paginate');
79
const { questionsDeployBuckets, questionsDeployTeams, questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollections, questionsConfirmDeployCollections } = require("../questions");
810
const { actionRunner, success, log, error, commandDescriptions } = require("../parser");
@@ -271,27 +273,38 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
271273
});
272274

273275
const updatesBar = new _progress.MultiBar({
274-
format: ` ${success('{status}')} | {function}({id})`,
276+
format: formatterFunction,
275277
hideCursor: true,
276278
clearOnComplete: false,
277279
stopOnComplete: true,
278280
noTTYOutput: true
279281
});
280-
log('Deploying functions')
281-
for (let func of functions) {
282-
const a = updatesBar.create(4, 0, { status: '', function: func.name, id: func['$id'] })
283-
{#log(`Deploying function ${func.name} ( ${func['$id']} )`)#}
282+
283+
log('Deploying functions\n');
284+
285+
let successfullyDeployed = 0;
286+
287+
await Promise.all(functions.map(async (func) => {
288+
const ignore = func.ignore ? 'appwrite.json' : '.gitignore';
289+
let functionExists = false;
290+
291+
const bar = updatesBar.create(100, 0, { status: 'Deploying', function: func.name, id: func['$id'], ignore })
292+
bar.update({ status: 'Getting' });
293+
const updatingInterval = loaderInterval(bar, 8, 1, 80);
284294

285295
try {
286296
response = await functionsGet({
287297
functionId: func['$id'],
288298
parseOutput: false,
289299
});
290-
300+
functionExists = true;
291301
if (response.runtime !== func.runtime) {
292-
throw new Error(`Runtime missmatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json`);
302+
bar.update({ status: 'Error', errorMessage: `Runtime missmatch! (local=${func.runtime},remote=${response.runtime}) Please delete remote function or update your appwrite.json` })
303+
clearLoaderInterval(bar, updatingInterval);
304+
return;
293305
}
294-
a.increment({status:'Updating'})
306+
bar.update({ status: 'Updating' });
307+
295308
response = await functionsUpdate({
296309
functionId: func['$id'],
297310
name: func.name,
@@ -307,9 +320,23 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
307320
parseOutput: false
308321
});
309322
} catch (e) {
323+
310324
if (e.code == 404) {
311-
a.increment({status:'Creating'})
312-
log(`Function ${func.name} ( ${func['$id']} ) does not exist in the project. Creating ... `);
325+
functionExists = false;
326+
} else {
327+
clearLoaderInterval(bar, updatingInterval)
328+
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' });
329+
return;
330+
}
331+
}
332+
333+
clearLoaderInterval(bar, updatingInterval)
334+
335+
if (!functionExists) {
336+
bar.update({ status: 'Creating' })
337+
const creatingInterval = loaderInterval(bar, 8, 1, 80);
338+
339+
try {
313340
response = await functionsCreate({
314341
functionId: func.$id || 'unique()',
315342
name: func.name,
@@ -329,15 +356,20 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
329356
localConfig.updateFunction(func['$id'], {
330357
"$id": response['$id'],
331358
});
332-
333359
func["$id"] = response['$id'];
334-
log(`Function ${func.name} created.`);
335-
} else {
336-
throw e;
360+
bar.update({ status: 'Created' });
361+
362+
clearLoaderInterval(bar, creatingInterval);
363+
} catch (e) {
364+
clearLoaderInterval(bar, creatingInterval);
365+
366+
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' });
367+
return;
337368
}
338369
}
339370

340371
if (func.variables) {
372+
// TODO:
341373
// Delete existing variables
342374

343375
const { total } = await functionsListVariables({
@@ -375,7 +407,8 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
375407

376408
let result = await awaitPools.wipeVariables(func['$id']);
377409
if (!result) {
378-
throw new Error("Variable deletion timed out.");
410+
bar.update({ status: 'Error', errorMessage: 'Variable deletion timed out' })
411+
return;
379412
}
380413

381414
// Deploy local variables
@@ -396,6 +429,8 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
396429
func.entrypoint = answers.entrypoint;
397430
localConfig.updateFunction(func['$id'], func);
398431
}
432+
bar.update({ status: 'Deploying' })
433+
const deployingInterval = loaderInterval(bar, 8, 1, 80);
399434

400435
try {
401436
response = await functionsCreateDeployment({
@@ -407,20 +442,23 @@ const deployFunction = async ({ functionId, all, yes } = {}) => {
407442
parseOutput: false
408443
})
409444

410-
success(`Deployed ${func.name} ( ${func['$id']} )`);
445+
bar.update({ status: 'Deployed' })
446+
successfullyDeployed++;
411447

412448
} catch (e) {
413449
switch (e.code) {
414450
case 'ENOENT':
415-
error(`Function ${func.name} ( ${func['$id']} ) not found in the current directory. Skipping ...`);
451+
bar.update({ status: 'Error', errorMessage: 'Not found in the current directory. Skipping...' })
416452
break;
417453
default:
418-
throw e;
454+
bar.update({ status: 'Error', errorMessage: e.message ?? 'General error occurs please try again' })
419455
}
420456
}
421-
}
457+
clearLoaderInterval(bar, deployingInterval);
458+
}))
422459

423-
success(`Deployed ${functions.length} functions`);
460+
updatesBar.stop()
461+
success(`Deployed ${successfullyDeployed} functions`);
424462
}
425463

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

templates/cli/lib/formatters.js.twig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const chalk = require('chalk');
2+
const dots = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
3+
4+
const formatterFunction = (options, params, payload) => {
5+
const status = payload.status.padEnd(12);
6+
const middle = `${payload.function} (${payload.id})`.padEnd(40);
7+
8+
let start = chalk.blue(status);
9+
let end = chalk.yellow(`Ignoring using: ${payload.ignore}`);
10+
let progress = '⌛';
11+
12+
if (status.toLowerCase().trim() === 'deployed') {
13+
start = chalk.green.bold(status);
14+
progress = chalk.green.bold('✓'.padEnd(2))
15+
} else if (status.toLowerCase().trim() === 'error') {
16+
start = chalk.red.bold(status);
17+
progress = chalk.red.bold('✗'.padEnd(2))
18+
end = chalk.red(payload.errorMessage);
19+
}
20+
21+
if (payload.progress && Number.isInteger(payload.progress)) {
22+
progress = dots[payload.progress - 1].padEnd(2);
23+
}
24+
25+
return formatter(start, middle, end, progress);
26+
}
27+
28+
const formatter = (start, middle, end, progress, separator = '•') => {
29+
return `${progress}${start} ${separator} ${middle} ${separator} ${end}`;
30+
31+
}
32+
33+
module.exports = {
34+
formatterFunction
35+
}

templates/cli/lib/utils.js.twig

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
const fs = require("fs");
22
const path = require("path");
33

4+
function loaderInterval(bar, max, start, timeout) {
5+
let a = start;
6+
7+
return setInterval(() => {
8+
if (a === max) a = start;
9+
bar.update({ progress: a++ })
10+
}, timeout);
11+
}
12+
13+
function clearLoaderInterval(bar, interval) {
14+
clearInterval(interval);
15+
bar.update({ progress: '⏳' })
16+
}
17+
418
function getAllFiles(folder) {
519
const files = [];
6-
for(const pathDir of fs.readdirSync(folder)) {
20+
for (const pathDir of fs.readdirSync(folder)) {
721
const pathAbsolute = path.join(folder, pathDir);
822
if (fs.statSync(pathAbsolute).isDirectory()) {
923
files.push(...getAllFiles(pathAbsolute));
@@ -15,5 +29,7 @@ function getAllFiles(folder) {
1529
}
1630

1731
module.exports = {
18-
getAllFiles
19-
};
32+
getAllFiles,
33+
loaderInterval,
34+
clearLoaderInterval
35+
};

0 commit comments

Comments
 (0)