-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(migrate): add schema push command (#3174)
Co-authored-by: prisma-bot <prismabots@gmail.com>
- Loading branch information
1 parent
b1d5a6d
commit 53b27d6
Showing
11 changed files
with
484 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import assert from 'assert' | ||
import del from 'del' | ||
import mkdir from 'make-dir' | ||
import fs from 'fs' | ||
import { promisify } from 'util' | ||
import { dirname, join } from 'path' | ||
import pkgup from 'pkg-up' | ||
import dedent from 'strip-indent' | ||
import Sqlite from 'better-sqlite3' | ||
import stripAnsi from 'strip-ansi' | ||
import { Migrate } from '../Migrate' | ||
import { SchemaPush } from '../commands/SchemaPush' | ||
|
||
const writeFile = promisify(fs.writeFile) | ||
|
||
describe('schema.create', () => { | ||
createTests().map((t) => { | ||
// eslint-disable-next-line jest/expect-expect | ||
test(t.name, async () => { | ||
const pkg = dirname((await pkgup({ cwd: __dirname })) || __filename) | ||
const root = join(pkg, 'tmp', 'schema-' + Date.now()) | ||
const schemaPath = join(root, Object.keys(t.fs)[0]) | ||
await writeFiles(root, t.fs) | ||
await t.fn(schemaPath) | ||
await del(root) | ||
}) | ||
}) | ||
}) | ||
|
||
// create a temporary set of files | ||
async function writeFiles( | ||
root: string, | ||
files: { | ||
[name: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any | ||
}, | ||
): Promise<string> { | ||
for (const name in files) { | ||
const filepath = join(root, name) | ||
await mkdir(dirname(filepath)) | ||
await writeFile(filepath, dedent(files[name])) | ||
} | ||
// return the test path | ||
return root | ||
} | ||
|
||
// create file tests | ||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type | ||
function createTests() { | ||
return [ | ||
{ | ||
name: 'simple ok', | ||
fs: { | ||
'schema.prisma': ` | ||
datasource my_db { | ||
provider = "sqlite" | ||
url = "file:./db/db_file.db" | ||
default = true | ||
} | ||
model User { | ||
id Int @id | ||
} | ||
`, | ||
'db/.keep': ``, | ||
}, | ||
fn: async (schemaPath: string): Promise<undefined> => { | ||
const migrate = new Migrate(schemaPath) | ||
const migration = await migrate.push() | ||
migrate.stop() | ||
expect(migration.executedSteps).toEqual(4) | ||
expect(migration.warnings).toEqual([]) | ||
expect(migration.unexecutable).toEqual([]) | ||
return | ||
}, | ||
}, | ||
{ | ||
name: 'custom schema filename ok', | ||
fs: { | ||
'myawesomeschema.file': ` | ||
datasource my_db { | ||
provider = "sqlite" | ||
url = "file:./db/db_file.db" | ||
default = true | ||
} | ||
model User { | ||
id Int @id | ||
} | ||
`, | ||
'db/.keep': ``, | ||
}, | ||
fn: async (schemaPath: string): Promise<undefined> => { | ||
const migrate = new Migrate(schemaPath) | ||
const migration = await migrate.push() | ||
migrate.stop() | ||
expect(migration.executedSteps).toEqual(4) | ||
expect(migration.warnings).toEqual([]) | ||
expect(migration.unexecutable).toEqual([]) | ||
return | ||
}, | ||
}, | ||
{ | ||
name: 'custom folder and schema filename name ok', | ||
fs: { | ||
'awesome/myawesomeschema.file': ` | ||
datasource my_db { | ||
provider = "sqlite" | ||
url = "file:../db/db_file.db" | ||
default = true | ||
} | ||
model User { | ||
id Int @id | ||
} | ||
`, | ||
'db/.keep': ``, | ||
}, | ||
fn: async (schemaPath: string): Promise<undefined> => { | ||
const migrate = new Migrate(schemaPath) | ||
const migration = await migrate.push() | ||
migrate.stop() | ||
expect(migration.executedSteps).toEqual(4) | ||
expect(migration.warnings).toEqual([]) | ||
expect(migration.unexecutable).toEqual([]) | ||
return | ||
}, | ||
}, | ||
{ | ||
name: 'invalid ok', | ||
fs: { | ||
'schema.prisma': ` | ||
datasource my_db { | ||
provider = "sqlite" | ||
url = "file:./db/db_file.db" | ||
default = true | ||
} | ||
model User { | ||
id Int @id | ||
canBeNull String? | ||
} | ||
`, | ||
'schema-not-null.prisma': ` | ||
datasource my_db { | ||
provider = "sqlite" | ||
url = "file:./db/db_file.db" | ||
default = true | ||
} | ||
model User { | ||
id Int @id | ||
canBeNull String | ||
requiredSomething String | ||
} | ||
`, | ||
'db/.keep': ``, | ||
}, | ||
fn: async (schemaPath: string): Promise<undefined> => { | ||
const migrate = new Migrate(schemaPath) | ||
const migration = await migrate.push() | ||
migrate.stop() | ||
expect(migration.executedSteps).toEqual(4) | ||
expect(migration.warnings).toEqual([]) | ||
expect(migration.unexecutable).toEqual([]) | ||
|
||
const db = new Sqlite( | ||
schemaPath.replace('schema.prisma', 'db/db_file.db'), | ||
{ | ||
// verbose: console.log, | ||
}, | ||
) | ||
const stmt = db.prepare('INSERT INTO User (canBeNull) VALUES (?)') | ||
const info = stmt.run('Something!') | ||
assert.equal(info.changes, 1) | ||
|
||
const oldConsoleLog = console.log | ||
const logs: string[] = [] | ||
console.log = (...args) => { | ||
logs.push(...args) | ||
} | ||
|
||
const schemaPath2 = schemaPath.replace( | ||
'schema.prisma', | ||
'schema-not-null.prisma', | ||
) | ||
|
||
try { | ||
await SchemaPush.new().parse([ | ||
`--schema=${schemaPath2}`, | ||
'--experimental', | ||
]) | ||
} catch (e) { | ||
// Should error with unexecutableMigrations: | ||
expect(stripAnsi(e.message)).toMatchSnapshot() | ||
} | ||
console.log = oldConsoleLog | ||
expect(stripAnsi(logs.join('\n'))).toMatchSnapshot() | ||
return | ||
}, | ||
}, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/packages/migrate/src/__tests__/__snapshots__/Schema.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`schema.create invalid ok 1`] = ` | ||
" | ||
⚠️ We found changes that cannot be executed: | ||
• Added the required column \`requiredSomething\` to the \`User\` table without a default value. There are 1 rows in this table, it is not possible to execute this migration. | ||
" | ||
`; | ||
|
||
exports[`schema.create invalid ok 2`] = `""`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { | ||
arg, | ||
Command, | ||
Commands, | ||
format, | ||
HelpError, | ||
isError, | ||
unknownCommand, | ||
} from '@prisma/sdk' | ||
import chalk from 'chalk' | ||
|
||
export class SchemaCommand implements Command { | ||
public static new(cmds: Commands): SchemaCommand { | ||
return new SchemaCommand(cmds) | ||
} | ||
|
||
// static help template | ||
private static help = format(` | ||
${ | ||
process.platform === 'win32' ? '' : chalk.bold('🏋️ ') | ||
}Powerful Prisma schema commands from your terminal | ||
${chalk.bold.yellow('WARNING')} ${chalk.bold( | ||
'This functionality is currently in an experimental state.', | ||
)} | ||
${chalk.dim( | ||
'When using any of the commands below you need to explicitly opt-in via the --experimental flag.', | ||
)} | ||
${chalk.bold('Usage')} | ||
With an existing schema.prisma: | ||
${chalk.dim('$')} prisma schema [command] [options] --experimental | ||
Or specify a schema: | ||
${chalk.dim( | ||
'$', | ||
)} prisma schema [command] [options] --experimental --schema=./schema.prisma | ||
${chalk.bold('Options')} | ||
-h, --help Display this help message | ||
${chalk.bold('Commands')} | ||
push Push the state from your schema.prisma to your database | ||
${chalk.bold('Examples')} | ||
Using prisma schema push | ||
${chalk.dim('$')} prisma schema push --experimental | ||
`) | ||
private constructor(private readonly cmds: Commands) {} | ||
|
||
public async parse(argv: string[]): Promise<string | Error> { | ||
// parse the arguments according to the spec | ||
const args = arg(argv, { | ||
'--help': Boolean, | ||
'-h': '--help', | ||
'--experimental': Boolean, | ||
}) | ||
|
||
if (isError(args)) { | ||
return this.help(args.message) | ||
} | ||
|
||
// display help for help flag or no subcommand | ||
if (args._.length === 0 || args['--help']) { | ||
return this.help() | ||
} | ||
|
||
// check if we have that subcommand | ||
const cmd = this.cmds[args._[0]] | ||
if (cmd) { | ||
const argsForCmd = args['--experimental'] | ||
? [...args._.slice(1), `--experimental=${args['--experimental']}`] | ||
: args._.slice(1) | ||
return cmd.parse(argsForCmd) | ||
} | ||
|
||
return unknownCommand(SchemaCommand.help, args._[0]) | ||
} | ||
|
||
public help(error?: string): string | HelpError { | ||
if (error) { | ||
return new HelpError( | ||
`\n${chalk.bold.red(`!`)} ${error}\n${SchemaCommand.help}`, | ||
) | ||
} | ||
return SchemaCommand.help | ||
} | ||
} |
Oops, something went wrong.