Skip to content

Commit cadddbc

Browse files
committed
install @netlify/neon package if not found in package.json
1 parent e151484 commit cadddbc

File tree

4 files changed

+64
-27
lines changed

4 files changed

+64
-27
lines changed

src/commands/database/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export const NEON_DATABASE_EXTENSION_SLUG = process.env.NEON_DATABASE_EXTENSION_SLUG ?? 'neon'
22
export const JIGSAW_URL = process.env.JIGSAW_URL ?? 'https://api.netlifysdk.com'
3+
export const NETLIFY_NEON_PACKAGE_NAME = '@netlify/neon'

src/commands/database/drizzle.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { spawn } from 'child_process'
2-
import { carefullyWriteFile } from './utils.js'
1+
import { carefullyWriteFile, getPackageJSON, spawnAsync } from './utils.js'
32
import BaseCommand from '../base-command.js'
43
import path from 'path'
54
import fs from 'fs/promises'
65
import inquirer from 'inquirer'
6+
import { NETLIFY_NEON_PACKAGE_NAME } from './constants.js'
77

88
export const initDrizzle = async (command: BaseCommand) => {
99
if (!command.project.root) {
@@ -29,12 +29,9 @@ export const initDrizzle = async (command: BaseCommand) => {
2929
}
3030

3131
const packageJsonPath = path.resolve(command.project.root, 'package.json')
32+
const packageJson = getPackageJSON(command.workingDir)
3233

33-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
34-
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'))
35-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
3634
packageJson.scripts = {
37-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
3835
...(packageJson.scripts ?? {}),
3936
...packageJsonScripts,
4037
}
@@ -59,15 +56,14 @@ export const initDrizzle = async (command: BaseCommand) => {
5956
}
6057
}
6158

62-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
63-
if (!Object.keys(packageJson?.devDependencies ?? {}).includes('drizzle-kit')) {
59+
if (!Object.keys(packageJson.devDependencies ?? {}).includes('drizzle-kit')) {
6460
await spawnAsync(command.project.packageManager?.installCommand ?? 'npm install', ['drizzle-kit@latest', '-D'], {
6561
stdio: 'inherit',
6662
shell: true,
6763
})
6864
}
69-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
70-
if (!Object.keys(packageJson?.dependencies ?? {}).includes('drizzle-orm')) {
65+
66+
if (!Object.keys(packageJson.dependencies ?? {}).includes('drizzle-orm')) {
7167
await spawnAsync(command.project.packageManager?.installCommand ?? 'npm install', ['drizzle-orm@latest'], {
7268
stdio: 'inherit',
7369
shell: true,
@@ -94,7 +90,7 @@ export const posts = pgTable('posts', {
9490
content: text().notNull().default('')
9591
});`
9692

97-
const dbIndex = `import { neon } from '@netlify/neon';
93+
const dbIndex = `import { neon } from '${NETLIFY_NEON_PACKAGE_NAME}';
9894
import { drizzle } from 'drizzle-orm/neon-http';
9995
10096
import * as schema from './schema';
@@ -109,17 +105,3 @@ const packageJsonScripts = {
109105
'db:migrate': 'netlify dev:exec drizzle-kit migrate',
110106
'db:studio': 'netlify dev:exec drizzle-kit studio',
111107
}
112-
113-
const spawnAsync = (command: string, args: string[], options: Parameters<typeof spawn>[2]): Promise<number> => {
114-
return new Promise((resolve, reject) => {
115-
const child = spawn(command, args, options)
116-
child.on('error', reject)
117-
child.on('exit', (code) => {
118-
if (code === 0) {
119-
resolve(code)
120-
}
121-
const errorMessage = code ? `Process exited with code ${code.toString()}` : 'Process exited with no code'
122-
reject(new Error(errorMessage))
123-
})
124-
})
125-
}

src/commands/database/init.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import { OptionValues } from 'commander'
22
import inquirer from 'inquirer'
33
import BaseCommand from '../base-command.js'
4-
import { getAccount, getExtension, getJigsawToken, installExtension } from './utils.js'
4+
import { getAccount, getExtension, getJigsawToken, getPackageJSON, installExtension, spawnAsync } from './utils.js'
55
import { initDrizzle } from './drizzle.js'
6-
import { NEON_DATABASE_EXTENSION_SLUG } from './constants.js'
6+
import { NEON_DATABASE_EXTENSION_SLUG, NETLIFY_NEON_PACKAGE_NAME } from './constants.js'
77
import prettyjson from 'prettyjson'
88
import { log } from '../../utils/command-helpers.js'
99
import { SiteInfo } from './database.js'
1010

1111
export const init = async (_options: OptionValues, command: BaseCommand) => {
12+
try {
13+
const packageJson = getPackageJSON(command.workingDir)
14+
if (packageJson.dependencies && !Object.keys(packageJson.dependencies).includes(NETLIFY_NEON_PACKAGE_NAME)) {
15+
await spawnAsync(command.project.packageManager?.installCommand ?? 'npm install', ['@netlify/neon@latest'], {
16+
stdio: 'inherit',
17+
shell: true,
18+
})
19+
}
20+
} catch (e) {
21+
console.error(`Failed to install @netlify/neon in ${command.workingDir}:`, e)
22+
}
23+
1224
const siteInfo = command.netlify.siteInfo as SiteInfo
1325
if (!command.siteId) {
1426
console.error(`The project must be linked with netlify link before initializing a database.`)

src/commands/database/utils.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,52 @@
1+
import { createRequire } from 'module'
2+
import { join } from 'path'
3+
14
import fsPromises from 'fs/promises'
25
import fs from 'fs'
36
import inquirer from 'inquirer'
47

58
import { JIGSAW_URL } from './constants.js'
69
import BaseCommand from '../base-command.js'
710
import { Extension } from './database.js'
11+
import { spawn } from 'child_process'
12+
13+
type PackageJSON = {
14+
dependencies?: Record<string, string>
15+
devDependencies?: Record<string, string>
16+
scripts?: Record<string, string>
17+
}
18+
19+
export function getPackageJSON(directory: string) {
20+
const require = createRequire(join(directory, 'package.json'))
21+
const packageJson = require('./package.json') as unknown
22+
if (typeof packageJson !== 'object' || packageJson === null) {
23+
throw new Error('Failed to load package.json')
24+
}
25+
if ('dependencies' in packageJson && typeof packageJson.dependencies !== 'object') {
26+
throw new Error(`Expected object at package.json#dependencies, got ${typeof packageJson.dependencies}`)
27+
}
28+
if ('devDependencies' in packageJson && typeof packageJson.devDependencies !== 'object') {
29+
throw new Error(`Expected object at package.json#devDependencies, got ${typeof packageJson.devDependencies}`)
30+
}
31+
if ('scripts' in packageJson && typeof packageJson.scripts !== 'object') {
32+
throw new Error(`Expected object at package.json#scripts, got ${typeof packageJson.scripts}`)
33+
}
34+
return packageJson as PackageJSON
35+
}
36+
37+
export const spawnAsync = (command: string, args: string[], options: Parameters<typeof spawn>[2]): Promise<number> => {
38+
return new Promise((resolve, reject) => {
39+
const child = spawn(command, args, options)
40+
child.on('error', reject)
41+
child.on('exit', (code) => {
42+
if (code === 0) {
43+
resolve(code)
44+
}
45+
const errorMessage = code ? `Process exited with code ${code.toString()}` : 'Process exited with no code'
46+
reject(new Error(errorMessage))
47+
})
48+
})
49+
}
850

951
export const getExtension = async ({
1052
accountId,

0 commit comments

Comments
 (0)