Skip to content

Commit

Permalink
refactor: move all configure command actions to codemods module
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Dec 19, 2023
1 parent 3bc53d1 commit 1e608ab
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 195 deletions.
133 changes: 29 additions & 104 deletions commands/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
*/

import { slash } from '@poppinss/utils'
import { installPackage, detectPackageManager } from '@antfu/install-pkg'
import type { CommandOptions } from '../types/ace.js'
import { args, BaseCommand, flags } from '../modules/ace/main.js'
import { CommandOptions } from '../types/ace.js'

/**
* The configure command is used to configure packages after installation
Expand All @@ -22,20 +21,35 @@ export default class Configure extends BaseCommand {
allowUnknownFlags: true,
}

/**
* Exposing all flags from the protected property "parsed"
*/
get parsedFlags() {
return this.parsed.flags
}

/**
* Exposing all args from the protected property "parsed"
*/
get parsedArgs() {
return this.parsed._
}

/**
* Name of the package to configure
*/
@args.string({ description: 'Package name' })
declare name: string

/**
* Turn on verbose mode for packages installation
*/
@flags.boolean({ description: 'Display logs in verbose mode' })
declare verbose?: boolean

/**
* Forcefully overwrite existing files.
*/
@flags.boolean({ description: 'Forcefully overwrite existing files' })
declare force?: boolean

Expand All @@ -52,31 +66,6 @@ export default class Configure extends BaseCommand {
return this.app.import(packageName)
}

/**
* Returns the installation command for different
* package managers
*/
#getInstallationCommands(
packages: string[],
packageManager: 'npm' | 'pnpm' | 'yarn',
isDev: boolean
) {
if (!packages.length) {
return ''
}

const devFlag = isDev ? ' -D' : ''

switch (packageManager) {
case 'npm':
return `${this.colors.yellow(`npm i${devFlag}`)} ${packages.join(' ')}`
case 'yarn':
return `${this.colors.yellow(`yarn add${devFlag}`)} ${packages.join(' ')}`
case 'pnpm':
return `${this.colors.yellow(`pnpm add${devFlag}`)} ${packages.join(' ')}`
}
}

/**
* Registers VineJS provider
*/
Expand All @@ -98,6 +87,16 @@ export default class Configure extends BaseCommand {
})
}

/**
* Creates codemods as per configure command options
*/
async createCodemods() {
const codemods = await super.createCodemods()
codemods.overwriteExisting = this.force === true
codemods.verboseInstallOutput = this.verbose === true
return codemods
}

/**
* Publish a stub file to the user project
*/
Expand Down Expand Up @@ -127,74 +126,6 @@ export default class Configure extends BaseCommand {
this.logger.action(`create ${entityFileName}`).succeeded()
}

/**
* Install packages using the correct package manager
* You can specify version of each package by setting it in the
* name like :
*
* ```
* installPackages(['@adonisjs/lucid@next', '@adonisjs/auth@3.0.0'])
* ```
*/
async installPackages(packages: { name: string; isDevDependency: boolean }[]) {
const appPath = this.app.makePath()
const silent = this.verbose === true ? false : true

const devDeps = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const deps = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)
const packageManager = await detectPackageManager(appPath)

let spinner = this.logger
.await(`installing dependencies using ${packageManager || 'npm'}`)
.start()

try {
await installPackage(deps, { cwd: appPath, silent })
await installPackage(devDeps, { dev: true, cwd: appPath, silent })

spinner.stop()
this.logger.success('dependencies installed')
this.logger.log(devDeps.map((dep) => ` ${this.colors.dim('dev')} ${dep}`).join('\n'))
this.logger.log(deps.map((dep) => ` ${this.colors.dim('prod')} ${dep}`).join('\n'))
} catch (error) {
spinner.update('unable to install dependencies')
spinner.stop()
this.exitCode = 1
this.logger.fatal(error)
}
}

/**
* List the packages one should install before using the packages
*/
listPackagesToInstall(packages: { name: string; isDevDependency: boolean }[]) {
const devDependencies = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const prodDependencies = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)
const instructions = this.ui.sticker().heading('Please install following packages')

;[
this.colors.dim('# npm'),
this.#getInstallationCommands(devDependencies, 'npm', true),
this.#getInstallationCommands(prodDependencies, 'npm', false),
' ',
]
.concat([
this.colors.dim('# yarn'),
this.#getInstallationCommands(devDependencies, 'yarn', true),
this.#getInstallationCommands(prodDependencies, 'yarn', false),
' ',
])
.concat([
this.colors.dim('# pnpm'),
this.#getInstallationCommands(devDependencies, 'pnpm', true),
this.#getInstallationCommands(prodDependencies, 'pnpm', false),
])
.filter((line) => line.length)
.forEach((line) => instructions.add(line))

instructions.render()
}

/**
* Run method is invoked by ace automatically
*/
Expand All @@ -219,18 +150,12 @@ export default class Configure extends BaseCommand {
}

/**
* Instructions needs stubs root
* Set stubsRoot property when package exports it
*/
if (!packageExports.stubsRoot) {
this.logger.error(
`Missing "stubsRoot" export from "${this.name}" package. The stubsRoot variable is required to lookup package stubs`
)
this.exitCode = 1
return
if (packageExports.stubsRoot) {
this.stubsRoot = packageExports.stubsRoot
}

this.stubsRoot = packageExports.stubsRoot

/**
* Run instructions
*/
Expand Down
124 changes: 122 additions & 2 deletions modules/ace/codemods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
*/

import { slash } from '@poppinss/utils'
import { EventEmitter } from 'node:events'
import type { Logger } from '@poppinss/cliui'
import { EnvEditor } from '@adonisjs/env/editor'
import type { CodeTransformer } from '@adonisjs/assembler/code_transformer'
import type { AddMiddlewareEntry, EnvValidationDefinition } from '@adonisjs/assembler/types'

import type { Application } from '../app.js'

/**
* Codemods to modify AdonisJS source files. The codemod APIs relies on
* "@adonisjs/assembler" package and it must be installed as a dependency
* inside user application.
*/
export class Codemods {
export class Codemods extends EventEmitter {
/**
* Flag to know if assembler is installed as a
* peer dependency or not.
Expand All @@ -41,7 +43,19 @@ export class Codemods {
*/
#cliLogger: Logger

/**
* Overwrite existing files when generating files
* from stubs
*/
overwriteExisting = false

/**
* Display verbose logs for package installation
*/
verboseInstallOutput = false

constructor(app: Application<any>, cliLogger: Logger) {
super()
this.#app = app
this.#cliLogger = cliLogger
}
Expand All @@ -56,6 +70,29 @@ export class Codemods {
}
}

/**
* Returns the installation command for different
* package managers
*/
#getInstallationCommands(packages: string[], packageManager: string, isDev: boolean) {
if (!packages.length) {
return ''
}

const colors = this.#cliLogger.getColors()
const devFlag = isDev ? ' -D' : ''

switch (packageManager) {
case 'yarn':
return `${colors.yellow(`yarn add${devFlag}`)} ${packages.join(' ')}`
case 'pnpm':
return `${colors.yellow(`pnpm add${devFlag}`)} ${packages.join(' ')}`
case 'npm':
default:
return `${colors.yellow(`npm i${devFlag}`)} ${packages.join(' ')}`
}
}

/**
* Define one or more environment variables
*/
Expand Down Expand Up @@ -91,6 +128,7 @@ export class Codemods {
await transformer.defineEnvValidations(validations)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
Expand All @@ -114,6 +152,7 @@ export class Codemods {
await transformer.addMiddlewareToStack(stack, middleware)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
Expand All @@ -136,6 +175,7 @@ export class Codemods {
await transformer.updateRcFile(...params)
action.succeeded()
} catch (error) {
this.emit('error', error)
action.failed(error.message)
}
}
Expand All @@ -146,7 +186,7 @@ export class Codemods {
async makeUsingStub(stubsRoot: string, stubPath: string, stubState: Record<string, any>) {
const stubs = await this.#app.stubs.create()
const stub = await stubs.build(stubPath, { source: stubsRoot })
const output = await stub.generate(stubState)
const output = await stub.generate({ force: this.overwriteExisting, ...stubState })

const entityFileName = slash(this.#app.relativePath(output.destination))
const result = { ...output, relativeFileName: entityFileName }
Expand All @@ -159,4 +199,84 @@ export class Codemods {
this.#cliLogger.action(`create ${entityFileName}`).succeeded()
return result
}

/**
* Install packages using the correct package manager
* You can specify version of each package by setting it in the
* name like :
*
* ```
* this.installPackages(['@adonisjs/lucid@next', '@adonisjs/auth@3.0.0'])
* ```
*/
async installPackages(packages: { name: string; isDevDependency: boolean }[]) {
await this.#importAssembler()
const appPath = this.#app.makePath()
const colors = this.#cliLogger.getColors()
const devDependencies = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const dependencies = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)

if (!this.#codeTransformer) {
this.#cliLogger.warning(
'Cannot install packages. Install "@adonisjs/assembler" or manually install following packages'
)
this.#cliLogger.log(`devDependencies: ${devDependencies.join(',')}`)
this.#cliLogger.log(`dependencies: ${dependencies.join(',')}`)
return
}

const transformer = new this.#codeTransformer.CodeTransformer(this.#app.appRoot)
const packageManager = await transformer.detectPackageManager(appPath)

let spinner = this.#cliLogger
.await(`installing dependencies using ${packageManager || 'npm'} `)
.start()

try {
await transformer.installPackage(dependencies, {
cwd: appPath,
silent: !this.verboseInstallOutput,
})
await transformer.installPackage(devDependencies, {
dev: true,
cwd: appPath,
silent: !this.verboseInstallOutput,
})

spinner.stop()
this.#cliLogger.success('Packages installed')
this.#cliLogger.log(
devDependencies.map((dependency) => ` ${colors.dim('dev')} ${dependency} `).join('\n')
)
this.#cliLogger.log(
dependencies.map((dependency) => ` ${colors.dim('prod')} ${dependency} `).join('\n')
)
} catch (error) {
spinner.update('unable to install dependencies')
spinner.stop()
this.#cliLogger.fatal(error)
this.emit('error', error)
}
}

/**
* List the packages one should install before using the packages
*/
async listPackagesToInstall(packages: { name: string; isDevDependency: boolean }[]) {
const appPath = this.#app.makePath()
const devDependencies = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name)
const dependencies = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name)

let packageManager: string | null = null
if (this.#codeTransformer) {
const transformer = new this.#codeTransformer.CodeTransformer(this.#app.appRoot)
packageManager = await transformer.detectPackageManager(appPath)
}

this.#cliLogger.log('Please install following packages')
this.#cliLogger.log(
this.#getInstallationCommands(devDependencies, packageManager || 'npm', true)
)
this.#cliLogger.log(this.#getInstallationCommands(dependencies, packageManager || 'npm', false))
}
}
7 changes: 6 additions & 1 deletion modules/ace/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ export class BaseCommand extends AceBaseCommand {
*/
async createCodemods() {
const { Codemods } = await import('./codemods.js')
return new Codemods(this.app, this.logger)
const codemods = new Codemods(this.app, this.logger)
codemods.on('error', () => {
this.exitCode = 1
})

return codemods
}

/**
Expand Down

0 comments on commit 1e608ab

Please sign in to comment.