Skip to content

Commit 8bf462a

Browse files
authored
feat: add db reset command (#7994)
1 parent 3150060 commit 8bf462a

File tree

4 files changed

+167
-5
lines changed

4 files changed

+167
-5
lines changed

package-lock.json

Lines changed: 8 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/commands/database/database.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const createDatabaseCommand = (program: BaseCommand) => {
3131
'netlify db status',
3232
'netlify db init',
3333
'netlify db init --help',
34-
...(process.env.EXPERIMENTAL_NETLIFY_DB_ENABLED === '1' ? ['netlify db migrate'] : []),
34+
...(process.env.EXPERIMENTAL_NETLIFY_DB_ENABLED === '1' ? ['netlify db migrate', 'netlify db reset'] : []),
3535
])
3636

3737
dbCommand
@@ -96,5 +96,14 @@ export const createDatabaseCommand = (program: BaseCommand) => {
9696
const { migrate } = await import('./migrate.js')
9797
await migrate(options, command)
9898
})
99+
100+
dbCommand
101+
.command('reset')
102+
.description('Reset the local development database, removing all data and tables')
103+
.option('--json', 'Output result as JSON')
104+
.action(async (options: { json?: boolean }, command: BaseCommand) => {
105+
const { reset } = await import('./reset.js')
106+
await reset(options, command)
107+
})
99108
}
100109
}

src/commands/database/reset.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { NetlifyDev } from '@netlify/dev'
2+
3+
import { log, logJson } from '../../utils/command-helpers.js'
4+
import BaseCommand from '../base-command.js'
5+
6+
export interface ResetOptions {
7+
json?: boolean
8+
}
9+
10+
export const reset = async (options: ResetOptions, command: BaseCommand) => {
11+
const { json } = options
12+
const buildDir = command.netlify.site.root ?? command.project.root ?? command.project.baseDirectory
13+
if (!buildDir) {
14+
throw new Error('Could not determine the project root directory.')
15+
}
16+
17+
const netlifyDev = new NetlifyDev({
18+
projectRoot: buildDir,
19+
aiGateway: { enabled: false },
20+
blobs: { enabled: false },
21+
edgeFunctions: { enabled: false },
22+
environmentVariables: { enabled: false },
23+
functions: { enabled: false },
24+
geolocation: { enabled: false },
25+
headers: { enabled: false },
26+
images: { enabled: false },
27+
redirects: { enabled: false },
28+
staticFiles: { enabled: false },
29+
serverAddress: null,
30+
})
31+
32+
try {
33+
await netlifyDev.start()
34+
35+
const { db } = netlifyDev
36+
if (!db) {
37+
throw new Error('Local database failed to start. Set EXPERIMENTAL_NETLIFY_DB_ENABLED=1 to enable.')
38+
}
39+
40+
await db.reset()
41+
42+
if (json) {
43+
logJson({ reset: true })
44+
} else {
45+
log('Local development database has been reset.')
46+
}
47+
} finally {
48+
await netlifyDev.stop()
49+
}
50+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, expect, test, vi, beforeEach } from 'vitest'
2+
3+
const { mockStart, mockStop, mockReset, MockNetlifyDev, logMessages, jsonMessages } = vi.hoisted(() => {
4+
const mockStart = vi.fn().mockResolvedValue({})
5+
const mockStop = vi.fn().mockResolvedValue(undefined)
6+
const mockReset = vi.fn().mockResolvedValue(undefined)
7+
const MockNetlifyDev = vi.fn().mockImplementation(() => ({
8+
start: mockStart,
9+
stop: mockStop,
10+
db: { reset: mockReset },
11+
}))
12+
const logMessages: string[] = []
13+
const jsonMessages: unknown[] = []
14+
return { mockStart, mockStop, mockReset, MockNetlifyDev, logMessages, jsonMessages }
15+
})
16+
17+
vi.mock('@netlify/dev', () => ({
18+
NetlifyDev: MockNetlifyDev,
19+
}))
20+
21+
vi.mock('../../../../src/utils/command-helpers.js', async () => ({
22+
...(await vi.importActual('../../../../src/utils/command-helpers.js')),
23+
log: (...args: string[]) => {
24+
logMessages.push(args.join(' '))
25+
},
26+
logJson: (message: unknown) => {
27+
jsonMessages.push(message)
28+
},
29+
}))
30+
31+
import { reset } from '../../../../src/commands/database/reset.js'
32+
33+
function createMockCommand(overrides: { buildDir?: string; projectRoot?: string } = {}) {
34+
const { buildDir = '/project', projectRoot = '/project' } = overrides
35+
36+
return {
37+
project: { root: projectRoot, baseDirectory: undefined },
38+
netlify: {
39+
site: { root: buildDir },
40+
config: {},
41+
},
42+
} as unknown as Parameters<typeof reset>[1]
43+
}
44+
45+
describe('reset', () => {
46+
beforeEach(() => {
47+
logMessages.length = 0
48+
jsonMessages.length = 0
49+
vi.clearAllMocks()
50+
})
51+
52+
test('starts NetlifyDev, resets the database, and stops', async () => {
53+
await reset({}, createMockCommand())
54+
55+
expect(mockStart).toHaveBeenCalledOnce()
56+
expect(mockReset).toHaveBeenCalledOnce()
57+
expect(mockStop).toHaveBeenCalledOnce()
58+
})
59+
60+
test('logs success message after reset', async () => {
61+
await reset({}, createMockCommand())
62+
63+
expect(logMessages).toContain('Local development database has been reset.')
64+
})
65+
66+
test('outputs JSON when --json flag is set', async () => {
67+
await reset({ json: true }, createMockCommand())
68+
69+
expect(jsonMessages).toHaveLength(1)
70+
expect(jsonMessages[0]).toEqual({ reset: true })
71+
})
72+
73+
test('stops NetlifyDev even when reset throws', async () => {
74+
mockReset.mockRejectedValueOnce(new Error('reset failed'))
75+
76+
await expect(reset({}, createMockCommand())).rejects.toThrow('reset failed')
77+
78+
expect(mockStop).toHaveBeenCalledOnce()
79+
})
80+
81+
test('throws when db is not available after start', async () => {
82+
MockNetlifyDev.mockImplementationOnce(() => ({
83+
start: mockStart,
84+
stop: mockStop,
85+
db: undefined,
86+
}))
87+
88+
await expect(reset({}, createMockCommand())).rejects.toThrow('Local database failed to start')
89+
})
90+
91+
test('throws when project root cannot be determined', async () => {
92+
const command = {
93+
project: { root: undefined, baseDirectory: undefined },
94+
netlify: { site: { root: undefined }, config: {} },
95+
} as unknown as Parameters<typeof reset>[1]
96+
97+
await expect(reset({}, command)).rejects.toThrow('Could not determine the project root directory.')
98+
})
99+
})

0 commit comments

Comments
 (0)