create-tac-app
Version:
Create hybrid dApps with TAC in seconds
251 lines (209 loc) • 7.61 kB
JavaScript
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora from 'ora';
import shell from 'shelljs';
import gitClone from 'git-clone/promise.js';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import { pastel, rainbow } from 'gradient-string';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Log the directory of the CLI for debugging
if (process.env.DEBUG) {
console.log(`CLI directory: ${__dirname}`);
}
const tacLogo = `
⚡ ████████╗ █████╗ ██████╗
╚══██╔══╝██╔══██╗██╔════╝
██║ ███████║██║
██║ ██╔══██║██║
██║ ██║ ██║╚██████╗
╚═╝ ╚═╝ ╚═╝ ╚═════╝
`;
console.log(pastel.multiline(tacLogo));
console.log(chalk.bold('Welcome to TAC - Build hybrid dApps in seconds!\n'));
const REPOS = {
frontend: 'https://github.com/tacbuild/starter-frontend.git',
hardhat: 'https://github.com/tacbuild/starter-hardhat.git',
foundry: 'https://github.com/tacbuild/starter-foundry.git'
};
const { projectName, contractFramework } = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'What would you like to name your TAC project?',
default: 'my-tac-app',
validate: (input) => {
if (/^([A-Za-z\-_\d])+$/.test(input)) return true;
return 'Project name may only include letters, numbers, underscores and hashes.';
}
},
{
type: 'list',
name: 'contractFramework',
message: 'Select a smart contract framework:',
choices: [
{ name: 'Hardhat (JavaScript/TypeScript)', value: 'hardhat' },
{ name: 'Foundry (Solidity)', value: 'foundry' }
],
default: 'hardhat'
}
]);
const projectPath = path.resolve(process.cwd(), projectName);
if (fs.existsSync(projectPath)) {
const { overwrite } = await inquirer.prompt([
{
type: 'confirm',
name: 'overwrite',
message: `Directory ${projectName} already exists. Do you want to overwrite it?`,
default: false
}
]);
if (!overwrite) {
console.log(chalk.red('Operation cancelled.'));
process.exit(1);
}
fs.removeSync(projectPath);
}
fs.ensureDirSync(projectPath);
const frontendPath = path.join(projectPath, 'frontend');
const contractsPath = path.join(projectPath, 'contracts');
fs.ensureDirSync(frontendPath);
fs.ensureDirSync(contractsPath);
let spinner = ora('Cloning frontend template...').start();
try {
await gitClone(REPOS.frontend, frontendPath);
fs.removeSync(path.join(frontendPath, '.git'));
spinner.succeed('Frontend template cloned successfully!');
} catch (error) {
spinner.fail('Failed to clone frontend template.');
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
spinner = ora(`Cloning ${contractFramework} template...`).start();
try {
await gitClone(REPOS[contractFramework], contractsPath);
fs.removeSync(path.join(contractsPath, '.git'));
spinner.succeed(`${contractFramework} template cloned successfully!`);
} catch (error) {
spinner.fail(`Failed to clone ${contractFramework} template.`);
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
const rootPackageJson = {
name: projectName,
version: '0.1.0',
private: true,
scripts: {
'frontend': 'cd frontend && npm run dev',
'contracts:build': `cd contracts && ${contractFramework === 'hardhat' ? 'npx hardhat compile' : 'forge build'}`
}
};
fs.writeFileSync(
path.join(projectPath, 'package.json'),
JSON.stringify(rootPackageJson, null, 2)
);
spinner = ora('Installing frontend dependencies...').start();
shell.cd(frontendPath);
let result = shell.exec('npm install', { silent: true });
if (result.code !== 0) {
spinner.fail('Failed to install frontend dependencies.');
console.error(chalk.red(`Error: ${result.stderr}`));
} else {
spinner.succeed('Frontend dependencies installed successfully!');
}
spinner = ora(`Installing ${contractFramework} dependencies...`).start();
shell.cd(contractsPath);
if (contractFramework === 'hardhat') {
result = shell.exec('npm install', { silent: true });
if (result.code !== 0) {
spinner.fail('Failed to install contract dependencies.');
console.error(chalk.red(`Error: ${result.stderr}`));
} else {
spinner.succeed('Contract dependencies installed successfully!');
}
} else if (contractFramework === 'foundry') {
result = shell.exec('forge install', { silent: true });
if (result.code !== 0) {
spinner.fail('Failed to install Foundry dependencies.');
console.error(chalk.red(`Error: ${result.stderr}`));
} else {
spinner.succeed('Foundry dependencies installed successfully!');
}
}
const readme = `# ${projectName}
A hybrid TAC dApp with Next.js frontend and ${contractFramework} smart contracts - powered by TAC
## Project Structure
- \`/frontend\` - Next.js frontend with TAC SDK integration
- \`/contracts\` - ${contractFramework} smart contracts for TAC
## Getting Started
### Frontend
\`\`\`bash
cd frontend
npm run dev
\`\`\`
### Contracts
${contractFramework === 'hardhat'
? '```bash\ncd contracts\nnpx hardhat compile\nnpx hardhat run scripts/deploy.js --network tacTestnet\n```'
: '```bash\ncd contracts\nforge build\nforge script script/DeployMessage.s.sol --rpc-url tac_testnet --broadcast\n```'}
## Learn More
- [TAC Documentation](https://docs.tac.build)
`;
fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
const gitignore = `# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
.next/
out/
build
dist
# misc
.DS_Store
*.pem
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# contracts
${contractFramework === 'hardhat'
? 'artifacts\ncache\ntypechain-types'
: 'out\ncache'}
`;
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
spinner = ora('Initializing git repository...').start();
shell.cd(projectPath);
result = shell.exec('git init', { silent: true });
if (result.code !== 0) {
spinner.fail('Failed to initialize git repository.');
} else {
spinner.succeed('Git repository initialized successfully!');
}
console.log('\n' + rainbow('Your TAC project is ready!') + '\n');
console.log(chalk.bold('Next steps:'));
console.log(` ${chalk.cyan('1.')} ${chalk.green('Start the frontend:')}`);
console.log(` ${chalk.cyan('cd')} ${chalk.green(projectName)}`);
console.log(` ${chalk.cyan('cd')} ${chalk.green('frontend')}`);
console.log(` ${chalk.cyan('npm run dev')}`);
console.log(`\n ${chalk.cyan('2.')} ${chalk.green('Deploy contracts:')}`);
console.log(` ${chalk.cyan('cd')} ${chalk.green(projectName)}`);
console.log(` ${chalk.cyan('cd')} ${chalk.green('contracts')}`);
if (contractFramework === 'hardhat') {
console.log(` ${chalk.cyan('npx hardhat run scripts/deploy.js --network tacTestnet')}`);
} else {
console.log(` ${chalk.cyan('forge script script/DeployMessage.s.sol --rpc-url tac_testnet --broadcast')}`);
}
console.log(`\n ${chalk.cyan('3.')} ${chalk.green('Update contract addresses:')}`);
console.log(` Edit ${chalk.green('frontend/lib/constants.ts')} with your deployed contract addresses`);
console.log(chalk.bold('\nTo learn more about TAC, visit:'));
console.log(` ${chalk.cyan('https://docs.tac.build')}\n`);