create-nxtstart-app
Version:
Nxtstart is an easy to use, interactive CLI tool to bootstrap your next web-based project. The template is aimed at students to get an easy access to web development with example implementations. It is also useful for experts to speed up prototyping.
349 lines (325 loc) • 9.31 kB
JavaScript
import shell from 'shelljs'
import chalk from 'chalk'
import * as fs from 'fs'
import path from 'path'
// update when adding new packages, added package managers to post process dockerfile
export const fullPackageList = [
'npm',
'yarn',
'general',
'linting',
'swr',
'mui',
'animations',
'redux',
'd3',
'auth',
'prisma',
'i18n',
'pwa',
'cypress',
'sse',
'webWorker',
'husky',
]
// reminder to also update the frozen Next JS version in projectCreationUtils.js
const frozenVersionsPackageBundles = {
general: {
dep: [],
devDep: [
'@eslint/eslintrc@3.3.5',
'@eslint/js@10.0.1',
'@next/eslint-plugin-next@16.2.7',
'@typescript-eslint/eslint-plugin@8.60.1',
'@typescript-eslint/parser@8.60.1',
'eslint-plugin-react-hooks@7.1.1',
],
},
linting: {
dep: [],
devDep: ['eslint-config-prettier@10.1.8', 'prettier@3.8.3'],
},
swr: {
dep: ['swr@2.4.1'],
devDep: [],
},
mui: {
dep: [
'@mui/icons-material@9.0.1',
'@mui/material@9.0.1',
'@babel/runtime@7.29.7',
'@emotion/cache@11.14.0',
'@emotion/react@11.14.0',
'@emotion/styled@11.14.1',
'@mui/material-nextjs@9.0.1',
],
devDep: [],
},
animations: {
dep: ['framer-motion@12.40.0'],
devDep: [],
},
redux: {
dep: ['@reduxjs/toolkit@2.12.0', 'react-redux@9.3.0'],
devDep: [],
},
d3: {
dep: ['d3@7.9.0'],
devDep: ['@types/d3@7.4.3'],
},
auth: {
// TODO remove pinned version after this is fixed https://github.com/better-auth/better-auth/issues/9868
dep: ['better-auth@1.6.11', 'better-sqlite3@12.10.0'],
devDep: ['@types/better-sqlite3@7.6.13'],
},
prisma: {
dep: ['@prisma/client@7.8.0', 'better-sqlite3@12.10.0', '@prisma/adapter-better-sqlite3@7.8.0', 'dotenv@17.4.2'],
devDep: ['prisma@7.8.0', 'tsx@4.22.4', '@types/better-sqlite3@7.6.13'],
},
i18n: {
dep: ['i18next@26.3.0', 'react-i18next@17.0.8', 'i18next-resources-to-backend@1.2.1', 'next-i18n-router@5.5.8'],
devDep: [],
},
pwa: {
dep: [],
devDep: [],
},
cypress: {
dep: [],
devDep: ['cypress@15.16.0'],
},
sse: {
dep: ['uuid@14.0.0'],
devDep: ['@types/uuid@11.0.0'],
},
webWorker: {
dep: [],
devDep: [],
},
husky: {
dep: [],
devDep: ['husky@9.1.7', 'lint-staged@17.0.7'],
},
}
const packageBundles = {
general: {
dep: [],
devDep: [
'@eslint/eslintrc',
'@eslint/js',
'@next/eslint-plugin-next',
'@typescript-eslint/eslint-plugin',
'@typescript-eslint/parser',
'eslint-plugin-react-hooks',
],
},
linting: {
dep: [],
devDep: ['eslint-config-prettier', 'prettier'],
},
swr: {
dep: ['swr'],
devDep: [],
},
mui: {
dep: [
'@mui/icons-material',
'@mui/material',
'@babel/runtime',
'@emotion/cache',
'@emotion/react',
'@emotion/styled',
'@mui/material-nextjs',
],
devDep: [],
},
animations: {
dep: ['framer-motion'],
devDep: [],
},
redux: {
dep: ['@reduxjs/toolkit', 'react-redux'],
devDep: [],
},
d3: {
dep: ['d3'],
devDep: ['@types/d3'],
},
auth: {
// TODO remove pinned version after this is fixed https://github.com/better-auth/better-auth/issues/9868
dep: ['better-auth@1.6.11', 'better-sqlite3'],
devDep: ['@types/better-sqlite3'],
},
prisma: {
dep: ['@prisma/client', 'better-sqlite3', '@prisma/adapter-better-sqlite3', 'dotenv'],
devDep: ['prisma', 'tsx', '@types/better-sqlite3'],
},
i18n: {
dep: ['i18next', 'react-i18next', 'i18next-resources-to-backend', 'next-i18n-router'],
devDep: [],
},
pwa: {
dep: [],
devDep: [],
},
cypress: {
dep: [],
devDep: ['cypress'],
},
sse: {
dep: ['uuid'],
devDep: ['@types/uuid'],
},
webWorker: {
dep: [],
devDep: [],
},
husky: {
dep: [],
devDep: ['husky', 'lint-staged'],
},
}
export function addPackages(packageManager, packageNamesUser, projectPath, useLatestVersions) {
shell.cd(projectPath)
// add general packages
const packageNames = ['general', ...packageNamesUser]
let cmdDep = packageManager === 'yarn' ? 'yarn add' : 'npm install --force'
let cmdDevDep = packageManager === 'yarn' ? 'yarn add' : 'npm install --force'
for (let i = 0; i < packageNames.length; i++) {
const packageName = packageNames[i]
const packageBundle = useLatestVersions ? packageBundles[packageName] : frozenVersionsPackageBundles[packageName]
packageBundle.dep.forEach((name) => {
cmdDep += ` ${name}`
})
packageBundle.devDep.forEach((name) => {
cmdDevDep += ` ${name}`
})
}
if (cmdDep) {
shell.exec(cmdDep)
}
if (cmdDevDep) {
cmdDevDep += ` ${packageManager === 'yarn' ? '--dev' : '--save-dev'}`
shell.exec(cmdDevDep)
}
console.log(chalk.green(`Installed using "${cmdDep}"`))
console.log(chalk.green(`Installed using "${cmdDevDep}"`))
}
export function updateEnvPrisma(projectPath) {
fs.appendFileSync(
path.join(projectPath, '.env'),
`
DATABASE_URL="file:nxtstart.db"
`,
function (err) {
if (err) throw err
console.log(chalk.green('Added Prisma .env'))
}
)
}
export function updateEnvauth(projectPath) {
fs.appendFileSync(
path.join(projectPath, '.env'),
`
AUTH_DB_URL="./auth.db"
GITHUB_ID=<enter github id>
GITHUB_SECRET=<enter github id>
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=qwertysecretForTheN3xtJ5Template
`,
function (err) {
if (err) throw err
console.log(chalk.green('Added auth .env!'))
}
)
}
export function addRunScripts(projectPath, packages, packageManager) {
fs.readFile(path.join(projectPath, 'package.json'), 'utf8', function (err, data) {
if (err) {
return console.log(err)
}
let result = data
if (packageManager === 'yarn') {
result = result.replace(
/"scripts": {([^}]+)}/g,
`"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",<§cypress§>
"test": "cypress run",
"cypressGui": "cypress open",</§cypress§>
"lint": "eslint ."<§linting§>,
"prettierCheck": "yarn prettier . --check",
"prettierFix": "yarn prettier . --write"</§linting§><§prisma§>,
"db:seed": "tsx prisma/seedDb.ts"</§prisma§>
}<§husky§>,
"lint-staged": {
"*.{js,jsx,ts,tsx,css,md,json}": "prettier --write"
}</§husky§>`
)
}
if (packageManager === 'npm') {
result = result.replace(
/"scripts": {([^}]+)}/g,
`"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",<§cypress§>
"test": "cypress run",
"cypressGui": "cypress open",</§cypress§>
"lint": "eslint ."<§linting§>,
"prettierCheck": "npx prettier . --check",
"prettierFix": "npx prettier . --write"</§linting§><§prisma§>,
"db:seed": "tsx prisma/seedDb.ts"</§prisma§>
}<§husky§>,
"lint-staged": {
"*.{js,jsx,ts,tsx,css,md,json}": "prettier --write"
}</§husky§>`
)
}
for (let i = 0; i < fullPackageList.length; i++) {
const curPackage = fullPackageList[i]
result = result.replace(new RegExp(`<§${curPackage}§>([^§]+)</§${curPackage}§>`, 'gm'), (match, $1) => {
// only remove the tags, keep enclosed code in capture group one if the current package is chosen by user
if (packages.includes(curPackage)) {
return $1
} else {
return ''
}
})
}
fs.writeFile(path.join(projectPath, 'package.json'), result, 'utf8', function (err) {
if (err) return console.log(err)
console.log(chalk.green('Added run scripts!'))
if (packages.includes('husky')) {
shell.cd(projectPath)
if (packageManager === 'yarn') {
shell.exec('npx husky init')
shell.exec('echo yarn lint-staged > .husky/pre-commit')
} else if (packageManager === 'npm') {
shell.exec('npx husky init')
shell.exec('echo npx lint-staged > .husky/pre-commit')
}
}
})
})
}
export function runFinalInstall(packageManager, projectPath) {
shell.cd(projectPath)
let cmd = packageManager === 'yarn' ? 'yarn install' : 'npm install'
const result = shell.exec(cmd)
console.log(chalk.green(`Final install using "${cmd}"`))
if (result.code !== 0) {
return false
}
return true
}