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
85 changes: 21 additions & 64 deletions package-lock.json

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

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')}
`)
} 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)
}
Loading