UNPKG

7.28 kBJavaScriptView Raw
1const { Command } = require('@oclif/command')
2const API = require('netlify')
3const merge = require('lodash.merge')
4const { format, inspect } = require('util')
5const { URL } = require('url')
6const { track, identify } = require('./telemetry')
7const openBrowser = require('./open-browser')
8const StateConfig = require('./state-config')
9const globalConfig = require('./global-config')
10const findRoot = require('./find-root')
11const chalkInstance = require('./chalk')
12const resolveConfig = require('@netlify/config')
13const getConfigPath = require('@netlify/config').getConfigPath
14
15const argv = require('minimist')(process.argv.slice(2))
16const { NETLIFY_AUTH_TOKEN, NETLIFY_API_URL } = process.env
17
18// Netlify CLI client id. Lives in bot@netlify.com
19// Todo setup client for multiple environments
20const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
21
22class BaseCommand extends Command {
23 constructor(...args) {
24 super(...args)
25 }
26 // Initialize context
27 async init(_projectRoot) {
28 const cwd = argv.cwd || process.cwd()
29 const projectRoot = findRoot(_projectRoot || cwd) // if calling programmatically, can use a supplied root, else in normal CLI context it just uses process.cwd()
30 // Grab netlify API token
31 const authViaFlag = getAuthArg(argv)
32
33 const [token] = this.getConfigToken(authViaFlag)
34
35 // Read new netlify.toml/yml/json
36 const configPath = await getConfigPath(argv.config, cwd)
37 const config = await resolveConfig(configPath, {
38 cwd: cwd,
39 context: argv.context
40 })
41
42 // Get site id & build state
43 const state = new StateConfig(projectRoot)
44
45 const apiOpts = {}
46 if (NETLIFY_API_URL) {
47 const apiUrl = new URL(NETLIFY_API_URL)
48 apiOpts.scheme = apiUrl.protocol.substring(0, apiUrl.protocol.length - 1)
49 apiOpts.host = apiUrl.host
50 apiOpts.pathPrefix = NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
51 }
52
53 this.netlify = {
54 // api methods
55 api: new API(token || '', apiOpts),
56 // current site context
57 site: {
58 root: projectRoot,
59 configPath: configPath,
60 get id() {
61 return state.get('siteId')
62 },
63 set id(id) {
64 state.set('siteId', id)
65 }
66 },
67 // Configuration from netlify.[toml/yml]
68 config: config,
69 // global cli config
70 globalConfig: globalConfig,
71 // state of current site dir
72 state: state
73 }
74 }
75
76 async isLoggedIn() {
77 try {
78 await this.netlify.api.getCurrentUser()
79 return true
80 } catch (_) {
81 return false
82 }
83 }
84
85 logJson(message = '', ...args) {
86 /* Only run json logger when --json flag present */
87 if (!argv.json) {
88 return
89 }
90 process.stdout.write(JSON.stringify(message, null, 2))
91 }
92
93 log(message = '', ...args) {
94 /* If --silent or --json flag passed disable logger */
95 if (argv.silent || argv.json) {
96 return
97 }
98 message = typeof message === 'string' ? message : inspect(message)
99 process.stdout.write(format(message, ...args) + '\n')
100 }
101
102 /* Modified flag parser to support global --auth, --json, & --silent flags */
103 parse(opts, argv = this.argv) {
104 /* Set flags object for commands without flags */
105 if (!opts.flags) {
106 opts.flags = {}
107 }
108 /* enrich parse with global flags */
109 const globalFlags = {}
110 if (!opts.flags.silent) {
111 globalFlags['silent'] = {
112 parse: (b, _) => b,
113 description: 'Silence CLI output',
114 allowNo: false,
115 type: 'boolean'
116 }
117 }
118 if (!opts.flags.json) {
119 globalFlags['json'] = {
120 parse: (b, _) => b,
121 description: 'Output return values as JSON',
122 allowNo: false,
123 type: 'boolean'
124 }
125 }
126 if (!opts.flags.auth) {
127 globalFlags['auth'] = {
128 parse: (b, _) => b,
129 description: 'Netlify auth token',
130 input: [],
131 multiple: false,
132 type: 'option'
133 }
134 }
135
136 // enrich with flags here
137 opts.flags = Object.assign({}, opts.flags, globalFlags)
138
139 return require('@oclif/parser').parse(
140 argv,
141 Object.assign(
142 {},
143 {
144 context: this
145 },
146 opts
147 )
148 )
149 }
150
151 get chalk() {
152 // If --json flag disable chalk colors
153 return chalkInstance(argv.json)
154 }
155 /**
156 * Get user netlify API token
157 * @param {string} - [tokenFromFlag] - value passed in by CLI flag
158 * @return {[string, string]} - tokenValue & location of resolved Netlify API token
159 */
160 getConfigToken(tokenFromFlag) {
161 // 1. First honor command flag --auth
162 if (tokenFromFlag) {
163 return [tokenFromFlag, 'flag']
164 }
165 // 2. then Check ENV var
166 if (NETLIFY_AUTH_TOKEN && NETLIFY_AUTH_TOKEN !== 'null') {
167 return [NETLIFY_AUTH_TOKEN, 'env']
168 }
169 // 3. If no env var use global user setting
170 const userId = globalConfig.get('userId')
171 const tokenFromConfig = globalConfig.get(`users.${userId}.auth.token`)
172 if (tokenFromConfig) {
173 return [tokenFromConfig, 'config']
174 }
175 return [null, 'not found']
176 }
177
178 async authenticate(tokenFromFlag) {
179 const [token] = this.getConfigToken(tokenFromFlag)
180 if (!token) {
181 return this.expensivelyAuthenticate()
182 } else {
183 return token
184 }
185 }
186
187 async expensivelyAuthenticate() {
188 const webUI = process.env.NETLIFY_WEB_UI || 'https://app.netlify.com'
189 this.log(`Logging into your Netlify account...`)
190
191 // Create ticket for auth
192 const ticket = await this.netlify.api.createTicket({
193 clientId: CLIENT_ID
194 })
195
196 // Open browser for authentication
197 const authLink = `${webUI}/authorize?response_type=ticket&ticket=${ticket.id}`
198
199 this.log(`Opening ${authLink}`)
200 await openBrowser(authLink)
201
202 const accessToken = await this.netlify.api.getAccessToken(ticket)
203
204 if (!accessToken) {
205 this.error('Could not retrieve access token')
206 }
207
208 const user = await this.netlify.api.getCurrentUser()
209 const userID = user.id
210
211 const userData = merge(this.netlify.globalConfig.get(`users.${userID}`), {
212 id: userID,
213 name: user.full_name,
214 email: user.email,
215 auth: {
216 token: accessToken,
217 github: {
218 user: undefined,
219 token: undefined
220 }
221 }
222 })
223 // Set current userId
224 this.netlify.globalConfig.set('userId', userID)
225 // Set user data
226 this.netlify.globalConfig.set(`users.${userID}`, userData)
227
228 const email = user.email
229 await identify({
230 name: user.full_name,
231 email: email
232 }).then(() => {
233 return track('user_login', {
234 email: email
235 })
236 })
237
238 // Log success
239 this.log()
240 this.log(`${this.chalk.greenBright('You are now logged into your Netlify account!')}`)
241 this.log()
242 this.log(`Run ${this.chalk.cyanBright('netlify status')} for account details`)
243 this.log()
244 this.log(`To see all available commands run: ${this.chalk.cyanBright('netlify help')}`)
245 this.log()
246 return accessToken
247 }
248}
249
250function getAuthArg(cliArgs) {
251 // If deploy command. Support shorthand 'a' flag
252 if (cliArgs && cliArgs._ && cliArgs._[0] === 'deploy') {
253 return cliArgs.auth || cliArgs.a
254 }
255 return cliArgs.auth
256}
257
258module.exports = BaseCommand