1 | 'use strict'
|
2 |
|
3 | require('console.table')
|
4 |
|
5 | const debug = require('debug')('now-pipeline')
|
6 | const R = require('ramda')
|
7 | const path = require('path')
|
8 | const fs = require('fs')
|
9 | const is = require('check-more-types')
|
10 | const la = require('lazy-ass')
|
11 | const Now = require('now-client')
|
12 | const combineDeploysAndAliases = require('./deploys-with-aliases')
|
13 | const moment = require('moment')
|
14 |
|
15 | function getPackage () {
|
16 | const packageFilename = path.join(process.cwd(), 'package.json')
|
17 | const pkg = require(packageFilename)
|
18 | return pkg
|
19 | }
|
20 |
|
21 | function addRelativeTimes (deploys) {
|
22 | return deploys.map(deploy => {
|
23 |
|
24 | deploy.when = moment(Number(deploy.created)).fromNow(true)
|
25 | return deploy
|
26 | })
|
27 | }
|
28 |
|
29 | function sortByAge (deploys) {
|
30 | return R.sortBy(
|
31 | R.compose(Number, R.prop('created'))
|
32 | )(deploys)
|
33 | }
|
34 |
|
35 | function 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,
|
87 | getPackage,
|
88 |
|
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 |
|
109 |
|
110 |
|
111 |
|
112 |
|
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 |
|
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 |
|
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 |
|
151 | params.package = JSON.parse(params['package.json'])
|
152 | delete params['package.json']
|
153 |
|
154 |
|
155 | delete params.package.devDependencies
|
156 |
|
157 | return now.createDeployment(params)
|
158 | .then(r => {
|
159 |
|
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 |
|
178 | const now = nowApi()
|
179 |
|
180 | module.exports = now
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|