Skip to content

Commit

Permalink
feat(deps): improving dependency flow, provider checks, and migration…
Browse files Browse the repository at this point in the history
… existance checks
  • Loading branch information
tomgobich committed Oct 25, 2024
1 parent 789dba1 commit 5a4dea0
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 143 deletions.
25 changes: 22 additions & 3 deletions src/scaffolds/base_scaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ConfigureCommand from '@adonisjs/core/commands/configure'
import { readFileOrDefault } from '../utils/file_helper.js'
import { slash } from '@adonisjs/core/helpers'
import { stubsRoot } from '../../stubs/main.js'
import { cp } from 'node:fs/promises'
import { cp, readdir } from 'node:fs/promises'
import {
SourceFile,
VariableDeclarationStructure,
Expand All @@ -15,6 +15,7 @@ export default class BaseScaffold {
declare codemods: Codemods

#contents: Map<string, string> = new Map()
#migrations?: string[]

constructor(protected command: ConfigureCommand) {}

Expand All @@ -34,7 +35,7 @@ export default class BaseScaffold {
this.codemods = await this.command.createCodemods()
}

async isProviderRegistered(path: string) {
async hasProvider(path: string) {
let contents = this.#contents.get('adonisrc.ts')

if (!contents) {
Expand All @@ -47,7 +48,7 @@ export default class BaseScaffold {

async copyView(stubName: string) {
const stub = this.app.makePath(stubsRoot, 'views', stubName)
const dest = this.app.viewsPath(stubName.replace('.stub', '.ts'))
const dest = this.app.viewsPath(stubName.replace('.stub', '.edge'))
await this.copyStub(stub, dest)
}

Expand Down Expand Up @@ -78,6 +79,24 @@ export default class BaseScaffold {
}
}

async stubMigration(migrationStub: string) {
const name = slash(migrationStub).split('.stub').at(0)?.split('/').reverse().at(0)

if (!name) {
throw new Error(`Migration name could note be found for: ${migrationStub}`)
}

if (!this.#migrations) {
this.#migrations = await readdir(this.app.migrationsPath())
}

if (this.#migrations.some((migration) => migration.includes(name))) {
return this.logger.action(`create ${name}`).skipped('migration already exists')
}

await this.codemods.makeUsingStub(stubsRoot, migrationStub, {})
}

getLogPath(path: string) {
return slash(this.app.relativePath(path))
}
Expand Down
177 changes: 125 additions & 52 deletions src/scaffolds/jumpstart_scaffold.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cp, readFile, writeFile } from 'node:fs/promises'
import BaseScaffold from './base_scaffold.js'
import ConfigureCommand from '@adonisjs/core/commands/configure'
import { readFile, writeFile } from 'node:fs/promises'
import { stubsRoot } from '../../stubs/main.js'
import BaseScaffold from './base_scaffold.js'
import TailwindScaffold from './tailwind_scaffold.js'

type Import = {
Expand All @@ -11,60 +11,137 @@ type Import = {
}

export default class JumpstartScaffold extends BaseScaffold {
#isWeb = true

constructor(protected command: ConfigureCommand) {
super(command)
}

static installs: { name: string; isDevDependency: boolean }[] = [
...TailwindScaffold.installs,
{ name: 'edge-iconify', isDevDependency: false },
{ name: '@iconify-json/ph', isDevDependency: false },
{ name: '@iconify-json/svg-spinners', isDevDependency: false },
]

async run() {
// have user install & configure required missing core packages
await this.#verifyCoreDependencies()

// once ace commands are done, let's get our codemods set up
await this.boot()

// first install packages, we'll install these into the user's project so they can
// update them as they see fit
const packageNames = JumpstartScaffold.installs.map((install) => install.name).join(', ')
this.logger.info(`We're going to install ${packageNames} to add TailwindCSS & Iconify`)
await this.codemods.installPackages(JumpstartScaffold.installs)

// complete tailwindcss scaffolding
await new TailwindScaffold(this.command).run()

// complete jumpstart scaffolding
await this.#updateEnv()
await this.#enableHttpMethodSpoofing()
await this.#registerPreloads()
await this.#generateStubs()
await this.#updateRoutes()
await this.#updateUserModel()

this.logger.success('Jumpstart is all set! Visit /jumpstart to get started.')
}

async #verifyCoreDependencies() {
const ace = await this.app.container.make('ace')
const isAuthConfigured = await this.isProviderRegistered('@adonisjs/auth/auth_provider')
const isLucidConfigured = await this.isProviderRegistered('@adonisjs/lucid/database_provider')
const isMailConfigured = await this.isProviderRegistered('@adonisjs/mail/mail_provider')

if (!isLucidConfigured) {
const isViteConfigured = await this.hasProvider('@adonisjs/vite/vite_provider')
const isVineConfigured = await this.hasProvider('@adonisjs/core/providers/vinejs_provider')
const isEdgeConfigured = await this.hasProvider('@adonisjs/core/providers/edge_provider')
const isSessionConfigured = await this.hasProvider('@adonisjs/session/session_provider')
const isShieldConfigured = await this.hasProvider('@adonisjs/shield/shield_provider')
const isAuthConfigured = await this.hasProvider('@adonisjs/auth/auth_provider')
const isLucidConfigured = await this.hasProvider('@adonisjs/lucid/database_provider')
const isMailConfigured = await this.hasProvider('@adonisjs/mail/mail_provider')

if (!isViteConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.blue("You'll need @adonisjs/lucid installed and configured to continue")
this.colors.bgBlue("Vite is needed to bundle tailwind assets, let's add @adonisjs/vite")
)
await ace.exec('add', ['@adonisjs/lucid'])

await ace.exec('add', ['@adonisjs/vite'])
}

if (!isAuthConfigured) {
if (!isVineConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.blue("You'll need @adonisjs/auth installed and configured to continue")
this.colors.bgBlue("VineJS is needed for Jumpstart's validations, let's add vinejs")
)
await ace.exec('add', ['@adonisjs/auth'])

await ace.exec('add', ['vinejs'])
}

if (!isMailConfigured) {
if (!isEdgeConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.blue("You'll need @adonisjs/mail installed and configured to continue")
this.colors.bgBlue("Jumpstart uses EdgeJS for its pages & emails, let's add edge")
)
await ace.exec('add', ['@adonisjs/mail'])

await ace.exec('add', ['edge'])
}

await this.boot()
if (!isSessionConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.bgBlue(
"Session is needed for authentication & toast messaging, let's add @adonisjs/session"
)
)

await this.codemods.installPackages([
...TailwindScaffold.installs,
...JumpstartScaffold.installs,
])
await ace.exec('add', ['@adonisjs/session'])
}

await new TailwindScaffold(this.command).run()
if (!isShieldConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.bgBlue(
"Shield is recommended for CSRF & other protections, let's add @adonisjs/shield"
)
)

await this.#updateEnv()
await this.#enableHttpMethodSpoofing()
await this.#registerPreloads()
await this.#generateStubs()
await this.#updateRoutes()
await this.#updateUserModel()
await ace.exec('add', ['@adonisjs/shield'])
}

if (!isLucidConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.bgBlue(
"Jumpstart uses Lucid as it's ORM for models & queries, let's add @adonisjs/lucid"
)
)

await ace.exec('add', ['@adonisjs/lucid'])
}

if (!isAuthConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.bgBlue("Jumpstart adds authentication scaffolding, let's add @adonisjs/auth")
)

this.logger.success('Jumpstart is all set! Visit /welcome to get started.')
await ace.exec('add', ['@adonisjs/auth', '--guard=session'])
}

if (!isMailConfigured) {
this.logger.log('') // let's add a blank line in-between these
this.logger.log(
this.colors.bgBlue(
"Jumpstart includes emails for the forgot password flow & email change notifications. Let's add @adonisjs/mail"
)
)

await ace.exec('add', ['@adonisjs/mail'])
}
}

async #updateEnv() {
Expand All @@ -78,6 +155,8 @@ export default class JumpstartScaffold extends BaseScaffold {
}

async #enableHttpMethodSpoofing() {
if (!this.#isWeb) return

const appConfigPath = this.app.makePath('config/app.ts')
let appConfig = await readFile(appConfigPath, 'utf8')

Expand All @@ -89,6 +168,8 @@ export default class JumpstartScaffold extends BaseScaffold {
}

async #registerPreloads() {
if (!this.#isWeb) return

await this.codemods.makeUsingStub(stubsRoot, 'start/globals.stub', {})
await this.codemods.updateRcFile((rcFile) => {
rcFile.addPreloadFile('#start/globals')
Expand All @@ -98,28 +179,18 @@ export default class JumpstartScaffold extends BaseScaffold {
async #generateStubs() {
//* NOTE: copy utils from base_scaffold exist because Tempura throws an exception on the backticked contents (escaped or not)

// stubs -> views -- using cp due to the number of files
await cp(this.app.makePath(stubsRoot, 'views'), this.app.viewsPath(), {
recursive: true,
force: false,
})
// stubs -> views
if (this.#isWeb) {
await this.copyView('components')
await this.copyView('pages')
}

this.logger
.action(`copy ${this.getLogPath(this.app.viewsPath())} -> pages, emails, components`)
.succeeded()
await this.copyView('emails')

// stubs -> migrations
await this.codemods.makeUsingStub(stubsRoot, 'migrations/create_email_histories_table.stub', {})
await this.codemods.makeUsingStub(
stubsRoot,
'migrations/create_password_reset_tokens_table.stub',
{}
)
await this.codemods.makeUsingStub(
stubsRoot,
'migrations/create_remember_me_tokens_table.stub',
{}
)
this.stubMigration('migrations/create_email_histories_table.stub')
this.stubMigration('migrations/create_password_reset_tokens_table.stub')
this.stubMigration('migrations/create_remember_me_tokens_table.stub')

// stubs -> models
await this.copyModel('email_history.stub')
Expand All @@ -130,7 +201,9 @@ export default class JumpstartScaffold extends BaseScaffold {
await this.codemods.makeUsingStub(stubsRoot, 'validators/settings.stub', {})

// stubs -> services
await this.codemods.makeUsingStub(stubsRoot, 'services/edge_form_service.stub', {})
if (this.#isWeb) {
await this.codemods.makeUsingStub(stubsRoot, 'services/edge_form_service.stub', {})
}

// stubs -> controllers
await this.copyController('auth/forgot_password_controller.stub')
Expand All @@ -157,7 +230,7 @@ export default class JumpstartScaffold extends BaseScaffold {
const contents = file.getText()

const lastImportIndex = file.getImportDeclarations().reverse().at(0)?.getChildIndex() ?? 0
console.log({ lastImportIndex })

file.insertVariableStatements(
lastImportIndex + 1,
[
Expand Down Expand Up @@ -194,7 +267,7 @@ export default class JumpstartScaffold extends BaseScaffold {
file.addStatements(
[
'\n',
"router.on('/welcome').render('pages/welcome').as('welcome')",
"router.on('/jumpstart').render('pages/jumpstart').as('jumpstart')",
'\n',
'//* AUTH -> LOGIN, REGISTER, LOGOUT',
"router.get('/login', [LoginController, 'show']).as('auth.login.show').use(middleware.guest())",
Expand Down Expand Up @@ -276,7 +349,7 @@ export default class JumpstartScaffold extends BaseScaffold {
login.setBodyText(`
const user = await this.verifyCredentials(email, password)
await auth.use('web').login(user, remember)
return user
return user
`)
}

Expand All @@ -294,7 +367,7 @@ export default class JumpstartScaffold extends BaseScaffold {
register.setBodyText(`
const user = await this.create(data)
await auth.use('web').login(user)
return user
return user
`)
}

Expand Down Expand Up @@ -341,7 +414,7 @@ export default class JumpstartScaffold extends BaseScaffold {
.to(emailOld)
.subject(\`Your \${app.appName} email has been successfully changed\`)
.htmlView('emails/account/email_changed', { user: this })
})
})
`)
}

Expand Down
Loading

0 comments on commit 5a4dea0

Please sign in to comment.