@xaidenlabs/uso 1.1.31 → 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 +14 -1
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
|
@@ -199,7 +199,20 @@ const validator = async (args = []) => {
|
|
|
199
199
|
// Extract optional flags from args if any
|
|
200
200
|
const flags = Array.isArray(args) ? args : [];
|
|
201
201
|
// Run Elevated: binary='solana-test-validator', command='', args=flags
|
|
202
|
-
await runElevatedWithProgress('', flags, 'solana-test-validator');
|
|
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.");
|
|
203
216
|
}
|
|
204
217
|
}
|
|
205
218
|
};
|