UNPKG

4.33 kBPlain TextView Raw
1/**
2 * @license
3 * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at
5 * http://polymer.github.io/LICENSE.txt
6 * The complete set of authors may be found at
7 * http://polymer.github.io/AUTHORS.txt
8 * The complete set of contributors may be found at
9 * http://polymer.github.io/CONTRIBUTORS.txt
10 * Code distributed by Google as part of the polymer project is also
11 * subject to an additional IP rights grant found at
12 * http://polymer.github.io/PATENTS.txt
13 */
14
15// Be mindful of adding imports here, as this is on the hot path of all
16// commands.
17
18import * as fs from 'fs';
19import * as inquirer from 'inquirer';
20import {execSync} from 'mz/child_process';
21import * as path from 'path';
22import {ProjectConfig} from 'polymer-project-config';
23
24/**
25 * Check if the current shell environment is MinGW. MinGW can't handle some
26 * yeoman features, so we can use this check to downgrade gracefully.
27 */
28function checkIsMinGW(): boolean {
29 const isWindows = /^win/.test(process.platform);
30 if (!isWindows) {
31 return false;
32 }
33
34 // uname might not exist if using cmd or powershell,
35 // which would throw an exception
36 try {
37 const uname = execSync('uname -s').toString();
38 return !!/^mingw/i.test(uname);
39 } catch (error) {
40 return false;
41 }
42}
43
44/**
45 * A wrapper around inquirer prompt that works around its awkward (incorrect?)
46 * typings, and is intended for asking a single list-based question.
47 */
48export async function prompt(
49 question: {message: string, choices: inquirer.ChoiceType[]}):
50 Promise<string> {
51 // Some windows emulators (mingw) don't handle arrows correctly
52 // https://github.com/SBoudrias/Inquirer.js/issues/266
53 // Fall back to rawlist and use number input
54 // Credit to
55 // https://gist.github.com/geddski/c42feb364f3c671d22b6390d82b8af8f
56 const rawQuestion = {
57 type: checkIsMinGW() ? 'rawlist' : 'list',
58 name: 'foo',
59 message: question.message,
60 choices: question.choices,
61 };
62
63 // TODO(justinfagnani): the typings for inquirer appear wrong
64 // tslint:disable-next-line: no-any
65 const answers = await inquirer.prompt([rawQuestion] as any);
66 return answers.foo;
67}
68
69export function indent(str: string, additionalIndentation = ' ') {
70 return str.split('\n')
71 .map((s) => s ? additionalIndentation + s : '')
72 .join('\n');
73}
74
75export function dashToCamelCase(text: string): string {
76 return text.replace(/-([a-z])/g, (v) => v[1].toUpperCase());
77}
78
79/**
80 * Gets the root source files of the project, for analysis & linting.
81 *
82 * First looks for explicit options on the command line, then looks in
83 * the config file. If none are specified in either case, returns undefined.
84 *
85 * Returned file paths are relative from config.root.
86 */
87export async function getProjectSources(
88 options: {input?: Array<string>},
89 config: ProjectConfig): Promise<string[]|undefined> {
90 const globby = await import('globby');
91
92 if (options.input !== undefined && options.input.length > 0) {
93 // Files specified from the command line are relative to the current
94 // working directory (which is usually, but not always, config.root).
95 const absPaths = await globby(options.input, {root: process.cwd()});
96 return absPaths.map((p) => path.relative(config.root, p));
97 }
98 const candidateFiles = await globby(config.sources, {root: config.root});
99 candidateFiles.push(...config.fragments);
100 if (config.shell) {
101 candidateFiles.push(config.shell);
102 }
103 /**
104 * A project config will always have an entrypoint of
105 * `${config.root}/index.html`, even if the polymer.json file is
106 * totally blank.
107 *
108 * So we should only return config.entrypoint here if:
109 * - the user has specified other sources in their config file
110 * - and if the entrypoint ends with index.html, we only include it if it
111 * exists on disk.
112 */
113 if (candidateFiles.length > 0 && config.entrypoint) {
114 if (!config.entrypoint.endsWith('index.html') ||
115 fs.existsSync(config.entrypoint)) {
116 candidateFiles.push(config.entrypoint);
117 }
118 }
119 if (candidateFiles.length > 0) {
120 // Files in the project config are all absolute paths.
121 return [...new Set(
122 candidateFiles.map((absFile) => path.relative(config.root, absFile)))];
123 }
124 return undefined;
125}