polymer-cli
Version:
A commandline tool for Polymer projects
276 lines (252 loc) • 8.96 kB
text/typescript
/**
* @license
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import chalk from 'chalk';
import * as fs from 'fs';
import * as logging from 'plylog';
import findup = require('findup-sync');
import * as YeomanEnvironment from 'yeoman-environment';
import {createApplicationGenerator} from '../init/application/application';
import {createElementGenerator} from '../init/element/element';
import {createGithubGenerator} from '../init/github';
import Generator = require('yeoman-generator');
import {prompt} from '../util';
const logger = logging.getLogger('init');
interface GeneratorDescription {
name: string;
value: string;
short: string;
}
interface GeneratorInfo {
id: string;
description: string;
generator: typeof Generator;
}
const localGenerators: {[name: string]: GeneratorInfo} = {
'polymer-3-element': {
id: 'polymer-init-polymer-3-element:app',
description: 'A simple Polymer 3.0 element template',
generator: createElementGenerator('polymer-3.x'),
},
'polymer-3-application': {
id: 'polymer-init-polymer-3-application:app',
description: 'A simple Polymer 3.0 application',
generator: createApplicationGenerator('polymer-3.x'),
},
'polymer-3-starter-kit': {
id: 'polymer-init-polymer-3-starter-kit:app',
description:
'A Polymer 3.x starter application template, with navigation and "PRPL pattern" loading',
generator: createGithubGenerator({
owner: 'Polymer',
repo: 'polymer-starter-kit',
semverRange: '^4.0.0',
installDependencies: {
bower: false,
npm: true,
},
}),
},
'polymer-2-element': {
id: 'polymer-init-polymer-2-element:app',
description: 'A simple Polymer 2.0 element template',
generator: createElementGenerator('polymer-2.x'),
},
'polymer-2-application': {
id: 'polymer-init-polymer-2-application:app',
description: 'A simple Polymer 2.0 application',
generator: createApplicationGenerator('polymer-2.x'),
},
'polymer-2-starter-kit': {
id: 'polymer-init-polymer-2-starter-kit:app',
description:
'A Polymer 2.x starter application template, with navigation and "PRPL pattern" loading',
generator: createGithubGenerator({
owner: 'Polymer',
repo: 'polymer-starter-kit',
semverRange: '^3.0.0',
installDependencies: {
bower: true,
npm: true,
},
}),
},
'shop': {
id: 'polymer-init-shop:app',
description: 'The "Shop" Progressive Web App demo',
generator: createGithubGenerator({
owner: 'Polymer',
repo: 'shop',
semverRange: '^2.0.0',
installDependencies: {
bower: true,
npm: false,
},
}),
},
};
/**
* Get a description for the given generator. If this is an external generator,
* read the description from its package.json.
*/
function getGeneratorDescription(
generator: YeomanEnvironment.GeneratorMeta,
generatorName: string): GeneratorDescription {
const meta = getGeneratorMeta(generator.resolved, generatorName, '');
const displayName = getDisplayName(meta.name);
let description = meta.description;
if (localGenerators.hasOwnProperty(displayName)) {
description = localGenerators[displayName].description;
}
// If a description exists, format it properly for the command-line
if (description.length > 0) {
description = chalk.dim(` - ${description}`);
}
return {
name: `${displayName}${description}`,
value: generatorName,
// inquirer is broken and doesn't print descriptions :(
// keeping this so things work when it does
short: displayName,
};
}
/**
* Get the metadata of a generator from its package.json
*/
function getGeneratorMeta(
rootDir: string, defaultName: string, defaultDescription: string):
{name: string, description: string} {
let name = defaultName;
let description = defaultDescription;
if (rootDir && rootDir !== 'unknown') {
try {
const metapath = findup('package.json', {cwd: rootDir});
const meta = JSON.parse(fs.readFileSync(metapath, 'utf8'));
description = meta.description || description;
name = meta.name || name;
} catch (error) {
if (error.message === 'not found') {
logger.debug('no package.json found for generator');
} else {
logger.debug('unable to read/parse package.json for generator', {
generator: defaultName,
err: error.message,
});
}
}
}
return {name, description};
}
/**
* Extract the meaningful name from the full Yeoman generator name.
* Strip the standard generator prefixes ("generator-" and "polymer-init-"),
* and extract the remainder of the name (the first part of the string before
* any colons).
*
* Examples:
*
* 'generator-polymer-init-foo' === 'foo'
* 'polymer-init-foo' === 'foo'
* 'foo-bar' === 'foo-bar'
* 'generator-polymer-init-foo:aaa' === 'foo'
* 'polymer-init-foo:bbb' === 'foo'
* 'foo-bar:ccc' === 'foo-bar'
*/
function getDisplayName(generatorName: string) {
// Breakdown of regular expression to extract name (group 3 in pattern):
//
// Pattern | Meaning
// -------------------------------------------------------------------
// (generator-)? | Grp 1; Match "generator-"; Optional
// (polymer-init)? | Grp 2; Match "polymer-init-"; Optional
// ([^:]+) | Grp 3; Match one or more characters != ":"
// (:.*)? | Grp 4; Match ":" followed by anything; Optional
return generatorName.replace(
/(generator-)?(polymer-init-)?([^:]+)(:.*)?/g, '$3');
}
/**
* Create & populate a Yeoman environment.
*/
async function createYeomanEnvironment() {
const env = new YeomanEnvironment();
Object.keys(localGenerators).forEach((generatorName) => {
const generatorInfo = localGenerators[generatorName];
env.registerStub(generatorInfo.generator, generatorInfo.id);
});
await new Promise((resolve, reject) => {
env.lookup((error?: Error) => error ? reject(error) : resolve());
});
return env;
}
/**
* Create the prompt used for selecting which template to run. Generate
* the list of available generators by filtering relevent ones out from
* the environment list.
*/
function createSelectPrompt(env: YeomanEnvironment) {
const generators = env.getGeneratorsMeta();
const allGeneratorNames = Object.keys(generators).filter((k) => {
return k.startsWith('polymer-init') && k !== 'polymer-init:app';
});
const choices = allGeneratorNames.map((generatorName: string) => {
const generator = generators[generatorName];
return getGeneratorDescription(generator, generatorName);
});
return {
message: 'Which starter template would you like to use?',
choices: choices,
};
}
/**
* Run the given generator. If no Yeoman environment is provided, a new one
* will be created. If the generator does not exist in the environment, an
* error will be thrown.
*/
export async function runGenerator(
generatorName: string,
// tslint:disable-next-line: no-any typings issues in yeoman
options: {[name: string]: any} = {}): Promise<void> {
const templateName = options['templateName'] || generatorName;
const env: YeomanEnvironment =
await (options['env'] || createYeomanEnvironment());
logger.info(`Running template ${templateName}...`);
logger.debug(`Running generator ${generatorName}...`);
const generators = env.getGeneratorsMeta();
const generator = generators[generatorName];
if (!generator) {
logger.error(`Template ${templateName} not found`);
throw new Error(`Template ${templateName} not found`);
}
return new Promise<void>((resolve, reject) => {
env.run(generatorName, {}, (error: {}) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
/**
* Prompt the user to select a generator. When the user
* selects a generator, run it.
*/
// tslint:disable-next-line: no-any typings issues
export async function promptGeneratorSelection(options?: {[name: string]: any}):
Promise<void> {
options = options || {};
const env = await (options['env'] || createYeomanEnvironment());
const generatorName = await prompt(createSelectPrompt(env));
await runGenerator(
generatorName, {templateName: getDisplayName(generatorName), env: env});
}