Create Your First Package
This guide will walk you through creating your first custom package for AI Scaffolding.
We'll create a simple NodeJS module that exports a "Hello World" function.
Prerequisites
Before starting, make sure you have:
- A basic understanding of TypeScript/JavaScript
- The AI Scaffolding codebase cloned and set up
Node.jsandpnpminstalled
Package Structure Overview
Our simple NodeJS package will have the following structure:
src/packages/nodejs/
├── files/ # Template files
│ ├── package.json # Package.json template
│ ├── README.md # README template
│ └── src/ # Source files
│ ├── index.ts # Main entry point
│ └── hello.ts # Hello world module
├── builder.ts # Package builder logic
└── prompt.ts # User prompts for configurationSteps
1 Create the Package Directory Structure
First, let's create the necessary directories for our package:
mkdir -p src/packages/nodejs/files/src2 Create the Prompt Module
Create src/packages/nodejs/prompt.ts to define the user configuration options:
import { input, confirm } from '@inquirer/prompts'
import { PackagePrompt } from '../../types.js';
export interface NodeJsConfig {
useTypescript: boolean
includeTests: boolean
[key: string]: unknown
}
const prompt: PackagePrompt<NodeJsConfig> = async (ctx) => {
const name = await input({
message: 'Enter the name of the NodeJS package:',
default: 'nodejs',
transformer: (value: string, { isFinal }) =>
isFinal ? `@${ctx.projectName.toLowerCase()}/${value}` : value
});
const useTypescript = await confirm({
message: 'Do you want to use TypeScript?',
default: true
});
const includeTests = await confirm({
message: 'Do you want to include tests?',
default: true
});
return {
name,
useTypescript,
includeTests
}
}
export default prompt;3 Create the Builder Class
Create src/packages/nodejs/builder.ts to define how the package will be built:
import path from 'path'
import { fileURLToPath } from 'url'
import picocolors from 'picocolors'
import { ProjectConfigV2 } from '../../types.js'
import { ListrTaskArg, PackageBuilder, ListrTaskList } from '../../builders/PackageBuilder.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { white } = picocolors;
export default class NodeJsBuilder extends PackageBuilder {
constructor(config: ProjectConfigV2) {
super(
config,
path.join(__dirname, 'files'),
config.packages.nodejs.name
);
}
public build(task: ListrTaskArg, nextSteps: string[]): ListrTaskList {
this._addNextSteps(nextSteps);
return task.newListr([
{
title: 'Copy package files',
task: this.copyPackageFiles.bind(this)
},
{
title: 'Create package.json',
task: this.createPackageJson.bind(this)
},
{
title: 'Create README.md',
task: async () => {
await this.createReadme({
packageManagerCmd: this.getPackageManagerCmd()
});
}
},
{
title: 'Parse template files',
task: async () => {
await this.parseAllFiles({
exclude: ['README.md', 'package.json']
});
}
}
]);
}
private _addNextSteps(nextSteps: string[]) {
const pkgCmd = this.config.packageManager;
const { packages } = this.config;
nextSteps.push(
white(`Run \`${pkgCmd} ${packages.nodejs.name} start\``),
white(`Run \`${pkgCmd} ${packages.nodejs.name} test\`` +
(this.config.packages.nodejs.includeTests ? '' : ' (after adding tests)'))
);
}
}4 Create the Template Files
4.1 Package.json Template
Create src/packages/nodejs/files/package.json:
{
"name": "{{name}}",
"version": "0.1.0",
"private": true,
"main": "{{#if useTypescript}}dist/index.js{{else}}src/index.js{{/if}}",
"scripts": {
"start": "{{#if useTypescript}}ts-node src/index.ts{{else}}node src/index.js{{/if}}",
"build": "{{#if useTypescript}}tsc{{else}}echo \"No build step needed\"{{/if}}",
{{#if includeTests}}
"test": "{{#if useTypescript}}jest{{else}}node --test{{/if}}"
{{/if}}
},
"dependencies": {
},
"devDependencies": {
{{#if useTypescript}}
"typescript": "^5.0.0",
"ts-node": "^10.9.1",
"@types/node": "^18.0.0",
{{/if}}
{{#if includeTests}}
{{#if useTypescript}}
"jest": "^29.0.0",
"ts-jest": "^29.0.0",
"@types/jest": "^29.0.0"
{{/if}}
{{/if}}
}
}4.2 README Template
Create src/packages/nodejs/files/README.md:
# {{name}}
A simple NodeJS module created with AI Scaffolding.
## Features
- Simple hello world function
- {{#if useTypescript}}TypeScript support{{else}}JavaScript{{/if}}
- {{#if includeTests}}Includes tests{{else}}Easily extendable{{/if}}
## Getting Started
```bash
# Install dependencies
{{packageManagerCmd}} {{name}} install
# Run the application
{{packageManagerCmd}} {{name}} start
{{#if includeTests}}
# Run tests
{{packageManagerCmd}} {{name}} test
{{/if}}
\```
## Usage
```javascript
const { hello } = require('{{name}}');
// Call the hello function
hello('World'); // Returns: "Hello, World!"
\```3. Hello World Module
Create src/packages/nodejs/files/src/hello.ts:
/**
* A simple hello world function
* @param name The name to greet
* @returns A greeting message
*/
export function hello(name: string): string {
return `Hello, ${name}!`;
}4.3 Index File
Create src/packages/nodejs/files/src/index.ts:
/*#{{#if useTypescript}}*/
export { hello } from './hello';
/*#{{else}}*/
const { hello } = require('./hello');
module.exports = { hello };
/*#{{/if}}*/
// Log a message when the module is imported
console.log('NodeJS module initialized');5 Register the Package
Update src/packages/index.ts to include your new package:
// Add these imports at the top
import nodejsPrompt, { NodeJsConfig } from './nodejs/prompt.js'
import NodeJsBuilder from './nodejs/builder.js'
// Add to the Packages object
export const Packages: PackageExport = {
hardhat: {
prompt: hardhatPrompt as PackagePrompt<HardhatConfig>,
builder: HardhatBuilder
},
// ... existing packages
nodejs: {
prompt: nodejsPrompt as PackagePrompt<NodeJsConfig>,
builder: NodeJsBuilder
}
};
// Add to packagesPrompt choices
export async function packagesPrompt() {
const packages = await checkbox<string>({
message: 'Select packages to install:',
choices: [
{ name: 'Hardhat', value: 'hardhat' },
// ... existing choices
{ name: 'NodeJS', value: 'nodejs' },
],
required: true,
pageSize: 20,
})
// ...
}
// Export your config type at the end of the file
export { NodeJsConfig } from './nodejs/prompt.js'6 Update TypeScript Types
Update src/types.ts to include the new package type:
// Add import at the top
import { NodeJsConfig } from './packages/nodejs/prompt.js'
// Update ProjectConfigV2 interface to include the new package
export interface ProjectConfigV2 {
projectName: string;
packageManager: PackageManager;
projectPath: string;
targetDir: string;
packages: Record<'hardhat', PackageConfig<HardhatConfig>>
& Record<'vite', PackageConfig<ViteConfig>>
& Record<'nextjs', PackageConfig<NextjsConfig>>
& Record<'nestjs', PackageConfig<NestJsConfig>>
& Record<'nodejs', PackageConfig<NodeJsConfig>>;
global: GlobalConfig;
skipInstall: boolean;
}7 Build and Test
Now build the project:
pnpm buildThen test your new package:
# Run the CLI
pnpm dev tmp/test-projectDuring setup, select the NodeJS package and configure it according to your preferences.
Understanding What's Happening
When the user selects your NodeJS package during project creation:
- The
prompt.tsfile is executed to collect configuration options (TypeScript, tests, etc.) - The collected options are stored in the project config object
- The
MainBuildercreates the project structure - The
NodeJsBuilderis instantiated with the config - The build method is called to create the NodeJS package
- Template files are copied and processed with Handlebars
- The code is generated with the user's selected options
Extending the Package
You can extend your package in several ways:
- Add more prompts: Collect more configuration options in
prompt.ts - Add more templates: Create additional template files in the
filesdirectory - Add build steps: Implement additional build tasks in the
buildmethod
Advanced Features
For more advanced packages, you might want to:
- Add dependencies based on user selections
- Generate different file structures based on configuration
- Integrate with other packages (e.g., connect to Hardhat if it's selected)
- Add more sophisticated template logic
Conclusion
Congratulations! You've created your first package for AI Scaffolding.
This simple example demonstrates the core concepts of creating packages and can serve as a foundation for more complex implementations.
Next Steps
- Learn about Handlebars Templates for more advanced templating
- Explore the Package Builder for more utility methods
- Check out other packages for implementation examples