Skip to content

Commit

Permalink
feat(db pull): add spinner in CLI when introspecting (#12897)
Browse files Browse the repository at this point in the history
Co-authored-by: Joël Galeran <Jolg42@users.noreply.github.com>
  • Loading branch information
jkomyno and Jolg42 authored Apr 25, 2022
1 parent 9bac5bb commit f2391f2
Show file tree
Hide file tree
Showing 8 changed files with 527 additions and 145 deletions.
330 changes: 224 additions & 106 deletions packages/migrate/src/__tests__/DbPull.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ model AwesomeProfile {
// introspectionSchemaVersion: NonPrisma,
`;
exports[`common/sqlite should succeed when schema is invalid and using --force 5`] = `
exports[`common/sqlite should succeed when schema is invalid and using --force 7`] = `
generator client {
provider = "prisma-client-js"
output = "../generated/client"
Expand Down
13 changes: 5 additions & 8 deletions packages/migrate/src/commands/DbPull.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Command, IntrospectionSchemaVersion, IntrospectionWarnings } from '@prisma/sdk'
import {
arg,
createSpinner,
drawBox,
format,
formatms,
Expand Down Expand Up @@ -92,12 +93,7 @@ Set composite types introspection depth to 2 levels
'--clean': Boolean,
})

const log = (...messages): void => {
if (!args['--print']) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
console.info(...messages)
}
}
const spinnerFactory = createSpinner(!args['--print'])

if (args instanceof Error) {
return this.help(args.message)
Expand Down Expand Up @@ -196,7 +192,7 @@ Some information will be lost (relations, comments, mapped fields, @ignore...),
!args['--url'] && schemaPath
? ` based on datasource defined in ${chalk.underline(path.relative(process.cwd(), schemaPath))}`
: ''
log(`\nIntrospecting${basedOn}`)
const introspectionSpinner = spinnerFactory(`Introspecting${basedOn}`)

const before = Date.now()
let introspectionSchema = ''
Expand All @@ -209,6 +205,7 @@ Some information will be lost (relations, comments, mapped fields, @ignore...),
introspectionWarnings = introspectionResult.warnings
introspectionSchemaVersion = introspectionResult.version
} catch (e: any) {
introspectionSpinner.failure()
if (e.code === 'P4001') {
if (introspectionSchema.trim() === '') {
throw new Error(`\n${chalk.red.bold('P4001 ')}${chalk.red('The introspected database was empty:')} ${
Expand Down Expand Up @@ -305,7 +302,7 @@ Learn more about the upgrade process in the docs:\n${link('https://pris.ly/d/upg
})
: ''

log(`\n✔ Introspected ${modelsAndTypesCountMessage} into ${chalk.underline(
introspectionSpinner.success(`Introspected ${modelsAndTypesCountMessage} into ${chalk.underline(
path.relative(process.cwd(), schemaPath),
)} in ${chalk.bold(formatms(Date.now() - before))}${prisma1UpgradeMessageBox}
${chalk.keyword('orange')(introspectionWarningsMessage)}
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"new-github-issue-url": "0.2.1",
"node-fetch": "2.6.7",
"open": "7",
"ora": "5.4.1",
"p-map": "4.0.0",
"prompts": "2.4.2",
"read-pkg-up": "7.0.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export { getEnvPaths } from './utils/getEnvPaths'
export { handlePanic } from './utils/handlePanic'
export { isCi } from './utils/isCi'
export { isCurrentBinInstalledGlobally } from './utils/isCurrentBinInstalledGlobally'
export { jestConsoleContext, jestContext } from './utils/jestContext'
export { jestConsoleContext, jestContext, jestProcessContext } from './utils/jestContext'
export { keyBy } from './utils/keyBy'
export { link } from './utils/link'
export { load } from './utils/load'
Expand All @@ -67,6 +67,7 @@ export { parseBinaryTargetsEnvValue, parseEnvValue } from './utils/parseEnvValue
export { pick } from './utils/pick'
export { platformRegex } from './utils/platformRegex'
export { printConfigWarnings } from './utils/printConfigWarnings'
export { createSpinner } from './utils/spinner'
export type { Position } from './utils/trimBlocksFromSchema'
export { trimBlocksFromSchema, trimNewLine } from './utils/trimBlocksFromSchema'
export { tryLoadEnvs } from './utils/tryLoadEnvs'
Expand Down
66 changes: 49 additions & 17 deletions packages/sdk/src/utils/jestContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,19 @@ type ContextContributorFactory<Settings, Context, NewContext> = Settings extends
: (settings: Settings) => ContextContributor<Context, NewContext>

/**
* A function that provides additonal test context.
* A function that provides additional test context.
*/
type ContextContributor<Context, NewContext> = (ctx: Context) => NewContext
type ContextContributor<Context, NewContext> = (ctx: Context) => Context & NewContext

/**
* Main context builder API that permits recursively building up context.
*/

function factory<Context>(ctx: Context) {
return {
add<NewContext>(contextContributor: ContextContributor<Context, NewContext>) {
contextContributor(ctx)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return factory<Context & NewContext>(ctx as any)
const newCtx = contextContributor(ctx)
return factory<Context & NewContext>(newCtx)
},
assemble(): Context {
return ctx
Expand All @@ -105,18 +105,19 @@ function factory<Context>(ctx: Context) {
/**
* Test context contributor. Mocks console.error with a Jest spy before each test.
*/
export const jestConsoleContext: ContextContributorFactory<
{},
BaseContext,
{
mocked: {
'console.error': jest.SpyInstance
'console.log': jest.SpyInstance
'console.info': jest.SpyInstance
'console.warn': jest.SpyInstance
}

type ConsoleContext = {
mocked: {
'console.error': jest.SpyInstance
'console.log': jest.SpyInstance
'console.info': jest.SpyInstance
'console.warn': jest.SpyInstance
}
> = () => (ctx) => {
}

export const jestConsoleContext: ContextContributorFactory<{}, BaseContext, ConsoleContext> = () => (c) => {
const ctx = c as BaseContext & ConsoleContext

beforeEach(() => {
ctx.mocked['console.error'] = jest.spyOn(console, 'error').mockImplementation(() => {})
ctx.mocked['console.log'] = jest.spyOn(console, 'log').mockImplementation(() => {})
Expand All @@ -131,5 +132,36 @@ export const jestConsoleContext: ContextContributorFactory<
ctx.mocked['console.warn'].mockRestore()
})

return null as any
return ctx
}

/**
* Test context contributor. Mocks process.std(out|err).write with a Jest spy before each test.
*/

type ProcessContext = {
mocked: {
'process.stderr.write': jest.SpyInstance
'process.stdout.write': jest.SpyInstance
}
}

export const jestProcessContext: ContextContributorFactory<{}, BaseContext, ProcessContext> = () => (c) => {
const ctx = c as BaseContext & ProcessContext

beforeEach(() => {
ctx.mocked['process.stderr.write'] = jest
.spyOn(process.stderr, 'write')
.mockImplementation((message: string | Uint8Array) => true)
ctx.mocked['process.stdout.write'] = jest
.spyOn(process.stdout, 'write')
.mockImplementation((message: string | Uint8Array) => true)
})

afterEach(() => {
ctx.mocked['process.stderr.write'].mockRestore()
ctx.mocked['process.stdout.write'].mockRestore()
})

return ctx
}
59 changes: 59 additions & 0 deletions packages/sdk/src/utils/spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Options as OraOptions } from 'ora'
import ora from 'ora'

const defaultOraOptions: OraOptions = {
spinner: 'dots',
color: 'cyan',
indent: 0,
stream: process.stdout,
}

/**
* Methods available to a spinner instance that has already started.
*/
export interface SpinnerStarted {
success(text?: string): void
failure(text?: string): void
}

/**
* Closure that starts a spinner if `enableOutput` is true, and returns a `SpinnerStarted` instance.
* Note: the spinner will only be enabled if the stream is being run inside a TTY context (not spawned or piped) and/or not in a CI environment.
* @param enableOutput Whether to enable or disable any output. Useful e.g. for "--print" flags in commands.
* @param oraOptions Additional options to pass to `ora` for customizing the spinner.
* @returns
*/
export function createSpinner(enableOutput = true, oraOptions: Partial<OraOptions> = {}) {
const actualOptions = { ...defaultOraOptions, ...oraOptions }

return (text: string): SpinnerStarted => {
if (!enableOutput) {
return {
success: () => {},
failure: () => {},
}
}

actualOptions.stream?.write('\n')
const spinner = ora(actualOptions)
spinner.start(text)

return {
/**
* Stop the spinner, change it to a green ✔ and persist the current text, or text if provided.
* @param textSuccess Will persist text if provided.
*/
success: (textSuccess) => {
spinner.succeed(textSuccess)
},

/**
* Stop the spinner, change it to a red ✖ and persist the current text, or text if provided.
* @param textFailure Will persist text if provided.
*/
failure: (textFailure) => {
spinner.fail(textFailure)
},
}
}
}
Loading

0 comments on commit f2391f2

Please sign in to comment.