Skip to content

Commit fcde6cb

Browse files
committed
PR review changes
1 parent 59a0d36 commit fcde6cb

File tree

4 files changed

+346
-338
lines changed

4 files changed

+346
-338
lines changed

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

Lines changed: 6 additions & 336 deletions
Original file line numberDiff line numberDiff line change
@@ -17,336 +17,8 @@ const { projectsCreateJWT } = require('./projects');
1717
const { questionsRunFunctions } = require("../questions");
1818
const { actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
1919
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');
35022

35123
const runFunction = async ({ port, functionId, noVariables, noReload, userId } = {}) => {
35224
// Selection
@@ -377,7 +49,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
37749
const taken = await isPortTaken(port);
37850

37951
if(taken) {
380-
log(`Port ${port} is already used.`);
52+
log(`Port ${port} is already in use by another process.`);
38153
port = null;
38254
}
38355
}
@@ -389,7 +61,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
38961

39062
// Configuration: Engine
39163
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/");
39365
}
39466

39567
// Settings
@@ -402,7 +74,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
40274

40375
log("Local function configuration:");
40476
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.');
40678

40779
await dockerCleanup();
40880

@@ -499,9 +171,7 @@ const runFunction = async ({ port, functionId, noVariables, noReload, userId } =
499171
try {
500172
log('Stopping the function ...');
501173

502-
for(const id in activeDockerIds) {
503-
await dockerStop(id);
504-
}
174+
await dockerStopActive();
505175

506176
const dependencyFile = files.find((filePath) => tool.dependencyFiles.includes(filePath));
507177
if(tool.isCompiled || dependencyFile) {

0 commit comments

Comments
 (0)