@@ -17,336 +17,8 @@ const { projectsCreateJWT } = require('./projects');
17
17
const { questionsRunFunctions } = require("../questions");
18
18
const { actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
19
19
const { systemHasCommand, isPortTaken, getAllFiles } = require('../utils');
20
-
21
- const activeDockerIds = {};
22
-
23
- const openRuntimesVersion = 'v3';
24
- const runtimeNames = {
25
- 'node': 'Node.js',
26
- 'php': 'PHP',
27
- 'ruby': 'Ruby',
28
- 'python': 'Python',
29
- 'python-ml': 'Python (ML)',
30
- 'deno': 'Deno',
31
- 'dart': 'Dart',
32
- 'dotnet': '.NET',
33
- 'java': 'Java',
34
- 'swift': 'Swift',
35
- 'kotlin': 'Kotlin',
36
- 'bun': 'Bun'
37
- };
38
- const systemTools = {
39
- 'node': {
40
- isCompiled: false,
41
- startCommand: "node src/server.js",
42
- dependencyFiles: [ "package.json", "package-lock.json" ]
43
- },
44
- 'php': {
45
- isCompiled: false,
46
- startCommand: "php src/server.php",
47
- dependencyFiles: [ "composer.json", "composer.lock" ]
48
- },
49
- 'ruby': {
50
- isCompiled: false,
51
- startCommand: "bundle exec puma -b tcp://0.0.0.0:3000 -e production",
52
- dependencyFiles: [ "Gemfile", "Gemfile.lock" ]
53
- },
54
- 'python': {
55
- isCompiled: false,
56
- startCommand: "python3 src/server.py",
57
- dependencyFiles: [ "requirements.txt", "requirements.lock" ]
58
- },
59
- 'python-ml': {
60
- isCompiled: false,
61
- startCommand: "python3 src/server.py",
62
- dependencyFiles: [ "requirements.txt", "requirements.lock" ]
63
- },
64
- 'deno': {
65
- isCompiled: false,
66
- startCommand: "deno start",
67
- dependencyFiles: [ ]
68
- },
69
- 'dart': {
70
- isCompiled: true,
71
- startCommand: "src/function/server",
72
- dependencyFiles: [ ]
73
- },
74
- 'dotnet': {
75
- isCompiled: true,
76
- startCommand: "dotnet src/function/DotNetRuntime.dll",
77
- dependencyFiles: [ ]
78
- },
79
- 'java': {
80
- isCompiled: true,
81
- startCommand: "java -jar src/function/java-runtime-1.0.0.jar",
82
- dependencyFiles: [ ]
83
- },
84
- 'swift': {
85
- isCompiled: true,
86
- startCommand: "src/function/Runtime serve --env production --hostname 0.0.0.0 --port 3000",
87
- dependencyFiles: [ ]
88
- },
89
- 'kotlin': {
90
- isCompiled: true,
91
- startCommand: "java -jar src/function/kotlin-runtime-1.0.0.jar",
92
- dependencyFiles: [ ]
93
- },
94
- 'bun': {
95
- isCompiled: false,
96
- startCommand: "bun src/server.ts",
97
- dependencyFiles: [ "package.json", "package-lock.json", "bun.lockb" ]
98
- },
99
- };
100
-
101
- const JwtManager = {
102
- userJwt: null,
103
- functionJwt: null,
104
-
105
- timerWarn: null,
106
- timerError: null,
107
-
108
- async setup(userId = null) {
109
- if(this.timerWarn) {
110
- clearTimeout(this.timerWarn);
111
- }
112
-
113
- if(this.timerError) {
114
- clearTimeout(this.timerError);
115
- }
116
-
117
- this.timerWarn = setTimeout(() => {
118
- log("Warning: Authorized JWT will expire in 5 minutes. Please stop and re-run the command to refresh tokens for 1 hour.");
119
- }, 1000 * 60 * 55); // 55 mins
120
-
121
- this.timerError = setTimeout(() => {
122
- log("Warning: Authorized JWT just expired. Please stop and re-run the command to obtain new tokens with 1 hour validity.");
123
- log("Some Appwrite API communication is not authorized now.")
124
- }, 1000 * 60 * 60); // 60 mins
125
-
126
- if(userId) {
127
- await usersGet({
128
- userId,
129
- parseOutput: false
130
- });
131
- const userResponse = await usersCreateJWT({
132
- userId,
133
- duration: 60*60,
134
- parseOutput: false
135
- });
136
- this.userJwt = userResponse.jwt;
137
- }
138
-
139
- const functionResponse = await projectsCreateJWT({
140
- projectId: localConfig.getProject().projectId,
141
- // TODO: Once we have endpoint for this, use it
142
- scopes: ["sessions.write","users.read","users.write","teams.read","teams.write","databases.read","databases.write","collections.read","collections.write","attributes.read","attributes.write","indexes.read","indexes.write","documents.read","documents.write","files.read","files.write","buckets.read","buckets.write","functions.read","functions.write","execution.read","execution.write","locale.read","avatars.read","health.read","providers.read","providers.write","messages.read","messages.write","topics.read","topics.write","subscribers.read","subscribers.write","targets.read","targets.write","rules.read","rules.write","migrations.read","migrations.write","vcs.read","vcs.write","assistant.read"],
143
- duration: 60*60,
144
- parseOutput: false
145
- });
146
- this.functionJwt = functionResponse.jwt;
147
- }
148
- };
149
-
150
- const Queue = {
151
- files: [],
152
- locked: false,
153
- events: new EventEmitter(),
154
- debounce: null,
155
- push(file) {
156
- if(!this.files.includes(file)) {
157
- this.files.push(file);
158
- }
159
-
160
- if(!this.locked) {
161
- this._trigger();
162
- }
163
- },
164
- lock() {
165
- this.files = [];
166
- this.locked = true;
167
- },
168
- unlock() {
169
- this.locked = false;
170
- if(this.files.length > 0) {
171
- this._trigger();
172
- }
173
- },
174
- _trigger() {
175
- if(this.debounce) {
176
- return;
177
- }
178
-
179
- this.debounce = setTimeout(() => {
180
- this.events.emit('reload', { files: this.files });
181
- this.debounce = null;
182
- }, 300);
183
- }
184
- };
185
-
186
- async function dockerStop(id) {
187
- delete activeDockerIds[id];
188
- const stopProcess = childProcess.spawn('docker', ['rm', '--force', id], {
189
- stdio: 'pipe',
190
- });
191
-
192
- await new Promise((res) => { stopProcess.on('close', res) });
193
- }
194
-
195
- async function dockerPull(func) {
196
- log('Pulling Docker image of function runtime ...');
197
-
198
- const runtimeChunks = func.runtime.split("-");
199
- const runtimeVersion = runtimeChunks.pop();
200
- const runtimeName = runtimeChunks.join("-");
201
- const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
202
-
203
- const pullProcess = childProcess.spawn('docker', ['pull', imageName], {
204
- stdio: 'pipe',
205
- pwd: path.join(process.cwd(), func.path)
206
- });
207
-
208
- pullProcess.stderr.on('data', (data) => {
209
- process.stderr.write(`\n${data}$ `);
210
- });
211
-
212
- await new Promise((res) => { pullProcess.on('close', res) });
213
- }
214
-
215
- async function dockerBuild(func, variables) {
216
- log('Building function using Docker ...');
217
-
218
- const runtimeChunks = func.runtime.split("-");
219
- const runtimeVersion = runtimeChunks.pop();
220
- const runtimeName = runtimeChunks.join("-");
221
- const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
222
-
223
- const functionDir = path.join(process.cwd(), func.path);
224
-
225
- const id = ID.unique();
226
-
227
- const params = [ 'run' ];
228
- params.push('--name', id);
229
- params.push('-v', `${functionDir}/:/mnt/code:rw`);
230
- params.push('-e', 'APPWRITE_ENV=development');
231
- params.push('-e', 'OPEN_RUNTIMES_ENV=development');
232
- params.push('-e', 'OPEN_RUNTIMES_SECRET=');
233
- params.push('-e', `OPEN_RUNTIMES_ENTRYPOINT=${func.entrypoint}`);
234
-
235
- for(const k of Object.keys(variables)) {
236
- params.push('-e', `${k}=${variables[k]}`);
237
- }
238
-
239
- params.push(imageName, 'sh', '-c', `helpers/build.sh "${func.commands}"`);
240
-
241
- const buildProcess = childProcess.spawn('docker', params, {
242
- stdio: 'pipe',
243
- pwd: functionDir
244
- });
245
-
246
- buildProcess.stdout.on('data', (data) => {
247
- process.stdout.write(`\n${data}`);
248
- });
249
-
250
- buildProcess.stderr.on('data', (data) => {
251
- process.stderr.write(`\n${data}`);
252
- });
253
-
254
- await new Promise((res) => { buildProcess.on('close', res) });
255
-
256
- const copyPath = path.join(process.cwd(), func.path, '.appwrite', 'build.tar.gz');
257
- const copyDir = path.dirname(copyPath);
258
- if (!fs.existsSync(copyDir)) {
259
- fs.mkdirSync(copyDir, { recursive: true });
260
- }
261
-
262
- const copyProcess = childProcess.spawn('docker', ['cp', `${id}:/mnt/code/code.tar.gz`, copyPath], {
263
- stdio: 'pipe',
264
- pwd: functionDir
265
- });
266
-
267
- await new Promise((res) => { copyProcess.on('close', res) });
268
-
269
- const cleanupProcess = childProcess.spawn('docker', ['rm', '--force', id], {
270
- stdio: 'pipe',
271
- pwd: functionDir
272
- });
273
-
274
- await new Promise((res) => { cleanupProcess.on('close', res) });
275
-
276
- delete activeDockerIds[id];
277
-
278
- const tempPath = path.join(process.cwd(), func.path, 'code.tar.gz');
279
- if (fs.existsSync(tempPath)) {
280
- fs.rmSync(tempPath, { force: true });
281
- }
282
- }
283
-
284
- async function dockerStart(func, variables, port) {
285
- log('Starting function using Docker ...');
286
-
287
- log("Permissions, events, CRON and timeouts dont apply when running locally.");
288
-
289
- log('💡 Hint: Function automatically restarts when you edit your code.');
290
-
291
- success(`Visit http://localhost:${port}/ to execute your function.`);
292
-
293
-
294
- const runtimeChunks = func.runtime.split("-");
295
- const runtimeVersion = runtimeChunks.pop();
296
- const runtimeName = runtimeChunks.join("-");
297
- const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
298
-
299
- const tool = systemTools[runtimeName];
300
-
301
- const functionDir = path.join(process.cwd(), func.path);
302
-
303
- const id = ID.unique();
304
-
305
- const params = [ 'run' ];
306
- params.push('--rm');
307
- params.push('-d');
308
- params.push('--name', id);
309
- params.push('-p', `${port}:3000`);
310
- params.push('-e', 'APPWRITE_ENV=development');
311
- params.push('-e', 'OPEN_RUNTIMES_ENV=development');
312
- params.push('-e', 'OPEN_RUNTIMES_SECRET=');
313
-
314
- for(const k of Object.keys(variables)) {
315
- params.push('-e', `${k}=${variables[k]}`);
316
- }
317
-
318
- params.push('-v', `${functionDir}/.appwrite/logs.txt:/mnt/logs/dev_logs.log:rw`);
319
- params.push('-v', `${functionDir}/.appwrite/errors.txt:/mnt/logs/dev_errors.log:rw`);
320
- params.push('-v', `${functionDir}/.appwrite/build.tar.gz:/mnt/code/code.tar.gz:ro`);
321
- params.push(imageName, 'sh', '-c', `helpers/start.sh "${tool.startCommand}"`);
322
-
323
- childProcess.spawn('docker', params, {
324
- stdio: 'pipe',
325
- pwd: functionDir
326
- });
327
-
328
- activeDockerIds[id] = true;
329
- }
330
-
331
- async function dockerCleanup() {
332
- const ids = Object.keys(activeDockerIds);
333
- for await (const id of ids) {
334
- await dockerStop(id);
335
- }
336
-
337
- const functions = localConfig.getFunctions();
338
- for(const func of functions) {
339
- const appwritePath = path.join(process.cwd(), func.path, '.appwrite');
340
- if (fs.existsSync(appwritePath)) {
341
- fs.rmSync(appwritePath, { recursive: true, force: true });
342
- }
343
-
344
- const tempPath = path.join(process.cwd(), func.path, 'code.tar.gz');
345
- if (fs.existsSync(tempPath)) {
346
- fs.rmSync(tempPath, { force: true });
347
- }
348
- }
349
- }
20
+ const { openRuntimesVersion, runtimeNames, systemTools, JwtManager, Queue } = require('../emulation/utils');
21
+ const { dockerStop, dockerCleanup, dockerStart, dockerBuild, dockerPull, dockerStopActive } = require('../emulation/docker');
350
22
351
23
const runFunction = async ({ port, functionId, noVariables, noReload, userId } = {}) => {
352
24
// Selection
@@ -377,7 +49,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
377
49
const taken = await isPortTaken(port);
378
50
379
51
if(taken) {
380
- log(`Port ${port} is already used .`);
52
+ log(`Port ${port} is already in use by another process .`);
381
53
port = null;
382
54
}
383
55
}
@@ -389,7 +61,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
389
61
390
62
// Configuration: Engine
391
63
if(!systemHasCommand('docker')) {
392
- return error("Please install Docker first : https://docs.docker.com/engine/install/");
64
+ return error("Docker Engine is required for local development. Please install Docker using : https://docs.docker.com/engine/install/");
393
65
}
394
66
395
67
// Settings
@@ -402,7 +74,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
402
74
403
75
log("Local function configuration:");
404
76
drawTable([settings]);
405
- log('If you wish to change local settings, update appwrite.json file and rerun the command. To deploy the function, run: appwrite push function ');
77
+ log('If you wish to change your local settings, update the appwrite.json file and rerun the `appwrite run` command. ');
406
78
407
79
await dockerCleanup();
408
80
@@ -499,9 +171,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
499
171
try {
500
172
log('Stopping the function ...');
501
173
502
- for(const id in activeDockerIds) {
503
- await dockerStop(id);
504
- }
174
+ await dockerStopActive();
505
175
506
176
const dependencyFile = files.find((filePath) => tool.dependencyFiles.includes(filePath));
507
177
if(tool.isCompiled || dependencyFile) {
0 commit comments