create-sbc-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # create-sbc-app
2
+
3
+ Create a new SBC Account Abstraction application with one command.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Using npm
9
+ npx create-sbc-app my-sbc-app
10
+
11
+ # Using yarn
12
+ yarn create sbc-app my-sbc-app
13
+
14
+ # Using pnpm
15
+ pnpm create sbc-app my-sbc-app
16
+ ```
17
+
18
+ ## Features
19
+
20
+ - 🚀 **Multiple Templates**: Choose from Next.js, React (Vite), or vanilla JavaScript
21
+ - ⛽ **Gasless Transactions**: Built-in support for gasless transactions via SBC
22
+ - 🔒 **Account Abstraction**: Smart account creation and management
23
+ - 🎨 **Modern Stack**: TypeScript, ESLint, and modern tooling
24
+ - 🌙 **Dark Mode**: Built-in dark mode support
25
+ - 🔧 **Developer Experience**: Hot reloading, debugging tools, and more
26
+
27
+ ## Templates
28
+
29
+ ### React (Vite)
30
+
31
+ - Lightning-fast development server
32
+ - Modern tooling and DX
33
+ - Minimal configuration
34
+
35
+ ## Options
36
+
37
+ ```bash
38
+ npx create-sbc-app [options]
39
+
40
+ Options:
41
+ -t, --template <template> Template to use (nextjs, react, vanilla) (default: "react")
42
+ --api-key <key> SBC API key
43
+ --skip-install Skip dependency installation
44
+ -h, --help Display help for command
45
+ ```
46
+
47
+ ## Development
48
+
49
+ ### Setup
50
+
51
+ ```bash
52
+ # Clone the repository
53
+ git clone https://github.com/stablecoinxyz/create-sbc-app.git
54
+ cd create-sbc-app
55
+
56
+ # Install dependencies
57
+ npm install
58
+
59
+ # Link the package locally
60
+ npm link
61
+ ```
62
+
63
+ ### Making Changes
64
+
65
+ 1. Create a new changeset:
66
+
67
+ ```bash
68
+ npm run changeset
69
+ ```
70
+
71
+ 2. Follow the prompts to describe your changes
72
+ - Choose the type of change (major, minor, patch)
73
+ - Write a description of the changes
74
+ - Select which packages are affected
75
+
76
+ 3. Commit your changes and the changeset
77
+
78
+ ### Publishing
79
+
80
+ 1. Update versions and changelogs:
81
+
82
+ ```bash
83
+ npm run version
84
+ ```
85
+
86
+ 2. Review the changes and commit
87
+
88
+ 3. Publish to npm:
89
+
90
+ ```bash
91
+ npm run release
92
+ ```
93
+
94
+ ## Contributing
95
+
96
+ 1. Fork the repository
97
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
98
+ 3. Create a changeset (`npm run changeset`)
99
+ 4. Commit your changes (`git commit -am 'Add some amazing feature'`)
100
+ 5. Push to the branch (`git push origin feature/amazing-feature`)
101
+ 6. Open a Pull Request
102
+
103
+ ## License
104
+
105
+ MIT © [Stable Coin Inc](https://stablecoin.xyz)
package/bin/cli.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import prompts from 'prompts';
4
+ import { copyTemplate } from './copyTemplate.js';
5
+ import path from 'path';
6
+ import fs from 'fs-extra';
7
+ import { fileURLToPath } from 'url';
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const program = new Command();
10
+ program
11
+ .name('create-sbc-app')
12
+ .argument('[project-directory]', 'Directory to create the new app in')
13
+ .option('-t, --template <template>', 'Template to use (react, nextjs, backend)')
14
+ .option('--api-key <apiKey>', 'Your SBC API key')
15
+ .option('--wallet <wallet>', 'Wallet integration (not yet implemented)')
16
+ .action(async (dir, options) => {
17
+ if (options.wallet) {
18
+ console.log('Wallet integration not yet implemented.');
19
+ process.exit(0);
20
+ }
21
+ const templateChoices = [
22
+ { title: 'React', value: 'react' },
23
+ { title: 'Next.js', value: 'nextjs' },
24
+ { title: 'Backend', value: 'backend' }
25
+ ];
26
+ // Use provided argument or prompt for project directory
27
+ let projectDir = dir && dir.trim() ? dir.trim() : '';
28
+ if (!projectDir) {
29
+ const res = await prompts({
30
+ type: 'text',
31
+ name: 'dir',
32
+ message: 'Project directory:'
33
+ });
34
+ if (!res.dir || !res.dir.trim()) {
35
+ console.log('Project directory is required.');
36
+ process.exit(1);
37
+ }
38
+ projectDir = res.dir.trim();
39
+ }
40
+ // Use provided option or prompt for template
41
+ let template = options.template && ['react', 'nextjs', 'backend'].includes(options.template) ? options.template : '';
42
+ if (!template) {
43
+ const res = await prompts({
44
+ type: 'select',
45
+ name: 'template',
46
+ message: 'Which template?',
47
+ choices: templateChoices
48
+ });
49
+ if (res.template === undefined) {
50
+ console.log('Template selection is required.');
51
+ process.exit(1);
52
+ }
53
+ template = templateChoices[res.template]?.value;
54
+ if (!template) {
55
+ console.log('Template selection is required.');
56
+ process.exit(1);
57
+ }
58
+ }
59
+ // Use provided option or prompt for API key
60
+ let apiKey = options.apiKey && options.apiKey.trim() ? options.apiKey.trim() : '';
61
+ if (!apiKey) {
62
+ const res = await prompts({
63
+ type: 'text',
64
+ name: 'apiKey',
65
+ message: 'Your SBC API key (or leave empty to set later):'
66
+ });
67
+ apiKey = res.apiKey ? res.apiKey.trim() : 'your-sbc-api-key';
68
+ }
69
+ const targetDir = path.resolve(process.cwd(), projectDir);
70
+ const templateDir = path.resolve(__dirname, '../templates', template);
71
+ if (fs.existsSync(targetDir)) {
72
+ const res = await prompts({
73
+ type: 'confirm',
74
+ name: 'overwrite',
75
+ message: `Directory ${projectDir} already exists. Overwrite?`,
76
+ initial: false
77
+ });
78
+ if (!res.overwrite) {
79
+ console.log('Aborted.');
80
+ process.exit(0);
81
+ }
82
+ await fs.remove(targetDir);
83
+ }
84
+ await copyTemplate(templateDir, targetDir, {
85
+ projectName: projectDir,
86
+ chain: 'baseSepolia',
87
+ apiKey: apiKey
88
+ });
89
+ console.log(`\nSuccess! Created ${projectDir} using the ${template} template.`);
90
+ console.log(`\nNext steps:`);
91
+ console.log(` cd ${projectDir}`);
92
+ if (!options.apiKey && apiKey === 'your-sbc-api-key') {
93
+ console.log(` # Edit .env and add your SBC API key`);
94
+ }
95
+ console.log(` pnpm install # or npm install`);
96
+ if (template === 'backend') {
97
+ console.log(' pnpm start # or npm run start');
98
+ }
99
+ else {
100
+ console.log(' pnpm dev # or npm run dev');
101
+ }
102
+ console.log('\nHappy hacking!');
103
+ });
104
+ program.parse();
@@ -0,0 +1,34 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ export async function copyTemplate(src, dest, templateVars = { projectName: 'my-sbc-app', chain: 'baseSepolia', apiKey: 'your-sbc-api-key' }) {
4
+ await fs.ensureDir(dest);
5
+ const items = await fs.readdir(src);
6
+ for (const item of items) {
7
+ const srcPath = path.join(src, item);
8
+ const stat = await fs.stat(srcPath);
9
+ if (stat.isDirectory()) {
10
+ // Recursively copy directories
11
+ const destPath = path.join(dest, item);
12
+ await copyTemplate(srcPath, destPath, templateVars);
13
+ }
14
+ else {
15
+ let destName = item;
16
+ if (item.endsWith('.template')) {
17
+ destName = item.replace(/\.template$/, '');
18
+ const destPath = path.join(dest, destName);
19
+ // Only do text replacement for .template files
20
+ const content = await fs.readFile(srcPath, 'utf-8');
21
+ const processedContent = content
22
+ .replace(/\{\{projectName\}\}/g, templateVars.projectName)
23
+ .replace(/\{\{chain\}\}/g, templateVars.chain)
24
+ .replace(/\{\{apiKey\}\}/g, templateVars.apiKey);
25
+ await fs.writeFile(destPath, processedContent);
26
+ }
27
+ else {
28
+ // Copy all other files (including images) as binary
29
+ const destPath = path.join(dest, destName);
30
+ await fs.copy(srcPath, destPath);
31
+ }
32
+ }
33
+ }
34
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "create-sbc-app",
3
+ "version": "0.1.1",
4
+ "description": "Create a new SBC Account Abstraction application with one command",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "create-sbc-app": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "release": "npm run build && changeset publish",
17
+ "build": "node scripts/prepare-templates.js",
18
+ "changeset": "changeset",
19
+ "version": "changeset version",
20
+ "test": "echo \"No tests yet\""
21
+ },
22
+ "keywords": [
23
+ "sbc",
24
+ "account-abstraction",
25
+ "ethereum",
26
+ "cli",
27
+ "create-app"
28
+ ],
29
+ "author": "Stable Coin Inc",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/stablecoinxyz/create-sbc-app.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/stablecoinxyz/create-sbc-app/issues"
37
+ },
38
+ "homepage": "https://github.com/stablecoinxyz/create-sbc-app#readme",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "dependencies": {
43
+ "chalk": "^4.1.2",
44
+ "commander": "^12.0.0",
45
+ "fs-extra": "^11.2.0",
46
+ "inquirer": "^8.2.6",
47
+ "ora": "^5.4.1"
48
+ },
49
+ "devDependencies": {
50
+ "@changesets/cli": "^2.27.1"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import prompts from 'prompts';
4
+ import { copyTemplate } from './copyTemplate.js';
5
+ import path from 'path';
6
+ import fs from 'fs-extra';
7
+ import { fileURLToPath } from 'url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('create-sbc-app')
15
+ .argument('[project-directory]', 'Directory to create the new app in')
16
+ .option('-t, --template <template>', 'Template to use (react, nextjs, backend)')
17
+ .option('--api-key <apiKey>', 'Your SBC API key')
18
+ .option('--wallet <wallet>', 'Wallet integration (not yet implemented)')
19
+ .action(async (dir, options) => {
20
+ if (options.wallet) {
21
+ console.log('Wallet integration not yet implemented.');
22
+ process.exit(0);
23
+ }
24
+
25
+ const templateChoices = [
26
+ { title: 'React', value: 'react' },
27
+ { title: 'Next.js', value: 'nextjs' },
28
+ { title: 'Backend', value: 'backend' }
29
+ ];
30
+
31
+ // Use provided argument or prompt for project directory
32
+ let projectDir = dir && dir.trim() ? dir.trim() : '';
33
+ if (!projectDir) {
34
+ const res = await prompts({
35
+ type: 'text',
36
+ name: 'dir',
37
+ message: 'Project directory:'
38
+ });
39
+ if (!res.dir || !res.dir.trim()) {
40
+ console.log('Project directory is required.');
41
+ process.exit(1);
42
+ }
43
+ projectDir = res.dir.trim();
44
+ }
45
+
46
+ // Use provided option or prompt for template
47
+ let template = options.template && ['react', 'nextjs', 'backend'].includes(options.template) ? options.template : '';
48
+ if (!template) {
49
+ const res = await prompts({
50
+ type: 'select',
51
+ name: 'template',
52
+ message: 'Which template?',
53
+ choices: templateChoices
54
+ });
55
+ if (res.template === undefined) {
56
+ console.log('Template selection is required.');
57
+ process.exit(1);
58
+ }
59
+ template = templateChoices[res.template]?.value;
60
+ if (!template) {
61
+ console.log('Template selection is required.');
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ // Use provided option or prompt for API key
67
+ let apiKey = options.apiKey && options.apiKey.trim() ? options.apiKey.trim() : '';
68
+ if (!apiKey) {
69
+ const res = await prompts({
70
+ type: 'text',
71
+ name: 'apiKey',
72
+ message: 'Your SBC API key (or leave empty to set later):'
73
+ });
74
+ apiKey = res.apiKey ? res.apiKey.trim() : 'your-sbc-api-key';
75
+ }
76
+
77
+ const targetDir = path.resolve(process.cwd(), projectDir);
78
+ const templateDir = path.resolve(__dirname, '../templates', template);
79
+
80
+ if (fs.existsSync(targetDir)) {
81
+ const res = await prompts({
82
+ type: 'confirm',
83
+ name: 'overwrite',
84
+ message: `Directory ${projectDir} already exists. Overwrite?`,
85
+ initial: false
86
+ });
87
+ if (!res.overwrite) {
88
+ console.log('Aborted.');
89
+ process.exit(0);
90
+ }
91
+ await fs.remove(targetDir);
92
+ }
93
+
94
+ await copyTemplate(templateDir, targetDir, {
95
+ projectName: projectDir,
96
+ chain: 'baseSepolia',
97
+ apiKey: apiKey
98
+ });
99
+
100
+ console.log(`\nSuccess! Created ${projectDir} using the ${template} template.`);
101
+ console.log(`\nNext steps:`);
102
+ console.log(` cd ${projectDir}`);
103
+ if (!options.apiKey && apiKey === 'your-sbc-api-key') {
104
+ console.log(` # Edit .env and add your SBC API key`);
105
+ }
106
+ console.log(` pnpm install # or npm install`);
107
+ if (template === 'backend') {
108
+ console.log(' pnpm start # or npm run start');
109
+ } else {
110
+ console.log(' pnpm dev # or npm run dev');
111
+ }
112
+ console.log('\nHappy hacking!');
113
+ });
114
+
115
+ program.parse();
@@ -0,0 +1,42 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+
4
+ interface TemplateVars {
5
+ projectName: string;
6
+ chain: string;
7
+ apiKey: string;
8
+ }
9
+
10
+ export async function copyTemplate(src: string, dest: string, templateVars: TemplateVars = { projectName: 'my-sbc-app', chain: 'baseSepolia', apiKey: 'your-sbc-api-key' }) {
11
+ await fs.ensureDir(dest);
12
+
13
+ const items = await fs.readdir(src);
14
+
15
+ for (const item of items) {
16
+ const srcPath = path.join(src, item);
17
+ const stat = await fs.stat(srcPath);
18
+
19
+ if (stat.isDirectory()) {
20
+ // Recursively copy directories
21
+ const destPath = path.join(dest, item);
22
+ await copyTemplate(srcPath, destPath, templateVars);
23
+ } else {
24
+ let destName = item;
25
+ if (item.endsWith('.template')) {
26
+ destName = item.replace(/\.template$/, '');
27
+ const destPath = path.join(dest, destName);
28
+ // Only do text replacement for .template files
29
+ const content = await fs.readFile(srcPath, 'utf-8');
30
+ const processedContent = content
31
+ .replace(/\{\{projectName\}\}/g, templateVars.projectName)
32
+ .replace(/\{\{chain\}\}/g, templateVars.chain)
33
+ .replace(/\{\{apiKey\}\}/g, templateVars.apiKey);
34
+ await fs.writeFile(destPath, processedContent);
35
+ } else {
36
+ // Copy all other files (including images) as binary
37
+ const destPath = path.join(dest, destName);
38
+ await fs.copy(srcPath, destPath);
39
+ }
40
+ }
41
+ }
42
+ }
package/src/index.js ADDED
@@ -0,0 +1,221 @@
1
+ const inquirer = require('inquirer');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const { exec } = require('child_process');
7
+ const { promisify } = require('util');
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ // Chain configuration mapping
12
+ const CHAIN_CONFIG = {
13
+ baseSepolia: 'baseSepolia',
14
+ base: 'base'
15
+ };
16
+
17
+ async function createApp(projectName, options) {
18
+ console.log(chalk.cyan('🚀 Welcome to SBC App Creator!'));
19
+ console.log();
20
+
21
+ // Interactive prompts if not provided
22
+ const answers = await inquirer.prompt([
23
+ {
24
+ type: 'input',
25
+ name: 'projectName',
26
+ message: 'What is your project name?',
27
+ default: projectName || 'my-sbc-app',
28
+ when: !projectName,
29
+ validate: (input) => {
30
+ if (!input.trim()) return 'Project name is required';
31
+ if (!/^[a-z0-9-_]+$/i.test(input)) return 'Project name can only contain letters, numbers, hyphens, and underscores';
32
+ return true;
33
+ }
34
+ },
35
+ {
36
+ type: 'list',
37
+ name: 'template',
38
+ message: 'Which template would you like to use?',
39
+ choices: [
40
+ { name: 'Next.js (Recommended)', value: 'nextjs' },
41
+ { name: 'React', value: 'react' },
42
+ { name: 'Vanilla JavaScript', value: 'vanilla' }
43
+ ],
44
+ when: !options.template
45
+ },
46
+ {
47
+ type: 'input',
48
+ name: 'apiKey',
49
+ message: 'Enter your SBC API key (or leave empty to set later):',
50
+ when: !options.apiKey
51
+ },
52
+ {
53
+ type: 'list',
54
+ name: 'chain',
55
+ message: 'Which chain would you like to target?',
56
+ choices: [
57
+ { name: 'Base Sepolia (Testnet)', value: 'baseSepolia' },
58
+ { name: 'Base Mainnet', value: 'base' }
59
+ ],
60
+ default: 'baseSepolia'
61
+ }
62
+ ]);
63
+
64
+ const config = {
65
+ projectName: projectName || answers.projectName,
66
+ template: options.template || answers.template,
67
+ apiKey: options.apiKey || answers.apiKey || 'sbc-your-api-key-here',
68
+ chain: answers.chain || 'baseSepolia',
69
+ skipInstall: options.skipInstall || false
70
+ };
71
+
72
+ await scaffoldProject(config);
73
+ }
74
+
75
+ async function scaffoldProject(config) {
76
+ const { projectName, template, apiKey, chain, skipInstall } = config;
77
+ const targetDir = path.join(process.cwd(), projectName);
78
+
79
+ // Check if directory exists
80
+ if (await fs.pathExists(targetDir)) {
81
+ console.log(chalk.red(`❌ Directory ${projectName} already exists!`));
82
+ const overwrite = await inquirer.prompt([
83
+ {
84
+ type: 'confirm',
85
+ name: 'overwrite',
86
+ message: 'Do you want to overwrite it?',
87
+ default: false
88
+ }
89
+ ]);
90
+
91
+ if (!overwrite.overwrite) {
92
+ console.log(chalk.yellow('Cancelled.'));
93
+ return;
94
+ }
95
+
96
+ await fs.remove(targetDir);
97
+ }
98
+
99
+ const spinner = ora('Creating project structure...').start();
100
+
101
+ try {
102
+ // Copy template files
103
+ const templateDir = path.join(__dirname, '..', 'templates', template);
104
+ await fs.copy(templateDir, targetDir);
105
+
106
+ spinner.text = 'Configuring project...';
107
+
108
+ // Replace template variables
109
+ await replaceTemplateVariables(targetDir, {
110
+ projectName,
111
+ apiKey,
112
+ chain: CHAIN_CONFIG[chain]
113
+ });
114
+
115
+ if (!skipInstall) {
116
+ spinner.text = 'Installing dependencies...';
117
+
118
+ // Install dependencies
119
+ await execAsync('npm install', { cwd: targetDir });
120
+ }
121
+
122
+ spinner.succeed(chalk.green('Project created successfully!'));
123
+
124
+ // Success message
125
+ console.log();
126
+ console.log(chalk.green(`✅ ${projectName} is ready!`));
127
+ console.log();
128
+
129
+ if (apiKey === 'sbc-your-api-key-here') {
130
+ console.log(chalk.yellow('🔑 IMPORTANT: Setup your SBC API key first!'));
131
+ console.log();
132
+ console.log('1. Get your API key:');
133
+ console.log(chalk.cyan(' Visit: https://dashboard.stablecoin.xyz'));
134
+ console.log();
135
+ console.log('2. Configure environment variables:');
136
+ console.log(chalk.cyan(` cd ${projectName}`));
137
+ if (template === 'react') {
138
+ console.log(chalk.cyan(' # Edit the .env file and replace the API key:'));
139
+ console.log(chalk.cyan(' VITE_SBC_API_KEY=your_real_api_key_here'));
140
+ } else if (template === 'nextjs') {
141
+ console.log(chalk.cyan(' # Edit the .env.local file and replace the API key:'));
142
+ console.log(chalk.cyan(' SBC_API_KEY=your_real_api_key_here'));
143
+ } else {
144
+ console.log(chalk.cyan(' # Check your project files for API key configuration'));
145
+ }
146
+ console.log();
147
+ console.log('3. Start the development server:');
148
+ } else {
149
+ console.log('Next steps:');
150
+ console.log(chalk.cyan(` cd ${projectName}`));
151
+ }
152
+
153
+ if (skipInstall) {
154
+ console.log(chalk.cyan(' npm install'));
155
+ }
156
+
157
+ console.log(chalk.cyan(' npm run dev'));
158
+ console.log();
159
+
160
+ if (template === 'react') {
161
+ console.log('🚀 Your React app includes:');
162
+ console.log(' • Gasless transaction example');
163
+ console.log(' • Wallet connection component');
164
+ console.log(' • Balance checking functionality');
165
+ console.log(' • Complete error handling');
166
+ console.log(' • Modern Vite + TypeScript setup');
167
+ console.log();
168
+ console.log('💡 After connecting your wallet, try the "Send Gasless TX" button!');
169
+ } else if (template === 'nextjs') {
170
+ console.log('🚀 Your Next.js app is ready with SBC integration!');
171
+ } else {
172
+ console.log('🚀 Your vanilla app is ready with SBC integration!');
173
+ }
174
+
175
+ console.log();
176
+ console.log('📚 Documentation: https://docs.stablecoin.xyz');
177
+ console.log('💬 Community: https://t.me/stablecoin_xyz');
178
+
179
+ } catch (error) {
180
+ spinner.fail('Failed to create project');
181
+ console.error(chalk.red('Error:', error.message));
182
+
183
+ // Cleanup on failure
184
+ if (await fs.pathExists(targetDir)) {
185
+ await fs.remove(targetDir);
186
+ }
187
+ process.exit(1);
188
+ }
189
+ }
190
+
191
+ async function replaceTemplateVariables(dir, variables) {
192
+ const walk = async (currentPath) => {
193
+ const items = await fs.readdir(currentPath);
194
+
195
+ for (const item of items) {
196
+ const fullPath = path.join(currentPath, item);
197
+ const stat = await fs.stat(fullPath);
198
+
199
+ if (stat.isDirectory()) {
200
+ await walk(fullPath);
201
+ } else if (item.endsWith('.template')) {
202
+ let content = await fs.readFile(fullPath, 'utf8');
203
+
204
+ // Replace variables
205
+ Object.entries(variables).forEach(([key, value]) => {
206
+ const regex = new RegExp(`{{${key}}}`, 'g');
207
+ content = content.replace(regex, value);
208
+ });
209
+
210
+ // Remove .template extension
211
+ const newPath = fullPath.replace('.template', '');
212
+ await fs.writeFile(newPath, content);
213
+ await fs.remove(fullPath);
214
+ }
215
+ }
216
+ };
217
+
218
+ await walk(dir);
219
+ }
220
+
221
+ module.exports = createApp;
@@ -0,0 +1,3 @@
1
+ declare module 'prompts' {
2
+ export default function prompts(options: any): any;
3
+ }