Skip to content

Commit

Permalink
Merge branch 'johnbiundo-biundo/add-lib-chooser'
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jan 11, 2020
2 parents 8b61ec8 + 0ae3673 commit 552ffc8
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 130 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.envrc
node_modules/
.DS_Store
*.map

# output
lib/**/*.js
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@

## Description

The Nest CLI is a command-line interface tool that helps you to initialize, develop, and maintain your Nest applications. It assists in multiple ways, including scaffolding the project, serving it in development mode, and building and bundling the application for production distribution. It embodies best-practice architectural patterns to encourage well-structured apps. Read more [here](https://docs.nestjs.com/cli/overview).
The Nest CLI is a command-line interface tool that helps you to initialize, develop, and maintain your Nest applications. It assists in multiple ways, including scaffolding the project, serving it in development mode, and building and bundling the application for production distribution. It embodies best-practice architectural patterns to encourage well-structured apps.

The CLI works with [schematics](https://github.com/angular/angular-cli/tree/master/packages/angular_devkit/schematics), and provides built in support from the schematics collection at [@nestjs/schematics](https://github.com/nestjs/schematics).

Read more [here](https://docs.nestjs.com/cli/overview).

## Installation

Expand Down
172 changes: 146 additions & 26 deletions actions/add.action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import chalk from 'chalk';
import { Input } from '../commands';
import { getValueOrDefault } from '../lib/compiler/helpers/get-value-or-default';
import {
AbstractPackageManager,
PackageManagerFactory,
Expand All @@ -8,51 +10,169 @@ import {
CollectionFactory,
SchematicOption,
} from '../lib/schematics';
import { MESSAGES } from '../lib/ui';
import { loadConfiguration } from '../lib/utils/load-configuration';
import {
askForProjectName,
moveDefaultProjectToStart,
shouldAskForProject,
} from '../lib/utils/project-utils';
import { AbstractAction } from './abstract.action';

const schematicName = 'nest-add';

export class AddAction extends AbstractAction {
public async handle(inputs: Input[], outputs: any[], extraFlags: string[]) {
const manager: AbstractPackageManager = await PackageManagerFactory.find();
const libraryInput: Input = inputs.find(
input => input.name === 'library',
) as Input;
public async handle(inputs: Input[], options: Input[], extraFlags: string[]) {
const libraryName = this.getLibraryName(inputs);
const packageName = this.getPackageName(libraryName);
const collectionName = this.getCollectionName(libraryName, packageName);
const tagName = this.getTagName(packageName);
const packageInstallSuccess = await this.installPackage(
collectionName,
tagName,
);
if (packageInstallSuccess) {
const sourceRootOption: Input = await this.getSourceRoot(
inputs.concat(options),
);
options.push(sourceRootOption);

if (!libraryInput) {
return;
await this.addLibrary(collectionName, options, extraFlags);
} else {
console.error(
chalk.red(
MESSAGES.LIBRARY_INSTALLATION_FAILED_BAD_PACKAGE(libraryName),
),
);
}
const library: string = libraryInput.value as string;
const packageName = library.startsWith('@')
? library.split('/', 2).join('/')
: library.split('/', 1)[0];
}

// Remove the tag/version from the package name.
const collectionName =
(packageName.startsWith('@')
? packageName.split('@', 2).join('@')
: packageName.split('@', 1).join('@')) +
library.slice(packageName.length);
private async getSourceRoot(inputs: Input[]): Promise<Input> {
const configuration = await loadConfiguration();
const configurationProjects = configuration.projects;

let tagName = packageName.startsWith('@')
? packageName.split('@', 3)[2]
: packageName.split('@', 2)[1];
const appName = inputs.find(option => option.name === 'project')!
.value as string;

let sourceRoot = appName
? getValueOrDefault(configuration, 'sourceRoot', appName)
: configuration.sourceRoot;

const shouldAsk = shouldAskForProject(
schematicName,
configurationProjects,
appName,
);
if (shouldAsk) {
const defaultLabel = ' [ Default ]';
let defaultProjectName = configuration.sourceRoot + defaultLabel;

for (const property in configurationProjects) {
if (
configurationProjects[property].sourceRoot ===
configuration.sourceRoot
) {
defaultProjectName = property + defaultLabel;
break;
}
}

const projects = moveDefaultProjectToStart(
configuration,
defaultProjectName,
defaultLabel,
);

const answers = await askForProjectName(
MESSAGES.LIBRARY_PROJECT_SELECTION_QUESTION,
projects,
);
const project = answers.appName.replace(defaultLabel, '');
if (project !== configuration.sourceRoot) {
sourceRoot = configurationProjects[project].sourceRoot;
}
}
return { name: 'sourceRoot', value: sourceRoot };
}

private async installPackage(
collectionName: string,
tagName: string,
): Promise<boolean> {
const manager: AbstractPackageManager = await PackageManagerFactory.find();
tagName = tagName || 'latest';
await manager.addProduction([collectionName], tagName);
let installResult = false;
try {
installResult = await manager.addProduction([collectionName], tagName);
} catch (error) {
if (error && error.message) {
console.error(chalk.red(error.message));
}
}
return installResult;
}

private async addLibrary(
collectionName: string,
options: Input[],
extraFlags: string[],
) {
console.info(MESSAGES.LIBRARY_INSTALLATION_STARTS);
const schematicOptions: SchematicOption[] = [];
schematicOptions.push(
new SchematicOption(
'sourceRoot',
options.find(option => option.name === 'sourceRoot')!.value as string,
),
);
const extraFlagsString = extraFlags ? extraFlags.join(' ') : undefined;

const schematicName = 'nest-add';
try {
const collection: AbstractCollection = CollectionFactory.create(
collectionName,
);
const schematicOptions: SchematicOption[] = [];
const extraFlagsString = extraFlags ? extraFlags.join(' ') : undefined;
await collection.execute(
schematicName,
schematicOptions,
extraFlagsString,
);
} catch (e) {
return;
} catch (error) {
if (error && error.message) {
console.error(chalk.red(error.message));
return Promise.reject();
}
}
}

private getLibraryName(inputs: Input[]): string {
const libraryInput: Input = inputs.find(
input => input.name === 'library',
) as Input;

if (!libraryInput) {
throw new Error('No library found in command input');
}
return libraryInput.value as string;
}

private getPackageName(library: string): string {
return library.startsWith('@')
? library.split('/', 2).join('/')
: library.split('/', 1)[0];
}

private getCollectionName(library: string, packageName: string): string {
return (
(packageName.startsWith('@')
? packageName.split('@', 2).join('@')
: packageName.split('@', 1).join('@')) +
library.slice(packageName.length)
);
}

private getTagName(packageName: string): string {
return packageName.startsWith('@')
? packageName.split('@', 3)[2]
: packageName.split('@', 2)[1];
}
}
66 changes: 12 additions & 54 deletions actions/generate.action.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import chalk from 'chalk';
import * as inquirer from 'inquirer';
import { Answers, Question } from 'inquirer';
import { Answers } from 'inquirer';
import { Input } from '../commands';
import { getValueOrDefault } from '../lib/compiler/helpers/get-value-or-default';
import {
Configuration,
ConfigurationLoader,
ProjectConfiguration,
} from '../lib/configuration';
import { NestConfigurationLoader } from '../lib/configuration/nest-configuration.loader';
import { generateSelect } from '../lib/questions/questions';
import { FileSystemReader } from '../lib/readers';
import {
AbstractCollection,
CollectionFactory,
SchematicOption,
} from '../lib/schematics';
import { MESSAGES } from '../lib/ui';
import { loadConfiguration } from '../lib/utils/load-configuration';
import {
askForProjectName,
moveDefaultProjectToStart,
shouldAskForProject,
} from '../lib/utils/project-utils';
import { AbstractAction } from './abstract.action';

export class GenerateAction extends AbstractAction {
Expand Down Expand Up @@ -68,7 +65,11 @@ const generateFiles = async (inputs: Input[]) => {
defaultLabel,
);

const answers: Answers = await askForProjectName(projects);
const answers: Answers = await askForProjectName(
MESSAGES.PROJECT_SELECTION_QUESTION,
projects,
);

const project: string = answers.appName.replace(defaultLabel, '');
if (project !== configuration.sourceRoot) {
sourceRoot = configurationProjects[project].sourceRoot;
Expand All @@ -89,36 +90,6 @@ const generateFiles = async (inputs: Input[]) => {
}
};

const moveDefaultProjectToStart = (
configuration: Configuration,
defaultProjectName: string,
defaultLabel: string,
) => {
let projects: string[] = Object.keys(configuration.projects as {});
if (configuration.sourceRoot !== 'src') {
projects = projects.filter(
p => p !== defaultProjectName.replace(defaultLabel, ''),
);
}
projects.unshift(defaultProjectName);
return projects;
};

const askForProjectName = async (projects: string[]): Promise<Answers> => {
const questions: Question[] = [
generateSelect('appName')(MESSAGES.PROJECT_SELECTION_QUESTION)(projects),
];
const prompt = inquirer.createPromptModule();
return await prompt(questions);
};

const loadConfiguration = async (): Promise<Required<Configuration>> => {
const loader: ConfigurationLoader = new NestConfigurationLoader(
new FileSystemReader(process.cwd()),
);
return loader.load();
};

const mapSchematicOptions = (inputs: Input[]): SchematicOption[] => {
const options: SchematicOption[] = [];
inputs.forEach(input => {
Expand All @@ -128,16 +99,3 @@ const mapSchematicOptions = (inputs: Input[]): SchematicOption[] => {
});
return options;
};

const shouldAskForProject = (
schematic: string,
configurationProjects: { [key: string]: ProjectConfiguration },
appName: string,
) => {
return (
['app', 'sub-app', 'library', 'lib'].includes(schematic) === false &&
configurationProjects &&
Object.entries(configurationProjects).length !== 0 &&
!appName
);
};
16 changes: 13 additions & 3 deletions actions/info.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ const displayPackageManagerVersion = async () => {
const manager: AbstractPackageManager = await PackageManagerFactory.find();
try {
const version: string = await manager.version();
console.info(`${manager.name} Version :`, chalk.blue(version));
console.info(`${manager.name} Version :`, chalk.blue(version), '\n');
} catch {
console.error(`${manager.name} Version :`, chalk.red('Unknown'));
console.error(`${manager.name} Version :`, chalk.red('Unknown'), '\n');
}
};

const displayNestInformation = async () => {
console.info(chalk.green('[Nest Information]'));
displayCliVersion();
console.info(chalk.green('[Nest Platform Information]'));
try {
const dependencies: PackageJsonDependencies = await readProjectPackageJsonDependencies();
displayNestVersions(dependencies);
Expand All @@ -58,6 +59,15 @@ const displayNestInformation = async () => {
}
};

const displayCliVersion = () => {
console.info(chalk.green('[Nest CLI]'));
console.info(
'Nest CLI Version :',
chalk.blue(require('../package.json').version),
'\n',
);
};

const readProjectPackageJsonDependencies = async (): Promise<PackageJsonDependencies> => {
return new Promise<PackageJsonDependencies>((resolve, reject) => {
readFile(
Expand Down
9 changes: 7 additions & 2 deletions bin/nest.ts
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import {
const bootstrap = () => {
const program: CommanderStatic = commander;
program
.version(require('../package.json').version)
.usage('<command> [options]');
.version(
require('../package.json').version,
'-v, --version',
'Output the current version.',
)
.usage('<command> [options]')
.helpOption('-h, --help', 'Output usage information.');

if (localBinExists()) {
const localCommandLoader = loadLocalBinCommandLoader();
Expand Down

0 comments on commit 552ffc8

Please sign in to comment.