Skip to content

Commit

Permalink
feat(creator): MVP of plugin creator CLI (#208)
Browse files Browse the repository at this point in the history
* inital commit

* file writes

* locking turbo version

* dynamic loading available chains

* loading actiontypes from the sdk

* templating out some contents of the files

* iterating on genereating project file

* removing tests from pnpm lock

* some cleanup

* adding changeset

* pr cleanup

* cleaning up merge build

* adding build utils command and expanding readme

* Update README.md

* resolve conflicts, update pnpm, remove bu task

* updating registry and starting feedback

* Update package.json

Co-authored-by: Quazia <alf40k@gmail.com>

* Update apps/create-plugin/src/index.ts

Co-authored-by: Quazia <alf40k@gmail.com>

* regen pnpm lock

* regen pnpm lock

* updating pnpm new lockfile

* Chore(creator): sync ts build target

* Chore(global): lock down viem dep target

* Chore(creator): use internal utils chains

* Chore(creator): pin dep versions

* Fix(creator): handle name validation correctly

---------

Co-authored-by: Chris Cashwell <ccashwell@gmail.com>
Co-authored-by: Quazia <alf40k@gmail.com>
  • Loading branch information
3 people authored Feb 8, 2024
1 parent ba3ec29 commit 43245a5
Show file tree
Hide file tree
Showing 56 changed files with 2,665 additions and 2,352 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-tigers-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rabbitholegg/create-plugin": major
---

Introducing the MVP first draft of the Plugin Creator, a tool to generate all the boilerplate code necessary for a new plugin
47 changes: 47 additions & 0 deletions apps/create-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# BoostDK Plugin Creator

## Using it

from the root of the project run:

```bash
pnpm build
cd apps/create-plugin
pnpm run create
```

then you can enter your plugins name, the supported chains, and the actions.

The script will create a template for you in the packages directory structure.


## Templates

the following actions will be performed, assuming the tool is used to collect [project] name, [chain] ids, and [actions]:

1. Create a new directory in the packages directory with the name `@rabbitholegg/questdk-plugin-<project>`
2. Create index.ts, [project].ts, [project].test.ts, and test-transaction.ts files in the new directory as well as readme, package.json, etc with the [project] name
3. index.ts imports and interface definition is written based on selected [action]
4. [project].ts imports, a function declaration for each action, and `getSupportedChainIds` are populated


## Development

the following command will both compile and run the plugin creator

first `cd apps/create-plugin`

`pnpm run dev`


### Summary

The `src/index.ts` file is the entry point for the plugin creator. This file renders and parses the arguments from the CLI.

The `src/builder.ts` file is the main logic for creating the plugin. It uses mustache templates and file operations to copy the `/template` directory to the `../../packages` directory and populate it appropriately.

so:

* if you want to change the output plugin, modify the source in the /template directory
* if you want to change the CLI, modify the source of src/index.ts
* if you want to change the logic of the plugin creator, modify the source of src/builder.ts
27 changes: 27 additions & 0 deletions apps/create-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@rabbitholegg/create-plugin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "pnpm run build && node dist/index.js",
"create": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"commander": "11.1.0",
"figlet": "1.7.0"
},
"devDependencies": {
"@types/prompt": "1.1.8",
"fs-extra": "11.2.0",
"handlebars": "4.7.8",
"picocolors": "1.0.0",
"prompts": "2.4.2",
"@rabbitholegg/questdk-plugin-utils": "workspace:*"
}
}
1 change: 1 addition & 0 deletions apps/create-plugin/src/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ABI_TO_DO = []
217 changes: 217 additions & 0 deletions apps/create-plugin/src/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
const fs = require('fs-extra')
const path = require('path')
import Handlebars from 'handlebars'
import { cyan, red } from 'picocolors'

type BuilderParams = {
projectName: string
chains: string[]
tx: string
actionTypes: string[]
}

const arrow = '=>'
const logo = '✛' // not the logo but pretty close

export async function createPlugin(params: BuilderParams) {
const result = await copyDirectory(params)
if (!result) {
return
}

registerHelpers()
await replaceProjectName(params)
await setActionNames(params)
await replaceFileNames(params)
logBoostStars()
console.log('Created a plugin for', cyan(`"${params.projectName}"`))
console.log()
console.log('run the following commands:')
console.log('cd ../..')
console.log('pnpm i')
console.log('pnpm run build')
console.log()
console.log(
'View your new plugin todo list in the README, located at',
cyan(`packages/${params.projectName}/README.md`),
)

console.log('\n\n\n')
}

export function logBoostStars() {
console.log()
console.log(`--------------------------- ${logo} ${logo} ${logo} `)
console.log(`--------------------------- ${logo} ${logo} ${logo} `)
console.log(`--------------------------- ${logo} ${logo} ${logo} `)
console.log()
}

function registerHelpers() {
Handlebars.registerHelper('lowercase', function (aString) {
return aString.toLowerCase()
})
Handlebars.registerHelper('capitalize', function (aString) {
return aString.charAt(0).toUpperCase() + aString.slice(1)
})
}

/**
* This function creates the directory structure and copies over the template
*
* @param params
* @returns
*/
async function copyDirectory(params: BuilderParams) {
if (params.projectName === undefined) {
console.log(` ${red('exiting')} `)
return false
}
// get the target directory location
const dest = path.join(__dirname, `../../../packages/${params.projectName}`)
// if there is already a directory with the name throw an error
if (await fs.pathExists(dest)) {
console.error(
`Could not create a plugin called ${red(
`"${params.projectName}"`,
)} because it already exists!`,
)
return false
}
// create the directory
try {
await fs.ensureDir(dest)
} catch (_e) {
console.error(
`Could not create a plugin called ${red(
`"${params.projectName}"`,
)} because of ${_e}`,
)
return false
}

// copy the template files to the new directory
const _source = path.join(__dirname, '../template')
try {
await fs.copy(_source, dest)
console.log(
`\t ${arrow} Created directory for ${cyan(`"${params.projectName}"`)}!`,
)
return true
} catch (_e) {
console.error(
`Could not create a plugin called ${red(
`"${params.projectName}"`,
)} because of ${_e}`,
)
return false
}
}

/**
* This function replaces multiple instances of the project name
*
* package.json
* readme
* changelog
* src/project.ts
* src/project.test.ts
* index.ts
*
* @param params
* @returns
*/
async function replaceProjectName(params: BuilderParams) {
// get the target directory location
const dest = path.join(__dirname, `../../../packages/${params.projectName}`)

// replace the project name in the package.json
const packageJsonPath = path.join(dest, 'package.json')
const packageJson = await fs.readJson(packageJsonPath)
packageJson.name = params.projectName
packageJson.description = `Plugin for ${params.projectName}`
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 })
console.log(`\t ${arrow} Updated file ${cyan('package.json')}!`)

//replace the project name in the readme
const readmePath = path.join(dest, 'README.md')
const readme = await fs.readFile(readmePath, 'utf8')
const readmeTemplate = Handlebars.compile(readme)
await fs.writeFile(readmePath, readmeTemplate(params))
console.log(`\t ${arrow} Updated file ${cyan('README.md')}!`)

//replace the project name in the changelog
const changelogPath = path.join(dest, 'CHANGELOG.md')
const changelog = await fs.readFile(changelogPath, 'utf8')
const changelogTemplate = Handlebars.compile(changelog)
await fs.writeFile(changelogPath, changelogTemplate(params))
console.log(`\t ${arrow} Updated file ${cyan('CHANGELOG.md')}!`)

//replace the project name in index.ts.t
const indexPath = path.join(dest, 'src/index.ts.t')
const index = await fs.readFile(indexPath, 'utf8')
const indexTemplate = Handlebars.compile(index)
await fs.writeFile(indexPath, indexTemplate(params))
console.log(`\t ${arrow} Updated file ${cyan('index.ts')}!`)
}

/**
* This function renames filenames to match the project name -and- it removes the .t(emplate) extension
*
* src/project.ts
* src/project.test.ts
* index.ts
*
* @param params
* @returns
*/
async function replaceFileNames(params: BuilderParams) {
// get the target directory location
const dest = path.join(__dirname, `../../../packages/${params.projectName}`)

//rename index.ts
const indexPath = path.join(dest, 'src/index.ts.t')
await fs.rename(indexPath, path.join(dest, 'src/index.ts'))
console.log(`\t ${arrow} finalized file ${cyan('index.ts')}!`)

//rename project.ts
const projectPath = path.join(dest, 'src/Project.ts.t')
await fs.rename(projectPath, path.join(dest, `src/${params.projectName}.ts`))
console.log(`\t ${arrow} finalized file ${cyan(`${params.projectName}.ts`)}!`)

//rename project.test.ts
const testPath = path.join(dest, 'src/Project.test.ts.t')
await fs.rename(
testPath,
path.join(dest, `src/${params.projectName}.test.ts`),
)
console.log(
`\t ${arrow} finalized file ${cyan(`${params.projectName}.test.ts`)}!`,
)
}

/**
* This function creates the code for the specified actions
*
*
* @param params
* @returns
*/
async function setActionNames(params: BuilderParams) {
// get the target directory location
const dest = path.join(__dirname, `../../../packages/${params.projectName}`)

//replace the action names in the index
const indexPath = path.join(dest, 'src/index.ts.t')
const index = await fs.readFile(indexPath, 'utf8')
const indexTemplate = Handlebars.compile(index)
await fs.writeFile(indexPath, indexTemplate(params))
console.log(`\t ${arrow} created actions in file ${cyan('index.ts')}!`)

//replace the action names in the project file
const projectPath = path.join(dest, 'src/Project.ts.t')
const project = await fs.readFile(projectPath, 'utf8')
const projectTemplate = Handlebars.compile(project)
await fs.writeFile(projectPath, projectTemplate(params))
console.log(`\t ${arrow} created actions in file ${cyan('Project.ts')}!`)
}
Loading

0 comments on commit 43245a5

Please sign in to comment.