8000 feat: add local dev branch option by khendrikse · Pull Request #7204 · netlify/cli · GitHub
[go: up one dir, main page]

Skip to content

feat: add local dev branch option #7204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
fix awaiting drizzle deps installing / add status check and getSiteCo…
…nfiguration
  • Loading branch information
Caleb Barnes authored and Karin committed Apr 9, 2025
commit d20528d38d8fcc95da5a5a6f9010dcd03a183891
90 changes: 79 additions & 11 deletions src/commands/database/database.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OptionValues } from 'commander'
import inquirer from 'inquirer'
import BaseCommand from '../base-command.js'
import { getExtension, getExtensionInstallations, installExtension } from './utils.js'
import { getExtension, getExtensionInstallations, getSiteConfiguration, installExtension } from './utils.js'
import { initDrizzle } from './drizzle.js'

const NETLIFY_DATABASE_EXTENSION_SLUG = '-94w9m6w-netlify-database-extension'
Expand Down Expand Up @@ -37,7 +37,8 @@ const init = async (_options: OptionValues, command: BaseCommand) => {
if (!command.netlify.api.accessToken) {
throw new Error(`No access token found, please login with netlify login`)
}

console.log(`Initializing a new database for site: ${command.siteId}
Please wait...`)
let site: Awaited<ReturnType<typeof command.netlify.api.getSite>>
try {
// @ts-expect-error -- feature_flags is not in the types
Expand Down Expand Up @@ -76,16 +77,27 @@ const init = async (_options: OptionValues, command: BaseCommand) => {
if (!dbExtensionInstallation) {
console.log(`Netlify Database extension not installed on team ${site.account_id}, attempting to install now...`)

const installed = await installExtension({
accountId: site.account_id,
token: netlifyToken,
slug: NETLIFY_DATABASE_EXTENSION_SLUG,
hostSiteUrl: extension.hostSiteUrl ?? '',
})
if (!installed) {
throw new Error(`Failed to install extension on team ${site.account_id}: ${NETLIFY_DATABASE_EXTENSION_SLUG}`)
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'installExtension',
message: `Netlify Database extension is not installed on team ${site.account_id}, would you like to install it now?`,
},
])
if (answers.installExtension) {
const installed = await installExtension({
accountId: site.account_id,
token: netlifyToken,
slug: NETLIFY_DATABASE_EXTENSION_SLUG,
hostSiteUrl: extension.hostSiteUrl ?? '',
})
if (!installed) {
throw new Error(`Failed to install extension on team ${site.account_id}: ${NETLIFY_DATABASE_EXTENSION_SLUG}`)
}
console.log(`Netlify Database extension installed on team ${site.account_id}`)
} else {
return
}
console.log(`Netlify Database extension installed on team ${site.account_id}`)
}

try {
Expand Down Expand Up @@ -125,6 +137,60 @@ const init = async (_options: OptionValues, command: BaseCommand) => {
return
}

const status = async (_options: OptionValues, command: BaseCommand) => {
if (!command.siteId) {
console.error(`The project must be linked with netlify link before initializing a database.`)
return
}
// check if this site has a db initialized
const site = await command.netlify.api.getSite({ siteId: command.siteId })
if (!site.account_id) {
throw new Error(`No account id found for site ${command.siteId}`)
}
if (!command.netlify.api.accessToken) {
throw new Error(`You must be logged in with netlify login to check the status of the database`)
}
const netlifyToken = command.netlify.api.accessToken.replace('Bearer ', '')
const extensionInstallation = await getExtensionInstallations({
accountId: site.account_id,
siteId: command.siteId,
token: netlifyToken,
})

if (!extensionInstallation) {
console.log(`Netlify Database extension not installed on team ${site.account_id}`)
return
}

const siteConfig = await getSiteConfiguration({
siteId: command.siteId,
accountId: site.account_id,
slug: NETLIFY_DATABASE_EXTENSION_SLUG,
token: netlifyToken,
})

if (!siteConfig) {
throw new Error(`Failed to get site configuration for site ${command.siteId}`)
}
try {
const siteEnv = await command.netlify.api.getEnvVar({
accountId: site.account_id,
siteId: command.siteId,
key: 'NETLIFY_DATABASE_URL',
})

if (siteEnv.key === 'NETLIFY_DATABASE_URL') {
console.log(`Database initialized for site: ${command.siteId}`)
return
}
} catch {
throw new Error(
`Database not initialized for site: ${command.siteId}.
Run 'netlify db init' to initialize a database`,
)
}
}

export const createDatabaseCommand = (program: BaseCommand) => {
const dbCommand = program.command('db').alias('database').description(`TODO: write description for database command`)

Expand All @@ -134,5 +200,7 @@ export const createDatabaseCommand = (program: BaseCommand) => {
.option('--drizzle', 'Sets up drizzle-kit and drizzle-orm in your project')
.action(init)

dbCommand.command('status').description('Check the status of the database').action(status)

return dbCommand
}
67 changes: 50 additions & 17 deletions src/commands/database/drizzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { spawn } from 'child_process'
import { carefullyWriteFile } from './utils.js'
import BaseCommand from '../base-command.js'
import path from 'path'
import fs from 'fs'
import fs from 'fs/promises'
import inquirer from 'inquirer'

export const initDrizzle = async (command: BaseCommand) => {
if (!command.project.root) {
Expand All @@ -11,31 +12,43 @@ export const initDrizzle = async (command: BaseCommand) => {
const drizzleConfigFilePath = path.resolve(command.project.root, 'drizzle.config.ts')
await carefullyWriteFile(drizzleConfigFilePath, drizzleConfig)

fs.mkdirSync(path.resolve(command.project.root, 'db'), { recursive: true })
await fs.mkdir(path.resolve(command.project.root, 'db'), { recursive: true })
const schemaFilePath = path.resolve(command.project.root, 'db/schema.ts')
await carefullyWriteFile(schemaFilePath, exampleDrizzleSchema)

const dbIndexFilePath = path.resolve(command.project.root, 'db/index.ts')
await carefullyWriteFile(dbIndexFilePath, exampleDbIndex)

console.log('Adding drizzle-kit and drizzle-orm to the project')
// install dev deps
const devDepProc = spawn(
command.project.packageManager?.installCommand ?? 'npm install',
['drizzle-kit@latest', '-D'],
const packageJsonPath = path.resolve(command.project.root, 'package.json')

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'))

const answers = await inquirer.prompt([
{
stdio: 'inherit',
shell: true,
type: 'confirm',
name: 'updatePackageJson',
message: `Add drizzle db commands to package.json?`,
},
)
devDepProc.on('exit', (code) => {
if (code === 0) {
// install deps
spawn(command.project.packageManager?.installCommand ?? 'npm install', ['drizzle-orm@latest'], {
stdio: 'inherit',
shell: true,
})
])
if (answers.updatePackageJson) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
packageJson.scripts = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
...(packageJson.scripts ?? {}),
...packageJsonScripts,
}
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2))
}

await spawnAsync(command.project.packageManager?.installCommand ?? 'npm install', ['drizzle-kit@latest', '-D'], {
stdio: 'inherit',
shell: true,
})

await spawnAsync(command.project.packageManager?.installCommand ?? 'npm install', ['drizzle-orm@latest'], {
stdio: 'inherit',
shell: true,
})
}

Expand Down Expand Up @@ -66,3 +79,23 @@ export const db = drizzle({
schema
});
`

const packageJsonScripts = {
'db:generate': 'netlify dev:exec --context dev drizzle-kit generate',
'db:migrate': 'netlify dev:exec --context dev drizzle-kit migrate',
'db:studio': 'netlify dev:exec --context dev drizzle-kit studio',
}

const spawnAsync = (command: string, args: string[], options: Parameters<typeof spawn>[2]): Promise<number> => {
return new Promise((resolve, reject) => {
const child = spawn(command, args, options)
child.on('error', reject)
child.on('exit', (code) => {
if (code === 0) {
resolve(code)
}
const errorMessage = code ? `Process exited with code ${code.toString()}` : 'Process exited with no code'
reject(new Error(errorMessage))
})
})
}
33 changes: 31 additions & 2 deletions src/commands/database/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fsPromises from 'fs/promises'
import fs from 'fs'
import inquirer from 'inquirer'
import path from 'path'
Expand Down Expand Up @@ -96,9 +97,37 @@ export const carefullyWriteFile = async (filePath: string, data: string) => {
},
])
if (answers.overwrite) {
fs.writeFileSync(filePath, data)
await fsPromises.writeFile(filePath, data)
}
} else {
fs.writeFileSync(filePath, data)
await fsPromises.writeFile(filePath, data)
}
}

export const getSiteConfiguration = async ({
siteId,
accountId,
token,
slug,
}: {
siteId: string
accountId: string
token: string
slug: string
}) => {
const siteConfigurationResponse = await fetch(
`${JIGSAW_URL}/team/${accountId}/integrations/${slug}/configuration/site/${siteId}`,
{
headers: {
'netlify-token': token,
},
},
)

if (!siteConfigurationResponse.ok) {
throw new Error(`Failed to fetch extension site configuration for ${siteId}. Is the extension installed?`)
}

const siteConfiguration = await siteConfigurationResponse.json()
return siteConfiguration
}
0