Snippet, una extensión para VSCode y CLI. Parte 2





¡Buen dia amigos!



Mientras desarrollaba la plantilla de inicio HTML moderna, pensé en ampliar su usabilidad. En ese momento, las opciones para su uso se limitaban a clonar el repositorio y descargar el archivo. Así es como apareció el fragmento de código HTML y la extensión para Microsoft Visual Studio Code - Plantilla HTML , así como la interfaz de línea de comandos - create-modern-template . Por supuesto, estas herramientas están lejos de ser perfectas y las perfeccionaré tanto como pueda. Sin embargo, en el proceso de crearlos, aprendí algunas cosas interesantes que quiero compartir con ustedes.



El fragmento y la expansión se trataron en la primera parte.... En esta parte, echaremos un vistazo a la CLI.



Si solo está interesado en el código fuente, aquí está el enlace al repositorio .



Oclif



Oclif es un marco de Heroku para construir interfaces de línea de comandos.



Lo usaremos para crear un truco que brinde la capacidad de agregar, actualizar, eliminar tareas y ver su lista.



El código fuente del proyecto está aquí . También hay una CLI para verificar la funcionalidad del sitio por URL.



Instale oclif globalmente:



npm i -g oclif / yarn global add oclif

      
      





Oclif ofrece la posibilidad de crear CLI de uno o varios comandos. Necesitamos una segunda opción.



Creamos un proyecto:



oclif multi todocli

      
      





  • el argumento múltiple le dice a oclif que cree una interfaz de múltiples comandos
  • todocli - nombre del proyecto






Agregue los comandos necesarios:



oclif command add
oclif command update
oclif command remove
oclif command show

      
      





El archivo src / commands / hello.js se puede eliminar.



Usaremos lowdb como base de datos local . También usaremos tiza para personalizar los mensajes que se muestran en el terminal . Instale estas bibliotecas:



npm i chalk lowdb / yarn add chalk lowdb

      
      





Cree un archivo db.json vacío en el directorio raíz. Este será nuestro repositorio de tareas.



En el directorio src, cree un archivo db.js con el siguiente contenido:



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 el archivo src / commands / 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 el archivo src / commands / 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

      
      





El archivo src / commands / remove.js se ve así:



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

      
      





Finalmente, edite el archivo src / commands / 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 en el directorio raíz del proyecto, ejecute el siguiente comando:



npm link / yarn link

      
      









A continuación, realizamos varias operaciones.







Multa. Todo funciona como se esperaba. Todo lo que queda es editar package.json y README.md, y puede publicar el paquete en el registro npm.



CLI de bricolaje



Nuestra CLI en funcionalidad se parecerá a create-react-app o vue-cli . En el comando crear, creará un proyecto en el directorio de destino que contiene todos los archivos necesarios para que la aplicación funcione. Además, proporcionará la capacidad de inicializar opcionalmente git e instalar dependencias.



El código fuente del proyecto está aquí .



Cree un directorio e inicialice el proyecto:



mkdir create-modern-template
cd create-modern-template
npm init -y / yarn init -y

      
      





Instale las bibliotecas necesarias:



yarn add arg chalk clear esm execa figlet inquirer listr ncp pkg-install

      
      







En el directorio raíz, cree un archivo bin / create (sin extensión) con el siguiente contenido:



#!/usr/bin/env node

require = require('esm')(module)

require('../src/cli').cli(process.argv)

      
      





Editando package.json:



"main": "src/main.js",
"bin": "bin/create"

      
      





El comando de creación está registrado.



Cree un directorio src / template y coloque los archivos del proyecto allí, que se copiarán en el directorio de destino.



Cree un archivo src / cli.js con el siguiente contenido:



//   
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)
}

      
      





El archivo src / main.js se ve así:



//   
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 la CLI (estando en el directorio raíz):



yarn link

      
      





Cree el directorio y el proyecto de destino:



mkdir test-dir
cd test-dir
create-modern-template && code .

      
      













Perfectamente. CLI listo para publicar.



Publicar un paquete en el registro npm



Para poder publicar paquetes, primero debe crear una cuenta en el registro de npm .



Luego, debe iniciar sesión ejecutando el comando npm login y especificando su correo electrónico y contraseña.



Después de eso, editamos package.json y creamos los archivos .gitignore, .npmignore, LICENSE y README.md (ver el repositorio del proyecto).



Empaquetamos los archivos del proyecto usando el comando npm package. Obtenemos el archivo create-modern-template.tgz. Publicamos este archivo ejecutando el comando npm publish create-modern-template.tgz.



Obtener un error al publicar un paquete generalmente significa que ya existe un paquete con el mismo nombre en el registro de npm. Para actualizar un paquete, debe cambiar la versión del proyecto en package.json, crear el archivo TGZ nuevamente y enviarlo para su publicación.



Una vez que se ha publicado un paquete, se puede instalar como cualquier otro paquete usando npm i / yarn add.







Como puede ver, crear la CLI y publicar el paquete en el registro npm es sencillo.



Espero que hayas encontrado algo interesante para ti. Gracias por su atención.



All Articles