@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaidenlabs/uso",
3
- "version": "1.1.30",
3
+ "version": "1.1.33",
4
4
  "description": "Universal Solana Orchestrator - A one-command setup tool for Solana and Anchor development environments on Windows, macOS, and Linux.",
5
5
  "bin": {
6
6
  "uso": "bin/index.js"
@@ -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
- if (!projectName) {
9
- log.error("❌ Please provide a project name: uso create <project-name>");
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
- // Validate project name (basic check for valid folder name)
14
- if (!/^[a-z0-9-_]+$/.test(projectName)) {
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
- const targetDir = path.resolve(process.cwd(), projectName);
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
- try {
32
- shell.cp('-R', templateDir, targetDir);
33
- spin.succeed('Template copied.');
34
- } catch (e) {
35
- spin.fail('Failed to copy template.');
36
- log.error(e.message);
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
- // 1b. Add Windows Defender exclusion for the project directory (prevents build blocks)
41
- if (os.platform() === 'win32') {
42
- const defSpin = spinner('Adding Windows Defender exclusion...').start();
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
- // 2. Customize Project (Rename files and content)
54
- log.info("✏️ Customizing project details...");
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
- // Rename programs/my-project to programs/<project-name>
57
- // Note: Rust crate names typically use underscores, not hyphens.
58
- const crateName = projectName.replace(/-/g, '_');
52
+ const projectPath = path.join(process.cwd(), projectName);
53
+ shell.cd(projectPath);
59
54
 
60
- const oldProgramDir = path.join(targetDir, 'programs', 'my-project');
61
- const newProgramDir = path.join(targetDir, 'programs', crateName);
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
- // Rename tests/my-project.ts to tests/<project-name>.ts
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
- try {
68
- if (fs.existsSync(oldProgramDir)) {
69
- fs.renameSync(oldProgramDir, newProgramDir);
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
- // 4. Generate Keypair if needed?
117
- // Anchor usually handles this on first build, but let's check if they have a wallet.
118
- // We won't force it here, "uso init" or "uso verify" handles wallet creation.
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
- log.header("\n🎉 Project Created Successfully!");
121
- log.info(`\n👉 Next steps:\n`);
122
- log.info(` cd ${projectName}`);
123
- log.info(` npm install`);
124
- log.info(` uso test`);
125
- log.info(`\nHappy building! 🚀`);
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 };
@@ -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 without capturing output so it streams to console
190
- shell.exec(fullCmd);
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 = () => {