UNPKG

huncwot

Version:

A Programming Environment for TypeScript apps built on top of VS Code

197 lines (162 loc) 6.07 kB
// Copyright 2019 Zaiste & contributors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. const debug = require('debug')('server'); // eslint-disable-line no-unused-vars const CWD = process.cwd(); const { join, parse, sep, extname } = require('path'); const color = require('chalk'); const { TypescriptCompiler } = require('@poppinss/chokidar-ts'); const fs = require('fs-extra'); const transformPaths = require('@zerollup/ts-transform-paths'); const pg = require('pg'); const fg = require('fast-glob'); const Huncwot = require('../'); const VERSION = require('../package.json').version; const { parser } = require('../parser'); const { generateRPCOnClient } = require('../rpc'); const Logger = require('../logger'); const SQLCompiler = require('../compiler/sql'); const reloadSQL = async (pool, file) => { const content = await fs.readFile(file); const isSQLFunction = content .toString() .split(' ')[0] .toLowerCase() === 'function'; if (isSQLFunction) { const query = `create or replace ${content.toString()}`; try { const _r = await pool.query(query); } catch (error) { console.error(error.message); } } }; let sockets = []; const start = async ({ port }) => { // const app = new Huncwot(); let routes = {}; try { await app.setup(); routes = require(join(CWD, 'dist/config/server/routes')).default; } catch (e) { console.error(e.message); } const server = await app.start({ routes, port }); server.on('connection', socket => { sockets.push(socket); }); // FIXME // const wss = new WebSocket.Server({ server }); console.log( color`{bold.blue ┌ Kretes} {bold ${VERSION}} {grey on} {bold localhost:${port}}\n{bold.blue └ }{grey Started: }${new Date().toLocaleTimeString()}` ); return server; }; const server = async ({ port }) => { const globalConfig = require('config'); const connection = globalConfig.get('db'); const pool = new pg.Pool(connection); try { await pool.connect(); } catch (error) { Logger.printError(error, 'Data Layer'); process.exit(1); } const compiler = new TypescriptCompiler( CWD, 'config/server/tsconfig.json', require('typescript/lib/typescript') ); const { error, config } = compiler.configParser().parse(); if (error || !config || config.errors.length) { return; } compiler.use((ts, _config) => { const { options, fileNames } = config; const host = ts.createCompilerHost(options); const program = ts.createProgram(fileNames, options, host); const r = transformPaths(program, config); return context => r.before(context); }, 'before'); const watcher = compiler.watcher(config); let app; watcher.on('watcher:ready', async () => { const stream = fg.stream([`${CWD}/features/**/*.sql`], { dot: true }); for await (const entry of stream) await reloadSQL(pool, entry); // start the HTTP server app = await start({ port }); }); // files other than `.ts` have changed watcher.on('change', async filePath => { if (extname(filePath) == '.sql') { reloadSQL(pool, filePath); try { const output = await SQLCompiler.compile(join(CWD, filePath)); const { dir } = parse(filePath); await fs.outputFile(join(CWD, dir, 'index.ts'), output); console.log(color` {underline ${filePath}} {green reloaded}`); } catch (error) { console.log( color` {red.bold Errors:}\n {grey in} {underline ${filePath}}\n → ${error.message}` ); } } }); watcher.on('subsequent:build', async ({ path: filePath, diagnostics }) => { console.clear(); console.log(color` {underline ${filePath}} {green reloaded}`); diagnostics.forEach(({ file, messageText }) => { const location = file.fileName.split(`${CWD}${sep}`)[1]; console.log( color` {red.bold Errors:}\n {grey in} {underline ${location}}\n → ${messageText.messageText}` ); }); // restart the HTTP server sockets.filter(socket => !socket.destroyed).forEach(socket => socket.destroy()); sockets = []; app.close(async () => { app = await start({ port }); }); // clean the `require` cache const { dir, name } = parse(filePath); const cacheKey = `${join(CWD, 'dist', dir, name)}.js`; delete require.cache[cacheKey]; if (dir.includes('Service') && name == 'Interface') { const interfaceFile = await fs.readFile(`${join(CWD, dir, name)}.ts`); const results = parser(interfaceFile.toString()); const [interface, methods] = Object.entries(results).shift(); const entityName = interface.split('ServiceInterface').shift(); const generated = generateRPCOnClient({ name: entityName, methods }); await fs.writeFile(join(CWD, 'features', entityName, 'Requester.ts'), generated); } }); const output = watcher.watch(['config/server', 'features'], { ignored: [ 'features/**/View/*', 'features/**/Store.ts', 'features/**/Store/*', 'features/**/Model.ts', 'features/**/Model/*' ] }); if (output.diagnostics.length > 0) console.log(color` {red.bold Errors:}`); output.diagnostics.forEach(({ file, messageText }) => { const location = file.fileName.split(`${CWD}${sep}`)[1]; console.log(color` {grey in} {underline ${location}}\n → ${messageText}`); }); }; module.exports = { builder: _ => _.option('port', { alias: 'p', default: 5544 }).default('dir', '.'), handler: server };