Skip to content

Commit

Permalink
Add --cwd option to CLI. (redwoodjs#2818)
Browse files Browse the repository at this point in the history
* Add `--cwd` option to CLI.

* Move prisma wrapper from builder to handler.
  • Loading branch information
peterp authored Jun 12, 2021
1 parent c759c3a commit bfede51
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 89 deletions.
145 changes: 63 additions & 82 deletions packages/cli/src/commands/prisma.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,122 +12,103 @@ export const command = 'prisma [commands..]'
export const description = 'Run Prisma CLI with experimental features'

/**
* This is a lightweight wrapper around Prisma's CLI.
*
* In order to test this command you can do the following:
* 0. cd __fixtures__/example-todo-main && yarn install
* 1. cd..; yarn build:watch
* 2. cd packages/cli
* 3. RWJS_CWD=../../__fixtures__/example-todo-main yarn node dist/index.js prisma <test commands>
* This is a lightweight wrapper around Prisma's CLI with some Redwood CLI modifications.
*/
export const builder = async (yargs) => {
// accept either help or --help, which is the same behavior as all the other RW Yargs commands.
const argv = mapHelpCommandToFlag(process.argv.slice(3))

// We dynamically create the `--options` that are passed to this command.
// TODO: Figure out if there's a way to turn off yargs parsing.
const options = argv
.filter((x) => x.startsWith('--'))
.map((x) => x.substr(2))
.reduce((pv, cv) => {
return {
...pv,
[cv]: {},
}
}, {})

export const builder = (yargs) => {
// Disable yargs parsing of commands and options because it's forwarded
// to Prisma CLI.
yargs
.strictOptions(false)
.strictCommands(false)
.strict(false)
.parserConfiguration({
'camel-case-expansion': false,
})
.help(false)
.version(false)
.option(options)
.option('version', { alias: 'v' })

const paths = getPaths()

const autoFlags = []
}

const hasHelpFlag = argv.some(
(arg) => arg.includes('--help') || arg.includes('-h')
)
// eslint-disable-next-line no-unused-vars
export const handler = async ({ _, $0, commands = [], ...options }) => {
const rwjsPaths = getPaths()

// Only pass auto flags, when not running help
if (!hasHelpFlag) {
if (['seed'].includes(argv[1])) {
// this is safe as is if a user also adds --preview-feature
autoFlags.push('--preview-feature')
}
// Prisma only supports '--help', but Redwood CLI supports `prisma <command> help`
const helpIndex = commands.indexOf('help')
if (helpIndex !== -1) {
options.help = true
commands.splice(helpIndex, 1)
}

// Automatically inject options for some commands.
const hasHelpOption = options.help || options.h
if (!hasHelpOption) {
if (
// `introspect` to be replaced by `db pull`; still valid as of [email protected]
['generate', 'introspect', 'db', 'migrate', 'studio', 'format'].includes(
argv[0]
commands[0]
)
) {
if (!fs.existsSync(paths.api.dbSchema)) {
console.error(
c.error('\n Cannot run command. No Prisma Schema found.\n')
)
if (!fs.existsSync(rwjsPaths.api.dbSchema)) {
console.error()
console.error(c.error('No Prisma Schema found.'))
console.error(`Snaplet searched here '${rwjsPaths.api.dbSchema}'`)
console.error()
process.exit(1)
}
autoFlags.push('--schema', `"${paths.api.dbSchema}"`)
options.schema = `"${rwjsPaths.api.dbSchema}"`

if (commands[1] === 'seed') {
options['preview-feature'] = true
}
}
}

// Set prevents duplicate flags
const args = Array.from(new Set([...argv, ...autoFlags]))
// Convert command and options into a string that's run via execa
let args = commands
for (const [name, value] of Object.entries(options)) {
args.push(`--${name}`)
if (typeof value !== 'boolean') {
args.push(value)
}
}

console.log(
c.green(`\nRunning Prisma CLI:\n`) + `yarn prisma ${args.join(' ')} \n`
)
console.log()
console.log(c.green('Running Prisma CLI...'))
console.log(c.underline('$ yarn prisma ' + args.join(' ')))
console.log()

try {
const prismaCommand = execa(
`"${path.join(paths.base, 'node_modules/.bin/prisma')}"`,
execa.sync(
`"${path.join(rwjsPaths.base, 'node_modules/.bin/prisma')}"`,
args,
{
shell: true,
cwd: paths.api.base,
extendEnv: true,
cleanup: true,
cwd: rwjsPaths.base,
stdio: 'inherit',
cleanup: true,
}
)
prismaCommand.stdout?.pipe(process.stdout)
prismaCommand.stderr?.pipe(process.stderr)

// So we can check for yarn prisma in the output
// e.g. yarn prisma db pull
const { stdout } = await prismaCommand

if (hasHelpFlag || stdout?.match('yarn prisma')) {
printRwWrapperInfo()
if (hasHelpOption || commands.length === 0) {
printWrapInfo()
}
} catch (e) {
process.exit(e?.exitCode || 1)
}
}

const mapHelpCommandToFlag = (argv) => {
return argv.some((x) => x.includes('help'))
? [
...argv.filter((x) => !['--help'].includes(x) && !['help'].includes(x)),
'--help',
]
: argv
}
const printWrapInfo = () => {
const message = [
c.bold('Redwood CLI wraps Prisma CLI'),
'',
'Use `yarn rw prisma` to automatically pass `--schema` and `--preview-feature` options.',
'Use `yarn prisma` to skip Redwood CLI automatic options.',
'',
'Find more information in our docs:',
c.underline('https://redwoodjs.com/docs/cli-commands#prisma'),
]

const printRwWrapperInfo = () => {
const message = `
${c.bold('🦺 Redwood CLI Tip')}\n
Use 'redwood prisma' to automatically pass the options
'--schema=[path]' and '--preview-feature'. For example:\n
${c.green('yarn redwood prisma [command]')}\n
🔍 Redwood Doc: ${c.underline(
'https://redwoodjs.com/docs/cli-commands#prisma'
)}
`
console.log(
boxen(message, {
boxen(message.join('\n'), {
padding: { top: 0, bottom: 0, right: 1, left: 1 },
margin: 1,
borderColor: 'gray',
Expand Down
65 changes: 58 additions & 7 deletions packages/cli/src/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,72 @@
#!/usr/bin/env node
import fs from 'fs'
import path from 'path'

import { config } from 'dotenv-defaults'
import yargs from 'yargs'

import { getPaths } from '@redwoodjs/internal'
import { getPaths, getConfigPath } from '@redwoodjs/internal'

config({
path: path.join(getPaths().base, '.env'),
encoding: 'utf8',
defaults: path.join(getPaths().base, '.env.defaults'),
})
/**
* The current working directory can be set via:
* 1. A `--cwd` option
* 2. The `RWJS_CWD` env-var
* 3. Found by traversing directories upwards for the first `redwood.toml`
*
* This middleware parses, validates, and sets current working directory
* in the order above.
*/
const getCwdMiddleware = (argv) => {
let configPath

try {
let cwd
if (argv.cwd) {
cwd = argv.cwd
// We delete the argument because it's not actually referenced in CLI,
// we use the `RWJS_CWD` env-var,
// and it conflicts with "forwarding" commands such as test and prisma.
delete argv.cwd
} else if (process.env.RWJS_CWD) {
cwd = process.env.RWJS_CWD
} else {
cwd = path.dirname(getConfigPath())
}

configPath = path.resolve(process.cwd(), cwd, 'redwood.toml')
if (!fs.existsSync(configPath)) {
throw new Error('Could not find `redwood.toml` config file.')
}

process.env.RWJS_CWD = cwd
} catch (e) {
console.error()
console.error('Error: Redwood CLI could not find your config file.')
console.error(`Expected '${configPath}'`)
console.error()
console.error(`Did you run Redwood CLI in a RedwoodJS project?`)
console.error(`Or specify an incorrect '--cwd' option?`)
console.error()
process.exit(1)
}
}

const loadDotEnvDefaultsMiddleware = () => {
config({
path: path.join(getPaths().base, '.env'),
defaults: path.join(getPaths().base, '.env.defaults'),
encoding: 'utf8',
})
}

// eslint-disable-next-line no-unused-expressions
yargs
.commandDir('./commands')
.scriptName('rw')
.middleware([getCwdMiddleware, loadDotEnvDefaultsMiddleware])
.option('cwd', {
describe: 'Working directory to use (where `redwood.toml` is located.)',
})
.commandDir('./commands')
.example(
'yarn rw g page home /',
"\"Create a page component named 'Home' at path '/'\""
Expand Down

0 comments on commit bfede51

Please sign in to comment.