@xaidenlabs/uso 1.1.30 → 1.1.33
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/bin/index.js +5 -4
- package/package.json +1 -1
- package/src/commands/create.js +75 -99
- package/src/commands/workflow.js +30 -3
package/bin/index.js
CHANGED
|
@@ -28,10 +28,6 @@ program
|
|
|
28
28
|
.description('Verify installation by building a test Anchor project')
|
|
29
29
|
.action(verify);
|
|
30
30
|
|
|
31
|
-
program
|
|
32
|
-
.command('create <project_name>')
|
|
33
|
-
.description('Scaffold a new Anchor project')
|
|
34
|
-
.action(create);
|
|
35
31
|
|
|
36
32
|
program
|
|
37
33
|
.command('build')
|
|
@@ -59,6 +55,11 @@ program
|
|
|
59
55
|
.allowUnknownOption() // Allow flags like --reset
|
|
60
56
|
.action(validator);
|
|
61
57
|
|
|
58
|
+
program
|
|
59
|
+
.command('create <projectName>')
|
|
60
|
+
.description('Scaffold a new Solana project with Next.js frontend')
|
|
61
|
+
.action(create);
|
|
62
|
+
|
|
62
63
|
program
|
|
63
64
|
.command('clean')
|
|
64
65
|
.description('Clean the project (wraps "anchor clean")')
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -1,128 +1,104 @@
|
|
|
1
|
+
const shell = require('shelljs');
|
|
2
|
+
const chalk = require('chalk');
|
|
1
3
|
const fs = require('fs');
|
|
2
4
|
const path = require('path');
|
|
3
5
|
const os = require('os');
|
|
4
|
-
const shell = require('shelljs');
|
|
5
6
|
const { log, spinner } = require('../utils/logger');
|
|
6
7
|
|
|
7
|
-
const create = async (projectName) => {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const create = async (projectName, options) => {
|
|
9
|
+
// 1. Validation
|
|
10
|
+
if (!shell.which('anchor')) {
|
|
11
|
+
log.error("❌ 'anchor' is not found in PATH.");
|
|
12
|
+
log.warn("👉 Run 'uso init' to install the Solana toolchain.");
|
|
10
13
|
return;
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
log.error("❌ Project name can only contain lowercase letters, numbers, hyphens, and underscores.");
|
|
15
|
+
if (!projectName) {
|
|
16
|
+
log.error("❌ Please specify a project name.");
|
|
17
|
+
log.warn("👉 Usage: uso create <project-name>");
|
|
16
18
|
return;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
if (fs.existsSync(targetDir)) {
|
|
21
|
+
if (fs.existsSync(projectName)) {
|
|
21
22
|
log.error(`❌ Directory '${projectName}' already exists.`);
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
log.header(`🏗️ Scaffolding new project: ${projectName}
|
|
26
|
-
|
|
27
|
-
// 1. Copy Template
|
|
28
|
-
const templateDir = path.resolve(__dirname, '../../templates/default');
|
|
29
|
-
const spin = spinner('Copying template files...').start();
|
|
26
|
+
log.header(`🏗️ Scaffolding new Solana project: ${projectName}...`);
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
28
|
+
// 2. Ensure Wallet Keypair Exists
|
|
29
|
+
const keypairPath = path.join(os.homedir(), '.config', 'solana', 'id.json');
|
|
30
|
+
if (!fs.existsSync(keypairPath)) {
|
|
31
|
+
log.info("🔑 No default wallet found. Generating one for you...");
|
|
32
|
+
const keyDir = path.dirname(keypairPath);
|
|
33
|
+
if (!fs.existsSync(keyDir)) shell.mkdir('-p', keyDir);
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const cargoHome = path.join(os.homedir(), '.cargo');
|
|
44
|
-
const cmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoProfile', '-Command', 'Add-MpPreference -ExclusionPath \\\"${targetDir.replace(/\\/g, '\\\\')}\\\"; Add-MpPreference -ExclusionPath \\\"${cargoHome.replace(/\\/g, '\\\\')}\\\"' -Verb RunAs -WindowStyle Hidden -Wait"`;
|
|
45
|
-
const defResult = shell.exec(cmd, { silent: true });
|
|
46
|
-
if (defResult.code === 0) {
|
|
47
|
-
defSpin.succeed('Defender exclusion added (builds won\'t be blocked).');
|
|
48
|
-
} else {
|
|
49
|
-
defSpin.warn('Could not add Defender exclusion. Builds may be blocked by antivirus.');
|
|
35
|
+
if (shell.exec('solana-keygen new --no-bip39-passphrase --outfile "' + keypairPath + '"').code !== 0) {
|
|
36
|
+
log.error("❌ Failed to generate wallet keypair.");
|
|
37
|
+
return;
|
|
50
38
|
}
|
|
39
|
+
log.success(`✅ Wallet created at: ${keypairPath}`);
|
|
51
40
|
}
|
|
52
41
|
|
|
53
|
-
//
|
|
54
|
-
|
|
42
|
+
// 3. Run Anchor Init
|
|
43
|
+
const initSpin = spinner(`Initializing Anchor project '${projectName}'...`).start();
|
|
44
|
+
// --javascript flag used to be common, but now TypeScript is default.
|
|
45
|
+
// We just run init.
|
|
46
|
+
if (shell.exec(`anchor init ${projectName}`).code !== 0) {
|
|
47
|
+
initSpin.fail("Failed to initialize Anchor project.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
initSpin.succeed("Anchor project initialized.");
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const crateName = projectName.replace(/-/g, '_');
|
|
52
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
53
|
+
shell.cd(projectPath);
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
// 4. Inject Frontend Template
|
|
56
|
+
// We'll use the official Solana dApp Scaffold (Next.js + Tailwind)
|
|
57
|
+
const templateRepo = "https://github.com/solana-developers/dapp-scaffold-4"; // A popular one, or we can use a simpler one.
|
|
58
|
+
// Let's use a reliable Next.js starter.
|
|
59
|
+
// "solana-lab/dapp-scaffold" is deprecated/archived.
|
|
60
|
+
// "solana-developers/solana-dapp-next" is good.
|
|
61
|
+
const frontendRepo = "https://github.com/solana-developers/solana-dapp-next.git";
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
const oldTestFile = path.join(targetDir, 'tests', 'my-project.ts');
|
|
65
|
-
const newTestFile = path.join(targetDir, 'tests', `${projectName}.ts`);
|
|
63
|
+
log.info("🧬 Injecting Next.js frontend (Solana Adapter + Tailwind)...");
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
if (fs.existsSync(oldTestFile)) {
|
|
72
|
-
fs.renameSync(oldTestFile, newTestFile);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Replace placeholders in files
|
|
76
|
-
const replaceInFile = (filePath, searchValue, replaceValue) => {
|
|
77
|
-
if (fs.existsSync(filePath)) {
|
|
78
|
-
let content = fs.readFileSync(filePath, 'utf8');
|
|
79
|
-
// Global replace
|
|
80
|
-
const regex = new RegExp(searchValue, 'g');
|
|
81
|
-
content = content.replace(regex, replaceValue);
|
|
82
|
-
fs.writeFileSync(filePath, content, 'utf8');
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Update Anchor.toml
|
|
87
|
-
replaceInFile(path.join(targetDir, 'Anchor.toml'), 'my_project', crateName);
|
|
88
|
-
|
|
89
|
-
// Update Cargo.toml
|
|
90
|
-
replaceInFile(path.join(newProgramDir, 'Cargo.toml'), 'my-project', projectName);
|
|
91
|
-
replaceInFile(path.join(newProgramDir, 'Cargo.toml'), 'my_project', crateName);
|
|
92
|
-
|
|
93
|
-
// Update lib.rs (if specific project name usage exists beyond mod name which is file-based in lib.rs typically, but here we defined inside lib.rs)
|
|
94
|
-
// Actually, in lib.rs: pub mod my_project -> pub mod crate_name
|
|
95
|
-
replaceInFile(path.join(newProgramDir, 'src', 'lib.rs'), 'my_project', crateName);
|
|
96
|
-
|
|
97
|
-
// Update test file
|
|
98
|
-
replaceInFile(newTestFile, 'my-project', projectName); // describe("my-project")
|
|
99
|
-
replaceInFile(newTestFile, 'my_project', crateName); // import ... "../target/types/my_project"
|
|
100
|
-
|
|
101
|
-
// Update class name in test file (MyProject -> ProjectNamePascalCase)
|
|
102
|
-
const toPascalCase = (str) => {
|
|
103
|
-
return str.match(/[a-z0-9]+/gi)
|
|
104
|
-
.map(word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())
|
|
105
|
-
.join('');
|
|
106
|
-
};
|
|
107
|
-
const pascalName = toPascalCase(projectName);
|
|
108
|
-
replaceInFile(newTestFile, 'MyProject', pascalName);
|
|
109
|
-
|
|
110
|
-
} catch (e) {
|
|
111
|
-
log.error(`❌ Failed during customization: ${e.message}`);
|
|
112
|
-
// clean up? maybe not, let user see what happened
|
|
113
|
-
return;
|
|
65
|
+
// Remove existing app folder if anchor created it (usually it doesn't)
|
|
66
|
+
if (fs.existsSync('app')) {
|
|
67
|
+
shell.rm('-rf', 'app');
|
|
114
68
|
}
|
|
115
69
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
70
|
+
const cloneSpin = spinner("Cloning frontend template...").start();
|
|
71
|
+
if (shell.exec(`git clone ${frontendRepo} app`).code !== 0) {
|
|
72
|
+
cloneSpin.fail("Failed to clone frontend template.");
|
|
73
|
+
log.warn("⚠️ Proceeding with backend only.");
|
|
74
|
+
} else {
|
|
75
|
+
cloneSpin.succeed("Frontend template injected into ./app");
|
|
76
|
+
// Remove .git from the frontend so it's part of the main repo
|
|
77
|
+
shell.rm('-rf', path.join('app', '.git'));
|
|
78
|
+
|
|
79
|
+
// Installing dependencies for frontend is slow, maybe skip or ask user?
|
|
80
|
+
// Let's print instructions instead to be fast.
|
|
81
|
+
}
|
|
119
82
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
log
|
|
83
|
+
// 5. Configure Anchor.toml for Local Development
|
|
84
|
+
// (Optional: depending on what 'anchor init' generates, we might want to ensure seeds=false or similar)
|
|
85
|
+
// For now, standard init is usually fine.
|
|
86
|
+
|
|
87
|
+
// 6. Final Success Message
|
|
88
|
+
console.log("");
|
|
89
|
+
log.header(`✅ Project '${projectName}' is ready! 🚀`);
|
|
90
|
+
|
|
91
|
+
log.subHeader("Quick Start:");
|
|
92
|
+
console.log(chalk.yellow(` cd ${projectName}`));
|
|
93
|
+
console.log(chalk.yellow(` uso val `) + chalk.gray(" # Start local validator (in new terminal)"));
|
|
94
|
+
console.log(chalk.yellow(` uso test `) + chalk.gray(" # Build & run tests"));
|
|
95
|
+
console.log("");
|
|
96
|
+
|
|
97
|
+
log.subHeader("Frontend:");
|
|
98
|
+
console.log(chalk.yellow(` cd app`));
|
|
99
|
+
console.log(chalk.yellow(` npm install`));
|
|
100
|
+
console.log(chalk.yellow(` npm run dev`));
|
|
101
|
+
console.log("");
|
|
126
102
|
};
|
|
127
103
|
|
|
128
104
|
module.exports = { create };
|
package/src/commands/workflow.js
CHANGED
|
@@ -168,7 +168,7 @@ const airdrop = (amount, recipient) => {
|
|
|
168
168
|
return runProxyCommand('airdrop', args, 'solana');
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
-
const validator = (args = []) => {
|
|
171
|
+
const validator = async (args = []) => {
|
|
172
172
|
if (!shell.which('solana-test-validator')) {
|
|
173
173
|
log.error("❌ 'solana-test-validator' is not found in PATH.");
|
|
174
174
|
log.warn("👉 Run 'uso init' to install it.");
|
|
@@ -186,8 +186,35 @@ const validator = (args = []) => {
|
|
|
186
186
|
log.header(`🚀 Starting Solana Test Validator (${fullCmd})...`);
|
|
187
187
|
log.info("👉 Press Ctrl+C to stop it.");
|
|
188
188
|
|
|
189
|
-
// Run
|
|
190
|
-
shell.exec
|
|
189
|
+
// Run and capture exit code
|
|
190
|
+
// We use shell.exec, which blocks. If it runs successfully, it blocks until user Ctrl+C.
|
|
191
|
+
// If it fails immediately (like Access Denied), it returns execution object.
|
|
192
|
+
const execution = shell.exec(fullCmd);
|
|
193
|
+
|
|
194
|
+
if (execution.code !== 0) {
|
|
195
|
+
const output = (execution.stderr || '') + (execution.stdout || '');
|
|
196
|
+
if (output.includes('Access is denied') || output.includes('os error 5')) {
|
|
197
|
+
log.warn("⚠️ Validator failed (Access Denied). Retrying with Administrator privileges...");
|
|
198
|
+
|
|
199
|
+
// Extract optional flags from args if any
|
|
200
|
+
const flags = Array.isArray(args) ? args : [];
|
|
201
|
+
// Run Elevated: binary='solana-test-validator', command='', args=flags
|
|
202
|
+
// await runElevatedWithProgress('', flags, 'solana-test-validator');
|
|
203
|
+
|
|
204
|
+
// Use Fire-and-Forget for validator (interactive/long-running)
|
|
205
|
+
// We don't want to capture logs, we want the user to see the new window.
|
|
206
|
+
const flagStr = flags.join(' ');
|
|
207
|
+
const targetCmd = `solana-test-validator ${flagStr}`;
|
|
208
|
+
|
|
209
|
+
log.info(`🚀 Spawning visible Administrator terminal for '${targetCmd}'...`);
|
|
210
|
+
log.warn("👉 A new window will appear. Keep it open to run the validator!");
|
|
211
|
+
|
|
212
|
+
const psCmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoExit', '-Command', '& ${targetCmd}' -Verb RunAs"`;
|
|
213
|
+
shell.exec(psCmd);
|
|
214
|
+
|
|
215
|
+
log.success("✅ Validator launch sequence initiated.");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
191
218
|
};
|
|
192
219
|
|
|
193
220
|
const unblock = () => {
|