Skip to content

Commit

Permalink
feat(cli): Improve CLI interface (#2753)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl authored Sep 16, 2022
1 parent 190d609 commit c7e1b7e
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 90 deletions.
9 changes: 3 additions & 6 deletions packages/cli/bin/feathers
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#!/usr/bin/env node
'use strict';
'use strict'

const { yargs } = require('@feathershq/pinion')
const { command } = require('../lib')
const { program } = require('../lib')

const cli = cmd => command(yargs(cmd)).argv

cli(process.argv.slice(2));
program.parse()
4 changes: 1 addition & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@
"mocha": "mocha --timeout 60000 --config ../../.mocharc.json --recursive test/**.test.ts test/**/*.test.ts",
"test": "npm run compile && npm run mocha"
},
"directories": {
"lib": "lib/cli"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@feathershq/pinion": "^0.3.5",
"chalk": "^4.0.1",
"commander": "^9.4.0",
"lodash": "^4.17.21",
"prettier": "^2.7.1"
},
Expand Down
86 changes: 48 additions & 38 deletions packages/cli/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type AppGeneratorArguments = FeathersBaseContext & Partial<AppGeneratorDa

export const generate = (ctx: AppGeneratorArguments) =>
generator(ctx)
.then(initializeBaseContext())
.then((ctx) => ({
...ctx,
dependencies: [],
Expand Down Expand Up @@ -104,7 +105,8 @@ export const generate = (ctx: AppGeneratorArguments) =>
message: 'Which package manager are you using?',
choices: [
{ value: 'npm', name: 'npm' },
{ value: 'yarn', name: 'Yarn' }
{ value: 'yarn', name: 'Yarn' },
{ value: 'pnpm', name: 'pnpm' }
]
},
...connectionPrompts(ctx),
Expand Down Expand Up @@ -149,50 +151,58 @@ export const generate = (ctx: AppGeneratorArguments) =>
)
)
.then(
install<AppGeneratorContext>(({ transports, framework, dependencyVersions, dependencies }) => {
const hasSocketio = transports.includes('websockets')
install<AppGeneratorContext>(
({ transports, framework, dependencyVersions, dependencies }) => {
const hasSocketio = transports.includes('websockets')

dependencies.push(
'@feathersjs/feathers',
'@feathersjs/errors',
'@feathersjs/schema',
'@feathersjs/configuration',
'@feathersjs/transport-commons',
'@feathersjs/authentication',
'winston'
)
dependencies.push(
'@feathersjs/feathers',
'@feathersjs/errors',
'@feathersjs/schema',
'@feathersjs/configuration',
'@feathersjs/transport-commons',
'@feathersjs/authentication',
'winston'
)

if (hasSocketio) {
dependencies.push('@feathersjs/socketio')
}
if (hasSocketio) {
dependencies.push('@feathersjs/socketio')
}

if (framework === 'koa') {
dependencies.push('@feathersjs/koa', 'koa-static')
}
if (framework === 'koa') {
dependencies.push('@feathersjs/koa', 'koa-static')
}

if (framework === 'express') {
dependencies.push('@feathersjs/express', 'compression')
}
if (framework === 'express') {
dependencies.push('@feathersjs/express', 'compression')
}

return addVersions(dependencies, dependencyVersions)
})
return addVersions(dependencies, dependencyVersions)
},
false,
ctx.packager
)
)
.then(
install<AppGeneratorContext>(({ language, framework, devDependencies, dependencyVersions }) => {
devDependencies.push('nodemon', 'axios', 'mocha', 'cross-env', 'prettier')
install<AppGeneratorContext>(
({ language, framework, devDependencies, dependencyVersions }) => {
devDependencies.push('nodemon', 'axios', 'mocha', 'cross-env', 'prettier', '@feathersjs/cli')

if (language === 'ts') {
devDependencies.push(
'@types/mocha',
framework === 'koa' ? '@types/koa-static' : '@types/compression',
'@types/node',
'nodemon',
'ts-node',
'typescript',
'shx'
)
}
if (language === 'ts') {
devDependencies.push(
'@types/mocha',
framework === 'koa' ? '@types/koa-static' : '@types/compression',
'@types/node',
'nodemon',
'ts-node',
'typescript',
'shx'
)
}

return addVersions(devDependencies, dependencyVersions)
}, true)
return addVersions(devDependencies, dependencyVersions)
},
true,
ctx.packager
)
)
18 changes: 15 additions & 3 deletions packages/cli/src/authentication/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import chalk from 'chalk'
import { generator, runGenerators, prompt, install } from '@feathershq/pinion'
import { addVersions, FeathersBaseContext, getDatabaseAdapter } from '../commons'
import {
addVersions,
checkPreconditions,
FeathersBaseContext,
getDatabaseAdapter,
initializeBaseContext
} from '../commons'
import { generate as serviceGenerator, ServiceGeneratorContext } from '../service/index'

export interface AuthenticationGeneratorContext extends ServiceGeneratorContext {
Expand Down Expand Up @@ -67,14 +73,16 @@ export const prompts = (ctx: AuthenticationGeneratorArguments) => [

export const generate = (ctx: AuthenticationGeneratorArguments) =>
generator(ctx)
.then(initializeBaseContext())
.then(checkPreconditions())
.then(prompt<AuthenticationGeneratorArguments, AuthenticationGeneratorContext>(prompts))
.then(async (ctx) => {
const serviceContext = await serviceGenerator({
...ctx,
name: ctx.service,
path: ctx.service,
isEntityService: true,
type: getDatabaseAdapter(ctx.feathers.database)
type: getDatabaseAdapter(ctx.feathers?.database)
})

return {
Expand All @@ -99,5 +107,9 @@ export const generate = (ctx: AuthenticationGeneratorArguments) =>
}
}

return install<AuthenticationGeneratorContext>(addVersions(dependencies, ctx.dependencyVersions))(ctx)
return install<AuthenticationGeneratorContext>(
addVersions(dependencies, ctx.dependencyVersions),
false,
ctx.feathers.packager
)(ctx)
})
2 changes: 1 addition & 1 deletion packages/cli/src/authentication/templates/knex.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function down(knex: Knex): Promise<void> {
export const generate = (ctx: AuthenticationGeneratorContext) =>
generator(ctx).then(
when(
(ctx) => getDatabaseAdapter(ctx.feathers.database) === 'knex',
(ctx) => getDatabaseAdapter(ctx.feathers?.database) === 'knex',
renderSource(
migrationTemplate,
toFile(
Expand Down
67 changes: 67 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import chalk from 'chalk'
import { Command } from 'commander'
import { generator, runGenerator, getContext } from '@feathershq/pinion'
import { FeathersBaseContext, version } from './commons'

export * from 'commander'

export const commandRunner = (name: string) => async (options: any) => {
const ctx = getContext<FeathersBaseContext>({
...options
})

await generator(ctx)
.then(runGenerator(__dirname, name, 'index'))
.catch((error) => {
const { logger } = ctx.pinion

logger.error(`Error: ${chalk.white(error.message)}`)
})
}

export const program = new Command()

program
.name('feathers')
.description('The Feathers command line interface 🕊️')
.version(version)
.showHelpAfterError()

const generate = program.command('generate').alias('g')

generate
.command('app')
.description('Generate a new application')
.option('--name <name>', 'The name of the application')
.action(commandRunner('app'))

generate
.command('service')
.description('Generate a new service')
.option('--name <name>', 'The service name')
.option('--path <path>', 'The path to register the service on')
.option('--type <type>', 'The service type (knex, mongodb, custom)')
.action(commandRunner('service'))

generate
.command('hook')
.description('Generate a hook')
.option('--name <name>', 'The name of the hook')
.option('--type <type>', 'The hook type (around or regular)')
.action(commandRunner('hook'))

generate
.command('connection')
.description('Add a new database connection')
.action(commandRunner('connection'))

generate
.command('authentication')
.description('Add authentication to the application')
.action(commandRunner('authentication'))

generate.description(
`Run a generator. Currently available: \n ${generate.commands
.map((cmd) => `${chalk.blue(cmd.name())}: ${cmd.description()} `)
.join('\n ')}`
)
27 changes: 26 additions & 1 deletion packages/cli/src/commons.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fs from 'fs'
import { join } from 'path'
import { PackageJson } from 'type-fest'
import { readFile, writeFile } from 'fs/promises'
import {
Expand All @@ -14,6 +16,8 @@ import * as ts from 'typescript'
import prettier, { Options as PrettierOptions } from 'prettier'
import path from 'path'

export const { version } = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString())

export type DependencyVersions = { [key: string]: string }

/**
Expand Down Expand Up @@ -110,7 +114,8 @@ export const initializeBaseContext =
loadJSON(path.join(__dirname, '..', 'package.json'), (pkg: PackageJson) => ({
dependencyVersions: {
...pkg.devDependencies,
...ctx.dependencyVersions
...ctx.dependencyVersions,
'@feathersjs/cli': version
}
}))
)
Expand All @@ -122,6 +127,26 @@ export const initializeBaseContext =
feathers: ctx.pkg?.feathers
}))

/**
* Checks if the current context contains a valid generated application. This is necesary for most
* generators (besides the app generator).
*
* @param ctx The context to check against
* @returns Throws an error or returns the original context
*/
export const checkPreconditions =
() =>
async <T extends FeathersBaseContext>(ctx: T) => {
if (!ctx.feathers) {
console.log(ctx)
throw new Error(`Can not run generator since the current folder does not appear to be a Feathers application.
Either your package.json is missing or it does not have \`feathers\` property.
`)
}

return ctx
}

const importRegex = /from '(\..*)'/g
const escapeNewLines = (code: string) => code.replace(/\n\n/g, '\n/* :newline: */')
const restoreNewLines = (code: string) => code.replace(/\/\* :newline: \*\//g, '\n')
Expand Down
17 changes: 15 additions & 2 deletions packages/cli/src/connection/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { generator, runGenerator, prompt, install, mergeJSON, toFile } from '@feathershq/pinion'
import chalk from 'chalk'
import { FeathersBaseContext, DatabaseType, getDatabaseAdapter, addVersions } from '../commons'
import {
FeathersBaseContext,
DatabaseType,
getDatabaseAdapter,
addVersions,
checkPreconditions,
initializeBaseContext
} from '../commons'

export interface ConnectionGeneratorContext extends FeathersBaseContext {
database: DatabaseType
Expand Down Expand Up @@ -60,6 +67,8 @@ export const getDatabaseClient = (database: DatabaseType) => DATABASE_CLIENTS[da

export const generate = (ctx: ConnectionGeneratorArguments) =>
generator(ctx)
.then(initializeBaseContext())
.then(checkPreconditions())
.then(prompt<ConnectionGeneratorArguments, ConnectionGeneratorContext>(prompts))
.then(
runGenerator<ConnectionGeneratorContext>(
Expand Down Expand Up @@ -105,5 +114,9 @@ export const generate = (ctx: ConnectionGeneratorArguments) =>
}
}

return install<ConnectionGeneratorContext>(addVersions(dependencies, ctx.dependencyVersions))(ctx)
return install<ConnectionGeneratorContext>(
addVersions(dependencies, ctx.dependencyVersions),
false,
ctx.feathers.packager
)(ctx)
})
4 changes: 3 additions & 1 deletion packages/cli/src/hook/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { generator, prompt, runGenerators } from '@feathershq/pinion'
import _ from 'lodash'
import { FeathersBaseContext } from '../commons'
import { checkPreconditions, FeathersBaseContext, initializeBaseContext } from '../commons'

export interface HookGeneratorContext extends FeathersBaseContext {
name: string
Expand All @@ -11,6 +11,8 @@ export interface HookGeneratorContext extends FeathersBaseContext {

export const generate = (ctx: HookGeneratorContext) =>
generator(ctx)
.then(initializeBaseContext())
.then(checkPreconditions())
.then(
prompt<HookGeneratorContext>(({ type, name }) => [
{
Expand Down
30 changes: 2 additions & 28 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,2 @@
import { Argv, generator, runGenerator, getContext } from '@feathershq/pinion'
import { FeathersBaseContext, initializeBaseContext } from './commons'

export const commandRunner = (yarg: any) => {
const ctx = getContext<FeathersBaseContext>({
...yarg.argv
})

return generate(ctx)
}

export const generate = (ctx: FeathersBaseContext) =>
generator(ctx)
.then(initializeBaseContext())
.then(runGenerator(__dirname, (ctx: FeathersBaseContext) => `${ctx._[1]}`, 'index'))

export const command = (yargs: Argv) =>
yargs
.command('generate', 'Run a generator', (yarg) =>
yarg
.command('app', 'Generate a new app', commandRunner)
.command('service', 'Generate a service', commandRunner)
.command('hook', 'Generate a hook', commandRunner)
.command('connection', 'Connect to a different database', commandRunner)
.command('authentication', 'Set up authentication with a custom entity', commandRunner)
)
.usage('Usage: $0 <command> [options]')
.help()
export * from './cli'
export * from './commons'
Loading

0 comments on commit c7e1b7e

Please sign in to comment.