1
1
import { OptionValues } from 'commander'
2
2
import inquirer from 'inquirer'
3
3
import BaseCommand from '../base-command.js'
4
- import { getExtension , getExtensionInstallations , getSiteConfiguration , installExtension } from './utils.js'
4
+ import { getAccount , getExtension , getSiteConfiguration , installExtension } from './utils.js'
5
5
import { initDrizzle } from './drizzle.js'
6
+ import { NEON_DATABASE_EXTENSION_SLUG } from './constants.js'
7
+ import prettyjson from 'prettyjson'
8
+ import { chalk , log } from '../../utils/command-helpers.js'
6
9
7
- const NETLIFY_DATABASE_EXTENSION_SLUG = '7jjmnqyo-netlify-neon'
10
+ type SiteInfo = {
11
+ id : string
12
+ name : string
13
+ account_id : string
14
+ admin_url : string
15
+ url : string
16
+ ssl_url : string
17
+ }
8
18
9
19
const init = async ( _options : OptionValues , command : BaseCommand ) => {
20
+ const siteInfo = command . netlify . siteInfo as SiteInfo
10
21
if ( ! command . siteId ) {
11
22
console . error ( `The project must be linked with netlify link before initializing a database.` )
12
23
return
13
24
}
14
25
15
26
const initialOpts = command . opts ( )
16
27
17
- if ( initialOpts . drizzle !== false && initialOpts . drizzle !== true ) {
18
- const answers = await inquirer . prompt (
19
- [
20
- {
21
- type : 'confirm' ,
22
- name : 'drizzle' ,
23
- message : 'Use Drizzle?' ,
24
- } ,
25
- ] . filter ( ( q ) => ! initialOpts [ q . name ] ) ,
26
- )
27
-
28
- if ( ! initialOpts . drizzle ) {
29
- command . setOptionValue ( 'drizzle' , answers . drizzle )
28
+ /**
29
+ * Only prompt for drizzle if the user did not pass in the `--drizzle` or `--no-drizzle` option
30
+ */
31
+ if ( initialOpts . drizzle !== false && initialOpts . drizzle !== true && ! initialOpts . yes ) {
32
+ type Answers = {
33
+ drizzle : boolean
30
34
}
35
+ const answers = await inquirer . prompt < Answers > ( [
36
+ {
37
+ type : 'confirm' ,
38
+ name : 'drizzle' ,
39
+ message : 'Use Drizzle?' ,
40
+ } ,
41
+ ] )
42
+ command . setOptionValue ( 'drizzle' , answers . drizzle )
31
43
}
32
- const opts = command . opts ( )
33
- if ( opts . drizzle && command . project . root ) {
44
+
45
+ const opts = command . opts < {
46
+ drizzle ?: boolean | undefined
47
+ /**
48
+ * Skip prompts and use default values (answer yes to all prompts)
49
+ */
50
+ yes ?: true | undefined
51
+ } > ( )
52
+
53
+ if ( opts . drizzle || ( opts . yes && opts . drizzle !== false ) ) {
34
54
await initDrizzle ( command )
35
55
}
36
56
37
57
if ( ! command . netlify . api . accessToken ) {
38
- throw new Error ( `No access token found, please login with netlify login` )
58
+ throw new Error ( `Please login with netlify login before running this command ` )
39
59
}
40
60
41
- let site : Awaited < ReturnType < typeof command . netlify . api . getSite > >
42
- try {
43
- // @ts -expect-error -- feature_flags is not in the types
44
- site = await command . netlify . api . getSite ( { siteId : command . siteId , feature_flags : 'cli' } )
45
- } catch ( e ) {
46
- throw new Error ( `Error getting site, make sure you are logged in with netlify login` , {
47
- cause : e ,
48
- } )
49
- }
50
- // console.log('site', site)
51
- if ( ! site . account_id ) {
52
- throw new Error ( `No account id found for site ${ command . siteId } ` )
61
+ // let site: Awaited<ReturnType<typeof command.netlify.api.getSite>>
62
+ // try {
63
+ // site = await command.netlify.api.getSite({
64
+ // siteId: command.siteId,
65
+ // // @ts -expect-error -- feature_flags is not in the types
66
+ // feature_flags: 'cli',
67
+ // })
68
+ // } catch (e) {
69
+ // throw new Error(`Error getting site, make sure you are logged in with netlify login`, {
70
+ // cause: e,
71
+ // })
72
+ // }
73
+ if ( ! siteInfo . account_id || ! siteInfo . name ) {
74
+ throw new Error ( `Error getting site, make sure you are logged in with netlify login` )
53
75
}
54
76
55
- console . log ( `Initializing a new database for site: ${ command . siteId } on account ${ site . account_id }
56
- Please wait...` )
77
+ const account = await getAccount ( command , { accountId : siteInfo . account_id } )
78
+
79
+ log ( `Initializing a new database...` )
57
80
58
81
const netlifyToken = command . netlify . api . accessToken . replace ( 'Bearer ' , '' )
59
82
const extension = await getExtension ( {
60
- accountId : site . account_id ,
83
+ accountId : siteInfo . account_id ,
61
84
token : netlifyToken ,
62
- slug : NETLIFY_DATABASE_EXTENSION_SLUG ,
85
+ slug : NEON_DATABASE_EXTENSION_SLUG ,
63
86
} )
64
-
65
87
if ( ! extension ?. hostSiteUrl ) {
66
88
throw new Error ( `Failed to get extension host site url when installing extension` )
67
89
}
68
90
69
- if ( ! extension . installedOnTeam ) {
70
- const answers = await inquirer . prompt ( [
91
+ const installNeonExtension = async ( ) => {
92
+ if ( ! siteInfo . account_id || ! account . name || ! extension . name || ! extension . hostSiteUrl ) {
93
+ throw new Error ( `Failed to install extension "${ extension . name } "` )
94
+ }
95
+ const installed = await installExtension ( {
96
+ accountId : siteInfo . account_id ,
97
+ token : netlifyToken ,
98
+ slug : NEON_DATABASE_EXTENSION_SLUG ,
99
+ hostSiteUrl : extension . hostSiteUrl ,
100
+ } )
101
+ if ( ! installed ) {
102
+ throw new Error ( `Failed to install extension on team "${ account . name } ": "${ extension . name } "` )
103
+ }
104
+ log ( `Extension "${ extension . name } " successfully installed on team "${ account . name } "` )
105
+ }
106
+
107
+ if ( ! extension . installedOnTeam && ! opts . yes ) {
108
+ type Answers = {
109
+ installExtension : boolean
110
+ }
111
+ const answers = await inquirer . prompt < Answers > ( [
71
112
{
72
113
type : 'confirm' ,
73
114
name : 'installExtension' ,
74
- message : `Netlify Database extension is not installed on team ${ site . account_id } , would you like to install it now?` ,
115
+ message : `The required extension " ${ extension . name } " is not installed on team " ${ account . name } " , would you like to install it now?` ,
75
116
} ,
76
117
] )
77
118
if ( answers . installExtension ) {
78
- const installed = await installExtension ( {
79
- accountId : site . account_id ,
80
- token : netlifyToken ,
81
- slug : NETLIFY_DATABASE_EXTENSION_SLUG ,
82
- hostSiteUrl : extension . hostSiteUrl ?? '' ,
83
- } )
84
- if ( ! installed ) {
85
- throw new Error ( `Failed to install extension on team ${ site . account_id } : ${ NETLIFY_DATABASE_EXTENSION_SLUG } ` )
86
- }
87
- console . log ( `Netlify Database extension installed on team ${ site . account_id } ` )
119
+ await installNeonExtension ( )
88
120
} else {
89
121
return
90
122
}
91
123
}
124
+ if ( ! extension . installedOnTeam && opts . yes ) {
125
+ await installNeonExtension ( )
126
+ }
92
127
93
128
try {
94
129
const siteEnv = await command . netlify . api . getEnvVar ( {
95
- accountId : site . account_id ,
130
+ accountId : siteInfo . account_id ,
96
131
siteId : command . siteId ,
97
132
key : 'NETLIFY_DATABASE_URL' ,
98
133
} )
99
134
100
135
if ( siteEnv . key === 'NETLIFY_DATABASE_URL' ) {
101
- console . error ( `Database already initialized for site: ${ command . siteId } ` )
136
+ log ( `Environment variable "NETLIFY_DATABASE_URL" already exists on site: ${ siteInfo . name } ` )
137
+ log ( `You can run "netlify db status" to check the status for this site` )
102
138
return
103
139
}
104
140
} catch {
105
141
// no op, env var does not exist, so we just continue
106
142
}
107
143
108
- const extensionSiteUrl = process . env . UNSTABLE_NETLIFY_DATABASE_EXTENSION_HOST_SITE_URL ?? extension . hostSiteUrl
144
+ const hostSiteUrl = process . env . NEON_DATABASE_EXTENSION_HOST_SITE_URL ?? extension . hostSiteUrl
145
+ const initEndpoint = new URL ( '/cli-db-init' , hostSiteUrl ) . toString ( )
109
146
110
- const initEndpoint = new URL ( '/cli-db-init' , extensionSiteUrl ) . toString ( )
111
- console . log ( 'initEndpoint' , initEndpoint )
112
147
const req = await fetch ( initEndpoint , {
113
148
method : 'POST' ,
114
149
headers : {
115
150
'Content-Type' : 'application/json' ,
116
151
'nf-db-token' : netlifyToken ,
117
152
'nf-db-site-id' : command . siteId ,
118
- 'nf-db-account-id' : site . account_id ,
153
+ 'nf-db-account-id' : siteInfo . account_id ,
119
154
} ,
120
155
} )
121
156
122
157
if ( ! req . ok ) {
123
158
throw new Error ( `Failed to initialize DB: ${ await req . text ( ) } ` )
124
159
}
125
160
126
- const res = await req . json ( )
127
- console . log ( res )
161
+ const res = ( await req . json ( ) ) as {
162
+ code ?: string
163
+ message ?: string
164
+ }
165
+ if ( res . code === 'DATABASE_INITIALIZED' ) {
166
+ if ( res . message ) {
167
+ log ( res . message )
168
+ }
169
+
170
+ log (
171
+ prettyjson . render ( {
172
+ 'Current team' : account . name ,
173
+ 'Current site' : siteInfo . name ,
174
+ [ `${ extension . name } extension` ] : 'installed' ,
175
+ Database : 'connected' ,
176
+ 'Site environment variable' : 'NETLIFY_DATABASE_URL' ,
177
+ } ) ,
178
+ )
179
+ }
128
180
return
129
181
}
130
182
131
183
const status = async ( _options : OptionValues , command : BaseCommand ) => {
184
+ const siteInfo = command . netlify . siteInfo as SiteInfo
132
185
if ( ! command . siteId ) {
133
- console . error ( `The project must be linked with netlify link before initializing a database.` )
134
- return
186
+ throw new Error ( `The project must be linked with netlify link before initializing a database.` )
135
187
}
136
- // check if this site has a db initialized
137
- const site = await command . netlify . api . getSite ( { siteId : command . siteId } )
138
- if ( ! site . account_id ) {
188
+ if ( ! siteInfo . account_id ) {
139
189
throw new Error ( `No account id found for site ${ command . siteId } ` )
140
190
}
141
191
if ( ! command . netlify . api . accessToken ) {
142
192
throw new Error ( `You must be logged in with netlify login to check the status of the database` )
143
193
}
144
194
const netlifyToken = command . netlify . api . accessToken . replace ( 'Bearer ' , '' )
145
- const extensionInstallation = await getExtensionInstallations ( {
146
- accountId : site . account_id ,
147
- siteId : command . siteId ,
148
- token : netlifyToken ,
149
- } )
150
195
151
- if ( ! extensionInstallation ) {
152
- console . log ( `Netlify Database extension not installed on team ${ site . account_id } ` )
153
- return
196
+ const account = await getAccount ( command , { accountId : siteInfo . account_id } )
197
+
198
+ let siteEnv : Awaited < ReturnType < typeof command . netlify . api . getEnvVar > > | undefined
199
+ try {
200
+ siteEnv = await command . netlify . api . getEnvVar ( {
201
+ accountId : siteInfo . account_id ,
202
+ siteId : command . siteId ,
203
+ key : 'NETLIFY_DATABASE_URL' ,
204
+ } )
205
+ } catch {
206
+ // no-op, env var does not exist, so we just continue
154
207
}
155
208
156
- const siteConfig = await getSiteConfiguration ( {
157
- siteId : command . siteId ,
158
- accountId : site . account_id ,
159
- slug : NETLIFY_DATABASE_EXTENSION_SLUG ,
209
+ const extension = await getExtension ( {
210
+ accountId : account . id ,
160
211
token : netlifyToken ,
212
+ slug : NEON_DATABASE_EXTENSION_SLUG ,
161
213
} )
162
-
163
- if ( ! siteConfig ) {
164
- throw new Error ( `Failed to get site configuration for site ${ command . siteId } ` )
165
- }
214
+ let siteConfig
166
215
try {
167
- const siteEnv = await command . netlify . api . getEnvVar ( {
168
- accountId : site . account_id ,
216
+ siteConfig = await getSiteConfiguration ( {
169
217
siteId : command . siteId ,
170
- key : 'NETLIFY_DATABASE_URL' ,
218
+ accountId : siteInfo . account_id ,
219
+ slug : NEON_DATABASE_EXTENSION_SLUG ,
220
+ token : netlifyToken ,
171
221
} )
172
-
173
- if ( siteEnv . key === 'NETLIFY_DATABASE_URL' ) {
174
- console . log ( `Database initialized for site: ${ command . siteId } ` )
175
- return
176
- }
177
222
} catch {
178
- throw new Error (
179
- `Database not initialized for site: ${ command . siteId } .
180
- Run 'netlify db init' to initialize a database` ,
181
- )
223
+ // no-op, site config does not exist or extension not installed
182
224
}
225
+
226
+ log (
227
+ prettyjson . render ( {
228
+ 'Current team' : account . name ,
229
+ 'Current site' : siteInfo . name ,
230
+ [ extension ?. name ? `${ extension . name } extension` : 'Database extension' ] : extension ?. installedOnTeam
231
+ ? 'installed'
232
+ : chalk . red ( 'not installed' ) ,
233
+ // @ts -expect-error -- siteConfig is not typed
234
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
235
+ Database : siteConfig ?. config ?. neonProjectId ? 'connected' : chalk . red ( 'not connected' ) ,
236
+ 'Site environment variable' :
237
+ siteEnv ?. key === 'NETLIFY_DATABASE_URL' ? 'NETLIFY_DATABASE_URL' : chalk . red ( 'NETLIFY_DATABASE_URL not set' ) ,
238
+ } ) ,
239
+ )
183
240
}
184
241
185
242
export const createDatabaseCommand = ( program : BaseCommand ) => {
@@ -190,6 +247,8 @@ export const createDatabaseCommand = (program: BaseCommand) => {
190
247
. description ( 'Initialize a new database' )
191
248
. option ( '--drizzle' , 'Sets up drizzle-kit and drizzle-orm in your project' )
192
249
. option ( '--no-drizzle' , 'Skips drizzle' )
250
+ . option ( '-y, --yes' , 'Skip prompts and use default values' )
251
+ . option ( '-o, --overwrite' , 'Overwrites existing files that would be created when setting up drizzle' )
193
252
. action ( init )
194
253
195
254
dbCommand . command ( 'status' ) . description ( 'Check the status of the database' ) . action ( status )
0 commit comments