Skip to content

Commit

Permalink
feat(cli): Add support for JavaScript to the new CLI (#2668)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl authored Jun 17, 2022
1 parent 7f59ae7 commit ebac587
Show file tree
Hide file tree
Showing 24 changed files with 1,341 additions and 1,611 deletions.
2,706 changes: 1,166 additions & 1,540 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"eslint-plugin-prettier": "^4.0.0",
"lerna": "^5.0.0",
"npm-check-updates": "^13.1.1",
"prettier": "2.6.2",
"prettier": "^2.7.1",
"typescript": "^4.7.3"
}
}
6 changes: 4 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@
},
"dependencies": {
"@feathershq/pinion": "^0.3.1",
"chalk": "^4.0.1",
"lodash": "^4.17.21",
"chalk": "^4.0.1"
"prettier": "^2.7.1"
},
"devDependencies": {
"@feathersjs/authentication": "^5.0.0-pre.22",
Expand All @@ -67,12 +68,13 @@
"@feathersjs/express": "^5.0.0-pre.22",
"@feathersjs/feathers": "^5.0.0-pre.22",
"@feathersjs/koa": "^5.0.0-pre.22",
"@feathersjs/mongodb": "^5.0.0-pre.22",
"@feathersjs/schema": "^5.0.0-pre.22",
"@feathersjs/socketio": "^5.0.0-pre.22",
"@feathersjs/transport-commons": "^5.0.0-pre.22",
"@feathersjs/mongodb": "^5.0.0-pre.22",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.40",
"@types/prettier": "^2.6.3",
"axios": "^0.27.2",
"mocha": "^10.0.0",
"shx": "^0.3.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/app/templates/app.test.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AppGeneratorContext } from '../index'
const template = ({ lib }: AppGeneratorContext) =>
`import assert from 'assert'
import axios from 'axios'
import { Server } from 'http'
import type { Server } from 'http'
import { app } from '../${lib}/app'
const port = app.get('port')
Expand Down
27 changes: 15 additions & 12 deletions packages/cli/src/app/templates/app.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import configuration from '@feathersjs/configuration'
import { koa, rest, bodyParser, errorHandler, parseAuthentication } from '@feathersjs/koa'
${transports.includes('websockets') ? "import socketio from '@feathersjs/socketio'" : ''}
import type { Application } from './declarations'
import { configurationSchema } from './schemas/configuration.schema'
import { logErrorHook } from './logger'
import { Application } from './declarations'
import { services } from './services'
import { services } from './services/index'
import { channels } from './channels'
const app: Application = koa(feathers())
Expand Down Expand Up @@ -41,36 +41,39 @@ const tsExpressApp = ({ transports }: AppGeneratorContext) =>
import helmet from 'helmet'
import { feathers } from '@feathersjs/feathers'
import * as express from '@feathersjs/express'
import express, {
rest, json, urlencoded,
serveStatic, notFound, errorHandler
} from '@feathersjs/express'
import configuration from '@feathersjs/configuration'
${transports.includes('websockets') ? "import socketio from '@feathersjs/socketio'" : ''}
import type { Application } from './declarations'
import { configurationSchema } from './schemas/configuration.schema'
import { logger, logErrorHook } from './logger'
import { Application } from './declarations'
import { services } from './services'
import { services } from './services/index'
import { channels } from './channels'
const app: Application = express.default(feathers())
const app: Application = express(feathers())
// Load app configuration
app.configure(configuration(configurationSchema))
app.use(helmet())
app.use(compress())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(json())
app.use(urlencoded({ extended: true }))
// Host the public folder
app.use('/', express.static(app.get('public')))
app.use('/', serveStatic(app.get('public')))
// Configure services and real-time functionality
app.configure(express.rest())
app.configure(rest())
${transports.includes('websockets') ? 'app.configure(socketio())' : ''}
app.configure(services)
app.configure(channels)
// Configure a middleware for 404s and the error handler
app.use(express.notFound())
app.use(express.errorHandler({ logger }))
app.use(notFound())
app.use(errorHandler({ logger }))
app.hooks([ logErrorHook ])
export { app }
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/app/templates/channels.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AppGeneratorContext } from '../index'

const template = ({}: AppGeneratorContext) =>
`import '@feathersjs/transport-commons'
import { Application, HookContext } from './declarations'
import type { Application, HookContext } from './declarations'
import { logger } from './logger'
export const channels = (app: Application) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/app/templates/configuration.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { renderSource } from '../../commons'
import { AppGeneratorContext } from '../index'

const template = ({}: AppGeneratorContext) =>
`import { schema, Infer, Ajv } from '@feathersjs/schema'
`import { schema, Ajv } from '@feathersjs/schema'
import type { Infer } from '@feathersjs/schema'
import { authenticationSettingsSchema } from '@feathersjs/authentication'
export const configurationSchema = schema({
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/app/templates/logger.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AppGeneratorContext } from '../index'

const template = ({}: AppGeneratorContext) =>
`import { createLogger, format, transports } from 'winston'
import { HookContext, NextFunction } from './declarations'
import type { HookContext, NextFunction } from './declarations'
// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston
export const logger = createLogger({
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/app/templates/services.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { renderSource } from '../../commons'
import { AppGeneratorContext } from '../index'

const template = ({}: AppGeneratorContext) =>
`import { Application } from '../declarations'
`import type { Application } from '../declarations'
export const services = (app: Application) => {
// All services will be registered here
}
`

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { generator, inject, before, toFile } from '@feathershq/pinion'
import { renderSource } from '../../commons'
import { getSource, renderSource } from '../../commons'
import { AuthenticationGeneratorContext } from '../index'

const template = ({ authStrategies }: AuthenticationGeneratorContext) =>
`import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'
import { LocalStrategy } from '@feathersjs/authentication-local'
import { expressOauth } from '@feathersjs/authentication-oauth'
import { Application } from './declarations'
import type { Application } from './declarations'
declare module './declarations' {
interface ServiceTypes {
Expand Down Expand Up @@ -37,5 +37,5 @@ export const generate = (ctx: AuthenticationGeneratorContext) =>
toFile<AuthenticationGeneratorContext>(({ lib }) => lib, 'authentication')
)
)
.then(inject(importTemplate, before('import { services } from'), toAppFile))
.then(inject(configureTemplate, before('app.configure(services)'), toAppFile))
.then(inject(getSource(importTemplate), before('import { services } from'), toAppFile))
.then(inject(getSource(configureTemplate), before('app.configure(services)'), toAppFile))
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ const template = ({
}: AuthenticationGeneratorContext) =>
`import { resolve } from '@feathersjs/schema'
${authStrategies.includes('local') ? `import { passwordHash } from '@feathersjs/authentication-local'` : ''}
import { HookContext } from '${relative}/declarations'
import {
import type { HookContext } from '${relative}/declarations'
import type {
${upperName}Data,
${upperName}Patch,
${upperName}Result,
${upperName}Query,
} from '../${schemaPath}'
import {
${camelName}DataSchema,
${camelName}PatchSchema,
${camelName}ResultSchema,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/authentication/templates/user.schema.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { renderSource } from '../../commons'
import { AuthenticationGeneratorContext } from '../index'

const template = ({ camelName, upperName, authStrategies, type }: AuthenticationGeneratorContext) =>
`import { schema, querySyntax, Infer } from '@feathersjs/schema'
`import { schema, querySyntax } from '@feathersjs/schema'
import type { Infer } from '@feathersjs/schema'
// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = schema({
Expand Down
53 changes: 47 additions & 6 deletions packages/cli/src/commons.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { PackageJson } from 'type-fest'
import { Callable, PinionContext, loadJSON, fromFile, getCallable, renderTemplate } from '@feathershq/pinion'
import * as ts from 'typescript'
import prettier from 'prettier'

export type FeathersAppInfo = {
/**
Expand Down Expand Up @@ -67,10 +69,34 @@ export const initializeBaseContext =
...ctx,
lib: ctx.pkg?.directories?.lib || 'src',
test: ctx.pkg?.directories?.test || 'test',
language: ctx.pkg?.feathers?.language || 'ts',
feathers: ctx.pkg?.feathers
}))

const importRegex = /from '(\..*)'/g
const escapeNewLines = (code: string) => code.replace(/\n\n/g, '\n/* :newline: */')
const restoreNewLines = (code: string) => code.replace(/\/\* :newline: \*\//g, '\n')
const fixLocalImports = (code: string) => code.replace(importRegex, "from '$1.js'")

export const getJavaScript = (typescript: string, options: ts.TranspileOptions = {}) => {
const source = escapeNewLines(typescript)
const transpiled = ts.transpileModule(source, {
...options,
compilerOptions: {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ES2020,
preserveValueImports: true,
...options.compilerOptions
}
})
const code = fixLocalImports(restoreNewLines(transpiled.outputText))

return prettier.format(code, {
semi: false,
parser: 'babel',
singleQuote: true
})
}

/**
* Render a source file template for the language set in the context. Will do nothing
* it there is no template for the selected language.
Expand All @@ -86,14 +112,29 @@ export const renderSource =
options?: { force: boolean }
) =>
async (ctx: C) => {
if (!template) {
return ctx
}

const { language } = ctx
const fileName = await getCallable<string, C>(toFile, ctx)
const content = language === 'js' ? getJavaScript(await getCallable<string, C>(template, ctx)) : template
const renderer = renderTemplate(content, `${fileName}.${language}`, options)

if (template) {
const renderer = renderTemplate(template, `${fileName}.${language}`, options)
return renderer(ctx)
}

return renderer(ctx)
}
/**
* Returns the TypeScript or transpiled JavaScript source code
*
* @param template The source template
* @returns
*/
export const getSource =
<C extends PinionContext & { language: 'js' | 'ts' }>(template: Callable<string, C>) =>
async <T extends C>(ctx: T) => {
const { language } = ctx
const source = await getCallable<string, C>(template, ctx)

return ctx
return language === 'js' ? getJavaScript(source) : source
}
11 changes: 6 additions & 5 deletions packages/cli/src/connection/templates/mongodb.tpl.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { generator, toFile, inject, before } from '@feathershq/pinion'
import { ConnectionGeneratorContext } from '../index'
import { renderSource } from '../../commons'
import { getSource, renderSource } from '../../commons'

const template = ({}: ConnectionGeneratorContext) =>
`import { MongoClient, Db } from 'mongodb'
import { Application } from './declarations'
`import { MongoClient } from 'mongodb'
import type { Db } from 'mongodb'
import type { Application } from './declarations'
declare module './declarations' {
interface Configuration {
Expand Down Expand Up @@ -34,5 +35,5 @@ export const generate = (ctx: ConnectionGeneratorContext) =>
toFile<ConnectionGeneratorContext>(({ lib }) => lib, 'mongodb')
)
)
.then(inject(importTemplate, before('import { services } from'), toAppFile))
.then(inject(configureTemplate, before('app.configure(services)'), toAppFile))
.then(inject(getSource(importTemplate), before('import { services } from'), toAppFile))
.then(inject(getSource(configureTemplate), before('app.configure(services)'), toAppFile))
4 changes: 2 additions & 2 deletions packages/cli/src/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ export const generate = (ctx: ServiceGeneratorArguments) =>
const pathElements = path.split('/').filter((el) => el !== '')
const relative = pathElements.map(() => '..').join('/')
const folder = _.initial(pathElements)
const schemaPath = `schemas/${folder.join('/')}/${kebabName}.schema`
const resolverPath = `resolvers/${folder.join('/')}/${kebabName}.resolver`
const schemaPath = `schemas/${folder.join('/')}${kebabName}.schema`
const resolverPath = `resolvers/${folder.join('/')}${kebabName}.resolver`

return {
name,
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/service/templates/resolver.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { ServiceGeneratorContext } from '../index'

const template = ({ camelName, upperName, relative, schemaPath }: ServiceGeneratorContext) =>
`import { resolve } from '@feathersjs/schema'
import { HookContext } from '${relative}/declarations'
import type { HookContext } from '${relative}/declarations'
import {
import type {
${upperName}Data,
${upperName}Patch,
${upperName}Result,
${upperName}Query,
} from '../${schemaPath}'
import {
${camelName}DataSchema,
${camelName}PatchSchema,
${camelName}ResultSchema,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/service/templates/schema.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { renderSource } from '../../commons'
import { ServiceGeneratorContext } from '../index'

const template = ({ camelName, upperName, type }: ServiceGeneratorContext) =>
`import { schema, querySyntax, Infer } from '@feathersjs/schema'
`import { schema, querySyntax } from '@feathersjs/schema'
import type { Infer } from '@feathersjs/schema'
// Schema for the basic data model (e.g. creating new entries)
export const ${camelName}DataSchema = schema({
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/service/templates/service.tpl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generator, inject, prepend, toFile, after } from '@feathershq/pinion'
import { renderSource } from '../../commons'
import { getSource, renderSource } from '../../commons'
import { ServiceGeneratorContext } from '../index'

const template = ({
Expand All @@ -15,8 +15,8 @@ const template = ({
}: ServiceGeneratorContext) =>
`import { resolveAll } from '@feathersjs/schema'
${isEntityService || authentication ? `import { authenticate } from '@feathersjs/authentication'` : ''}
import { Application } from '${relative}/declarations'
import {
import type { Application } from '${relative}/declarations'
import type {
${upperName}Data,
${upperName}Result,
${upperName}Query,
Expand Down Expand Up @@ -72,7 +72,7 @@ export const hooks = {
// A configure function that registers the service and its hooks via \`app.configure\`
export function ${camelName} (app: Application) {
const options = {
const options = { // Service options will go here
}
// Register our service on the Feathers application
Expand Down Expand Up @@ -118,5 +118,5 @@ export const generate = (ctx: ServiceGeneratorContext) =>
])
)
)
.then(inject(importTemplate, prepend(), toServiceIndex))
.then(inject(getSource(importTemplate), prepend(), toServiceIndex))
.then(inject(configureTemplate, after('export const services'), toServiceIndex))
Loading

0 comments on commit ebac587

Please sign in to comment.