UNPKG

6.11 kBJavaScriptView Raw
1'use strict'
2
3require('console.table')
4
5const debug = require('debug')('now-pipeline')
6const R = require('ramda')
7const path = require('path')
8const fs = require('fs')
9const is = require('check-more-types')
10const la = require('lazy-ass')
11const Now = require('now-client')
12const combineDeploysAndAliases = require('./deploys-with-aliases')
13const moment = require('moment')
14
15function getPackage () {
16 const packageFilename = path.join(process.cwd(), 'package.json')
17 const pkg = require(packageFilename)
18 return pkg
19}
20
21function addRelativeTimes (deploys) {
22 return deploys.map(deploy => {
23 // relative time without "ago" suffix
24 deploy.when = moment(Number(deploy.created)).fromNow(true)
25 return deploy
26 })
27}
28
29function sortByAge (deploys) {
30 return R.sortBy(
31 R.compose(Number, R.prop('created'))
32 )(deploys)
33}
34
35function nowApi () {
36 const authToken = process.env.NOW_TOKEN
37 if (!authToken) {
38 console.log('WARNING: Cannot find NOW_TOKEN environment variable')
39 }
40
41 const now = Now(authToken)
42
43 function wait (seconds) {
44 return new Promise((resolve, reject) => {
45 setTimeout(resolve, seconds * 1000)
46 })
47 }
48
49 function checkDeploy (id) {
50 la(is.unemptyString(id), 'expected deploy id', id)
51 return now.getDeployment(id)
52 }
53
54 function getDeploysAndAliases () {
55 return Promise.all([
56 now.getDeployments(),
57 now.getAliases()
58 ]).then(([deploys, aliases]) => combineDeploysAndAliases({deploys, aliases}))
59 }
60
61 function isDeploying (state) {
62 return state === 'DEPLOYING' || state === 'BOOTED' || state === 'BUILDING'
63 }
64
65 function waitUntilDeploymentReady (id, secondsRemaining) {
66 la(is.number(secondsRemaining), 'wrong waiting limit', secondsRemaining)
67 const sleepSeconds = 5
68 return checkDeploy(id)
69 .then(r => {
70 console.log(r.state, r.host, 'limit', secondsRemaining, 'seconds')
71 if (r.state === 'READY') {
72 return r
73 }
74 if (isDeploying(r.state)) {
75 if (secondsRemaining < sleepSeconds) {
76 throw new Error('Deploy timed out\n' + JSON.stringify(r))
77 }
78 return wait(sleepSeconds)
79 .then(() => waitUntilDeploymentReady(id, secondsRemaining - sleepSeconds))
80 }
81 throw new Error('Something went wrong with the deploy\n' + JSON.stringify(r))
82 })
83 }
84
85 const api = {
86 now, // expose the actual now client
87 getPackage,
88 // lists current deploy optionally limited with given predicate
89 deployments (filter) {
90 if (is.string(filter)) {
91 filter = R.propEq('name', filter)
92 }
93 filter = filter || R.T
94 return getDeploysAndAliases()
95 .then(sortByAge)
96 .then(addRelativeTimes)
97 .then(R.filter(filter))
98 },
99 aliases () {
100 return now.getAliases()
101 },
102 remove (id) {
103 la(is.unemptyString(id), 'expected deployment id', id)
104 debug('deleting deployment %s', id)
105 return now.deleteDeployment(id)
106 },
107 /**
108 deploys given filenames. Returns object with result
109 {
110 uid: 'unique id',
111 host: 'now-pipeline-test-lqsibottrb.now.sh',
112 state: 'READY'
113 }
114 */
115 deploy (filenames) {
116 debug('deploying %d files', filenames.length)
117 debug(filenames)
118
119 la(is.strings(filenames), 'missing file names', filenames)
120 la(is.not.empty(filenames), 'expected list of files', filenames)
121
122 // Files not required, but might be checked for by la
123 const optionalFiles = ['.npmignore']
124
125 filenames.forEach(name => {
126 if (!(optionalFiles.includes(name))) {
127 la(fs.existsSync(name), 'cannot find file', name)
128 }
129 })
130
131 const isPackageJson = R.test(/package\.json$/)
132 const packageJsonPresent = R.any(isPackageJson)
133 la(packageJsonPresent(filenames),
134 'missing package.json file in the list', filenames)
135
136 const packageJsonFilename = filenames.find(isPackageJson)
137 const packageJsonFolder = path.dirname(packageJsonFilename)
138 debug('package.json filename is', packageJsonFilename)
139 debug('in folder', packageJsonFolder)
140
141 // TODO make sure all files exist
142
143 const sources = R.map(name => fs.readFileSync(name, 'utf8'))(filenames)
144 const names = R.map(filename => {
145 return path.relative(packageJsonFolder, filename)
146 })(filenames)
147 debug('sending files', names)
148
149 const params = R.zipObj(names, sources)
150 // parsed JSON object
151 params.package = JSON.parse(params['package.json'])
152 delete params['package.json']
153
154 // we do not need dev dependencies in the deployed server
155 delete params.package.devDependencies
156
157 return now.createDeployment(params)
158 .then(r => {
159 // TODO make an option
160 const maxWaitSeconds = 60 * 10
161 return waitUntilDeploymentReady(r.uid, maxWaitSeconds)
162 })
163 .catch(r => {
164 if (is.error(r)) {
165 console.error('error during deployment')
166 console.error(r.message)
167 return Promise.reject(r)
168 }
169 console.error('error')
170 console.error(r.response.data)
171 return Promise.reject(new Error(r.response.data.err.message))
172 })
173 }
174 }
175 return api
176}
177
178const now = nowApi()
179
180module.exports = now
181
182//
183// examples
184//
185// function showDeploysForProject () { // eslint-disable-line no-unused-vars
186// const name = 'now-pipeline-test'
187// now.deployments(R.propEq('name', name))
188// .then(console.table).catch(console.error)
189// }
190
191// function showAllDeploys () { // eslint-disable-line no-unused-vars
192// now.deployments().then(console.table).catch(console.error)
193// }
194
195// function deployTest () { // eslint-disable-line no-unused-vars
196// const relative = require('path').join.bind(null, __dirname)
197// const files = [
198// relative('../test/package.json'),
199// relative('../test/index.js')
200// ]
201// return now.deploy(files)
202// }
203
204// showAllDeploys()
205// showDeploysForProject()
206
207// deployTest()
208// .then(result => {
209// console.log('deployment done with result', result)
210// })
211// .catch(console.error)