Skip to content

Commit

Permalink
feat(create-tensei-app): create the create-tensei-app package
Browse files Browse the repository at this point in the history
Create the create-tensei-app package for rapidly setting up new tensei packages
  • Loading branch information
Frantz Kati committed Nov 29, 2020
1 parent 333c8c6 commit 1188a0f
Show file tree
Hide file tree
Showing 23 changed files with 524 additions and 61 deletions.
10 changes: 5 additions & 5 deletions packages/core/Tensei.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dotenv/config'
import Path from 'path'
import { Signale } from 'signale'
import BodyParser from 'body-parser'
Expand Down Expand Up @@ -245,8 +246,9 @@ export class Tensei implements TenseiContract {

this.server.listen(port, () => {
this.ctx.logger.success(
`🚀 Access your server on ${this.ctx.serverUrl ||
`http://127.0.0.1:${port}`}`
`🚀 Access your server on ${
this.ctx.serverUrl || `http://127.0.0.1:${port}`
}`
)
})
}
Expand Down Expand Up @@ -627,9 +629,7 @@ export class Tensei implements TenseiContract {
}

public mail(driverName: SupportedDrivers, mailConfig = {}) {
this.ctx.mailer = mail()
.connection(driverName)
.config(mailConfig)
this.ctx.mailer = mail().connection(driverName).config(mailConfig)

return this
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class Database {
}

private generateEntityClass(resource: ResourceContract) {
const entityClass = function() {}
const entityClass = function () {}

Object.defineProperty(entityClass, 'name', {
value: resource.data.pascalCaseName,
Expand Down
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"copy:files": "cp index.mustache build/index.mustache"
},
"dependencies": {
"@mikro-orm/core": "^4.3.0",
"@mikro-orm/core": "^4.3.2",
"@slynova/flydrive": "^1.0.3",
"@tensei/common": "^0.3.0",
"@tensei/mail": "^0.3.0",
Expand All @@ -41,7 +41,6 @@
"change-case": "^4.1.1",
"cookie-parser": "^1.4.5",
"date-fns": "^2.14.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-async-handler": "^1.1.4",
"express-response-formatter": "^2.0.2",
Expand Down
58 changes: 58 additions & 0 deletions packages/create-tensei-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<div align="center">
<br />
<br />
<img src="https://res.cloudinary.com/bahdcoder/image/upload/v1604236130/Asset_1_4x_fhcfyg.png" width="450px">
</div>

<br />

<br />

<div align="center">
<h3>
<strong>
The fastest and easiest way to build powerful and secure APIs
</strong>
</h3>
<p>Open source Node.js Headless CMS 🚀. </p>
</div>

<br />

<div align="center">


[![github-actions-image]][github-actions-url] [![npm-image]][npm-url] ![][typescript-image] [![license-image]][license-url]

</div>

<div align="center">
<h3>
<a href="https://tenseijs.com">
Website
</a>
<span> | </span>
<a href="https://tenseijs.com/docs">
Guides
</a>
<span> | </span>
<a href="CONTRIBUTING.md">
Contributing
</a>
</h3>
</div>

<div align="center">
<sub>Built with ❤︎ by <a href="https://github.com/bahdcoder">Kati Frantz</a>
</div>

[github-actions-image]: https://img.shields.io/github/workflow/status/tenseijs/tensei/Tests?style=for-the-badge
[github-actions-url]: https://github.com/tenseijs/tensei/actions?query=workflow%3ATests "github-actions"

[npm-image]: https://img.shields.io/npm/v/@tensei/core.svg?style=for-the-badge&logo=npm
[npm-url]: https://www.npmjs.com/package/@tensei/core "npm"

[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript

[license-url]: LICENSE.md
[license-image]: https://img.shields.io/github/license/tenseijs/tensei?style=for-the-badge
46 changes: 46 additions & 0 deletions packages/create-tensei-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "create-tensei-app",
"version": "0.0.1",
"main": "./build/index.js",
"license": "MIT",
"types": "./build/index.d.ts",
"files": [
"build/"
],
"bin": {
"create-tensei-app": "./build/index.js"
},
"devDependencies": {},
"scripts": {
"prettier": "prettier --write './**/*.{js,json,ts,css}'",
"build": "tsc --p tsconfig.json",
"dev": "tsc --watch --p tsconfig.json",
"test": "jest --verbose --runInBand --forceExit"
},
"dependencies": {
"@types/change-case": "^2.3.1",
"@types/commander": "^2.12.2",
"@types/node": "^14.14.10",
"@types/signale": "^1.4.1",
"change-case": "^4.1.1",
"commander": "^6.2.0",
"edge.js": "^1.1.4",
"execa": "^4.1.0",
"latest-version": "^5.1.0",
"ora": "^5.1.0",
"signale": "^1.4.0"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"publishConfig": {
"access": "public"
}
}
84 changes: 84 additions & 0 deletions packages/create-tensei-app/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Command } from 'commander'
import { snakeCase } from 'change-case'

import Signale from './utils/signale'
import {
getAvailableTemplates,
getAvailableDatabases,
getPackageVersions,
parseTemplateFiles,
projectDirectoryExists,
createProjectFolder,
createProjectFile,
hasYarnInstalled,
installProjectDependencies,
} from './utils/helpers'

export const cli = async (program: Command) => {
const packageManager = program.npm
? 'npm'
: hasYarnInstalled()
? 'yarn'
: 'npm'

const project_name = program.args[0]

const api = program.rest ? 'rest' : 'graphql'
const database = program.database || 'sqlite'
const template = program.template || 'quickstart'
let mikro_orm_db_name = program.dbname || snakeCase(program.args[0])
const mikro_orm_db_pass = program.dbpassword
const mikro_orm_db_host = program.dbhost
const mikro_orm_db_username = program.dbusername

mikro_orm_db_name = database === 'sqlite' ? `${mikro_orm_db_name}.sqlite` : mikro_orm_db_name

if (!getAvailableTemplates().includes(template)) {
Signale.error(`The ${template} template does not exist.`)

process.exit(1)
}

if (!getAvailableDatabases().includes(database)) {
Signale.error(`The ${template} database is not supported yet.`)

process.exit(1)
}

if (projectDirectoryExists(project_name)) {
Signale.error(`The directory ${project_name} already exists.`)

process.exit(1)
}

const [
mikro_orm_package_version,
core_package_version,
auth_package_version,
api_package_version,
nodemon_version
] = await getPackageVersions(database, api)

const templateVariables = {
api_package: api,
project_name,
mikro_orm_database: database,
mikro_orm_package_version,
core_package_version,
auth_package_version,
api_package_version,
nodemon_version,
mikro_orm_db_name,
mikro_orm_db_pass,
mikro_orm_db_host,
mikro_orm_db_username
}

createProjectFolder(project_name)

parseTemplateFiles(template, templateVariables).map(([file, content]) =>
createProjectFile(project_name, file, content)
)

installProjectDependencies(packageManager, project_name)
}
32 changes: 32 additions & 0 deletions packages/create-tensei-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env node
'use strict'

import { cli } from './cli'
import { Command } from 'commander'
import Signale from './utils/signale'
const packageJson = require('../package.json')

const program = new Command(packageJson.name)

program
.version(packageJson.version)
.arguments('<directory>')
.option('--template <template>', 'Specify a supported template')
.option('--rest', 'Bootstrap a rest API project')
.option('--database <database>', 'Specify the database to setup')
.option('--dbname <dbname>', 'Database name')
.option('--dbhost <dbhost>', 'Database host')
.option('--dbusername <dbusername>', 'Database username')
.option('--dbpassword <dbpassword>', 'Database password')
.option('--no-run', 'Do not start the application after it is created')
.option('--npm', 'Use npm package manager in place of yarn')
.description('Scaffold a new tensei application')
.parse(process.argv)

if (!program.args[0]) {
Signale.error('Please specify a valid project name.')

process.exit(1)
}

cli(program as any)
84 changes: 84 additions & 0 deletions packages/create-tensei-app/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Fs from 'fs'
import Ora from 'ora'
import Path from 'path'
import Execa from 'execa'
import Edge from 'edge.js'
import latestVersion from 'latest-version'
import signale from './signale'

export const getAvailableTemplates = () => ['quickstart']

export const getAvailableDatabases = () => ['sqlite', 'mysql', 'postgresql']

export const getPackageVersions = (database: string, api: string) =>
Promise.all([
latestVersion(`@mikro-orm/${database}`),
latestVersion(`@tensei/core`),
latestVersion(`@tensei/auth`),
latestVersion(`@tensei/${api}`),
latestVersion(`nodemon`)
])

export const parseTemplateFiles = (
template: string,
templateVariables: any
) => {
const basePath = Path.resolve(__dirname, '..', '..', 'templates', template)

return Fs.readdirSync(basePath).map(file => [
file,
Edge.renderString(
Fs.readFileSync(Path.resolve(basePath, file)).toString(),
templateVariables
)
])
}

export const projectDirectoryExists = (project: string) =>
Fs.existsSync(Path.resolve(process.cwd(), project))

export const createProjectFolder = (project: string) =>
console.log(Path.resolve(process.cwd(), project)) as any || Fs.mkdirSync(Path.resolve(process.cwd(), project))

export const createProjectFile = (
project: string,
file: string,
content: string
) =>
Fs.writeFileSync(
Path.resolve(
process.cwd(),
project,
file.endsWith('.edge') ? file.substring(0, file.length - 5) : file
),
content
)

export const hasYarnInstalled = () => {
try {
return Execa.commandSync('yarn --version').exitCode === 0
} catch (e) {
return false
}
}

export const installProjectDependencies = (packageManager: string, project: string, runDev =true) => {
try {
const spinner = Ora(`Installing dependencies with ${packageManager}`).start()

Execa.sync(packageManager, ['install'], {
cwd: Path.resolve(process.cwd(), project),
stdin: 'ignore'
})

spinner.stop()

signale.success(`🚀 Done installing project.\n\n🎉 Congratulations on the start of something amazing!\n\n💻 Run the following command to get started:\n\ncd ${project} && ${packageManager} run dev\n\n`)

process.exit(0)
} catch (e) {
signale.error(e)

process.exit(1)
}
}
3 changes: 3 additions & 0 deletions packages/create-tensei-app/src/utils/signale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Signale } from 'signale'

export default new Signale()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
@if(mikro_orm_database === 'sqlite')
{{ mikro_orm_db_name }}
@endif
26 changes: 26 additions & 0 deletions packages/create-tensei-app/templates/quickstart/index.js.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { auth } = require('@tensei/auth')
const { tensei } = require('@tensei/core')
const { {{ api_package }} } = require('@tensei/{{ api_package }}')

tensei()
.plugins([
auth()
.noCookies()
.plugin(),
{{ api_package }}()
.plugin()
])
.databaseConfig({
type: '{{ mikro_orm_database }}',
dbName: '{{ mikro_orm_db_name }}',
@if(mikro_orm_db_pass !== undefined)
password: '{{ mikro_orm_db_pass }}',
@endif
@if(mikro_orm_db_host !== undefined)
host: '{{ mikro_orm_db_host }}',
@endif
@if(mikro_orm_db_username !== undefined)
user: '{{ mikro_orm_db_username }}',
@endif
})
.start()
Loading

0 comments on commit 1188a0f

Please sign in to comment.