Skip to content

Commit b8f741a

Browse files
authored
feat(cli): integrate D1 with "migrate diff" and "db pull" (#23662)
1 parent 4f51dba commit b8f741a

File tree

14 files changed

+573
-75
lines changed

14 files changed

+573
-75
lines changed

packages/internals/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export { default as byline } from './utils/byline'
5656
export { callOnceOnSuccess } from './utils/callOnce'
5757
export { canPrompt } from './utils/canPrompt'
5858
export { chmodPlusX } from './utils/chmodPlusX'
59+
export { locateLocalCloudflareD1 } from './utils/cloudflareD1'
5960
export { drawBox } from './utils/drawBox'
6061
export { extractPreviewFeatures } from './utils/extractPreviewFeatures'
6162
export { formatms } from './utils/formatms'
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import path from 'node:path'
2+
import process from 'node:process'
3+
4+
import glob from 'globby'
5+
import { match } from 'ts-pattern'
6+
7+
const defaultD1DirPath = path.join('.wrangler', 'state', 'v3', 'd1', 'miniflare-D1DatabaseObject')
8+
9+
type TocateLocalCloudflareD1Args = {
10+
arg: '--to-local-d1' | '--from-local-d1'
11+
}
12+
13+
// Utility to find the location of the local Cloudflare D1 sqlite database.
14+
// When using `wrangler`, the database is located in `${cwd}/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/<UUID>.sqlite`,
15+
// where `<UUID>` is a unique identifier for the database.
16+
export async function locateLocalCloudflareD1({ arg }: TocateLocalCloudflareD1Args) {
17+
const cwd = process.cwd()
18+
const d1DirPath = path.join(cwd, defaultD1DirPath)
19+
20+
const d1Databases = await glob(path.join(d1DirPath, '*.sqlite'), {})
21+
22+
if (d1Databases.length === 0) {
23+
throw new Error(
24+
`No Cloudflare D1 databases found in ${defaultD1DirPath}. Did you run \`wrangler d1 create <DATABASE_NAME>\` and \`wrangler dev\`?`,
25+
)
26+
}
27+
28+
if (d1Databases.length > 1) {
29+
const { originalArg, recommendedArg } = match(arg)
30+
.with('--to-local-d1', (originalArg) => ({
31+
originalArg,
32+
recommendedArg: '--to-url file:',
33+
}))
34+
.with('--from-local-d1', (originalArg) => ({
35+
originalArg,
36+
recommendedArg: '--from-url file:',
37+
}))
38+
.exhaustive()
39+
40+
throw new Error(
41+
`Multiple Cloudflare D1 databases found in ${defaultD1DirPath}. Please manually specify the local D1 database with \`${recommendedArg}\`, without using the \`${originalArg}\` flag.`,
42+
)
43+
}
44+
45+
const d1Database = d1Databases[0]
46+
return d1Database
47+
}

packages/migrate/src/__tests__/DbPull/__snapshots__/sqlite.test.ts.snap

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,36 @@ model AwesomeProfile {
163163
164164
`;
165165
166+
exports[`common/sqlite should succeed when --local-d1 and a single local Cloudflare D1 database exists 2`] = `
167+
generator client {
168+
provider = "prisma-client-js"
169+
previewFeatures = ["driverAdapters"]
170+
}
171+
172+
datasource db {
173+
provider = "sqlite"
174+
url = "file:.wrangler/state/v3/d1/miniflare-D1DatabaseObject/5d11bcce386042472d19a6a4f58e40041ebc5932c972e1449cbf404f3e3c4a7a.sqlite"
175+
}
176+
177+
model Post {
178+
id Int @id @default(autoincrement())
179+
title String
180+
authorId Int
181+
User User @relation(fields: [authorId], references: [id])
182+
}
183+
184+
model User {
185+
id Int @id @default(autoincrement())
186+
email String @unique
187+
count1 Int
188+
name String?
189+
Post Post[]
190+
}
191+
192+
`;
193+
194+
exports[`common/sqlite should succeed when reintrospecting with --local-d1 and a single local Cloudflare D1 database exists 2`] = ``;
195+
166196
exports[`common/sqlite should succeed when schema is invalid and using --force 7`] = `
167197
generator client {
168198
provider = "prisma-client-js"

packages/migrate/src/__tests__/DbPull/sqlite.test.ts

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,45 @@ const ctx = jestContext.new().add(jestConsoleContext()).add(jestProcessContext()
1313
process.env.CI = 'true'
1414

1515
describe('common/sqlite', () => {
16+
test('should succeed when --local-d1 and a single local Cloudflare D1 database exists', async () => {
17+
ctx.fixture('cloudflare-d1-one-db')
18+
19+
const introspect = new DbPull()
20+
const result = introspect.parse(['--local-d1', '--print'])
21+
await expect(result).resolves.toMatchInlineSnapshot(``)
22+
expect(ctx.mocked['console.log'].mock.calls.join('\n')).toMatchSnapshot()
23+
expect(ctx.mocked['console.info'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
24+
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
25+
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
26+
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
27+
})
28+
29+
test('should succeed when reintrospecting with --local-d1 and a single local Cloudflare D1 database exists', async () => {
30+
ctx.fixture('re-introspection/sqlite/cloudflare-d1-one-db')
31+
32+
const introspect = new DbPull()
33+
const result = introspect.parse(['--local-d1'])
34+
await expect(result).resolves.toMatchInlineSnapshot(``)
35+
expect(ctx.mocked['console.log'].mock.calls.join('\n')).toMatchSnapshot()
36+
expect(ctx.mocked['console.info'].mock.calls.join('\n')).toMatchInlineSnapshot(`
37+
Prisma schema loaded from prisma/schema.prisma
38+
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
39+
`)
40+
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
41+
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
42+
43+
44+
- Introspecting based on datasource defined in prisma/schema.prisma
45+
46+
✔ Introspected 2 models and wrote them into prisma/schema.prisma in XXXms
47+
48+
Run prisma generate to generate Prisma Client.
49+
Without the driverAdapters preview feature, the schema introspected via the --local-d1 flag will not work with @prisma/client.
50+
51+
`)
52+
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
53+
})
54+
1655
test('basic introspection', async () => {
1756
ctx.fixture('introspection/sqlite')
1857
const introspect = new DbPull()
@@ -96,13 +135,13 @@ describe('common/sqlite', () => {
96135
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
97136
98137
99-
- Introspecting based on datasource defined in schema.prisma
138+
- Introspecting based on datasource defined in schema.prisma
100139
101-
✔ Introspected 3 models and wrote them into schema.prisma in XXXms
102-
103-
Run prisma generate to generate Prisma Client.
140+
✔ Introspected 3 models and wrote them into schema.prisma in XXXms
141+
142+
Run prisma generate to generate Prisma Client.
104143
105-
`)
144+
`)
106145
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
107146
})
108147

@@ -119,13 +158,13 @@ describe('common/sqlite', () => {
119158
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
120159
121160
122-
- Introspecting
161+
- Introspecting
123162
124-
✔ Introspected 3 models and wrote them into schema.prisma in XXXms
125-
126-
Run prisma generate to generate Prisma Client.
163+
✔ Introspected 3 models and wrote them into schema.prisma in XXXms
164+
165+
Run prisma generate to generate Prisma Client.
127166
128-
`)
167+
`)
129168
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
130169
})
131170

@@ -159,20 +198,20 @@ describe('common/sqlite', () => {
159198
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
160199
161200
162-
- Introspecting based on datasource defined in prisma/reintrospection.prisma
201+
- Introspecting based on datasource defined in prisma/reintrospection.prisma
163202
164-
✔ Introspected 3 models and wrote them into prisma/reintrospection.prisma in XXXms
165-
166-
*** WARNING ***
203+
✔ Introspected 3 models and wrote them into prisma/reintrospection.prisma in XXXms
204+
205+
*** WARNING ***
167206
168-
These models were enriched with \`@@map\` information taken from the previous Prisma schema:
169-
- "AwesomeNewPost"
170-
- "AwesomeProfile"
171-
- "AwesomeUser"
207+
These models were enriched with \`@@map\` information taken from the previous Prisma schema:
208+
- "AwesomeNewPost"
209+
- "AwesomeProfile"
210+
- "AwesomeUser"
172211
173-
Run prisma generate to generate Prisma Client.
212+
Run prisma generate to generate Prisma Client.
174213
175-
`)
214+
`)
176215
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
177216

178217
expect(ctx.fs.read('prisma/reintrospection.prisma')).toMatchInlineSnapshot(`
@@ -229,14 +268,14 @@ describe('common/sqlite', () => {
229268
expect(ctx.mocked['console.info'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
230269
expect(ctx.mocked['console.error'].mock.calls.join('\n')).toMatchInlineSnapshot(`
231270
232-
// *** WARNING ***
233-
//
234-
// These models were enriched with \`@@map\` information taken from the previous Prisma schema:
235-
// - "AwesomeNewPost"
236-
// - "AwesomeProfile"
237-
// - "AwesomeUser"
238-
//
239-
`)
271+
// *** WARNING ***
272+
//
273+
// These models were enriched with \`@@map\` information taken from the previous Prisma schema:
274+
// - "AwesomeNewPost"
275+
// - "AwesomeProfile"
276+
// - "AwesomeUser"
277+
//
278+
`)
240279
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
241280
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
242281

@@ -256,13 +295,13 @@ describe('common/sqlite', () => {
256295
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
257296
258297
259-
- Introspecting based on datasource defined in prisma/schema.prisma
298+
- Introspecting based on datasource defined in prisma/schema.prisma
260299
261-
✔ Introspected 3 models and wrote them into prisma/schema.prisma in XXXms
262-
263-
Run prisma generate to generate Prisma Client.
300+
✔ Introspected 3 models and wrote them into prisma/schema.prisma in XXXms
301+
302+
Run prisma generate to generate Prisma Client.
264303
265-
`)
304+
`)
266305
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
267306
})
268307

@@ -292,11 +331,11 @@ describe('common/sqlite', () => {
292331
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
293332
294333
295-
- Introspecting based on datasource defined in prisma/schema.prisma
334+
- Introspecting based on datasource defined in prisma/schema.prisma
296335
297-
✖ Introspecting based on datasource defined in prisma/schema.prisma
336+
✖ Introspecting based on datasource defined in prisma/schema.prisma
298337
299-
`)
338+
`)
300339
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
301340
})
302341

@@ -327,11 +366,11 @@ describe('common/sqlite', () => {
327366
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
328367
329368
330-
- Introspecting based on datasource defined in prisma/schema.prisma
369+
- Introspecting based on datasource defined in prisma/schema.prisma
331370
332-
✖ Introspecting based on datasource defined in prisma/schema.prisma
371+
✖ Introspecting based on datasource defined in prisma/schema.prisma
333372
334-
`)
373+
`)
335374
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
336375
})
337376

@@ -382,11 +421,11 @@ describe('common/sqlite', () => {
382421
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
383422
384423
385-
- Introspecting based on datasource defined in prisma/invalid.prisma
424+
- Introspecting based on datasource defined in prisma/invalid.prisma
386425
387-
✖ Introspecting based on datasource defined in prisma/invalid.prisma
426+
✖ Introspecting based on datasource defined in prisma/invalid.prisma
388427
389-
`)
428+
`)
390429
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
391430
})
392431

@@ -405,13 +444,13 @@ describe('common/sqlite', () => {
405444
expect(ctx.mocked['process.stdout.write'].mock.calls.join('\n')).toMatchInlineSnapshot(`
406445
407446
408-
- Introspecting based on datasource defined in prisma/invalid.prisma
447+
- Introspecting based on datasource defined in prisma/invalid.prisma
409448
410-
✔ Introspected 3 models and wrote them into prisma/invalid.prisma in XXXms
411-
412-
Run prisma generate to generate Prisma Client.
449+
✔ Introspected 3 models and wrote them into prisma/invalid.prisma in XXXms
450+
451+
Run prisma generate to generate Prisma Client.
413452
414-
`)
453+
`)
415454
expect(ctx.mocked['process.stderr.write'].mock.calls.join('\n')).toMatchInlineSnapshot(``)
416455

417456
expect(ctx.fs.read('prisma/invalid.prisma')).toMatchSnapshot()

0 commit comments

Comments
 (0)