Home Reference Source Repository

es6/Services/Repo.js

import os         from 'os'
import path       from 'path'
import Promise    from 'bluebird'

import cp         from '../utils/child_process'
import fs         from '../utils/fs'
import Expressive from '../utils/Expressive'
import Ensure     from '../utils/Ensure'
import Events     from '../utils/Events' 
import * as Errors from '../Errors'

import Registry   from './Registry'

export default class Repo extends Expressive {

  static exec ( dir, cmd ) {
    return cp.exec$(cmd, { cwd: dir }).then( result => {
      result.unshift(`$  ${cmd}\n`)
      return result
    }).catch( err => {
      if (err.code === "NOENT") {
        err = new Errors.Executable_Runtime_Error(`Could not execute cmd ${cmd} in ${dir}`)
        err.code = "NOENT"
      }
      throw err
    })
  }

  static git (dir, args) {
    return Repo.exec( dir, [ Repo.bin.git ].concat(args).join(' ') )
  }

  static npm (dir, args) {
    return Repo.exec( dir, [ Repo.bin.npm ].concat(args).join(' ') )
  }
  
  static safeDirName (name) {
    const delim = "-"
    name = name.replace(/\W+/g, delim).split('')
    
    if (name[0] === delim) name.shift()

    if (name[name.length-1] === delim) name.pop()

    return name.join('')
  }

  constructor (info) {
    super()
    this.info      = info
    this.makeUnavailable()
    return this.build()
  }

  get name () {
    return this.info.name
  }

  get id () {
    return this.info.id
  }

  update (fields) {
    Object.assign(this.info, fields)
    return Promise.resolve(this)
  }

  dir (...args) {
    args.unshift( Repo.safeDirName(this.name) )
    return Registry.appDir.apply(Registry, args)
  }

  git (...args) {
    return Repo.git(this.dir(), args)
  }

  npm (...args) {
    return Repo.npm(this.dir(), args)
  }

  exists () {
    return fs.stat$( this.dir() ).catch( err => {
      if ( err.code === "ENOENT" ) return false
      throw err
    })
  }

  makeAvailable () {
    this.available = true
    return this
  }

  makeUnavailable () {
    this.available = false
    return this
  }

  pull () {
    return this.git('pull')
  }

  install () {
    return this.npm('install')
  }

  build () {
    this.makeUnavailable()
    
    return this.exists()
      .bind(this)
      .then( exists => exists ? this.pull() : this.clone() )
      .then(this.install)
      .then(this.invalidateCache)
      .then(this.makeAvailable)
      .return(this)
   
  }

  clone () {
    return Repo.git( Registry.appDir(), [ 
        'clone'
      , '--depth=10'
      , `--branch=${this.info.branch || 'master'}`
      , this.info.repo
      , this.info.name
    ]).then( result => {
      this.emitClone(result)
      return result
    })
  }

  rm () {
    this.removeAllListeners()
    return fs.remove$(this.dir()).return(this)
  }

  get script () {
    return this.dir(this.pkg.main)
  }

  get pkg () {
    if (!this._pkg) {
      this._pkg = require( this.dir('package.json') )
    }
    return this._pkg
  }

  invalidateCache () {
    this.emitInvalidate( this.script )
    delete require.cache[ require.resolve( this.script ) ]
    delete require.cache[ require.resolve( this.dir('package.json') ) ]
    return Promise.resolve(this)
  }

}

Repo.Event = {
    install    : "repo:install"
  , rm         : "repo:rm"
  , clone      : "repo:clone"
  , build      : "repo:build"
  , pull       : "repo:pull"
  , invalidate : "cache:invalidate"
}

Events.methodize( Repo, Repo.Event )

Repo.bin = {
    git : Ensure.executable('git')
  , npm : Ensure.executable('npm')
}