1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import * as commandLineArgs from 'command-line-args';
|
19 | import {sep as pathSeperator} from 'path';
|
20 | import * as logging from 'plylog';
|
21 | import {ProjectConfig, ProjectOptions} from 'polymer-project-config';
|
22 |
|
23 | import {globalArguments, mergeArguments} from './args';
|
24 | import {AnalyzeCommand} from './commands/analyze';
|
25 | import {BuildCommand} from './commands/build';
|
26 | import {Command} from './commands/command';
|
27 | import {HelpCommand} from './commands/help';
|
28 | import {InitCommand} from './commands/init';
|
29 | import {InstallCommand} from './commands/install';
|
30 | import {LintCommand} from './commands/lint';
|
31 | import {ServeCommand} from './commands/serve';
|
32 | import {TestCommand} from './commands/test';
|
33 | import {dashToCamelCase} from './util';
|
34 |
|
35 | import commandLineCommands = require('command-line-commands');
|
36 | import {ParsedCommand} from 'command-line-commands';
|
37 |
|
38 | const logger = logging.getLogger('cli.main');
|
39 |
|
40 | process.on('uncaughtException', (error: null|undefined|Partial<Error>) => {
|
41 | logger.error(`Uncaught exception: ${error}`);
|
42 | if (error && error.stack) {
|
43 | logger.error(error.stack);
|
44 | }
|
45 | process.exit(1);
|
46 | });
|
47 |
|
48 | process.on('unhandledRejection', (error: null|undefined|Partial<Error>) => {
|
49 | logger.error(`Promise rejection: ${error}`);
|
50 | if (error && error.stack) {
|
51 | logger.error(error.stack);
|
52 | }
|
53 | process.exit(1);
|
54 | });
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | function parseCLIArgs(commandOptions: any): {[name: string]: string} {
|
64 | commandOptions = commandOptions && commandOptions['_all'];
|
65 | const parsedOptions = Object.assign({}, commandOptions);
|
66 |
|
67 | if (commandOptions['extra-dependencies']) {
|
68 | parsedOptions.extraDependencies = commandOptions['extra-dependencies'];
|
69 | }
|
70 | if (commandOptions.fragment) {
|
71 | parsedOptions.fragments = commandOptions.fragment;
|
72 | }
|
73 |
|
74 | return parsedOptions;
|
75 | }
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | function objectDashToCamelCase<V>(input: {[key: string]: V}) {
|
81 | const output: {[key: string]: V} = {};
|
82 | for (const key of Object.keys(input)) {
|
83 | output[dashToCamelCase(key)] = input[key];
|
84 | }
|
85 | return output;
|
86 | }
|
87 |
|
88 |
|
89 | export class PolymerCli {
|
90 | commands: Map<string, Command> = new Map();
|
91 | args: string[];
|
92 | defaultConfigOptions: ProjectOptions;
|
93 |
|
94 | constructor(args: string[], configOptions?: ProjectOptions) {
|
95 |
|
96 |
|
97 | if (args.indexOf('--quiet') > -1 || args.indexOf('-q') > -1) {
|
98 | logging.setQuiet();
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 | if (args.indexOf('--verbose') > -1 || args.indexOf('-v') > -1) {
|
104 | logging.setVerbose();
|
105 | }
|
106 |
|
107 | this.args = args;
|
108 | logger.debug('got args:', {args: args});
|
109 |
|
110 | if (typeof configOptions !== 'undefined') {
|
111 | this.defaultConfigOptions = configOptions;
|
112 | logger.debug(
|
113 | 'got default config from constructor argument:',
|
114 | {config: this.defaultConfigOptions});
|
115 | } else {
|
116 | this.defaultConfigOptions =
|
117 | ProjectConfig.loadOptionsFromFile('polymer.json')!;
|
118 | if (this.defaultConfigOptions) {
|
119 | logger.debug(
|
120 | 'got default config from polymer.json file:',
|
121 | {config: this.defaultConfigOptions});
|
122 | } else {
|
123 | logger.debug('no polymer.json file found, no config loaded');
|
124 | }
|
125 | }
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | this.defaultConfigOptions = this.defaultConfigOptions || {};
|
133 | this.defaultConfigOptions.extraDependencies =
|
134 | this.defaultConfigOptions.extraDependencies || [];
|
135 | this.defaultConfigOptions.extraDependencies.unshift(
|
136 | `bower_components${pathSeperator}webcomponentsjs${pathSeperator}*.js`);
|
137 |
|
138 | this.addCommand(new AnalyzeCommand());
|
139 | this.addCommand(new BuildCommand());
|
140 | this.addCommand(new HelpCommand(this.commands));
|
141 | this.addCommand(new InitCommand());
|
142 | this.addCommand(new InstallCommand());
|
143 | this.addCommand(new LintCommand());
|
144 | this.addCommand(new ServeCommand());
|
145 | this.addCommand(new TestCommand());
|
146 | }
|
147 |
|
148 | addCommand(command: Command) {
|
149 | logger.debug('adding command', command.name);
|
150 | this.commands.set(command.name, command);
|
151 |
|
152 | command.aliases.forEach((alias) => {
|
153 | logger.debug('adding alias', alias);
|
154 | this.commands.set(alias, command);
|
155 | });
|
156 | }
|
157 |
|
158 | async run() {
|
159 | const helpCommand = this.commands.get('help')!;
|
160 | const commandNames = Array.from(this.commands.keys());
|
161 | let parsedArgs: ParsedCommand;
|
162 | logger.debug('running...');
|
163 |
|
164 |
|
165 |
|
166 | if (this.args.indexOf('--version') > -1) {
|
167 | console.log(require('../package.json').version);
|
168 | return Promise.resolve();
|
169 | }
|
170 |
|
171 | try {
|
172 | parsedArgs = commandLineCommands(commandNames, this.args);
|
173 | } catch (error) {
|
174 |
|
175 |
|
176 |
|
177 | if (error.name === 'INVALID_COMMAND') {
|
178 | if (error.command) {
|
179 | logger.warn(`'${error.command}' is not an available command.`);
|
180 | }
|
181 | return helpCommand.run(
|
182 | {command: error.command},
|
183 | new ProjectConfig(this.defaultConfigOptions));
|
184 | }
|
185 |
|
186 | throw error;
|
187 | }
|
188 |
|
189 | const commandName = parsedArgs.command;
|
190 | const commandArgs = parsedArgs.argv;
|
191 | const command = this.commands.get(commandName)!;
|
192 | if (command == null) {
|
193 | throw new TypeError('command is null');
|
194 | }
|
195 |
|
196 | logger.debug(
|
197 | `command '${commandName}' found, parsing command args:`,
|
198 | {args: commandArgs});
|
199 |
|
200 | const commandDefinitions = mergeArguments([command.args, globalArguments]);
|
201 | const commandOptionsRaw =
|
202 | commandLineArgs(commandDefinitions, {argv: commandArgs});
|
203 | const commandOptions = parseCLIArgs(commandOptionsRaw);
|
204 | logger.debug(`command options parsed from args:`, commandOptions);
|
205 |
|
206 | const mergedConfigOptions = {
|
207 | ...this.defaultConfigOptions,
|
208 | ...objectDashToCamelCase(commandOptions),
|
209 | };
|
210 | logger.debug(`final config options:`, mergedConfigOptions);
|
211 |
|
212 | const config = new ProjectConfig(mergedConfigOptions);
|
213 | logger.debug(`final project configuration generated:`, config);
|
214 |
|
215 |
|
216 |
|
217 |
|
218 | if (commandOptions['help']) {
|
219 | logger.debug(
|
220 | `'--help' option found, running 'help' for given command...`);
|
221 | return helpCommand.run({command: commandName}, config);
|
222 | }
|
223 |
|
224 | logger.debug('Running command...');
|
225 | return command.run(commandOptions, config);
|
226 | }
|
227 | }
|