Skip to content
Draft
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
33,535 changes: 7,724 additions & 25,811 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 25 additions & 15 deletions packages/create-feathers/bin/create-feathers.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
#!/usr/bin/env node
'use strict';
'use strict'

import path from 'path'
import { existsSync } from 'fs'
import { mkdir } from 'fs/promises'
import { Command, commandRunner, chalk } from '@feathersjs/cli'
import path from 'node:path'
import { existsSync } from 'node:fs'
import { mkdir } from 'node:fs/promises'
import { Command } from 'commander'
import { generate, getContext } from '../lib/index.js'

const color = {
green: (text) => `\x1b[32m${text}\x1b[0m`,
red: (text) => `\x1b[31m${text}\x1b[0m`,
grey: (text) => `\x1b[90m${text}\x1b[0m`
}

const program = new Command()
const generateApp = commandRunner('app')

program
.name('npm init feathers')
.description(`Create a new Feathers application 🕊️
.name('npm create feathers')
.description(
`Create a new Feathers application 🕊️

${chalk.grey('npm init feathers myapp')}
`)
${color.grey('npm create feathers myapp')}
`
)
.argument('<name>', 'The name of your new application')
// .version(version)
.showHelpAfterError()
Expand All @@ -28,21 +36,23 @@ ${chalk.grey('npm init feathers myapp')}

await mkdir(cwd)

await generateApp({
const ctx = getContext({
name,
cwd,
...options
})

await generate(ctx)

console.log(`

${chalk.green('Hooray')}! Your Feathers app is ready to go! 🚀
Go to the ${chalk.grey(name)} folder to get started.
${color.green('Hooray')}! Your Feathers app is ready to go! 🚀
Go to the ${color.grey(name)} folder to get started.

To learn more visit ${chalk.grey('https://feathersjs.com/guides')}
To learn more visit ${color.grey('https://feathersjs.com/guides')}
`)
} catch (error) {
console.error(`${chalk.red('Error')}: ${error.message}`)
console.error(`${color.red('Error')}: ${error.message}`)
process.exit(1)
}
})
Expand Down
16 changes: 12 additions & 4 deletions packages/create-feathers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,24 @@
"*.js"
],
"scripts": {
"test": "bin/create-feathers.js --help"
"test": "bin/create-feathers.js --help",
"prepublish": "npm run compile",
"compile": "shx rm -rf lib/ && tsc"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@feathersjs/cli": "^5.0.34"
},
"gitHead": "90caf635aec850550b9d37bea2762af959d9e8d5",
"devDependencies": {
"@types/node": "^24.1.0",
"@vitest/coverage-v8": "^3.2.4",
"shx": "^0.4.0",
"typescript": "^5.8.0",
"vitest": "^3.2.4"
},
"dependencies": {
"@featherscloud/pinion": "^0.5.5",
"commander": "^12.1.0",
"type-fest": "^0.21.3"
}
}
115 changes: 115 additions & 0 deletions packages/create-feathers/src/commons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fs from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { PackageJson } from 'type-fest'
import { Callable, PinionContext, loadJSON, fromFile, getCallable, exec } from '@featherscloud/pinion'

// Set __dirname in es module
const __dirname = dirname(fileURLToPath(import.meta.url))

export const { version } = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString())

export type DependencyVersions = { [key: string]: string }

export type FeathersAppInfo = {
packager: 'yarn' | 'npm' | 'pnpm'

platform: 'node' | 'deno' | 'bun'

sse: boolean
}

export interface AppPackageJson extends PackageJson {
feathers?: FeathersAppInfo
}

export interface FeathersBaseContext extends PinionContext {
/**
* Information about the Feathers application (like chosen language, database etc.)
* usually taken from `package.json`
*/
feathers: FeathersAppInfo
/**
* The package.json file
*/
pkg: AppPackageJson
/**
* The folder where source files are put
*/
lib: string
/**
* The folder where test files are put
*/
test: string
/**
* A list dependencies that should be installed with a certain version.
* Used for installing development dependencies during testing.
*/
dependencyVersions?: DependencyVersions
}

export interface AppGeneratorData extends FeathersAppInfo {
/**
* The application name
*/
name: string
/**
* A short description of the app
*/
description: string
}

export type AppGeneratorContext = FeathersBaseContext & AppGeneratorData

export type AppGeneratorArguments = FeathersBaseContext & Partial<AppGeneratorData>

/**
* Loads the application package.json and populates information like the library and test directory
* and Feathers app specific information.
*
* @returns The updated context
*/
export const initializeBaseContext =
() =>
<C extends FeathersBaseContext>(ctx: C) =>
Promise.resolve(ctx).then(loadJSON(fromFile('package.json'), (pkg) => ({ pkg }), {}))

/**
* A special error that can be thrown by generators. It contains additional
* information about the error that can be used to display a more helpful
* error message to the user.
*/
export class FeathersGeneratorError extends Error {
/**
* Additional information about the error. This can include things like
* the reason for the error and suggested actions to take.
*/
context?: Record<string, unknown>

/**
* Creates a new FeathersGeneratorError
* @param message The error message
* @param context Additional information about the error
*/
constructor(message: string, context?: Record<string, unknown>) {
super(message)
this.name = 'FeathersGeneratorError'
this.context = context
}
}

export const install =
<C extends PinionContext>(
dependencies: Callable<string[], C>,
dev: Callable<boolean, C>,
packager: Callable<string, C>
) =>
async (ctx: C) => {
const dependencyList = await getCallable(dependencies, ctx)
const packageManager = await getCallable(packager, ctx)
const isDev = await getCallable(dev, ctx)
const flag = isDev ? (packageManager === 'yarn' ? ' --dev' : ' --save-dev') : ''
const command = packageManager === 'yarn' ? 'add' : 'install'

return exec(`${packageManager} ${command} ${dependencyList.join(' ')}${flag}`, [])(ctx)
}
81 changes: 81 additions & 0 deletions packages/create-feathers/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { sep, dirname } from 'path'
import { prompt, runGenerators } from '@featherscloud/pinion'
import { fileURLToPath } from 'url'

import type { AppGeneratorArguments } from './commons.js'
import { initializeBaseContext, install } from './commons.js'

export { getContext } from '@featherscloud/pinion'

// Set __dirname in es module
const __dirname = dirname(fileURLToPath(import.meta.url))

export const generate = (ctx: AppGeneratorArguments) =>
Promise.resolve(ctx)
.then(initializeBaseContext())
.then((ctx) => ({
...ctx,
dependencies: [] as string[],
devDependencies: [] as string[]
}))
.then(
prompt((ctx) => [
{
name: 'name',
type: 'input',
when: !ctx.name,
message: 'What is the name of your application?',
default: ctx.cwd.split(sep).pop(),
validate: (input) => {
if (ctx.dependencyVersions[input]) {
return `Application can not have the same name as a dependency`
}

return true
}
},
{
name: 'description',
type: 'input',
when: !ctx.description,
message: 'Write a short description'
},
{
name: 'platform',
type: 'list',
when: !ctx.platform,
message: 'Which platform do you want to use?',
choices: [
{ value: 'node', name: 'Node' },
{ value: 'deno', name: 'Deno' },
{ value: 'bun', name: 'Bun' }
]
},
{
name: 'packager',
type: 'list',
when: ({ platform }) => platform === 'node' && !ctx.packager,
message: 'Which package manager are you using?',
choices: [
{ value: 'npm', name: 'npm' },
{ value: 'yarn', name: 'Yarn' },
{ value: 'pnpm', name: 'pnpm' }
]
}
])
)
.then(runGenerators(__dirname, 'templates'))
.then(initializeBaseContext())
.then(
install(['feathers@pre'], false, (ctx): string => {
if (ctx.packager) {
return ctx.packager
}

if (ctx.platform === 'deno' || ctx.platform === 'bun') {
return ctx.platform
}

return 'npm'
})
)
16 changes: 16 additions & 0 deletions packages/create-feathers/src/templates/app.tpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { renderTemplate, toFile } from '@featherscloud/pinion'
import type { AppGeneratorContext } from '../commons.js'

const template = ({}: AppGeneratorContext) => /* ts */ `import { feathers } from 'feathers'

export type Services = {}

export type Configuration = {}

const app = feathers<Services, Configuration>()

export { app }
`

export const generate = (ctx: AppGeneratorContext) =>
Promise.resolve(ctx).then(renderTemplate(template, toFile('src', 'app.ts')))
Loading
Loading