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 +105 -0
- package/bin/cli.js +104 -0
- package/bin/copyTemplate.js +34 -0
- package/package.json +55 -0
- package/src/cli.ts +115 -0
- package/src/copyTemplate.ts +42 -0
- package/src/index.js +221 -0
- package/src/types/prompts.d.ts +3 -0
- package/templates/README.md +48 -0
- package/templates/react/.env +2 -0
- package/templates/react/.eslintrc.cjs +18 -0
- package/templates/react/README.md +146 -0
- package/templates/react/eslint.config.js +32 -0
- package/templates/react/index.html +18 -0
- package/templates/react/package.json +25 -0
- package/templates/react/public/sbc-logo.png +0 -0
- package/templates/react/src/App.css +604 -0
- package/templates/react/src/App.tsx +505 -0
- package/templates/react/src/env.d.ts +1 -0
- package/templates/react/src/index.css +13 -0
- package/templates/react/src/main.tsx +10 -0
- package/templates/react/tsconfig.json +25 -0
- package/templates/react/tsconfig.node.json +10 -0
- package/templates/react/vite.config.ts +19 -0
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;
|