Practice of front-end deployment of scaffold private network project

Posted by x_filed2k on Mon, 17 Jan 2022 21:42:53 +0100

preface

Front end scaffolding is an important tool to improve team efficiency in front-end engineering. Therefore, building scaffolding is an unavailable skill for front-end engineers, while there are relatively few scaffolds in the industry for deployment. Generally speaking, it is related to the relevant templates of the business, This paper aims to provide some scaffolding practice schemes related to front-end deployment, hoping to be helpful to students related to building engineering links.

framework

For a scaffold, it is not just a deployment scheme. Therefore, in the scaffold architecture design, the plug-in mode is adopted, and the required functional parts are provided through the plug-in mechanism

catalogue

  • packages

    • @pnw

      • cli
      • cli-service
      • cli-shared-utils
      • cli-ui
    • test
  • scripts

    • bootstrap.js
    • dev.js
    • prod.js
    • release.js
  • env.json

case

The @ pnw/cli scaffold is used to build the deployment directory, and the command pnw deploy is used to build the deployment directory. The ci/cd process is followed, where default Conf is mainly used for nginx construction, Dockerfile is used for image construction, and yaml file is mainly used for k8s related construction

Source code

cli

The core of the deployment part is deploy JS, and this part implements specific functions in cli plugin deploy

create.js

const { isDev } = require('../index');

console.log('isDev', isDev)

const { Creator } = isDev ? require('../../cli-service') : require('@pnw/cli-service');

const creator = new Creator();

module.exports = (name, targetDir, fetch) => {
    return creator.create(name, targetDir, fetch)
}

deploy.js

const { isDev } = require('../index');

const {
    path,
    stopSpinner,
    error,
    info
} = require('@pnw/cli-shared-utils');

const create = require('./create');

const { Service } = isDev ? require('../../cli-service') : require('@pnw/cli-service');

const { deployFn } = isDev ? require('../../cli-plugin-deploy') :require('@pnw/cli-plugin-deploy');

// console.log('deployFn', deployFn);

async function fetchDeploy(...args) {
    info('fetchDeploy Yes')
    const service = new Service();
    service.apply(deployFn);
    service.run(...args);
}


async function deploy(options) {
    // Customize the deploy ment content TODO
    info('deploy Yes')
    const targetDir = path.resolve(process.cwd(), '.');
    return await create('deploy', targetDir, fetchDeploy)
}

module.exports = (...args) => {
    return deploy(...args).catch(err => {
        stopSpinner(false)
        error(err)
    })
};

cli-plugin-deploy

Implement the core part of the deployment part, in which build, nginx, docker and yaml are mainly used to generate template content files

build.js

exports.build = config => {
    return `npm install
npm run build`
};

docker.js

exports.docker = config => {
    const {
        forward_name,
        env_directory
    } = config;
    return `FROM harbor.dcos.ncmp.unicom.local/platpublic/nginx:1.20.1
COPY ./dist /usr/share/nginx/html/${forward_name}/
COPY ./deploy/${env_directory}/default.conf /etc/nginx/conf.d/
EXPOSE 80`
}

nginx.js

exports.nginx = config => {
    return `client_max_body_size 1000m;
server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html;
        gzip_static on;
    }
}`
}

yaml.js

exports.yaml = config => {
  const {
    git_name
  } = config;
  return `apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${git_name}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ${git_name}
  template:
    metadata:
      labels:
        app: ${git_name}
    spec:
      containers:
        - name: ${git_name}
          image: harbor.dcos.ncmp.unicom.local/custom/${git_name}:1.0
          imagePullPolicy: Always
          resources:
            limits:
              cpu: 5
              memory: 10G
            requests:
              cpu: 1
              memory: 1G
          ports:
            - containerPort: 80`
}

index.js

// const { fs, path } = require('@pnw/cli-shared-utils');

const { TEMPLATES } = require('../constant');

/**
 * 
 * @param {*} template Template path
 * @param {*} config Parameters of injection template
 */
function generate(template, config) {
    // console.log('template', template);
    return require(`./${template}`).generateJSON(config);
}


function isExitTemplate(template) {
    return TEMPLATES.includes(template)
}

TEMPLATES.forEach(m => {
    Object.assign(exports, {
        [`${m}`]: require(`./${m}`)
    })
})



exports.createTemplate = (tempalteName, env_dirs) => {
    if( isExitTemplate(tempalteName) ) {
        return generate(tempalteName, {
            // git_name: 'fescreenrj',
            // forward_name: 'rj',
            env_dirs
        });
    } else {
        return `${tempalteName} is NOT A Template, Please SELECT A correct TEMPLATE`
    }
}

main.js

const {
    createTemplate
} = require('./__template__');

const { isDev } = require('../index');

console.log('isDev', isDev);

const {
    REPO_REG,
    PATH_REG
} = require('./reg');

const {
    TEMPLATES
} = require('./constant');

const {
    path,
    fs,
    inquirer,
    done,
    error
} = isDev ? require('../../cli-shared-utils') : require('@pnw/cli-shared-utils');

/**
 * 
 * @param {*} targetDir Absolute path to the destination folder
 * @param {*} fileName File name
 * @param {*} fileExt File extension
 * @param {*} data File content
 */
const createFile = async (targetDir, fileName, fileExt, data) => {
    // console.log('fileName', fileName);
    // console.log('fileExt', fileExt);
    let file = fileName + '.' + fileExt;
    if (!fileExt) file = fileName;
    // console.log('file', file)
    await fs.promises.writeFile(path.join(targetDir, file), data)
            .then(() => done(`create a file ${file}success`))
            .catch(err => {
                if (err) {
                    error(err);
                    return err;
                }
            });
}



/**
 * 
 * @param {*} targetDir The destination path address of the directory that needs to be created
 * @param {*} projectName Directory name to be created
 * @param {*} templateStr Configuration string in directory
 * @param {*} answers Parameters obtained from the command line
 */
const createCatalogue = async (targetDir, projectName, templateMap, answers) => {
    const templateKey = Object.keys(templateMap)[0],
        templateValue = Object.values(templateMap)[0];

    // console.log('templateKey', templateKey);

    // console.log('templateValue', templateValue);

    // Obtain various tool functions corresponding to the template
    const {
        yaml,
        nginx,
        build,
        docker
    } = require('./__template__')[`${templateKey}`];

    // Get environment folder
    const ENV = templateValue.ENV;

    console.log('path.join(targetDir, projectName)', targetDir, projectName)
    // 1. Create a folder
    await fs.promises.mkdir(path.join(targetDir, projectName)).then(() => {
            done(`Create project catalog ${projectName}success`);
            return true
        })
        .then((flag) => {
            // console.log('flag', flag);
            // Get Options for build
            const buildOptions = templateValue.FILE.filter(f => f.KEY == 'build')[0];

            // console.log('buildOptions', buildOptions);
            
            flag && createFile(path.join(targetDir, projectName), buildOptions[`NAME`], buildOptions[`EXT`], build());
        })
        .catch(err => {
            if (err) {
                error(err);
                return err;
            }
        });

    ENV.forEach(env => {
        fs.promises.mkdir(path.join(targetDir, projectName, env))
            .then(() => {
                done(`Create project catalog ${projectName}/${env}success`);
                return true;
            })
            .then(flag => {
                // Get Options for docker
                const dockerOptions = templateValue.FILE.filter(f => f.KEY == 'docker')[0];
                flag && createFile(path.join(targetDir, projectName, env), dockerOptions[`NAME`], dockerOptions[`EXT`], docker({
                    forward_name: answers[`forward_name`],
                    env_directory: env
                }));
                // Get Options for yaml
                const yamlOptions = templateValue.FILE.filter(f => f.KEY == 'yaml')[0];
                flag && createFile(path.join(targetDir, projectName, env), yamlOptions[`NAME`], yamlOptions[`EXT`], yaml({
                    git_name: answers[`repo_name`]
                }));
                // Get Options for nginx
                const nginxOptions = templateValue.FILE.filter(f => f.KEY == 'nginx')[0];
                flag && createFile(path.join(targetDir, projectName, env), nginxOptions[`NAME`], nginxOptions[`EXT`], nginx());
            })
            .catch(err => {
                if (err) {
                    error(err);
                    return err;
                }
            });
    });
}

/**
 * 
 * @param {*} projectName Generated directory name
 * @param {*} targetDir Absolute path
 */
 module.exports = async (projectName, targetDir) => {
    let options = [];
    async function getOptions() {
        return fs.promises.readdir(path.resolve(__dirname, './__template__')).then(files => files.filter(f => TEMPLATES.includes(f)))
    }

    options = await getOptions();

    console.log('options', options);

    const promptList = [{
            type: 'list',
            message: 'Please select the application template you need to deploy',
            name: 'template_name',
            choices: options
        },
        {
            type: 'checkbox',
            message: 'Please select the environment you need to deploy',
            name: 'env_dirs',
            choices: [{
                    value: 'dev',
                    name: 'development environment '
                },
                {
                    value: 'demo',
                    name: 'Demo environment'
                },
                {
                    value: 'production',
                    name: 'production environment '
                },
            ]
        },
        {
            type: 'input',
            name: 'repo_name',
            message: 'It is recommended to use the current git The abbreviation of the warehouse is used as the image name',
            filter: function (v) {
                return v.match(REPO_REG).join('')
            }
        },
        {
            type: 'input',
            name: 'forward_name',
            message: 'Please use url Name of the path rule',
            filter: function (v) {
                return v.match(PATH_REG).join('')
            }
        },
    ];

    inquirer.prompt(promptList).then(answers => {
        console.log('answers', answers);
        const {
            template_name
        } = answers;
        // console.log('templateName', templateName)
        // Get template string
        const templateStr = createTemplate(template_name, answers.env_dirs);
        // console.log('template', JSON.parse(templateStr));
        const templateMap = {
            [`${template_name}`]: JSON.parse(templateStr)
        }
        createCatalogue(targetDir, projectName, templateMap, answers);
    })
};

cli-service

Service and Creator are two base classes respectively, which are used to provide basic related services

Creator.js

const {
    path,
    fs,
    chalk,
    stopSpinner,
    inquirer,
    error
} = require('@pnw/cli-shared-utils');

class Creator {
    constructor() {

    }
    async create(projectName, targetDir, fetch) {
        // const cwd = process.cwd();
        // const inCurrent = projectName === '.';
        // const name = inCurrent ? path.relative('../', cwd) : projectName;
        // targetDir = path.resolve(cwd, name || '.');
        // if (fs.existsSync(targetDir)) {
        //     const {
        //         action
        //     } = await inquirer.prompt([{
        //         name: 'action',
        //         type: 'list',
        //         message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
        //         choices: [{
        //                 name: 'Overwrite',
        //                 value: 'overwrite'
        //             },
        //             {
        //                 name: 'Merge',
        //                 value: 'merge'
        //             },
        //             {
        //                 name: 'Cancel',
        //                 value: false
        //             }
        //         ]
        //     }])
        //     if (!action) {
        //         return
        //     } else if (action === 'overwrite') {
        //         console.log(`\nRemoving ${chalk.cyan(targetDir)}...`)
        //         await fs.remove(targetDir)
        //     }
        // }

        await fetch(projectName, targetDir);
    }
}

module.exports = Creator;

Service.js

const { isFunction } = require('@pnw/cli-shared-utils')

class Service {
    constructor() {
        this.plugins = [];
    }
    apply(fn) {
        if(isFunction(fn)) this.plugins.push(fn)
    }
    run(...args) {
        if( this.plugins.length > 0 ) {
            this.plugins.forEach(plugin => plugin(...args))
        }
    }
}

module.exports = Service;

cli-shared-utils

Share the tool library, converge the third-party and node related core modules to this, and unify the output and magic change

is.js

exports.isFunction= fn => typeof fn === 'function';

logger.js

const chalk = require('chalk');
const {
    stopSpinner
} = require('./spinner');

const format = (label, msg) => {
    return msg.split('\n').map((line, i) => {
        return i === 0 ?
            `${label} ${line}` :
            line.padStart(stripAnsi(label).length + line.length + 1)
    }).join('\n')
};

exports.log = (msg = '', tag = null) => {
    tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
};

exports.info = (msg, tag = null) => {
    console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg))
};

exports.done = (msg, tag = null) => {
    console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg))
};

exports.warn = (msg, tag = null) => {
    console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)))
};

exports.error = (msg, tag = null) => {
    stopSpinner()
    console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)))
    if (msg instanceof Error) {
        console.error(msg.stack)
    }
}

spinner.js

const ora = require('ora')
const chalk = require('chalk')

const spinner = ora()
let lastMsg = null
let isPaused = false

exports.logWithSpinner = (symbol, msg) => {
  if (!msg) {
    msg = symbol
    symbol = chalk.green('✔')
  }
  if (lastMsg) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  }
  spinner.text = ' ' + msg
  lastMsg = {
    symbol: symbol + ' ',
    text: msg
  }
  spinner.start()
}

exports.stopSpinner = (persist) => {
  if (!spinner.isSpinning) {
    return
  }

  if (lastMsg && persist !== false) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  } else {
    spinner.stop()
  }
  lastMsg = null
}

exports.pauseSpinner = () => {
  if (spinner.isSpinning) {
    spinner.stop()
    isPaused = true
  }
}

exports.resumeSpinner = () => {
  if (isPaused) {
    spinner.start()
    isPaused = false
  }
}

exports.failSpinner = (text) => {
  spinner.fail(text)
}

summary

The front-end engineering link is not only the construction of front-end application projects, but also needs to pay attention to the upstream and downstream related construction of the whole front-end, including but not limited to ui construction, test construction, deployment construction, etc. for front-end engineering, all things that can be abstracted into templates should be engineered, so as to reduce costs and increase efficiency and improve development experience and efficiency, Encourage each other!!!

reference resources

Topics: Front-end Operation & Maintenance