Browse Source

🎨 apply prettier style to project

pull/3/head
Nicolas Beaussart 6 years ago
parent
commit
55b6970175
  1. 2
      .circleci/config.yml
  2. 26
      README.md
  3. 145
      src/commands/add/prettier.ts
  4. 24
      src/commands/hello/index.ts
  5. 99
      src/commands/wall/index.ts
  6. 2
      src/index.ts
  7. 44
      src/utls/base-command.ts
  8. 4
      tsconfig.json

2
.circleci/config.yml

@ -43,7 +43,7 @@ jobs:
workflows: workflows:
version: 2 version: 2
"nbx":
'nbx':
jobs: jobs:
- node-latest - node-latest
- node-12 - node-12

26
README.md

@ -1,5 +1,4 @@
nbx
===
# nbx
My own personal cli My own personal cli
@ -11,11 +10,15 @@ My own personal cli
[![License](https://img.shields.io/npm/l/nbx.svg)](https://github.com/beaussart/nbx/blob/master/package.json) [![License](https://img.shields.io/npm/l/nbx.svg)](https://github.com/beaussart/nbx/blob/master/package.json)
<!-- toc --> <!-- toc -->
* [Usage](#usage)
* [Commands](#commands)
<!-- tocstop -->
- [Usage](#usage)
- [Commands](#commands)
<!-- tocstop -->
# Usage # Usage
<!-- usage --> <!-- usage -->
```sh-session ```sh-session
$ npm install -g nbx $ npm install -g nbx
$ nbx COMMAND $ nbx COMMAND
@ -27,13 +30,17 @@ USAGE
$ nbx COMMAND $ nbx COMMAND
... ...
``` ```
<!-- usagestop --> <!-- usagestop -->
# Commands # Commands
<!-- commands --> <!-- commands -->
* [`nbx add:prettier`](#nbx-addprettier)
* [`nbx hello [FILE]`](#nbx-hello-file)
* [`nbx help [COMMAND]`](#nbx-help-command)
* [`nbx wall TERMS`](#nbx-wall-terms)
- [`nbx add:prettier`](#nbx-addprettier)
- [`nbx hello [FILE]`](#nbx-hello-file)
- [`nbx help [COMMAND]`](#nbx-help-command)
- [`nbx wall TERMS`](#nbx-wall-terms)
## `nbx add:prettier` ## `nbx add:prettier`
@ -120,4 +127,5 @@ EXAMPLE
``` ```
_See code: [src/commands/wall/index.ts](https://github.com/beaussart/nbx/blob/v0.0.0/src/commands/wall/index.ts)_ _See code: [src/commands/wall/index.ts](https://github.com/beaussart/nbx/blob/v0.0.0/src/commands/wall/index.ts)_
<!-- commandsstop --> <!-- commandsstop -->

145
src/commands/add/prettier.ts

@ -1,39 +1,40 @@
// import {flags} from '@oclif/command' // import {flags} from '@oclif/command'
import {BaseCommand} from '../../utls/base-command'
import * as latestVersion from 'latest-version'
import {system, filesystem} from 'gluegun'
import * as prompts from 'prompts'
import * as fs from 'fs'
import {plugins, statusMatrix, add, commit, config as gitConfig} from 'isomorphic-git'
import { BaseCommand } from '../../utls/base-command';
import * as latestVersion from 'latest-version';
import { system, filesystem } from 'gluegun';
import * as prompts from 'prompts';
import * as fs from 'fs';
import { plugins, statusMatrix, add, commit, config as gitConfig } from 'isomorphic-git';
plugins.set('fs', fs)
plugins.set('fs', fs);
export default class Prettier extends BaseCommand { export default class Prettier extends BaseCommand {
static description = 'describe the command here'
static description = 'describe the command here';
static examples = [ static examples = [
`$ nbx wall `$ nbx wall
hello world from ./src/hello.ts! hello world from ./src/hello.ts!
`, `,
]
];
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
}
};
async run() { async run() {
const packagePath = filesystem.path('.', 'package.json')
const packagePath = filesystem.path('.', 'package.json');
if (filesystem.exists(packagePath) !== 'file') { if (filesystem.exists(packagePath) !== 'file') {
this.error('There is no package.json not found in the current folder')
this.error('There is no package.json not found in the current folder');
} }
const packageJson = filesystem.read(packagePath, 'json')
const packageJson = filesystem.read(packagePath, 'json');
if (packageJson.devDependencies.prettier) { if (packageJson.devDependencies.prettier) {
this.error('Prettier is already installed in this project.')
this.error('Prettier is already installed in this project.');
} }
const {mask, shouldCommit} = await prompts([
const { mask, shouldCommit } = await prompts(
[
{ {
type: 'text', type: 'text',
message: 'On what files it should run prettier', message: 'On what files it should run prettier',
@ -46,18 +47,20 @@ hello world from ./src/hello.ts!
name: 'shouldCommit', name: 'shouldCommit',
initial: true, initial: true,
}, },
], {onCancel: () => this.error('User canceled prompt.')})
],
{ onCancel: () => this.error('User canceled prompt.') },
);
if (shouldCommit) { if (shouldCommit) {
this.initGit()
this.initGit();
} }
await this.addDevDependency('prettier', shouldCommit)
await this.addDevDependency('husky', shouldCommit)
await this.addDevDependency('pretty-quick', shouldCommit)
await this.addDevDependency('prettier', shouldCommit);
await this.addDevDependency('husky', shouldCommit);
await this.addDevDependency('pretty-quick', shouldCommit);
await this.runWithSpinner('Adding package.json scripts', async () => { await this.runWithSpinner('Adding package.json scripts', async () => {
const packageJsonWithDeps = filesystem.read(packagePath, 'json')
const packageJsonWithDeps = filesystem.read(packagePath, 'json');
const finalPackageJson = { const finalPackageJson = {
...packageJsonWithDeps, ...packageJsonWithDeps,
scripts: { scripts: {
@ -72,16 +75,16 @@ hello world from ./src/hello.ts!
'pre-commit': 'pretty-quick --staged', 'pre-commit': 'pretty-quick --staged',
}, },
}, },
}
};
await filesystem.write(packagePath, finalPackageJson, {jsonIndent: 2})
await system.exec('yarn')
await filesystem.write(packagePath, finalPackageJson, { jsonIndent: 2 });
await system.exec('yarn');
if (shouldCommit) { if (shouldCommit) {
await add({filepath: 'package.json', dir: '.'})
await commit({dir: '.', message: ':wrench: add script and husky to package.json'})
await add({ filepath: 'package.json', dir: '.' });
await commit({ dir: '.', message: ':wrench: add script and husky to package.json' });
} }
})
});
await this.runWithSpinner('Adding .prettierrc config file', async () => { await this.runWithSpinner('Adding .prettierrc config file', async () => {
const prettierRcFile = { const prettierRcFile = {
@ -90,79 +93,80 @@ hello world from ./src/hello.ts!
printWidth: 120, printWidth: 120,
quoteProps: 'consistent', quoteProps: 'consistent',
trailingComma: 'all', trailingComma: 'all',
}
};
await filesystem.write(filesystem.path('.', '.prettierrc'), prettierRcFile, {jsonIndent: 2})
await filesystem.write(filesystem.path('.', '.prettierrc'), prettierRcFile, { jsonIndent: 2 });
if (shouldCommit) { if (shouldCommit) {
await add({filepath: '.prettierrc', dir: '.'})
await commit({dir: '.', message: ':wrench: add prettierrc config file'})
await add({ filepath: '.prettierrc', dir: '.' });
await commit({ dir: '.', message: ':wrench: add prettierrc config file' });
} }
})
});
await this.runWithSpinner('Updating code to match prettier style', async () => { await this.runWithSpinner('Updating code to match prettier style', async () => {
await system.exec('yarn format:write')
await system.exec('yarn format:write');
if (shouldCommit) { if (shouldCommit) {
const commitsPromice = (await statusMatrix({dir: '.', pattern: '**'}))
const commitsPromice = (await statusMatrix({ dir: '.', pattern: '**' }))
.filter(([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1)) .filter(([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1))
.map(arr => arr[0]) .map(arr => arr[0])
.map(filepath => add({filepath, dir: '.'}))
.map(filepath => add({ filepath, dir: '.' }));
await Promise.all(commitsPromice)
await commit({dir: '.', message: ':art: apply prettier style to project'})
await Promise.all(commitsPromice);
await commit({ dir: '.', message: ':art: apply prettier style to project' });
} }
})
});
await this.handleMaybeEslint(shouldCommit)
await this.handleMaybeEslint(shouldCommit);
} }
async initGit(): Promise<void> { async initGit(): Promise<void> {
const {config} = await this.getConfig()
const { config } = await this.getConfig();
if (!config?.git?.user) { if (!config?.git?.user) {
this.error('Missing config key git.user for git commits')
this.error('Missing config key git.user for git commits');
} }
if (!config?.git?.email) { if (!config?.git?.email) {
this.error('Missing config key git.email for git commits')
this.error('Missing config key git.email for git commits');
} }
await gitConfig({ await gitConfig({
dir: '.', dir: '.',
path: 'user.name', path: 'user.name',
value: config?.git?.user, value: config?.git?.user,
})
});
await gitConfig({ await gitConfig({
dir: '.', dir: '.',
path: 'user.email', path: 'user.email',
value: config?.git?.email, value: config?.git?.email,
})
const changes = (await statusMatrix({dir: '.', pattern: '**'}))
.filter(([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1))
});
const changes = (await statusMatrix({ dir: '.', pattern: '**' })).filter(
([_, head, workdir, stage]) => !(head === 1 && workdir === 1 && stage === 1),
);
if (changes.length > 0) { if (changes.length > 0) {
this.error('There is unsaved changed in the git repository, aborting')
this.error('There is unsaved changed in the git repository, aborting');
} }
} }
async addDevDependency(name: string, shouldCommit: boolean): Promise<void> { async addDevDependency(name: string, shouldCommit: boolean): Promise<void> {
await this.runWithSpinner(`Adding ${name} dependency`, async () => { await this.runWithSpinner(`Adding ${name} dependency`, async () => {
const versionToInstall = await latestVersion(name)
await system.exec(`yarn add -D ${name}@${versionToInstall}`)
const versionToInstall = await latestVersion(name);
await system.exec(`yarn add -D ${name}@${versionToInstall}`);
if (shouldCommit) { if (shouldCommit) {
await add({filepath: 'package.json', dir: '.'})
await add({filepath: 'yarn.lock', dir: '.'})
await commit({dir: '.', message: `:heavy_plus_sign: add ${name}@${versionToInstall}`})
await add({ filepath: 'package.json', dir: '.' });
await add({ filepath: 'yarn.lock', dir: '.' });
await commit({ dir: '.', message: `:heavy_plus_sign: add ${name}@${versionToInstall}` });
} }
})
});
} }
async handleMaybeEslint(shouldCommit: boolean) { async handleMaybeEslint(shouldCommit: boolean) {
const eslintPath = filesystem.path('.', '.eslintrc')
const hasEslintFile = filesystem.isFile(eslintPath)
const eslintPath = filesystem.path('.', '.eslintrc');
const hasEslintFile = filesystem.isFile(eslintPath);
if (!hasEslintFile) { if (!hasEslintFile) {
return
return;
} }
const {shouldOverrideEslint} = await prompts(
const { shouldOverrideEslint } = await prompts(
[ [
{ {
type: 'confirm', type: 'confirm',
@ -171,31 +175,28 @@ hello world from ./src/hello.ts!
initial: true, initial: true,
}, },
], ],
{onCancel: () => this.error('User canceled prompt.')},
)
{ onCancel: () => this.error('User canceled prompt.') },
);
if (!shouldOverrideEslint) { if (!shouldOverrideEslint) {
return
return;
} }
await this.addDevDependency('eslint-config-prettier', shouldCommit)
await this.addDevDependency('eslint-plugin-prettier', shouldCommit)
await this.addDevDependency('eslint-config-prettier', shouldCommit);
await this.addDevDependency('eslint-plugin-prettier', shouldCommit);
await this.runWithSpinner('Updating .eslintrc', async () => { await this.runWithSpinner('Updating .eslintrc', async () => {
const eslintConfig = filesystem.read(eslintPath, 'json')
const eslintConfig = filesystem.read(eslintPath, 'json');
const finalEslintConfig = { const finalEslintConfig = {
...eslintConfig, ...eslintConfig,
extends: [
...eslintConfig.extends,
'plugin:prettier/recommended',
],
}
extends: [...eslintConfig.extends, 'plugin:prettier/recommended'],
};
await filesystem.write(eslintPath, finalEslintConfig, {jsonIndent: 2})
await filesystem.write(eslintPath, finalEslintConfig, { jsonIndent: 2 });
if (shouldCommit) { if (shouldCommit) {
await add({filepath: '.eslintrc', dir: '.'})
await commit({dir: '.', message: ':wrench: update eslint to use prettier'})
await add({ filepath: '.eslintrc', dir: '.' });
await commit({ dir: '.', message: ':wrench: update eslint to use prettier' });
} }
})
});
} }
} }

24
src/commands/hello/index.ts

@ -1,31 +1,31 @@
import {Command, flags} from '@oclif/command'
import { Command, flags } from '@oclif/command';
export default class Index extends Command { export default class Index extends Command {
static description = 'describe the command here'
static description = 'describe the command here';
static examples = [ static examples = [
`$ nbx hello `$ nbx hello
hello world from ./src/hello.ts! hello world from ./src/hello.ts!
`, `,
]
];
static flags = { static flags = {
help: flags.help({char: 'h'}),
help: flags.help({ char: 'h' }),
// flag with a value (-n, --name=VALUE) // flag with a value (-n, --name=VALUE)
name: flags.string({char: 'n', description: 'name to print'}),
name: flags.string({ char: 'n', description: 'name to print' }),
// flag with no value (-f, --force) // flag with no value (-f, --force)
force: flags.boolean({char: 'f'}),
}
force: flags.boolean({ char: 'f' }),
};
static args = [{name: 'file'}]
static args = [{ name: 'file' }];
async run() { async run() {
const {args, flags} = this.parse(Index)
const { args, flags } = this.parse(Index);
const name = flags.name || 'world'
this.log(`hello ${name} (${args.file}) from ./src/commands/hello.ts`)
const name = flags.name || 'world';
this.log(`hello ${name} (${args.file}) from ./src/commands/hello.ts`);
if (args.file && flags.force) { if (args.file && flags.force) {
this.log(`you input --force and --file: ${args.file}`)
this.log(`you input --force and --file: ${args.file}`);
} }
} }
} }

99
src/commands/wall/index.ts

@ -1,10 +1,10 @@
import {flags} from '@oclif/command'
import {BaseCommand} from '../../utls/base-command'
import axios from 'axios'
import {filesystem} from 'gluegun'
import { flags } from '@oclif/command';
import { BaseCommand } from '../../utls/base-command';
import axios from 'axios';
import { filesystem } from 'gluegun';
function booleanToNumber(value: boolean): string { function booleanToNumber(value: boolean): string {
return value ? '1' : '0'
return value ? '1' : '0';
} }
interface WallhavenItem { interface WallhavenItem {
@ -14,91 +14,92 @@ interface WallhavenItem {
} }
export default class Wall extends BaseCommand { export default class Wall extends BaseCommand {
static description = 'describe the command here'
static description = 'describe the command here';
static examples = [ static examples = [
`$ nbx wall `$ nbx wall
hello world from ./src/hello.ts! hello world from ./src/hello.ts!
`, `,
]
];
static args = [
{name: 'terms', description: 'The search terms for the wallpaper', required: true},
]
static args = [{ name: 'terms', description: 'The search terms for the wallpaper', required: true }];
static flags = { static flags = {
...BaseCommand.flags, ...BaseCommand.flags,
random: flags.boolean({char: 'r', description: 'Pick one randomly'}),
sketchy: flags.boolean({char: 's', description: 'Enables sketchy search'}),
general: flags.boolean({char: 'g', description: 'Enable general category'}),
anime: flags.boolean({char: 'a', description: 'Enable anime category'}),
people: flags.boolean({char: 'p', description: 'Enable people category'}),
output: flags.string({char: 'o', description: 'Output for the wallpaper'}),
force: flags.boolean({char: 'f', description: 'Override the file if found'}),
}
random: flags.boolean({ char: 'r', description: 'Pick one randomly' }),
sketchy: flags.boolean({ char: 's', description: 'Enables sketchy search' }),
general: flags.boolean({ char: 'g', description: 'Enable general category' }),
anime: flags.boolean({ char: 'a', description: 'Enable anime category' }),
people: flags.boolean({ char: 'p', description: 'Enable people category' }),
output: flags.string({ char: 'o', description: 'Output for the wallpaper' }),
force: flags.boolean({ char: 'f', description: 'Override the file if found' }),
};
async run() { async run() {
const {args, argv, flags} = this.parse(Wall)
const {general, anime, people, output, force, random, sketchy} = flags
const {terms} = args
const {print: {spin}} = this.tools
const { args, argv, flags } = this.parse(Wall);
const { general, anime, people, output, force, random, sketchy } = flags;
const { terms } = args;
const {
print: { spin },
} = this.tools;
this.vprint(args, 'args')
this.vprint(argv, 'argv')
this.vprint(flags, 'flags')
this.vprint(args, 'args');
this.vprint(argv, 'argv');
this.vprint(flags, 'flags');
if (!general && !anime && !people) { if (!general && !anime && !people) {
this.error('You must use at least one category flag')
this.error('You must use at least one category flag');
} }
const spinFetchList = spin('Searching wallpapers')
const spinFetchList = spin('Searching wallpapers');
const categories = `${booleanToNumber(general)}${booleanToNumber(
anime
)}${booleanToNumber(people)}`
const {data: {data}} = await axios.get<{ data: WallhavenItem[] }>('https://wallhaven.cc/api/v1/search', {params: {
const categories = `${booleanToNumber(general)}${booleanToNumber(anime)}${booleanToNumber(people)}`;
const {
data: { data },
} = await axios.get<{ data: WallhavenItem[] }>('https://wallhaven.cc/api/v1/search', {
params: {
q: `${terms}`, q: `${terms}`,
sorting: random ? 'random' : 'relevance', sorting: random ? 'random' : 'relevance',
categories, categories,
purity: `1${booleanToNumber(sketchy)}0`, purity: `1${booleanToNumber(sketchy)}0`,
atleast: '1920x1080', atleast: '1920x1080',
ratios: '16x9', ratios: '16x9',
}})
spinFetchList.succeed()
},
});
spinFetchList.succeed();
if (!data || data.length === 0) { if (!data || data.length === 0) {
this.error('No image found, try another search term')
this.error('No image found, try another search term');
} }
const firstImage = data[0]
this.vprint(firstImage, 'Image raw data')
const firstImage = data[0];
this.vprint(firstImage, 'Image raw data');
const filename = output ? output : `wallhaven-${firstImage.id}.jpg`
const filename = output ? output : `wallhaven-${firstImage.id}.jpg`;
const spinner = spin('Downloading wallpaper')
const spinner = spin('Downloading wallpaper');
if (filesystem.exists(filename)) { if (filesystem.exists(filename)) {
if (force) { if (force) {
spinner.warn('Overrinding ' + filename)
spinner.warn('Overrinding ' + filename);
} else { } else {
spinner.fail('The file ' + filename + ' already exist')
this.error('The flag force was not set, aborting')
spinner.fail('The file ' + filename + ' already exist');
this.error('The flag force was not set, aborting');
} }
} }
const writer = filesystem.createWriteStream(filename, {})
const writer = filesystem.createWriteStream(filename, {});
const response = await axios.get(firstImage.path, { const response = await axios.get(firstImage.path, {
method: 'GET', method: 'GET',
responseType: 'stream', responseType: 'stream',
})
});
response.data.pipe(writer)
response.data.pipe(writer);
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
spinner.succeed()
writer.on('finish', resolve);
writer.on('error', reject);
});
spinner.succeed();
} }
} }

2
src/index.ts

@ -1 +1 @@
export {run} from '@oclif/command'
export { run } from '@oclif/command';

44
src/utls/base-command.ts

@ -1,6 +1,6 @@
import {Command, flags} from '@oclif/command'
import {Input} from '@oclif/parser'
import {print} from 'gluegun'
import { Command, flags } from '@oclif/command';
import { Input } from '@oclif/parser';
import { print } from 'gluegun';
export interface BaseCommandFlags { export interface BaseCommandFlags {
verbose: boolean; verbose: boolean;
@ -15,8 +15,8 @@ export interface NbxConfig {
export abstract class BaseCommand extends Command { export abstract class BaseCommand extends Command {
static flags = { static flags = {
verbose: flags.boolean({char: 'v', description: 'Verbose output'}),
help: flags.help({char: 'h'}),
verbose: flags.boolean({ char: 'v', description: 'Verbose output' }),
help: flags.help({ char: 'h' }),
}; };
flags?: BaseCommandFlags; flags?: BaseCommandFlags;
@ -27,47 +27,47 @@ export abstract class BaseCommand extends Command {
async init() { async init() {
// do some initialization // do some initialization
const {flags, args, argv} = this.parse(<Input<any>> this.constructor)
this.flags = flags
const { flags, args, argv } = this.parse(<Input<any>>this.constructor);
this.flags = flags;
if (flags.verbose) { if (flags.verbose) {
this.tools.print.debug(args, 'args')
this.tools.print.debug(argv, 'argv')
this.tools.print.debug(flags, 'flags')
this.tools.print.debug(args, 'args');
this.tools.print.debug(argv, 'argv');
this.tools.print.debug(flags, 'flags');
} }
} }
vprint(value: any, title?: string) { vprint(value: any, title?: string) {
if (this.flags && this.flags.verbose) { if (this.flags && this.flags.verbose) {
this.tools.print.debug(value, title)
this.tools.print.debug(value, title);
} }
} }
async runWithSpinner(name: string, func: (spinner: any) => Promise<any>): Promise<any> { async runWithSpinner(name: string, func: (spinner: any) => Promise<any>): Promise<any> {
const spinner = this.tools.print.spin(name)
const spinner = this.tools.print.spin(name);
try { try {
const maybeRet = await func(spinner)
const maybeRet = await func(spinner);
if (spinner.isSpinning) { if (spinner.isSpinning) {
spinner.succeed()
spinner.succeed();
} }
return maybeRet
return maybeRet;
// eslint-disable-next-line unicorn/catch-error-name // eslint-disable-next-line unicorn/catch-error-name
} catch (maybeError) { } catch (maybeError) {
spinner.fail(maybeError.message)
this.error(maybeError)
spinner.fail(maybeError.message);
this.error(maybeError);
} }
} }
async getConfig(): Promise<{ config: NbxConfig; path: string }> { async getConfig(): Promise<{ config: NbxConfig; path: string }> {
const cosmic = await import('cosmiconfig').then(cosmic => cosmic.cosmiconfig)
const explorer = cosmic('nbx')
const data = await explorer.search()
const cosmic = await import('cosmiconfig').then(cosmic => cosmic.cosmiconfig);
const explorer = cosmic('nbx');
const data = await explorer.search();
if (!data || data.isEmpty || !data.config) { if (!data || data.isEmpty || !data.config) {
this.error('Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js')
this.error('Config not found. Tried to look for a .nbxrc, .nbxrc.json, .nbxrc.yaml, .nbxrc.yml, .nbxrc.js');
} }
return { return {
config: data?.config, config: data?.config,
path: data?.filepath, path: data?.filepath,
}
};
} }
} }

4
tsconfig.json

@ -8,7 +8,5 @@
"strict": true, "strict": true,
"target": "es2017" "target": "es2017"
}, },
"include": [
"src/**/*"
]
"include": ["src/**/*"]
} }
Loading…
Cancel
Save