Bom dia amigos!
Enquanto desenvolvia o Modern HTML Starter Template, pensei em expandir sua usabilidade. Naquela época, as opções para seu uso se limitavam a clonar o repositório e baixar o arquivo. É assim que o trecho de HTML e a extensão para Microsoft Visual Studio Code - HTML Template , bem como a interface de linha de comando - create-modern-template apareceram . É claro que essas ferramentas estão longe de ser perfeitas e irei refiná-las o máximo que puder. No entanto, no processo de criação deles, aprendi algumas coisas interessantes que quero compartilhar com você.
O snippet e a expansão foram abordados na primeira parte . Nesta parte, daremos uma olhada na CLI.
Se você está interessado apenas no código-fonte, aqui está o link para o repositório .
Oclif
Oclif é uma estrutura Heroku para construir interfaces de linha de comando.
Vamos usá-lo para criar um truque que fornece a capacidade de adicionar, atualizar, excluir tarefas e visualizar sua lista.
O código-fonte do projeto está aqui . Também existe um CLI para verificar a funcionalidade do site por URL.
Instale o oclif globalmente:
npm i -g oclif / yarn global add oclif
Oclif fornece a capacidade de criar CLIs de comando único e multi-comando. Precisamos de uma segunda opção.
Criamos um projeto:
oclif multi todocli
- o argumento multi diz ao oclif para criar uma interface multi-comando
- todocli - nome do projeto
Adicione os comandos necessários:
oclif command add oclif command update oclif command remove oclif command show
O arquivo src / comandos / hello.js pode ser excluído.
Usaremos lowdb como banco de dados local . Também usaremos giz para personalizar as mensagens exibidas no terminal . Instale essas bibliotecas:
npm i chalk lowdb / yarn add chalk lowdb
Crie um arquivo db.json vazio no diretório raiz. Este será nosso repositório de tarefas.
No diretório src, crie um arquivo db.js com o seguinte conteúdo:
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync('db.json')
const db = low(adapter)
// todos db.json
db.defaults({ todos: [] }).write()
//
const Todo = db.get('todos')
module.exports = Todo
Editando o arquivo src / command / add.js:
//
const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')
class AddCommand extends Command {
async run() {
//
const { argv } = this.parse(AddCommand)
try {
//
await Todo.push({
id: Todo.value().length,
// ,
//
task: argv.join(' '),
done: false
}).write()
//
this.log(chalk.green('New todo created.'))
} catch {
//
this.log(chalk.red('Operation failed.'))
}
}
}
//
AddCommand.description = `Adds a new todo`
//
AddCommand.strict = false
//
module.exports = AddCommand
Editando o arquivo src / command / update.js:
const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')
class UpdateCommand extends Command {
async run() {
//
const { id } = this.parse(UpdateCommand).args
try {
// id
await Todo.find({ id: parseInt(id, 10) })
.assign({ done: true })
.write()
this.log(chalk.green('Todo updated.'))
} catch {
this.log('Operation failed.')
}
}
}
UpdateCommand.description = `Marks a task as done by id`
//
UpdateCommand.args = [
{
name: 'id',
description: 'todo id',
required: true
}
]
module.exports = UpdateCommand
O arquivo src / command / remove.js tem a seguinte aparência:
const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')
class RemoveCommand extends Command {
async run() {
const { id } = this.parse(RemoveCommand).args
try {
await Todo.remove({ id: parseInt(id, 10) }).write()
this.log(chalk.green('Todo removed.'))
} catch {
this.log(chalk.red('Operation failed.'))
}
}
}
RemoveCommand.description = `Removes a task by id`
RemoveCommand.args = [
{
name: 'id',
description: 'todo id',
required: true
}
]
module.exports = RemoveCommand
Por fim, edite o arquivo src / comandos / show.js:
const { Command } = require('@oclif/command')
const Todo = require('../db')
const chalk = require('chalk')
class ShowCommand extends Command {
async run() {
// id
const res = await Todo.sortBy('id').value()
//
//
if (res.length) {
res.forEach(({ id, task, done }) => {
this.log(
`[${
done ? chalk.green('DONE') : chalk.red('NOT DONE')
}] id: ${chalk.yellowBright(id)}, task: ${chalk.yellowBright(task)}`
)
})
//
} else {
this.log('There are no todos.')
}
}
}
ShowCommand.description = `Shows existing tasks`
module.exports = ShowCommand
Estando no diretório raiz do projeto, execute o seguinte comando:
npm link / yarn link
Em seguida, realizamos várias operações.
Bem. Tudo funciona conforme o esperado. Tudo o que resta é editar package.json e README.md, e você pode publicar o pacote no registro npm.
DIY CLI
Nossa CLI em funcionalidade será semelhante a criar-reagir-app ou vue-cli . No comando create, ele irá criar um projeto no diretório de destino contendo todos os arquivos necessários para o funcionamento da aplicação. Além disso, ele fornecerá a capacidade de inicializar opcionalmente o git e instalar dependências.
O código-fonte do projeto está aqui .
Crie um diretório e inicialize o projeto:
mkdir create-modern-template cd create-modern-template npm init -y / yarn init -y
Instale as bibliotecas necessárias:
yarn add arg chalk clear esm execa figlet inquirer listr ncp pkg-install
- arg - uma ferramenta para analisar argumentos de linha de comando
- limpar - uma ferramenta para limpar o terminal
- esm é uma ferramenta que fornece suporte ao módulo ES6 em Node.js
- execa é uma ferramenta para executar automaticamente algumas operações comuns (vamos usá-la para inicializar o git)
- figlet - uma ferramenta para enviar texto personalizado para o terminal
- inquiridor - uma ferramenta para trabalhar com a linha de comando, em particular, permite que você faça perguntas ao usuário e analise suas respostas
- Listra - uma ferramenta para criar uma lista de tarefas e visualizar sua execução no terminal
- ncp - ferramenta para copiar arquivos e diretórios
- pkg-install - uma ferramenta para instalar programaticamente as dependências do projeto
No diretório raiz, crie um arquivo bin / create (sem extensão) com o seguinte conteúdo:
#!/usr/bin/env node
require = require('esm')(module)
require('../src/cli').cli(process.argv)
Editando package.json:
"main": "src/main.js",
"bin": "bin/create"
O comando de criação é registrado.
Crie um diretório src / template e coloque os arquivos do projeto lá, que serão copiados para o diretório de destino.
Crie um arquivo src / cli.js com o seguinte conteúdo:
//
import arg from 'arg'
import inquirer from 'inquirer'
import { createProject } from './main'
//
// --yes -y git
// --git -g git
// --install -i
const parseArgumentsIntoOptions = (rawArgs) => {
const args = arg(
{
'--yes': Boolean,
'--git': Boolean,
'--install': Boolean,
'-y': '--yes',
'-g': '--git',
'-i': '--install'
},
{
argv: rawArgs.slice(2)
}
)
//
return {
template: 'template',
skipPrompts: args['--yes'] || false,
git: args['--git'] || false,
install: args['--install'] || false
}
}
//
const promptForMissingOptions = async (options) => {
// --yes -y
if (options.skipPrompts) {
return {
...options,
git: false,
install: false
}
}
//
const questions = []
// git
if (!options.git) {
questions.push({
type: 'confirm',
name: 'git',
message: 'Would you like to initialize git?',
default: false
})
}
//
if (!options.install) {
questions.push({
type: 'confirm',
name: 'install',
message: 'Would you like to install dependencies?',
default: false
})
}
//
const answers = await inquirer.prompt(questions)
//
return {
...options,
git: options.git || answers.git,
install: options.install || answers.install
}
}
//
export async function cli(args) {
let options = parseArgumentsIntoOptions(args)
options = await promptForMissingOptions(options)
await createProject(options)
}
O arquivo src / main.js tem a seguinte aparência:
//
import path from 'path'
import chalk from 'chalk'
import execa from 'execa'
import fs from 'fs'
import Listr from 'listr'
import ncp from 'ncp'
import { projectInstall } from 'pkg-install'
import { promisify } from 'util'
import clear from 'clear'
import figlet from 'figlet'
//
const access = promisify(fs.access)
const copy = promisify(ncp)
//
clear()
// HTML -
console.log(
chalk.yellowBright(figlet.textSync('HTML', { horizontalLayout: 'full' }))
)
//
const copyFiles = async (options) => {
try {
// templateDirectory - ,
// targetDirectory -
await copy(options.templateDirectory, options.targetDirectory)
} catch {
//
console.error('%s Failed to copy files', chalk.red.bold('ERROR'))
process.exit(1)
}
}
// git
const initGit = async (options) => {
try {
await execa('git', ['init'], {
cwd: options.targetDirectory,
})
} catch {
//
console.error('%s Failed to initialize git', chalk.red.bold('ERROR'))
process.exit(1)
}
}
//
export const createProject = async (options) => {
//
options.targetDirectory = process.cwd()
//
const fullPath = path.resolve(__filename)
//
const templateDir = fullPath.replace('main.js', `${options.template}`)
options.templateDirectory = templateDir
try {
//
// R_OK -
await access(options.templateDirectory, fs.constants.R_OK)
} catch {
//
console.error('%s Invalid template name', chalk.red.bold('ERROR'))
process.exit(1)
}
//
const tasks = new Listr(
[
{
title: 'Copy project files',
task: () => copyFiles(options),
},
{
title: 'Initialize git',
task: () => initGit(options),
enabled: () => options.git,
},
{
title: 'Install dependencies',
task: () =>
projectInstall({
cwd: options.targetDirectory,
}),
enabled: () => options.install,
},
],
{
exitOnError: false,
}
)
//
await tasks.run()
//
console.log('%s Project ready', chalk.green.bold('DONE'))
return true
}
Conectamos a CLI (estando no diretório raiz):
yarn link
Crie o diretório de destino e o projeto:
mkdir test-dir cd test-dir create-modern-template && code .
Perfeitamente. CLI pronto para publicar.
Publicar um pacote no registro npm
Para poder publicar pacotes, primeiro você precisa criar uma conta no registro npm .
Em seguida, você precisa efetuar login executando o comando npm login e especificando seu e-mail e senha.
Depois disso, editamos o package.json e criamos os arquivos .gitignore, .npmignore, LICENSE e README.md (consulte o repositório do projeto).
Empacotamos os arquivos do projeto usando o comando npm package. Pegamos o arquivo create-modern-template.tgz. Publicamos esse arquivo executando o comando npm publish create-modern-template.tgz.
Receber um erro ao publicar um pacote geralmente significa que um pacote com o mesmo nome já existe no registro npm. Para atualizar um pacote, você precisa alterar a versão do projeto em package.json, criar o arquivo TGZ novamente e enviá-lo para publicação.
Depois que um pacote é publicado, ele pode ser instalado como qualquer outro pacote usando npm i / yarn add.
Como você pode ver, criar a CLI e publicar o pacote no registro npm é simples.
Espero que você tenha encontrado algo interessante para você. Obrigado pela atenção.