UNPKG

easymina

Version:

![CircleCI](https://img.shields.io/circleci/build/github/EasyMina/easyMina/main)

391 lines (317 loc) 12.8 kB
import express from 'express' import path from 'path' import { fileURLToPath } from 'url' import { marked } from 'marked' import axios from 'axios' import { Markdown } from './Markdown.mjs' import { html, css } from './templates/html.mjs' import { frontend, overview } from './templates/index.mjs' import fs from 'fs' import { printMessages } from '../helpers/mixed.mjs' export class Server { #config #app #state #container #environment #account #contract #encryption #markdown constructor( { server, validate } ) { this.#config = { server, validate } return true } init( { projectName, environment, account, contract, encryption } ) { this.#app = express() this.#state = this.#addState( { projectName } ) this.#container = this.#addContainer() this.#environment = environment this.#account = account this.#contract = contract this.#encryption = encryption this.#markdown = new Markdown() const [ messages, comments ] = this.#validateState( { 'state': this.#state } ) printMessages( { messages, comments } ) this.#addRoutes( { projectName } ) return this } start() { this.#app.listen( this.#config['server']['port'], () => {} ) return true } #addContainer() { return html .replace( '{{style}}', css ) } #addState( { projectName } ) { const state = { 'projectName': null, 'absoluteRoot': null, 'accounts': null, 'contracts': null, 'localO1js': null, 'smartContracts': null, 'buildFolder': null, 'publicFolder': null } state['projectName'] = projectName state['absoluteRoot'] = process.cwd() // this.#getRootAbsolutePath()['result'] state['accounts'] = [ 'Account1', 'Account2', 'Account3' ] state['contracts'] = [ 'Contract1', 'Contract2', 'Contract3' ] state['localO1js'] = `${state['absoluteRoot']}/node_modules/o1js/dist/web/index.js` state['smartContracts'] = [ 'SmartContract1', 'SmartContract2', 'SmartContract3' ] state['publicFolder'] = '' state['publicFolder'] += state['absoluteRoot'] + '/' state['publicFolder'] += this.#config['validate']['folders']['workdir']['name'] + '/' state['publicFolder'] += `${projectName}/` state['publicFolder'] += this.#config['validate']['folders']['workdir']['subfolders']['subfolders']['frontend']['name'] state['buildFolder'] = '' state['buildFolder'] += state['absoluteRoot'] + '/' state['buildFolder'] += this.#config['validate']['folders']['workdir']['name'] + '/' state['buildFolder'] += `${projectName}/` state['buildFolder'] += this.#config['validate']['folders']['workdir']['subfolders']['subfolders']['contracts']['name'] + '/' state['buildFolder'] += this.#config['server']['routes']['build']['source'] return state } #validateState() { const messages = [] const comments = [] if( this.#state['absoluteRoot'] === null ) { messages.push( `No 'package.json' file in root detected. 'npm init -y' ?. ` ) } const tmp = [ [ 'publicFolder', 'folder', true ], [ 'buildFolder', 'folder', true ], [ 'localO1js', 'file', false ] ] .forEach( a => { const [ key, type, required ] = a const path = this.#state[ key ] let msg = '' switch( type ) { case 'folder': if( !fs.existsSync( path ) ) { msg = `Folder '${path}' is not a valid path.` } else if( !fs.statSync( path ).isDirectory() ) { msg = `Folder '${path}' is not a valid directory.` } break case 'file': if( !fs.existsSync( path ) ) { msg = `File '${path}' is not a valid path.` } else if( !fs.statSync( path ).isFile() ) { msg = `File '${path}' is not a valid file.` } break default: console.log( `Unknown type with value '${type}'.` ) process.exit( 1 ) break } if( msg !== '' ) { if( required ) { messages.push( msg ) } else { comments.push( msg ) } } } ) return [ messages, comments ] } #addRoutes( { projectName } ) { // this.#addRouteBuild() // this.#addRouteOverview() // this.#addRoutePublic() this.#addApiGetAccounts() this.#addApiGetDeployedContracts( { projectName } ) this.#addApiGetLocalO1js() this.#addRouteIndex( { projectName } ) this.#addRouteMarkdownContractScripts( { projectName } ) this.#addRouteFrontend( { projectName } ) // this.#addRouteGetSmartContracts() return true } #addRouteFrontend( { projectName } ) { let folderPath = '' folderPath += this.#state['absoluteRoot'] + '/' folderPath += `${this.#config['validate']['folders']['workdir']['name']}/` folderPath += projectName + '/' folderPath += 'frontend' this.#app.use( '/', express.static( folderPath ) ) return true } #addRouteMarkdownContractScripts( { projectName } ) { const scripts = this.#environment .getScripts() Object .entries( scripts[ projectName ]['backend'] ) .filter( a => a[ 1 ]['mdUrl'] !== '' ) .forEach( a => { const [ key, value ] = a const file = value['mdUrl'] this.#app.get( `/${file}`, ( req, res ) => { let path = '' path += this.#state['absoluteRoot'] + '/' path += value['md'] const _insert = fs.readFileSync( path, 'utf-8' ) const html = this.#container .replace( '{{markdown}}', marked( _insert ) ) res.send( html ) } ) } ) return true } #addRouteIndex( { projectName } ) { this.#app.get( '/', ( req, res ) => { const accountTables = this.#markdown .createAccountGroupTables( { 'environment': this.#environment, 'account': this.#account, 'encryption': this.#encryption } ) const deployedContractTables = this.#markdown .createDeployedContractGroupTables( { 'environment': this.#environment, 'contract': this.#contract, 'encryption': this.#encryption } ) const projectTables = this.#markdown .createProjects( { projectName, 'environment': this.#environment } ) const scripts = this.#environment .getScripts() const _insert = overview .replace( '{{accountTables}}', accountTables ) .replace( '{{deployedContracts}}', deployedContractTables ) .replace( '{{projects}}', projectTables ) const html = this.#container .replace( '{{markdown}}', marked( _insert ) ) return res.send( html ) } ) return true } #getRootAbsolutePath() { const __filename = fileURLToPath( import.meta.url ) const __dirname = path.dirname( __filename ) const root = new Array( 10 ) .fill() .reduce( ( acc, a ) => { try { acc['_acc'] = path.resolve( acc['_acc'], '..' ) const files = fs.readdirSync( acc['_acc'] ) if( files.includes( 'package.json' ) ) { const tmp = fs.readFileSync( `${acc['_acc']}/package.json` ) const json = JSON.parse( tmp ) if( Object.hasOwn( json, 'main' ) && acc['result'] === null ) { acc['result'] = acc['_acc'] } } } catch( e ) {} return acc }, { '_acc': __dirname, 'result': null } ) return root } #addApiGetAccounts() { this.#app.get( this.#config['server']['routes']['getAccounts']['route'], ( req, res ) => { const availableDeyployers = this.#environment.getAccounts( { 'account': this.#account, 'encryption': this.#encryption } ) res.json( { 'data': availableDeyployers } ) } ) return true } #addApiGetDeployedContracts( { projectName } ) { this.#app.get( this.#config['server']['routes']['getContracts']['route'], ( req, res ) => { const contracts = this.#environment.getDeployedContracts( { 'contract': this.#contract, 'encryption': this.#encryption } ) let data = [] if( Object.hasOwn( contracts, projectName ) === false ) { } else { data = Object.keys( contracts[ projectName ] ) } res.json( { data } ) } ) this.#app.use( (req, res, next) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); next() } ) this.#app.get( `${this.#config['server']['routes']['getContractSourceCode']['route']}/:contractName`, ( req, res ) => { const contracts = this.#environment.getDeployedContracts( { 'contract': this.#contract, 'encryption': this.#encryption } ) const contractName = req.params.contractName.split( '.' )[ 0 ] const sourceCode = contracts[ projectName ][ contractName ]['sourceCode'] .split( "\n" ) .map( line => { const match = line.match( /import\s*\{\s*([^}]*)\s*\}\s*from\s*'o1js';/ ) if( match ){ const extractedContent = match[ 1 ].trim() return `const {${extractedContent}} = o1js` } else { return line } } ) .join( "\n" ) res.type('application/javascript') res.set( 'Content-Type', 'application/javascript' ) res.send( sourceCode ) } ) return true } #addApiGetLocalO1js() { this.#app.use( (req, res, next) => { res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); next(); } ) this.#app.get( this.#config['server']['routes']['getLocalO1js']['route'], ( req, res ) => { if( fs.existsSync( this.#state['localO1js'] ) ) { const fileContent = fs.readFileSync( this.#state['localO1js'], 'utf-8' ) res.set( 'Content-Type', 'application/javascript' ) res.send( fileContent ) } else { res .status( 404 ) .send( 'File not found' ) } } ) return true } }