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