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 |
|
18 | import * as fs from 'fs';
|
19 | import * as inquirer from 'inquirer';
|
20 | import {execSync} from 'mz/child_process';
|
21 | import * as path from 'path';
|
22 | import {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 | */
|
28 | function 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 | */
|
48 | export 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 |
|
69 | export function indent(str: string, additionalIndentation = ' ') {
|
70 | return str.split('\n')
|
71 | .map((s) => s ? additionalIndentation + s : '')
|
72 | .join('\n');
|
73 | }
|
74 |
|
75 | export 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 | */
|
87 | export 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 | }
|