Skip to content
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

feat(cli): Add hook generator #2667

Merged
merged 2 commits into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.7.0
- run: npm install -g codeclimate-test-reporter
- run: npm install
- run: npm test
Expand Down
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 31 additions & 24 deletions packages/cli/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
fromFile,
install,
copyFiles,
toFile
toFile,
when
} from '@feathershq/pinion'
import { FeathersBaseContext, FeathersAppInfo, initializeBaseContext } from '../commons'
import { generate as authenticationGenerator } from '../authentication'
Expand Down Expand Up @@ -110,7 +111,7 @@ export const generate = (ctx: AppGeneratorArguments) =>
message: 'What APIs do you want to offer?',
choices: [
{ value: 'rest', name: 'HTTP (REST)', checked: true },
{ value: 'websockets', name: 'Real-time (So)', checked: true }
{ value: 'websockets', name: 'Real-time', checked: true }
]
},
{
Expand Down Expand Up @@ -180,30 +181,36 @@ export const generate = (ctx: AppGeneratorArguments) =>
.then(runGenerators(__dirname, 'templates'))
.then(copyFiles(fromFile(__dirname, 'static'), toFile('.')))
.then(initializeBaseContext())
.then(async (ctx) => {
if (ctx.database === 'custom') {
return ctx
}

const { dependencies } = await connectionGenerator(ctx)
.then(
when<AppGeneratorContext>(
({ authStrategies, database }) => authStrategies.length > 0 && database !== 'custom',
async (ctx) => {
const { dependencies } = await connectionGenerator(ctx)

return {
...ctx,
dependencies
}
})
.then(async (ctx) => {
const { dependencies } = await authenticationGenerator({
...ctx,
service: 'users',
entity: 'user'
})
return {
...ctx,
dependencies
}
}
)
)
.then(
when<AppGeneratorContext>(
({ authStrategies }) => authStrategies.length > 0,
async (ctx) => {
const { dependencies } = await authenticationGenerator({
...ctx,
service: 'users',
entity: 'user'
})

return {
...ctx,
dependencies
}
})
return {
...ctx,
dependencies
}
}
)
)
.then(
install<AppGeneratorContext>(({ transports, framework, dependencyVersions, dependencies }) => {
const hasSocketio = transports.includes('websockets')
Expand Down
32 changes: 30 additions & 2 deletions packages/cli/src/app/templates/app.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,21 @@ app.configure(rest())
${transports.includes('websockets') ? 'app.configure(socketio())' : ''}
app.configure(services)
app.configure(channels)
app.hooks([ logErrorHook ])

// Register hooks that run on all service methods
app.hooks({
around: {
all: [ logErrorHook ]
},
before: {},
after: {},
error: {}
})
// Register application setup and teardown hooks here
app.hooks({
setup: [],
teardown: []
})

export { app }
`
Expand Down Expand Up @@ -74,7 +88,21 @@ app.configure(channels)
// Configure a middleware for 404s and the error handler
app.use(notFound())
app.use(errorHandler({ logger }))
app.hooks([ logErrorHook ])

// Register hooks that run on all service methods
app.hooks({
around: {
all: [ logErrorHook ]
},
before: {},
after: {},
error: {}
})
// Register application setup and teardown hooks here
app.hooks({
setup: [],
teardown: []
})

export { app }
`
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/app/templates/readme.md.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const template = ({ name, description }: AppGeneratorContext) =>

## About

This project uses [Feathers](http://feathersjs.com). An open source web framework for building APIs and real-time applications.
This project uses [Feathers](http://feathersjs.com). An open source framework for building APIs and real-time applications.

## Getting Started

Expand Down Expand Up @@ -38,7 +38,6 @@ Feathers has a powerful command line interface. Here are a few things it can do:
$ npm install -g @feathersjs/cli # Install Feathers CLI

$ feathers generate service # Generate a new Service
$ feathers generate hook # Generate a new Hook
$ feathers help # Show all commands
\`\`\`

Expand Down
22 changes: 17 additions & 5 deletions packages/cli/src/authentication/templates/authentication.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { generator, inject, before, toFile } from '@feathershq/pinion'
import { getSource, renderSource } from '../../commons'
import { AuthenticationGeneratorContext } from '../index'

const template = ({ authStrategies }: AuthenticationGeneratorContext) =>
const template = ({ authStrategies, feathers }: AuthenticationGeneratorContext) =>
`import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { expressOauth } from '@feathersjs/authentication-oauth'
import { OAuthStrategy } from '@feathersjs/authentication-oauth'
${feathers.framework === 'express' ? `import { expressOauth } from '@feathersjs/authentication-oauth'` : ''}
import type { Application } from './declarations'

declare module './declarations' {
Expand All @@ -18,10 +19,21 @@ export const authentication = (app: Application) => {
const authentication = new AuthenticationService(app)

authentication.register('jwt', new JWTStrategy())
${authStrategies.includes('local') ? "authentication.register('local', new LocalStrategy())" : ''}
${authStrategies
.map(
(strategy) =>
` authentication.register('${strategy}', ${
strategy === 'local' ? `new LocalStrategy()` : `new OAuthStrategy()`
})`
)
.join('\n')}

app.use('authentication', authentication)
app.configure(expressOauth())
app.use('authentication', authentication)${
feathers.framework === 'express'
? `
app.configure(expressOauth())`
: ''
}
}
`

Expand Down
50 changes: 50 additions & 0 deletions packages/cli/src/authentication/templates/test.tpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { generator, toFile } from '@feathershq/pinion'
import { renderSource } from '../../commons'
import { AuthenticationGeneratorContext } from '../index'

const template = ({ authStrategies, relative, lib }: AuthenticationGeneratorContext) =>
`import assert from 'assert';
import { app } from '${relative}/${lib}/app';

describe('authentication', () => {
${
authStrategies.includes('local')
? `
const userInfo = {
email: 'someone@example.com',
password: 'supersecret'
}

before(async () => {
try {
await app.service('users').create(userInfo)
} catch (error) {
// Do nothing, it just means the user already exists and can be tested
}
});

it('authenticates user and creates accessToken', async () => {
const { user, accessToken } = await app.service('authentication').create({
strategy: 'local',
...userInfo
}, {})

assert.ok(accessToken, 'Created access token for user')
assert.ok(user, 'Includes user in authentication data')
})`
: ''
}

it('registered the authentication service', () => {
assert.ok(app.service('authentication'))
})
})
`

export const generate = (ctx: AuthenticationGeneratorContext) =>
generator(ctx).then(
renderSource(
template,
toFile<AuthenticationGeneratorContext>(({ test }) => test, 'authentication.test')
)
)
45 changes: 45 additions & 0 deletions packages/cli/src/hook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { generator, prompt, runGenerators } from '@feathershq/pinion'
import _ from 'lodash'
import { FeathersBaseContext } from '../commons'

export interface HookGeneratorContext extends FeathersBaseContext {
name: string
camelName: string
kebabName: string
type: 'regular' | 'around'
}

export const generate = (ctx: HookGeneratorContext) =>
generator(ctx)
.then(
prompt<HookGeneratorContext>(({ type, name }) => [
{
type: 'input',
name: 'name',
message: 'What is the name of the hook?',
when: !name
},
{
name: 'type',
type: 'list',
when: !type,
message: 'What kind of hook is it?',
choices: [
{ value: 'around', name: 'Around' },
{ value: 'regular', name: 'Before, After or Error' }
]
}
])
)
.then((ctx) => {
const { name } = ctx
const kebabName = _.kebabCase(name)
const camelName = _.camelCase(name)

return {
...ctx,
kebabName,
camelName
}
})
.then(runGenerators(__dirname, 'templates'))
28 changes: 28 additions & 0 deletions packages/cli/src/hook/templates/hook.tpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { generator, toFile } from '@feathershq/pinion'
import { HookGeneratorContext } from '../index'
import { renderSource } from '../../commons'

const aroundTemplate = ({ camelName, name }: HookGeneratorContext) => `
import { HookContext, NextFunction } from '../declarations'

export const ${camelName} = async (context: HookContext, next: NextFunction) => {
console.log(\`Running hook ${name} on \${context.path}\.\${context.method}\`)
await next()
}
`

const regularTemplate = ({
camelName
}: HookGeneratorContext) => `import { HookContext } from '../declarations'

export const ${camelName} = async (context: HookContext) => {
console.log(\`Running hook ${name} on \${context.path}\.\${context.method}\`)
}`

export const generate = (ctx: HookGeneratorContext) =>
generator(ctx).then(
renderSource(
(ctx) => (ctx.type === 'around' ? aroundTemplate(ctx) : regularTemplate(ctx)),
toFile<HookGeneratorContext>(({ lib, kebabName }) => [lib, 'hooks', kebabName])
)
)
1 change: 1 addition & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const command = (yargs: Argv) =>
yarg
.command('app', 'Generate a new app', commandRunner)
.command('service', 'Generate a service', commandRunner)
.command('hook', 'Generate a hook', commandRunner)
)
.usage('Usage: $0 <command> [options]')
.help()