1
+ const childProcess = require('child_process');
2
+ const chokidar = require('chokidar');
1
3
const inquirer = require("inquirer");
4
+ const path = require("path");
2
5
const { Command } = require("commander");
3
6
const { localConfig, globalConfig } = require("../config");
4
7
const { paginate } = require('../paginate');
5
8
const { questionsRunFunctions } = require("../questions");
6
9
const { actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
7
10
const { systemHasCommand, isPortTaken } = require('../utils');
11
+ const { info } = require('console');
12
+
13
+ const activeDockerIds = {};
8
14
9
15
const systemTools = {
10
16
'node': {
17
+ isCompiled: false,
18
+ startCommand: "node src/server.js",
11
19
commands: [
12
20
{ command: "node", docs: "https://nodejs.org/en/download/package-manager" },
13
21
{ command: "npm", docs: "https://nodejs.org/en/download/package-manager" },
@@ -17,6 +25,101 @@ const systemTools = {
17
25
// TODO: Add all runtime needs
18
26
};
19
27
28
+ async function dockerStop(id) {
29
+ delete activeDockerIds[id];
30
+ const stopProcess = childProcess.spawn('docker', ['rm', '--force', id], {
31
+ stdio: 'pipe',
32
+ });
33
+
34
+ await new Promise((res) => { stopProcess.on('close', res) });
35
+ }
36
+
37
+ async function dockerPull(func) {
38
+ log('Pulling Docker image of function runtime ...');
39
+
40
+ const [ runtimeName, runtimeVersion ] = func.runtime.split('-', 2);
41
+ const imageName = `openruntimes/${runtimeName}:v3-${runtimeVersion}`;
42
+
43
+ const pullProcess = childProcess.spawn('docker', ['pull', imageName], {
44
+ stdio: 'pipe',
45
+ pwd: path.join(process.cwd(), func.path)
46
+ });
47
+
48
+ pullProcess.stderr.on('data', (data) => {
49
+ process.stderr.write(`\n${data}$ `);
50
+ });
51
+
52
+ await new Promise((res) => { pullProcess.on('close', res) });
53
+ }
54
+
55
+ async function dockerBuild(func) {
56
+ log('Building function using Docker engine ...');
57
+
58
+ const [ runtimeName, runtimeVersion ] = func.runtime.split('-', 2);
59
+ const imageName = `openruntimes/${runtimeName}:v3-${runtimeVersion}`;
60
+
61
+ const functionDir = path.join(process.cwd(), func.path);
62
+
63
+ const id = `${new Date().getTime().toString(16)}${Math.round(Math.random() * 1000000000).toString(16)}`;
64
+ const params = ['run', '--rm', '--name', id, '-i', '-e', `OPEN_RUNTIMES_ENTRYPOINT=${func.entrypoint}`, '-v', `${functionDir}/:/mnt/code:rw`, imageName, 'sh', '-c', ` helpers/build.sh "${func.commands}"`];
65
+
66
+ const buildProcess = childProcess.spawn('docker', params, {
67
+ stdio: 'pipe',
68
+ pwd: functionDir
69
+ });
70
+
71
+ buildProcess.stdout.on('data', (data) => {
72
+ process.stdout.write(`\n${data}`);
73
+ });
74
+
75
+ buildProcess.stderr.on('data', (data) => {
76
+ process.stderr.write(`\n${data}`);
77
+ });
78
+
79
+ activeDockerIds[id] = true;
80
+
81
+ await new Promise((res) => { buildProcess.on('close', res) });
82
+
83
+ delete activeDockerIds[id];
84
+ }
85
+
86
+ async function dockerStart(func, port) {
87
+ log('Starting function using Docker engine ...');
88
+
89
+ log("Permissions, events, CRON and timeouts dont apply when running locally.");
90
+
91
+ log('💡 Hint: Function automatically restarts when you edit your code.');
92
+
93
+ success(`Visit http://localhost:${port}/ to execute your function.`);
94
+
95
+ const [ runtimeName, runtimeVersion ] = func.runtime.split('-', 2);
96
+ const imageName = `openruntimes/${runtimeName}:v3-${runtimeVersion}`;
97
+
98
+ const tool = systemTools[runtimeName];
99
+
100
+ const functionDir = path.join(process.cwd(), func.path);
101
+
102
+ const id = `${new Date().getTime().toString(16)}${Math.round(Math.random() * 1000000000).toString(16)}`;
103
+ const params = ['run', '--rm', '--name', id, '-i', '-e', 'OPEN_RUNTIMES_SECRET=', '-p', `${port}:3000`, '-v', `${functionDir}/:/mnt/code:rw`, imageName, 'sh', '-c', ` helpers/start.sh "${tool.startCommand}"`];
104
+
105
+ const execProcess = childProcess.spawn('docker', params, {
106
+ stdio: 'pipe',
107
+ pwd: functionDir
108
+ });
109
+
110
+ // TODO: Find a way to see context.log
111
+
112
+ execProcess.stdout.on('data', (data) => {
113
+ process.stdout.write(`\n${data}`);
114
+ });
115
+
116
+ execProcess.stderr.on('data', (data) => {
117
+ process.stderr.write(`\n${data}`);
118
+ });
119
+
120
+ activeDockerIds[id] = true;
121
+ }
122
+
20
123
const runFunction = async ({ port, engine, functionId } = {}) => {
21
124
// Selection
22
125
if(!functionId) {
@@ -30,6 +133,9 @@ const runFunction = async ({ port, engine, functionId } = {}) => {
30
133
throw new Error("Function '" + functionId + "' not found.")
31
134
}
32
135
136
+ const runtimeName = func.runtime.split('-')[0];
137
+ const tool = systemTools[runtimeName];
138
+
33
139
// Configuration: Port
34
140
if(port) {
35
141
port = +port;
@@ -72,9 +178,6 @@ const runFunction = async ({ port, engine, functionId } = {}) => {
72
178
} else if(engine === 'system') {
73
179
log('💡 Hint: Docker simulates the production environment precisely, but using system is faster');
74
180
75
- const runtimeName = func.runtime.split('-')[0];
76
- const tool = systemTools[runtimeName];
77
-
78
181
for(const command of tool.commands) {
79
182
if(!systemHasCommand(command.command)) {
80
183
return error(`Your system is missing command "${command.command}". Please install it first: ${command.docs}`);
@@ -93,8 +196,56 @@ const runFunction = async ({ port, engine, functionId } = {}) => {
93
196
drawTable([settings]);
94
197
log('If you wish to change local settings, update appwrite.json file and rerun the command. To deploy the function, run: appwrite push function');
95
198
96
- childProcess.execSync('where ' + command, { stdio: 'pipe' })
199
+ process.on('SIGINT', () => {
200
+ for(const id in activeDockerIds) {
201
+ dockerStop(id);
202
+ }
203
+
204
+ process.exit();
205
+ });
206
+
207
+ if(engine === "docker") {
208
+ await dockerPull(func);
209
+ await dockerBuild(func);
210
+ await dockerStart(func, port);
211
+
212
+ let watcherRunning = false;
213
+
214
+ chokidar.watch('.', {
215
+ cwd: path.join(process.cwd(), func.path),
216
+ ignoreInitial: true,
217
+ ignored: [ ...(func.ignore ?? []), 'code.tar.gz', '.appwrite' ]
218
+ }).on('all', async (event, path) => {
219
+ if(watcherRunning) {
220
+ info("File change detected but ignored, because live reload is already being ran.");
221
+ return;
222
+ }
97
223
224
+ watcherRunning = true;
225
+
226
+ log('Detected a change in ' + path);
227
+
228
+ try {
229
+ log('Stopping the function ...');
230
+
231
+ for(const id in activeDockerIds) {
232
+ await dockerStop(id);
233
+ }
234
+
235
+ if(tool.isCompiled || tool.dependencyFiles.includes(path)) {
236
+ await dockerBuild(func);
237
+ await dockerStart(func, port);
238
+ } else {
239
+ // TODO: Update code.tar.gz with latest changes
240
+ await dockerStart(func, port);
241
+ }
242
+ } catch(err) {
243
+ console.error(err);
244
+ } finally {
245
+ watcherRunning = false;
246
+ }
247
+ });
248
+ }
98
249
}
99
250
100
251
const run = new Command("run")
0 commit comments