UNPKG

netlify-cli

Version:

Netlify command line tool

247 lines 9.24 kB
import { once } from 'events'; import os from 'os'; import fs from 'fs'; import process from 'process'; import { format, inspect } from 'util'; import { Chalk } from 'chalk'; import chokidar from 'chokidar'; import decache from 'decache'; import WSL from 'is-wsl'; import debounce from 'lodash/debounce.js'; import terminalLink from 'terminal-link'; import { clearSpinner, startSpinner } from '../lib/spinner.js'; import getGlobalConfigStore from './get-global-config-store.js'; import getCLIPackageJson from './get-cli-package-json.js'; import { reportError } from './telemetry/report-error.js'; /** The parsed process argv without the binary only arguments and flags */ const argv = process.argv.slice(2); /** * Chalk instance for CLI that can be initialized with no colors mode * needed for json outputs where we don't want to have colors * @param {boolean} noColors - disable chalk colors * @return {import('chalk').ChalkInstance} - default or custom chalk instance */ // @ts-expect-error TS(7006) FIXME: Parameter 'noColors' implicitly has an 'any' type. const safeChalk = function (noColors) { if (noColors) { const colorlessChalk = new Chalk({ level: 0 }); return colorlessChalk; } return new Chalk(); }; export const chalk = safeChalk(argv.includes('--json')); /** * Adds the filler to the start of the string * @param {string} str * @param {number} count * @param {string} [filler] * @returns {string} */ // @ts-expect-error TS(7006) FIXME: Parameter 'str' implicitly has an 'any' type. export const padLeft = (str, count, filler = ' ') => str.padStart(str.length + count, filler); const platform = WSL ? 'wsl' : os.platform(); const arch = os.arch() === 'ia32' ? 'x86' : os.arch(); const { name, version: packageVersion } = await getCLIPackageJson(); export const version = packageVersion; export const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process.version}`; /** A list of base command flags that needs to be sorted down on documentation and on help pages */ const BASE_FLAGS = new Set(['--debug', '--http-proxy', '--http-proxy-certificate-filename']); export const NETLIFY_CYAN = chalk.rgb(40, 180, 170); export const NETLIFYDEV = `${chalk.greenBright('◈')} ${NETLIFY_CYAN('Netlify Dev')} ${chalk.greenBright('◈')}`; export const NETLIFYDEVLOG = chalk.greenBright('◈'); export const NETLIFYDEVWARN = chalk.yellowBright('◈'); export const NETLIFYDEVERR = chalk.redBright('◈'); export const BANG = process.platform === 'win32' ? '»' : '›'; /** * Sorts two options so that the base flags are at the bottom of the list * @param {import('commander').Option} optionA * @param {import('commander').Option} optionB * @returns {number} * @example * options.sort(sortOptions) */ // @ts-expect-error TS(7006) FIXME: Parameter 'optionA' implicitly has an 'any' type. export const sortOptions = (optionA, optionB) => { // base flags should be always at the bottom if (BASE_FLAGS.has(optionA.long) || BASE_FLAGS.has(optionB.long)) { return -1; } return optionA.long.localeCompare(optionB.long); }; // Poll Token timeout 5 Minutes const TOKEN_TIMEOUT = 3e5; export const pollForToken = async ({ api, ticket, }) => { const spinner = startSpinner({ text: 'Waiting for authorization...' }); try { const accessToken = await api.getAccessToken(ticket, { timeout: TOKEN_TIMEOUT }); if (!accessToken) { return logAndThrowError('Could not retrieve access token'); } return accessToken; } catch (error_) { // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. if (error_.name === 'TimeoutError') { return logAndThrowError(`Timed out waiting for authorization. If you do not have a ${chalk.bold.greenBright('Netlify')} account, please create one at ${chalk.magenta('https://app.netlify.com/signup')}, then run ${chalk.cyanBright('netlify login')} again.`); } else { return logAndThrowError(error_); } } finally { clearSpinner({ spinner }); } }; export const getToken = async (tokenFromOptions) => { // 1. First honor command flag --auth if (tokenFromOptions) { return [tokenFromOptions, 'flag']; } // 2. then Check ENV var const { NETLIFY_AUTH_TOKEN } = process.env; if (NETLIFY_AUTH_TOKEN && NETLIFY_AUTH_TOKEN !== 'null') { return [NETLIFY_AUTH_TOKEN, 'env']; } // 3. If no env var use global user setting const globalConfig = await getGlobalConfigStore(); const userId = globalConfig.get('userId'); const tokenFromConfig = globalConfig.get(`users.${userId}.auth.token`); if (tokenFromConfig) { return [tokenFromConfig, 'config']; } return [null, 'not found']; }; // 'api' command uses JSON output by default // 'functions:invoke' need to return the data from the function as is const isDefaultJson = () => argv[0] === 'functions:invoke' || (argv[0] === 'api' && !argv.includes('--list')); /** * logs a json message */ export const logJson = (message = '') => { if (argv.includes('--json') || isDefaultJson()) { process.stdout.write(JSON.stringify(message, null, 2)); } }; export const log = (message = '', ...args) => { // If --silent or --json flag passed disable logger if (argv.includes('--json') || argv.includes('--silent') || isDefaultJson()) { return; } message = typeof message === 'string' ? message : inspect(message); process.stdout.write(`${format(message, ...args)}\n`); }; export const logPadded = (message = '', ...args) => { log(''); log(message, ...args); log(''); }; /** * logs a warning message */ export const warn = (message = '') => { const bang = chalk.yellow(BANG); log(` ${bang} Warning: ${message}`); }; const toError = (val) => { if (val instanceof Error) return val; if (typeof val === 'string') return new Error(val); const err = new Error(inspect(val)); err.stack = undefined; return err; }; export const logAndThrowError = (message) => { const err = toError(message); void reportError(err, { severity: 'error' }); throw err; }; export const logError = (message) => { const err = toError(message); const bang = chalk.red(BANG); if (process.env.DEBUG) { process.stderr.write(` ${bang} Warning: ${err.stack?.split('\n').join(`\n ${bang} `)}\n`); } else { process.stderr.write(` ${bang} ${chalk.red(`${err.name}:`)} ${err.message}\n`); } }; export const exit = (code = 0) => { process.exit(code); }; export const normalizeConfig = (config) => { // Unused var here is in order to omit 'publish' from build config const { publish, publishOrigin, ...build } = config.build; return publishOrigin === 'default' ? { ...config, build } : config; }; const DEBOUNCE_WAIT = 100; /** * Adds a file watcher to a path or set of paths and debounces the events. */ export const watchDebounced = async (target, { depth, ignored = [], onAdd = noOp, onChange = noOp, onUnlink = noOp }) => { const baseIgnores = [/\/(node_modules|.git)\//]; const watcher = chokidar.watch(target, { depth, ignored: [...baseIgnores, ...ignored], ignoreInitial: true }); await once(watcher, 'ready'); let onChangeQueue = []; let onAddQueue = []; let onUnlinkQueue = []; const debouncedOnChange = debounce(() => { onChange(onChangeQueue); onChangeQueue = []; }, DEBOUNCE_WAIT); const debouncedOnAdd = debounce(() => { onAdd(onAddQueue); onAddQueue = []; }, DEBOUNCE_WAIT); const debouncedOnUnlink = debounce(() => { onUnlink(onUnlinkQueue); onUnlinkQueue = []; }, DEBOUNCE_WAIT); watcher .on('change', (path) => { // @ts-expect-error decache(path); onChangeQueue.push(path); debouncedOnChange(); }) .on('unlink', (path) => { // @ts-expect-error decache(path); onUnlinkQueue.push(path); debouncedOnUnlink(); }) .on('add', (path) => { // @ts-expect-error decache(path); onAddQueue.push(path); debouncedOnAdd(); }); return watcher; }; export const getTerminalLink = (text, url) => terminalLink(text, url, { fallback: () => `${text} (${url})` }); export const isNodeError = (err) => err instanceof Error; export const nonNullable = (value) => value !== null && value !== undefined; export const noOp = () => { // no-op }; export class GitHubAPIError extends Error { status; constructor(status, message) { super(message); this.status = status; this.name = 'GitHubAPIError'; } } export const checkFileForLine = (filename, line) => { let filecontent = ''; try { filecontent = fs.readFileSync(filename, 'utf8'); } catch (error_) { return logAndThrowError(error_); } return !!filecontent.match(line); }; export const TABTAB_CONFIG_LINE = '[[ -f ~/.config/tabtab/__tabtab.zsh ]] && . ~/.config/tabtab/__tabtab.zsh || true'; export const AUTOLOAD_COMPINIT = 'autoload -U compinit; compinit'; //# sourceMappingURL=command-helpers.js.map