import {parse, stringify} from 'yaml'; (async () => { const serviceName = prompt('Service name? (This is used by the router to determine the route prefix)')?.replace('-service', ''); if (!serviceName) return; type permissionShorts = 'r' | 'w' | 'x' | 'n'; type permissionNames = 'read' | 'write' | 'execute' | 'run' | 'net'; const getPermissionByNameOrShort = (name: permissionNames | permissionShorts | string) => { switch (name) { case "read": case "r": return { name: 'read', denoPerm: '--allow-read' }; case "write": case "w": return { name: 'write', denoPerm: '--allow-write' }; case "execute": case "run": case "x": return { name: 'run', denoPerm: '--allow-run' }; case "net": case "n": return { name: 'net', denoPerm: '--allow-net' }; default: return { name: 'custom-' + name, denoPerm: '--allow-' + name } } } const perms = []; if (Deno.args[0]) { const permShort = Deno.args[0].split('') as permissionShorts[]; for (const short of permShort) { perms.push(getPermissionByNameOrShort(short)); } } else if (confirm('Permissions not specified, would you like to add specific permissions?')) { let permsPrompt: string | null = ''; while (!permsPrompt) { permsPrompt = prompt('Please enter the permission short code or names of permissions you would like'); if (!permsPrompt) continue let permNames; if (permsPrompt?.match(' ')) { permNames = permsPrompt.split(' '); } else { permNames = permsPrompt.split(''); } for (const perm of permNames) { perms.push(getPermissionByNameOrShort(perm)); } } } console.log(`Service will run with following permissions: ${perms.map(p => p.name).join()}`); if (!perms.find(p => p.name === 'net')) { if (!confirm('Service does not have permissions to "net", is this correct?')) perms.push(getPermissionByNameOrShort('net')); } const serviceFile = `./${serviceName}-service/`; let port; await Deno.mkdir(serviceFile); port = await getAvailablePort(); if (perms.find(p => p.name === 'net')) { await Deno.writeTextFile(serviceFile + 'port', port) } await Deno.writeTextFile(serviceFile + 'main.ts', ` import { CGGService } from 'cgg/Application.ts'; import { Router } from 'oak'; const app = new CGGService({ prefix: '/${serviceName}' }); app.route(new Router() .get('/', ctx => ctx.response.body = '${serviceName} service') ); app.start(); console.log('${serviceName} service running on ' + Deno.args.at(0)); `); await Deno.writeTextFile(serviceFile + 'perms', perms.map(p => p.denoPerm).join('\n')); await Deno.writeTextFile(serviceFile + 'prefix', serviceName); if (confirm('Containerize this service?')) { await Deno.writeTextFile(serviceFile + 'Dockerfile', ` FROM denoland/deno:1.33.2 ${port ? 'EXPOSE ' + port : ''} WORKDIR /${serviceName} ADD ${serviceFile} . ADD ./deno.jsonc . ADD ./secrets.json . ADD ./key.txt . ADD ./common ./common ADD ./lib ./lib ADD ./middleware ./middleware CMD ["run", "${perms.join('", "')}", main.ts${port ? `, "${port}"` : ''}] `); const dockerCompose: any = parse(await Deno.readTextFile('./docker-compose.yml')); dockerCompose['services'][serviceName] = { build: { context: "./", dockerfile: serviceFile + 'Dockerfile' } } await Deno.writeTextFile('./docker-compose.yml', stringify(dockerCompose)) } if (confirm('Does this service need DB access?')) await Deno.writeTextFile(serviceFile + 'data.ts', ` import mongoose from 'mongoose'; import { configDatabase } from 'lib/data.ts'; configDatabase(mongoose); `) })(); async function getAvailablePort() { let start = 6900; const missingPorts = []; const takenPorts = []; for await (const dirEntry of Deno.readDir('.')) { if (dirEntry.isFile || !dirEntry.name.includes('-service')) continue; const dir = './' + dirEntry.name + '/'; if (!Array.from(Deno.readDirSync(dir)).find(e => e.name === 'port')) { missingPorts.push(dir); continue; } takenPorts.push(Number(await Deno.readTextFile(dir + 'port'))); } takenPorts.sort(); for (const port of takenPorts) { if (start === port) start++; } for (const missing of missingPorts) { Deno.writeTextFile(missing + 'port', start.toString()); takenPorts.push(start); while(takenPorts.includes(start)) start++; } return start.toString(); }