feat: add @tpmjs/create-basic-tools CLI generator
- Interactive CLI generator for scaffolding TPMJS tool packages - Generates packages with minimum 2 tools (ideally 2-3) - Zod 4 schemas - uses Zod directly (not jsonSchema wrapper) - One file per tool in src/tools/<toolName>.ts - TPMJS validated against official schemas from @tpmjs/types - Complete package generation ready to publish to npm - Works both standalone and in monorepo packages/ folders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8b000cf0ca
commit
38c85bd904
28 changed files with 1426 additions and 2 deletions
180
packages/tools/create-basic-tools/src/cli.ts
Normal file
180
packages/tools/create-basic-tools/src/cli.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import * as path from 'node:path';
|
||||
import * as clack from '@clack/prompts';
|
||||
import { generateTsConfig, generateTsupConfig } from './generators/config-files.js';
|
||||
import { generatePackageJson } from './generators/package-json.js';
|
||||
import { generateReadme } from './generators/readme.js';
|
||||
import { generateIndexFile, generateToolFile } from './generators/source-code.js';
|
||||
import { promptCategoryAndMode, promptConfirmation, promptOutputPath } from './prompts/advanced.js';
|
||||
import { promptBasicInfo } from './prompts/basic.js';
|
||||
import { promptTools } from './prompts/tools.js';
|
||||
import type { GenerationResult, GeneratorConfig } from './types.js';
|
||||
import {
|
||||
copyTemplate,
|
||||
ensureDir,
|
||||
getAbsolutePath,
|
||||
pathExists,
|
||||
writeFile,
|
||||
} from './utils/file-writer.js';
|
||||
import * as logger from './utils/logger.js';
|
||||
|
||||
/**
|
||||
* Runs the interactive CLI workflow
|
||||
*/
|
||||
export async function runInteractiveCLI(): Promise<GenerationResult> {
|
||||
clack.intro(logger.bold('create-tpmjs-tool'));
|
||||
|
||||
// Step 1: Get basic package info
|
||||
const packageInfo = await promptBasicInfo();
|
||||
if (!packageInfo) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 2: Get tool definitions (minimum 2)
|
||||
const tools = await promptTools();
|
||||
if (!tools || tools.length < 2) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 3: Get category and mode
|
||||
const categoryAndMode = await promptCategoryAndMode();
|
||||
|
||||
if (!categoryAndMode) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
packageInfo.category = categoryAndMode.category;
|
||||
|
||||
// Step 4: Get output path
|
||||
const packageNameWithoutScope = packageInfo.name.split('/').pop() || packageInfo.name;
|
||||
const defaultPath = `./${packageNameWithoutScope}`;
|
||||
|
||||
const outputPath = await promptOutputPath(defaultPath);
|
||||
if (!outputPath) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const absoluteOutputPath = getAbsolutePath(outputPath);
|
||||
|
||||
// Check if directory exists
|
||||
if (await pathExists(absoluteOutputPath)) {
|
||||
clack.log.error(`Directory already exists: ${absoluteOutputPath}`);
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Step 5: Confirm generation
|
||||
const confirmed = await promptConfirmation(packageInfo.name, tools.length, outputPath);
|
||||
if (!confirmed) {
|
||||
clack.cancel('Operation cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Step 6: Generate the package
|
||||
const spinner = clack.spinner();
|
||||
spinner.start('Generating package...');
|
||||
|
||||
const config: GeneratorConfig = {
|
||||
packageInfo,
|
||||
tools,
|
||||
outputPath: absoluteOutputPath,
|
||||
mode: categoryAndMode.mode,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await generatePackage(config);
|
||||
|
||||
spinner.stop('Package generated successfully!');
|
||||
|
||||
clack.outro(`
|
||||
${logger.green('✓')} Success! Created ${logger.bold(packageInfo.name)} at ${logger.cyan(outputPath)}
|
||||
|
||||
Files created:
|
||||
${result.filesCreated.map((f) => ` ${f}`).join('\n')}
|
||||
|
||||
Next steps:
|
||||
${logger.cyan(`cd ${outputPath}`)}
|
||||
${logger.cyan('pnpm install')}
|
||||
${logger.cyan('pnpm build')}
|
||||
${logger.cyan('pnpm type-check')}
|
||||
${logger.cyan('pnpm publish')}
|
||||
`);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
spinner.stop('Generation failed');
|
||||
clack.log.error(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
clack.cancel('Operation failed');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the package files
|
||||
*/
|
||||
async function generatePackage(config: GeneratorConfig): Promise<GenerationResult> {
|
||||
const { packageInfo, tools, outputPath } = config;
|
||||
const filesCreated: string[] = [];
|
||||
|
||||
// Create directory structure
|
||||
await ensureDir(outputPath);
|
||||
await ensureDir(path.join(outputPath, 'src'));
|
||||
await ensureDir(path.join(outputPath, 'src', 'tools'));
|
||||
|
||||
// Generate package.json
|
||||
const packageJsonPath = path.join(outputPath, 'package.json');
|
||||
await writeFile(packageJsonPath, generatePackageJson(config));
|
||||
filesCreated.push('package.json');
|
||||
|
||||
// Generate tool files
|
||||
for (const tool of tools) {
|
||||
const toolPath = path.join(outputPath, 'src', 'tools', `${tool.exportName}.ts`);
|
||||
await writeFile(toolPath, generateToolFile(tool));
|
||||
filesCreated.push(`src/tools/${tool.exportName}.ts`);
|
||||
}
|
||||
|
||||
// Generate index.ts
|
||||
const indexPath = path.join(outputPath, 'src', 'index.ts');
|
||||
await writeFile(indexPath, generateIndexFile(tools));
|
||||
filesCreated.push('src/index.ts');
|
||||
|
||||
// Generate tsconfig.json
|
||||
const tsconfigPath = path.join(outputPath, 'tsconfig.json');
|
||||
await writeFile(tsconfigPath, generateTsConfig());
|
||||
filesCreated.push('tsconfig.json');
|
||||
|
||||
// Generate tsup.config.ts
|
||||
const tsupConfigPath = path.join(outputPath, 'tsup.config.ts');
|
||||
await writeFile(tsupConfigPath, generateTsupConfig());
|
||||
filesCreated.push('tsup.config.ts');
|
||||
|
||||
// Generate README.md
|
||||
const readmePath = path.join(outputPath, 'README.md');
|
||||
await writeFile(readmePath, generateReadme(config));
|
||||
filesCreated.push('README.md');
|
||||
|
||||
// Copy static templates
|
||||
const gitignorePath = path.join(outputPath, '.gitignore');
|
||||
await copyTemplate('gitignore.txt', gitignorePath);
|
||||
filesCreated.push('.gitignore');
|
||||
|
||||
const npmignorePath = path.join(outputPath, '.npmignore');
|
||||
await copyTemplate('npmignore.txt', npmignorePath);
|
||||
filesCreated.push('.npmignore');
|
||||
|
||||
const licensePath = path.join(outputPath, 'LICENSE');
|
||||
await copyTemplate('license-mit.txt', licensePath, {
|
||||
YEAR: new Date().getFullYear().toString(),
|
||||
AUTHOR: packageInfo.author || 'Author Name',
|
||||
});
|
||||
filesCreated.push('LICENSE');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
outputPath,
|
||||
filesCreated,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue