@xaidenlabs/uso 1.1.33 → 1.1.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaidenlabs/uso",
3
- "version": "1.1.33",
3
+ "version": "1.1.34",
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"
@@ -7,11 +7,6 @@ const { log, spinner } = require('../utils/logger');
7
7
 
8
8
  const create = async (projectName, options) => {
9
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.");
13
- return;
14
- }
15
10
  if (!projectName) {
16
11
  log.error("❌ Please specify a project name.");
17
12
  log.warn("👉 Usage: uso create <project-name>");
@@ -25,72 +20,150 @@ const create = async (projectName, options) => {
25
20
 
26
21
  log.header(`🏗️ Scaffolding new Solana project: ${projectName}...`);
27
22
 
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);
23
+ // 2. Locate Template
24
+ // We expect templates/default relative to this file
25
+ // src/commands/create.js -> templates/default is two levels up -> templates/default
26
+ const templatePath = path.resolve(__dirname, '../../templates/default');
27
+ if (!fs.existsSync(templatePath)) {
28
+ log.error(`❌ Template not found at: ${templatePath}`);
29
+ log.error("This installation of uso might be corrupted.");
30
+ return;
31
+ }
32
+
33
+ // 3. Create Project Directory & Copy Files
34
+ const projectPath = path.join(process.cwd(), projectName);
35
+ shell.mkdir('-p', projectPath);
36
+
37
+ const copySpin = spinner(`Copying template files...`).start();
38
+ // Use recursive copy
39
+ shell.cp('-R', `${templatePath}/*`, projectPath);
40
+ // Copy hidden files like .gitignore separately if needed, or check if cp -R includes them
41
+ shell.cp('-R', `${templatePath}/.gitignore`, projectPath);
42
+ shell.cp('-R', `${templatePath}/.prettierignore`, projectPath);
43
+ copySpin.succeed("Template files copied.");
34
44
 
35
- if (shell.exec('solana-keygen new --no-bip39-passphrase --outfile "' + keypairPath + '"').code !== 0) {
36
- log.error("❌ Failed to generate wallet keypair.");
37
- return;
45
+ shell.cd(projectPath);
46
+
47
+ // 4. Customization: Rename Project & Generate Program ID
48
+ const configSpin = spinner(`Configuring project...`).start();
49
+
50
+ // 4a. Program Keypair & ID
51
+ // Create 'target/deploy' folder where keypair usually lives
52
+ const deployDir = path.join(projectPath, 'target', 'deploy');
53
+ shell.mkdir('-p', deployDir);
54
+
55
+ // Check if user has solana-keygen
56
+ let programId = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"; // Default fallback
57
+ if (shell.which('solana-keygen')) {
58
+ const keypairFile = path.join(deployDir, `${projectName}-keypair.json`);
59
+ // Generate new keypair
60
+ const result = shell.exec(`solana-keygen new --no-bip39-passphrase --outfile "${keypairFile}"`, { silent: true });
61
+ if (result.code === 0) {
62
+ // Extract pubkey
63
+ const pubkey = shell.exec(`solana-keygen pubkey "${keypairFile}"`, { silent: true }).stdout.trim();
64
+ if (pubkey) programId = pubkey;
38
65
  }
39
- log.success(`✅ Wallet created at: ${keypairPath}`);
40
66
  }
41
67
 
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;
68
+ // 4b. Rename Program Directory
69
+ // Default template has programs/my-project
70
+ const oldProgramDir = path.join(projectPath, 'programs', 'my-project');
71
+ const newProgramDir = path.join(projectPath, 'programs', projectName);
72
+
73
+ // Rust crate names usually use underscores instead of dashes
74
+ // e.g. project-name -> project_name
75
+ const crateName = projectName.replace(/-/g, '_');
76
+ const newProgramDirCrate = path.join(projectPath, 'programs', crateName);
77
+
78
+ if (fs.existsSync(oldProgramDir)) {
79
+ shell.mv(oldProgramDir, newProgramDirCrate);
49
80
  }
50
- initSpin.succeed("Anchor project initialized.");
51
81
 
52
- const projectPath = path.join(process.cwd(), projectName);
53
- shell.cd(projectPath);
82
+ // 4c. Update Cargo.toml (root and program)
83
+ // Root Cargo.toml
84
+ const rootCargoPath = path.join(projectPath, 'Cargo.toml');
85
+ if (fs.existsSync(rootCargoPath)) {
86
+ let content = fs.readFileSync(rootCargoPath, 'utf8');
87
+ content = content.replace(/programs\/my-project/g, `programs/${crateName}`);
88
+ fs.writeFileSync(rootCargoPath, content);
89
+ }
54
90
 
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";
91
+ // Program Cargo.toml
92
+ const programCargoPath = path.join(newProgramDirCrate, 'Cargo.toml');
93
+ if (fs.existsSync(programCargoPath)) {
94
+ let content = fs.readFileSync(programCargoPath, 'utf8');
95
+ content = content.replace(/name = "my-project"/g, `name = "${crateName}"`);
96
+ content = content.replace(/name = "my_project"/g, `name = "${crateName}"`);
97
+ fs.writeFileSync(programCargoPath, content);
98
+ }
62
99
 
63
- log.info("🧬 Injecting Next.js frontend (Solana Adapter + Tailwind)...");
100
+ // 4d. Update Anchor.toml
101
+ const anchorTomlPath = path.join(projectPath, 'Anchor.toml');
102
+ if (fs.existsSync(anchorTomlPath)) {
103
+ let content = fs.readFileSync(anchorTomlPath, 'utf8');
104
+ // Replace Program ID
105
+ content = content.replace(/my_project = ".*"/g, `${crateName} = "${programId}"`);
106
+ // Replace scripts if needed (template might have default)
107
+ fs.writeFileSync(anchorTomlPath, content);
108
+ }
109
+
110
+ // 4e. Update lib.rs with new Program ID
111
+ const libRsPath = path.join(newProgramDirCrate, 'src', 'lib.rs');
112
+ if (fs.existsSync(libRsPath)) {
113
+ let content = fs.readFileSync(libRsPath, 'utf8');
114
+ // declare_id!("...")
115
+ content = content.replace(/declare_id!\(".*"\);/g, `declare_id!("${programId}");`);
116
+ fs.writeFileSync(libRsPath, content);
117
+ }
118
+
119
+ // 4f. Update Tests
120
+ // tests/my-project.ts -> tests/<projectName>.ts
121
+ const oldTestFile = path.join(projectPath, 'tests', 'my-project.ts');
122
+ const newTestFile = path.join(projectPath, 'tests', `${projectName}.ts`);
123
+ if (fs.existsSync(oldTestFile)) {
124
+ shell.mv(oldTestFile, newTestFile);
125
+ // Build needs to reference this? No, anchor test runs all .ts files usually.
126
+ // Update content of test file
127
+ let content = fs.readFileSync(newTestFile, 'utf8');
128
+ content = content.replace(/anchor\.workspace\.MyProject/g, `anchor.workspace.${toPascalCase(crateName)}`);
129
+ fs.writeFileSync(newTestFile, content);
130
+ }
131
+
132
+ configSpin.succeed("Project configured.");
64
133
 
65
- // Remove existing app folder if anchor created it (usually it doesn't)
66
- if (fs.existsSync('app')) {
67
- shell.rm('-rf', 'app');
134
+ // 5. Initialize Git
135
+ if (shell.which('git')) {
136
+ shell.exec('git init', { silent: true });
137
+ shell.exec('git add .', { silent: true });
138
+ shell.exec('git commit -m "Initial commit by uso"', { silent: true });
68
139
  }
69
140
 
141
+ // 6. Inject Frontend Template
142
+ const frontendRepo = "https://github.com/solana-developers/solana-dapp-next.git";
143
+ log.info("🧬 Injecting Next.js frontend (Solana Adapter + Tailwind)...");
144
+
70
145
  const cloneSpin = spinner("Cloning frontend template...").start();
71
146
  if (shell.exec(`git clone ${frontendRepo} app`).code !== 0) {
72
147
  cloneSpin.fail("Failed to clone frontend template.");
73
- log.warn("⚠️ Proceeding with backend only.");
148
+ log.warn("⚠️ Proceeding without frontend.");
74
149
  } else {
75
150
  cloneSpin.succeed("Frontend template injected into ./app");
76
- // Remove .git from the frontend so it's part of the main repo
77
151
  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
152
  }
82
153
 
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.
154
+ // 7. Install Dependencies (Optional but nice)
155
+ // Skipping to keep it fast, user can run npm install
86
156
 
87
- // 6. Final Success Message
157
+ // 8. Final Success Message
88
158
  console.log("");
89
159
  log.header(`✅ Project '${projectName}' is ready! 🚀`);
160
+ log.info(` Program ID: ${programId}`);
90
161
 
91
162
  log.subHeader("Quick Start:");
92
163
  console.log(chalk.yellow(` cd ${projectName}`));
93
164
  console.log(chalk.yellow(` uso val `) + chalk.gray(" # Start local validator (in new terminal)"));
165
+ // We should probably run npm install for them?
166
+ console.log(chalk.yellow(` npm install `) + chalk.gray(" # Install dependencies"));
94
167
  console.log(chalk.yellow(` uso test `) + chalk.gray(" # Build & run tests"));
95
168
  console.log("");
96
169
 
@@ -101,4 +174,10 @@ const create = async (projectName, options) => {
101
174
  console.log("");
102
175
  };
103
176
 
177
+ // Helper: snake_case to PascalCase
178
+ function toPascalCase(str) {
179
+ return str.replace(/(\w)(\w*)/g,
180
+ function (g0, g1, g2) { return g1.toUpperCase() + g2.toLowerCase(); }).replace(/_/g, '');
181
+ }
182
+
104
183
  module.exports = { create };