preface

It is a script that updates code to the server with a single command

The lightweight update solution originally came from the nuggets article, and then we implemented an update script from scratch, and it has been used in production environment for a long time, which is a very stable version. Personally, the lightweight update solution is very small development team

Now we switch to GitLab’s CI/CD. Therefore, this scheme is no longer our mainstream scheme, but it has been used all the way and solved the update problem stably, which is still very good

Advantages: Fast, stable, automatic backup of designated folders (high flexibility, but need to implement their own)

Cons: Manual rollback (automatic rollback requires coding), relatively uncanonical, no record left, sensitive data stored on the computer, configuration files ignored by Git

The core processes

  1. Identify and package projects
  2. throughnode-sshConnect the server on the line
  3. Compresses the packaging code by name
  4. Back up the previous code, delete the previous code package, and unzip the package
  5. Delete the package code and disconnect the SSH link

How to use

The code address

  1. Put the warehouse files into the project

  2. Install dependencies

npm install node-ssh inquirer archiver -D
Copy the code
  1. Modify theupload.config.jscontent
  2. Adding script Commands
"upload": "node build/upload.js"
Copy the code
  1. Run the name to verify that the function is normal

The core code

build/upload.js

const fs = require('fs')
const path = require('path')
const { NodeSSH } = require('node-ssh')
const archiver = require('archiver')
const inquirer = require('inquirer')
const exec = require('child_process').exec
const ssh = new NodeSSH()
const uploadFun = require('.. /upload.js')

/** * get the current platform */
let objName = process.argv[2] // Update the name
let startTime = null // The time when the program starts to update
// Get the upload server configuration
let config = uploadFun(objName)
const verifyList = [
  {
    type: 'input'.message: 'You are updating to the online environment, please confirm the interface domain name'.name: 'objName',
  },
]
inquirer.prompt(verifyList).then(() = > {
  uploadBuild()
})

function uploadBuild() {
  startTime = new Date(a)console.log(`${objName}Start updating ')
  let buildcmd = exec(config.buildScript, (error, stdout, stderr) = > {
    if(! error) {console.log('Packed and done', stdout)
      app()
    } else {
      console.error('Error in packing', stderr)
      process.exit(0)
    }
  })
  buildcmd.stdout.on('data'.(data) = > {
    console.log(data.toString())
  })
}

/** * SSH to the server */
function app() {
  ssh
    .connect({
      host: config.host,
      username: config.username,
      password: config.password,
    })
    .then((res) = > {
      // Upload the code compression package
      uploadData()
    })
    .catch((err) = > {
      console.log(err)
    })
}

/** * Upload code to compress existing code */
function uploadData() {
  // Create a file output stream
  let output = fs.createWriteStream(`${path.join(__dirname, '.. / ')}${config.buildPath}/${config.objname}.zip`)
  // Set the compression level
  let archive = archiver('zip', {
    zlib: {
      level: 8,}})// Archive warnings
  archive.on('warning'.function(err) {
    if (err.code === 'ENOENT') {
      console.warn('Stat failures and other non-blocking errors')}else {
      throw err
    }
  })
  // Error archiving
  archive.on('error'.function(err) {
    throw err
  })
  // Archive the output stream to a file through the pipe method
  archive.pipe(output)
  archive.directory(`${path.join(__dirname, '.. / ')}${config.buildPath}/${config.buildobj}`.'/')
  archive.finalize()
  // File output stream ends
  output.on('close'.function() {
    console.log(The total `${(archive.pointer() / 1024 / 1024).toFixed(2)}MB, complete source code compression ')
    ssh
      .putFile(
        `${path.join(__dirname, '.. / ')}${config.buildPath}/${config.objname}.zip`.`${config.uploadDir}/${config.objname}.zip`
      )
      .then(() = > {
        console.log('Program ZIP uploaded successfully, determine whether online backup is required')
        runcmd()
      })
      .catch((err) = > {
        console.log(err)
      })
  })
}

/** * Run the SSH command to check whether the current backup exists */
function runcmd() {
  ssh
    .execCommand('ls', {
      cwd: config.uploadDir,
    })
    .then((res) = > {
      if (res.stdout) {
        let fileList = res.stdout.split('\n')
        if (config.objname == config.backObject) {
          if (fileList.includes(config.objname)) {
            console.log('Current update to normal online environment, start backup')
            backupData()
          } else {
            console.log('Current update to normal online environment, and this is the first time, will skip backup')
            cmdunzip()
          }
        } else {
          console.log('This is a test environment, no backup required, just decompress and upload the zip package')
          cmdunzip()
        }
      } else if (res.stderr) {
        console.log('Failed to query specified directory')}else {
        console.log('SSH connection error')}}}/** * Backup project */
function backupData() {
  ssh
    .execCommand(`mv ${config.objname} backup/${config.objname}_backupThe ${new Date().getTime()}`, {
      cwd: config.uploadDir,
    })
    .then((res) = > {
      if (res.stderr) {
        console.log('Backup error occurred', res.stderr)
      } else {
        console.log('Complete the backup, extract the latest code')
        cmdunzip()
      }
    })
    .catch((err) = > {
      console.log('Backup has an unknown link error', err)
    })
}

/** * Extract the latest code zip */
function cmdunzip() {
  // Unzip
  ssh
    .execCommand(
      `rm -rf ${config.objname} && unzip -o -d ${config.uploadDir}/${config.objname} ${config.objname}.zip  && rm -f ${config.objname}.zip`,
      {
        cwd: config.uploadDir,
      }
    )
    .then(() = > {
      console.log('The project package is decompressed,${config.objname}Project deployment is successful! `)
      console.log('Project update durationThe ${(new Date().getTime() - startTime.getTime()) / 1000}s`)
      return deletelocalFile().then(() = > {
        console.log('Local cache ZIP cleared')
      })
    })
    .then(() = > {
      ssh
        .execCommand(`rm -rf ${config.objname}/static/.DS_Store`, {
          cwd: config.uploadDir,
        })
        .then(() = > {
          console.log('Online project.ds_store deleted')
          ssh.dispose()
          process.exit(0)
        })
        .catch((err) = > {
          console.log(err)
        })
    })

    .catch((err) = > {
      console.log('Error unzipping', err)
    })
}
/** * Delete the locally generated compressed package */
function deletelocalFile() {
  return new Promise((resolve, reject) = > {
    fs.unlink(`${path.join(__dirname, '.. / ')}${config.buildPath}/${config.objname}.zip`.(err) = > {
      if (err) {
        reject(err)
        throw err
      } else {
        resolve()
      }
    })
  })
}
Copy the code

The configuration file

upload.config.js

// Package the core configuration file
let Available = ['dist-a'.'dist-b'] // dist-a NPM run upload dist-a
/** * Get updated configuration *@param {String} ObjName Current update name *@returns* /
module.exports = (objName) = > {
  if(! Available.includes(objName)) {console.log('The update command you entered does not exist in the current project, please check the update name')
    process.exit(0)}return {
    host: 'xx.xx.xx.xx'.// Server address
    username: 'root'.password: 'xxxxxxxxxx'.buildPath: ' '.// Package the project address locally (multilayer path uses this)
    buildobj: 'dist'.// The name of the local package file
    uploadDir: '/xx/xx/xx'.// Server project address
    objname: objName, // Package the project name
    backObject: 'objName'.// The name of the backup folder
    buildScript: 'npm run build' // Update the command}}Copy the code

Trigger a command

Finally, add a command line to package.json to run the previous script file

 "scripts": {
    / /...
    "upload": "node build/upload.js"
  },
Copy the code

Rely on the version

Because the update script is in the project, additional dependencies need to be installed

Recommended version number

 "node-ssh": "^ 12.0.0"."inquirer": "^ 7.3.3." "."archiver": "^ 3.1.1." ".Copy the code

The actual use

NPM run upload XXXX // line code folder nameCopy the code

Upload.config.js () {upload: “/ upload.config.js”, “/ upload.config.js”, “/ upload.config.js”, “/ upload.config.js”

The last

Script files also have a high ceiling, you can optimize the backup part of the backup code generation rules, and add a rollback code script, you can achieve online unconscious rollback

If you encounter any problems in use, please go to QQ group 530496237 and blow water together