Skip to content

Commit 0575002

Browse files
committed
More progress on local development
1 parent 353d2a6 commit 0575002

File tree

3 files changed

+159
-5
lines changed

3 files changed

+159
-5
lines changed

templates/cli/base/params.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

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

19+
ignore.add('.appwrite');
20+
1921
if (func.ignore) {
2022
ignorer.add(func.ignore);
2123
} else if (fs.existsSync(pathLib.join({{ parameter.name | caseCamel | escapeKeyword }}, '.gitignore'))) {

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

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
const childProcess = require('child_process');
2+
const chokidar = require('chokidar');
13
const inquirer = require("inquirer");
4+
const path = require("path");
25
const { Command } = require("commander");
36
const { localConfig, globalConfig } = require("../config");
47
const { paginate } = require('../paginate');
58
const { questionsRunFunctions } = require("../questions");
69
const { actionRunner, success, log, error, commandDescriptions, drawTable } = require("../parser");
710
const { systemHasCommand, isPortTaken } = require('../utils');
11+
const { info } = require('console');
12+
13+
const activeDockerIds = {};
814

915
const systemTools = {
1016
'node': {
17+
isCompiled: false,
18+
startCommand: "node src/server.js",
1119
commands: [
1220
{ command: "node", docs: "https://nodejs.org/en/download/package-manager" },
1321
{ command: "npm", docs: "https://nodejs.org/en/download/package-manager" },
@@ -17,6 +25,101 @@ const systemTools = {
1725
// TODO: Add all runtime needs
1826
};
1927

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+
20123
const runFunction = async ({ port, engine, functionId } = {}) => {
21124
// Selection
22125
if(!functionId) {
@@ -30,6 +133,9 @@ const runFunction = async ({ port, engine, functionId } = {}) => {
30133
throw new Error("Function '" + functionId + "' not found.")
31134
}
32135

136+
const runtimeName = func.runtime.split('-')[0];
137+
const tool = systemTools[runtimeName];
138+
33139
// Configuration: Port
34140
if(port) {
35141
port = +port;
@@ -72,9 +178,6 @@ const runFunction = async ({ port, engine, functionId } = {}) => {
72178
} else if(engine === 'system') {
73179
log('💡 Hint: Docker simulates the production environment precisely, but using system is faster');
74180

75-
const runtimeName = func.runtime.split('-')[0];
76-
const tool = systemTools[runtimeName];
77-
78181
for(const command of tool.commands) {
79182
if(!systemHasCommand(command.command)) {
80183
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 } = {}) => {
93196
drawTable([settings]);
94197
log('If you wish to change local settings, update appwrite.json file and rerun the command. To deploy the function, run: appwrite push function');
95198

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+
}
97223

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+
}
98249
}
99250

100251
const run = new Command("run")

templates/cli/package.json.twig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"json-bigint": "^1.0.0",
3232
"inquirer": "^8.2.4",
3333
"tar": "^6.1.11",
34-
"ignore": "^5.2.0"
34+
"ignore": "^5.2.0",
35+
"chokidar": "^3.6.0"
3536
},
3637
"devDependencies": {
3738
"pkg": "5.8.1"

0 commit comments

Comments
 (0)