Starting from scratch

Update: 🎉 has been updated to the cross-platform automated deployment tool. For details, see the open source ⚡ auto-deploy-app Automated Build deployment Tool

Results show

1. Pack and build the project to be deployed locally

2. Determine the remote deployment directory and release folder

3. Modify the configuration

4. Run automated deployment

5. View the remote effect

6. The original directory has been backed up (remote backup takes effect).

preface

During front-end project deployment, after nginx configuration is complete, you only need to upload the packaged files to the specified directory on the server.

Generally, the following methods are used:

  • xshellEtc command line tool upload
  • ftpAnd other visual tools to upload
  • jenkinsAnd other automated deployment services

For simple front-end projects, xshell and FTP are cumbersome in frequent deployment, while Jenkins and other automated deployment services require software installation in advance and familiarity with the configuration process. Therefore, we hope to use the local Node service to realize the upload of the front-end packaged files, which does not require the server to install additional procedures, but also helps us to achieve rapid upload and deployment, and helps us to understand node deeply.

PS: I have been preparing for the implementation of front-end automation deployment. Recently, I just saw the article mentioned in the article, and completed the implementation of node-SSH and Archiver. I would like to express my thanks to the authors of Node-SSH, Archiver and Inquirer.

Attached: NPM address

  • node-ssh
  • archiver
  • inquirer

start

1. Identify your needs

Requirements need to be identified before development, which can be summarized as the following according to common front-end deployment processes:

Define requirements for automated deployment according to the deployment process:

2. Preparation before development

2.1 Importing dependent Modules

Because you need to implement file compression, connect to a remote server, and implement remote command invocation, you need at least the following modules:

  • sshModule (common operations such as connecting to the server and calling commands)
  • File compressionModule (implementable.zipLocal packaging of common compressed files)
  • Command line selectionModule (enables the selection and use of multiple configuration item files)

Search for data and select Node-SSH, Archiver, and Inquirer to implement the above functions respectively.

# install dependencies
npm i node-ssh --save
npm i archiver --save
npm i inquirer --save
Copy the code

2.2 How to implement the specification

In order to realize reasonable demand of decoupling and logic clear/flexible, need to pay attention to the overall program logic, select encapsulates related functions, and freedom in the main program scheduling (flexible call/closed/modify related function), and the executive function give hints on the current to ensure the integrity of functions and abnormal hints.

Asynchronous processes such as file compression, file uploading, and remote command execution exist. Therefore, it is necessary to specify the sequence of function completion. That is, enable the current task in the callback of the completion of the pre-task. Here, ES6 Promise and ES7 syntactic sugar Async awiat are selected to achieve logical flow control.

Here is the completion of the program function to build the sorting work, the following into the project implementation.

3. Function realization

Project Catalog PreviewThe project catalog of the final result is presented here for reference:

  • node_modules
  • utils
    • compressfile.js
    • HandleCommand. Js
    • Handletime.js
    • Helper.js [Deployment Project Selection Tips]
    • Ssh.js connect to remote server
    • uploadfile.js
  • App.js [main program]
  • Config.js [config file]
  • Dist. Zip [compressed file] (current version will not be deleted automatically)
  • package.json
  • Readme. md [Project Introduction]

3.1 Compressing Local Files

CompressFile receives the directory to be compressed and the package generated file, which is passed in for local file compression.

Compressfile.js reference code

// compressFile.js
const fs = require('fs')
const archiver = require('archiver')

function compressFile (targetDir, localFile) {
  return new Promise((resolve, reject) = >{
    console.log('1- Compressing files... ')
    let output = fs.createWriteStream(localFile) // Create a file to write to the stream
    const archive = archiver('zip', {
      zlib: { level: 9 } // Set the compression level
    })
    output.on('close'.() = > {
      resolve(
        console.log('2- Compression done! A total of ' + (archive.pointer() / 1024 /1024).toFixed(3) + 'MB')
      )
    }).on('error'.(err) = > {
      reject(console.error('Compression failed', err))
    })
    archive.pipe(output) // Pipe archive data to a file
    archive.directory(targetDir, 'dist') // Store the target file and rename it
    archive.finalize() // Complete file appending to ensure that the write to the stream is complete})}module.exports = compressFile
Copy the code

3.2 Connecting a Remote Server

ConnectServe Receives information such as the REMOTE IP address, user name, and password to connect to the remote server. For details, see config.js.

Ssh.js reference code

// ssh.js
const node_ssh = require('node-ssh')
const ssh = new node_ssh()

function connectServe (sshInfo) {
  return new Promise((resolve, reject) = >{ ssh.connect({ ... sshInfo }).then(() = > {
      resolve(console.log('3' + sshInfo.host + 'Connection successful'))
    }).catch((err) = > {
      reject(console.error('3' + sshInfo.host + 'Connection failed', err))
    })
  })
}

module.exports = connectServe
Copy the code

3.3 Executing commands remotely

RunCommand receives the command to be executed and the remote path of the command to be executed. It is separated separately to facilitate the invocation of the main program and module encapsulation of functions such as file uploading, thus achieving decoupling effect.

HandleCommand. Js reference code

// handleCommand.js
// run Linux shell(SSH object, shell instruction, execution path)
function runCommand (ssh, command, path) {
  return new Promise((resolve, reject) = > {
    ssh.execCommand(command, {
      cwd: path
    }).then((res) = > {
      if (res.stderr) {
        reject(console.error(Command execution error: + res.stderr))
        process.exit()
      } else {
        resolve(console.log(command + 'Execution complete! ')}})})}module.exports = runCommand
Copy the code

3.4 Uploading Files

UploadFile receives system configuration parameters and local files to be uploaded, and uploads the local files to the specified server directory. HandleCommand. Js and handleTime. The directory with the same name is decompressed. For details, see config.js.

Uploadfile.js reference code

// uploadFile.js
const runCommand = require ('./handleCommand')
const getCurrentTime = require ('./handleTime')

// Upload files (SSH objects, configuration information, local files to be uploaded)
async function uploadFile (ssh, config, localFile) {
  return new Promise((resolve, reject) = > {
    console.log('4- Start file upload ')
    handleSourceFile(ssh, config)
    ssh.putFile(localFile, config.deployDir + config.targetFile).then(async () => {
      resolve(console.log('5- File upload completed '))},(err) = > {
      reject(console.error('5- Upload failed! ', err))
    })
  })
}

// Process source files (SSH objects, configuration information)
async function handleSourceFile (ssh, config) {
  if (config.openBackUp) {
    console.log('Remote backup enabled! ')
    await runCommand(
      ssh,
      `
      if [ -d ${config.releaseDir} ];
      then mv ${config.releaseDir} ${config.releaseDir}_${getCurrentTime()}
      fi
      `,
      config.deployDir)
  } else {
    console.log('Reminder: Remote backup not enabled! ')
    await runCommand(
      ssh,
      `
      if [ -d ${config.releaseDir} ];
      then mv ${config.releaseDir} /tmp/${config.releaseDir}_${getCurrentTime()}
      fi
      `,
      config.deployDir)
  }
}

module.exports = uploadFile
Copy the code

3.5 Time Processing

GetCurrentTime Gets and returns the current time, used for remote backup.

Handletime.js reference code

// Get the current time
function getCurrentTime () {
  const date = new Date
  const yyyy = date.getFullYear()
  const MM = coverEachUnit(date.getMonth() + 1)
  const dd = coverEachUnit(date.getDate())
  const HH = coverEachUnit(date.getHours())
  const mm = coverEachUnit(date.getMinutes())
  const ss = coverEachUnit(date.getSeconds())
  return `${yyyy}-${MM}-${dd}_${HH}:${mm}:${ss}`
}

// Convert one to two bits of time
function coverEachUnit (val) {
  return val < 10 ? '0' + val : val
}

module.exports = getCurrentTime
Copy the code

3.6 the main program

When the encapsulation of all functional modules is completed, asynchronous processes are processed with Promise, and then combined with async AWIAT implementation, which not only ensures the sequence of function implementation, but also makes function combination more concise and elegant.

The combination of customs clearance functions in the main function realizes the process of automatic deployment, and other functions are added later. The upgrade can be completed by introduction and combination in the main program.

App.js reference code

const config = require ('./config')
const helper = require ('./utils/helper')
const compressFile = require ('./utils/compressFile')
const sshServer = require ('./utils/ssh')
const uploadFile = require ('./utils/uploadFile')
const runCommand = require ('./utils/handleCommand')

// Main program (can be executed separately)
async function main () {
  try {
    console.log('Please make sure the file is unzipped as dist directory!! ')
    const SELECT_CONFIG = (await helper(config)).value // Configuration information for the selected deployment project
    console.log('You chose to deploy' + SELECT_CONFIG.name)
    const localFile =  __dirname + '/' + SELECT_CONFIG.targetFile // Local file to be uploaded
    SELECT_CONFIG.openCompress ? await compressFile(SELECT_CONFIG.targetDir, localFile) : ' ' / / compression
    await sshServer.connectServe(SELECT_CONFIG.ssh) / / the connection
    await uploadFile(sshServer.ssh, SELECT_CONFIG, localFile) / / upload
    await runCommand(sshServer.ssh, 'unzip ' + SELECT_CONFIG.targetFile, SELECT_CONFIG.deployDir) / /
    await runCommand(sshServer.ssh, 'mv dist ' + SELECT_CONFIG.releaseDir, SELECT_CONFIG.deployDir) // Change the file name
    await runCommand(sshServer.ssh, 'rm -f ' + SELECT_CONFIG.targetFile, SELECT_CONFIG.deployDir) / / delete
  } catch (err) {
    console.log('Deployment process error! ', err)
  } finally {
    process.exit()
  }
}

// run main
main()
Copy the code

3.7 Configuration Files

To facilitate automatic front-end deployment, extract key information and generate a configuration file. You only need to modify the configuration file to implement automatic deployment.

Config.js reference code

/* config.js Note: Ensure that the decompressed file directory is dist SSH: user information for connecting to the server targetDir: file directory to be compressed (effective after local compression is enabled) targetFile: OpenCompress: After this function is disabled, the openBackUp file will be directly uploaded to the openBackUp file in the same directory. After this function is enabled, the original directory name is changed and deployDir does not overwrite deployDir: specifies the remote deployment address releaseDir: Update the publishing directory name under the specified remote deployment address: 🎉 Multiple configuration information can be added, and the configuration information can be selected during automatic deployment. Run 🎉 The server connection port can be changed, and SSH private key and decryption password can be used for connection (ps: If you do not use this method, comment privateKey) 🎉 The module reference logic has been updated to change the remote backup time format to 'YYYY-MM-DD_hh: MM: SS' */

const config = [
  {
    name: 'project A - dev.ssh: {
      host: '192.168.0.110'.port: 22.username: 'root'.password: 'root'.// privateKey: 'E:/id_rsa', // SSH privateKey
      passphrase: '123456' // SSH private key corresponding decryption password (if no, set to '')
    },
    targetDir: 'E:/private/my-vue-cli/dist'.// Target compressed directory (relative address can be used)
    targetFile: 'dist.zip'.// Target file
    openCompress: true.// Whether to enable local compression
    openBackUp: true.// Whether to enable remote backup
    deployDir: '/home/node_test' + '/'.// Remote directory
    releaseDir: 'web' // Publish directory
  },
  {
    name: 'project A - prod'.ssh: {
      host: '192.168.0.110'.port: 22.username: 'root'.password: 'root'.privateKey: 'E:/id_rsa'.// SSH private key (do not enter if you do not use this method, just comment)
      passphrase: '123456' // SSH private key corresponding decryption password (if no, set to '')
    },
    targetDir: 'E:/private/my-vue-cli/dist'.// Target compressed directory (relative address can be used)
    targetFile: 'dist.zip'.// Target file
    openCompress: true.// Whether to enable local compression
    openBackUp: true.// Whether to enable remote backup
    deployDir: '/home/node_test' + '/'.// Remote directory
    releaseDir: 'web2' // Publish directory}]module.exports = config
Copy the code

use

Pull the source code, install dependencies, modify the configuration file, and run it

npm install
npm run deploy
Copy the code

The last

🎉 The project has been open source to Github welcome to download and use the follow-up will improve more functions 🎉 source code and project description

If you like, don’t forget star 😘. If you have any questions, please raise your questions about pr and issues.

Other articles:

React Hook implementation of online wallpaper site from scratch