create-stylus-ide 1.0.0
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 +1515 -0
- package/cli.js +28 -0
- package/frontend/.vscode/settings.json +9 -0
- package/frontend/app/api/chat/route.ts +101 -0
- package/frontend/app/api/check-setup/route.ts +93 -0
- package/frontend/app/api/cleanup/route.ts +14 -0
- package/frontend/app/api/compile/route.ts +95 -0
- package/frontend/app/api/compile-stream/route.ts +98 -0
- package/frontend/app/api/complete/route.ts +86 -0
- package/frontend/app/api/deploy/route.ts +118 -0
- package/frontend/app/api/export-abi/route.ts +58 -0
- package/frontend/app/favicon.ico +0 -0
- package/frontend/app/globals.css +177 -0
- package/frontend/app/layout.tsx +29 -0
- package/frontend/app/ml/page.tsx +694 -0
- package/frontend/app/page.tsx +1132 -0
- package/frontend/app/providers.tsx +18 -0
- package/frontend/app/qlearning/page.tsx +188 -0
- package/frontend/app/raytracing/page.tsx +268 -0
- package/frontend/components/abi/ABIDialog.tsx +132 -0
- package/frontend/components/ai/AICompletionPopup.tsx +76 -0
- package/frontend/components/ai/ChatPanel.tsx +292 -0
- package/frontend/components/ai/QuickActions.tsx +128 -0
- package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
- package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
- package/frontend/components/deploy/DeployDialog.tsx +334 -0
- package/frontend/components/editor/FileTabs.tsx +181 -0
- package/frontend/components/editor/MonacoEditor.tsx +306 -0
- package/frontend/components/file-tree/ContextMenu.tsx +110 -0
- package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
- package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
- package/frontend/components/file-tree/FileNode.tsx +60 -0
- package/frontend/components/file-tree/FileTree.tsx +259 -0
- package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
- package/frontend/components/file-tree/FolderNode.tsx +105 -0
- package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
- package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
- package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
- package/frontend/components/github/URLCopyButton.tsx +60 -0
- package/frontend/components/interact/ContractInteraction.tsx +323 -0
- package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
- package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
- package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
- package/frontend/components/project/ProjectActions.tsx +176 -0
- package/frontend/components/q-learning/ContractConfig.tsx +172 -0
- package/frontend/components/q-learning/MazeGrid.tsx +346 -0
- package/frontend/components/q-learning/PathAnimation.tsx +384 -0
- package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
- package/frontend/components/q-learning/TrainingForm.tsx +349 -0
- package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
- package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
- package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
- package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
- package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
- package/frontend/components/setup/SetupGuide.tsx +290 -0
- package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
- package/frontend/components/ui/alert-dialog.tsx +157 -0
- package/frontend/components/ui/alert.tsx +66 -0
- package/frontend/components/ui/badge.tsx +46 -0
- package/frontend/components/ui/button.tsx +62 -0
- package/frontend/components/ui/card.tsx +92 -0
- package/frontend/components/ui/context-menu.tsx +252 -0
- package/frontend/components/ui/dialog.tsx +143 -0
- package/frontend/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/components/ui/input.tsx +21 -0
- package/frontend/components/ui/label.tsx +24 -0
- package/frontend/components/ui/progress.tsx +31 -0
- package/frontend/components/ui/scroll-area.tsx +58 -0
- package/frontend/components/ui/select.tsx +190 -0
- package/frontend/components/ui/separator.tsx +28 -0
- package/frontend/components/ui/sheet.tsx +139 -0
- package/frontend/components/ui/skeleton.tsx +13 -0
- package/frontend/components/ui/slider.tsx +63 -0
- package/frontend/components/ui/sonner.tsx +40 -0
- package/frontend/components/ui/tabs.tsx +66 -0
- package/frontend/components/ui/textarea.tsx +18 -0
- package/frontend/components/wallet/ConnectButton.tsx +167 -0
- package/frontend/components/wallet/FaucetButton.tsx +256 -0
- package/frontend/components.json +22 -0
- package/frontend/eslint.config.mjs +18 -0
- package/frontend/hooks/useAICompletion.ts +75 -0
- package/frontend/hooks/useBlockchainLoader.ts +58 -0
- package/frontend/hooks/useChats.ts +137 -0
- package/frontend/hooks/useCompilation.ts +173 -0
- package/frontend/hooks/useFileTabs.ts +178 -0
- package/frontend/hooks/useGitHubLoader.ts +50 -0
- package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
- package/frontend/hooks/usePanelState.ts +115 -0
- package/frontend/hooks/useProjectState.ts +276 -0
- package/frontend/hooks/useResponsive.ts +29 -0
- package/frontend/lib/abi-parser.ts +58 -0
- package/frontend/lib/blockchain-api.ts +374 -0
- package/frontend/lib/blockchain-explorers.ts +75 -0
- package/frontend/lib/blockchain-loader.ts +112 -0
- package/frontend/lib/cargo-template.ts +64 -0
- package/frontend/lib/compilation.ts +529 -0
- package/frontend/lib/constants.ts +31 -0
- package/frontend/lib/deployment.ts +176 -0
- package/frontend/lib/file-utils.ts +83 -0
- package/frontend/lib/github-api.ts +246 -0
- package/frontend/lib/github-loader.ts +369 -0
- package/frontend/lib/ml-contract-template.txt +900 -0
- package/frontend/lib/orbit-chains.ts +181 -0
- package/frontend/lib/output-formatter.ts +68 -0
- package/frontend/lib/project-manager.ts +632 -0
- package/frontend/lib/ray-tracing-abi.ts +206 -0
- package/frontend/lib/storage.ts +189 -0
- package/frontend/lib/templates.ts +1662 -0
- package/frontend/lib/url-parser.ts +188 -0
- package/frontend/lib/utils.ts +6 -0
- package/frontend/lib/wagmi-config.ts +24 -0
- package/frontend/next.config.ts +7 -0
- package/frontend/package-lock.json +16259 -0
- package/frontend/package.json +60 -0
- package/frontend/postcss.config.mjs +7 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/ml-weights/.gitkeep +0 -0
- package/frontend/public/ml-weights/model.pkl +0 -0
- package/frontend/public/ml-weights/model_weights.json +27102 -0
- package/frontend/public/ml-weights/test_samples.json +7888 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/scripts/check-env.js +52 -0
- package/frontend/scripts/setup.js +285 -0
- package/frontend/tailwind.config.ts +64 -0
- package/frontend/tsconfig.json +34 -0
- package/frontend/types/blockchain.ts +63 -0
- package/frontend/types/github.ts +54 -0
- package/frontend/types/project.ts +106 -0
- package/ml-training/README.md +56 -0
- package/ml-training/train_tiny_model.py +325 -0
- package/ml-training/update_template.py +59 -0
- package/package.json +30 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { COMPILATION_CONSTANTS, ERROR_MESSAGES } from "./constants";
|
|
3
|
+
import { createProjectStructure, writeProjectFiles } from "./file-utils";
|
|
4
|
+
import {
|
|
5
|
+
CARGO_TOML_TEMPLATE,
|
|
6
|
+
RUST_TOOLCHAIN_TOML,
|
|
7
|
+
MAIN_RS_TEMPLATE,
|
|
8
|
+
GITIGNORE_TEMPLATE,
|
|
9
|
+
} from "./cargo-template";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import fs from "fs/promises";
|
|
12
|
+
|
|
13
|
+
export interface CompilationOutput {
|
|
14
|
+
type: "stdout" | "stderr" | "error" | "complete" | "result";
|
|
15
|
+
data: string;
|
|
16
|
+
timestamp?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CompilationResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
exitCode: number;
|
|
22
|
+
output: CompilationOutput[];
|
|
23
|
+
wasmSize?: number;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// NEW: Project file interface
|
|
28
|
+
export interface ProjectFile {
|
|
29
|
+
path: string;
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stripAnsi(input: string) {
|
|
34
|
+
return input.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Install WASM target for the pinned toolchain
|
|
38
|
+
async function installWasmTarget(projectPath: string): Promise<boolean> {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const args = [
|
|
41
|
+
"target",
|
|
42
|
+
"add",
|
|
43
|
+
"wasm32-unknown-unknown",
|
|
44
|
+
"--toolchain",
|
|
45
|
+
COMPILATION_CONSTANTS.RUST_TOOLCHAIN_CHANNEL,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const proc = spawn("rustup", args, {
|
|
49
|
+
cwd: projectPath,
|
|
50
|
+
shell: false,
|
|
51
|
+
env: process.env,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
55
|
+
proc.on("error", () => resolve(false));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Generate Cargo.lock file
|
|
60
|
+
async function generateLockfile(projectPath: string): Promise<boolean> {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
const proc = spawn("cargo", ["generate-lockfile"], {
|
|
63
|
+
cwd: projectPath,
|
|
64
|
+
shell: false,
|
|
65
|
+
env: process.env,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
69
|
+
proc.on("error", () => resolve(false));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get WASM file size
|
|
74
|
+
async function getWasmSize(projectPath: string): Promise<number | null> {
|
|
75
|
+
try {
|
|
76
|
+
const wasmPath = path.join(
|
|
77
|
+
projectPath,
|
|
78
|
+
"target",
|
|
79
|
+
"wasm32-unknown-unknown",
|
|
80
|
+
"release",
|
|
81
|
+
"stylus_hello_world.wasm"
|
|
82
|
+
);
|
|
83
|
+
const stats = await fs.stat(wasmPath);
|
|
84
|
+
return stats.size;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// NEW: Write multiple project files
|
|
91
|
+
async function writeMultipleProjectFiles(
|
|
92
|
+
projectPath: string,
|
|
93
|
+
projectFiles: ProjectFile[]
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
for (const file of projectFiles) {
|
|
96
|
+
const filePath = path.join(projectPath, file.path);
|
|
97
|
+
|
|
98
|
+
// Create directory if needed
|
|
99
|
+
const fileDir = path.dirname(filePath);
|
|
100
|
+
await fs.mkdir(fileDir, { recursive: true });
|
|
101
|
+
|
|
102
|
+
// Write file
|
|
103
|
+
await fs.writeFile(filePath, file.content, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// NEW: Check if files include Cargo.toml
|
|
108
|
+
function hasCargoToml(files: ProjectFile[]): boolean {
|
|
109
|
+
return files.some(
|
|
110
|
+
(f) => f.path === "Cargo.toml" || f.path === "./Cargo.toml"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// NEW: Check if files include rust-toolchain.toml
|
|
115
|
+
function hasRustToolchain(files: ProjectFile[]): boolean {
|
|
116
|
+
return files.some(
|
|
117
|
+
(f) =>
|
|
118
|
+
f.path === "rust-toolchain.toml" || f.path === "./rust-toolchain.toml"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function runCargoStylusCheck(
|
|
123
|
+
projectPath: string,
|
|
124
|
+
code: string,
|
|
125
|
+
onOutput?: (output: CompilationOutput) => void,
|
|
126
|
+
projectFiles?: ProjectFile[] // NEW: Optional multi-file support
|
|
127
|
+
): Promise<CompilationResult> {
|
|
128
|
+
try {
|
|
129
|
+
// STEP 1: Create project structure
|
|
130
|
+
const { srcPath } = await createProjectStructure(projectPath);
|
|
131
|
+
|
|
132
|
+
// STEP 2: Write project files
|
|
133
|
+
if (projectFiles && projectFiles.length > 0) {
|
|
134
|
+
// NEW: Multi-file mode
|
|
135
|
+
await writeMultipleProjectFiles(projectPath, projectFiles);
|
|
136
|
+
|
|
137
|
+
// Add default files if not provided
|
|
138
|
+
if (!hasCargoToml(projectFiles)) {
|
|
139
|
+
await fs.writeFile(
|
|
140
|
+
path.join(projectPath, "Cargo.toml"),
|
|
141
|
+
CARGO_TOML_TEMPLATE,
|
|
142
|
+
"utf-8"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!hasRustToolchain(projectFiles)) {
|
|
147
|
+
await fs.writeFile(
|
|
148
|
+
path.join(projectPath, "rust-toolchain.toml"),
|
|
149
|
+
RUST_TOOLCHAIN_TOML,
|
|
150
|
+
"utf-8"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Add .gitignore if not provided
|
|
155
|
+
const hasGitignore = projectFiles.some(
|
|
156
|
+
(f) => f.path === ".gitignore" || f.path === "./.gitignore"
|
|
157
|
+
);
|
|
158
|
+
if (!hasGitignore) {
|
|
159
|
+
await fs.writeFile(
|
|
160
|
+
path.join(projectPath, ".gitignore"),
|
|
161
|
+
GITIGNORE_TEMPLATE,
|
|
162
|
+
"utf-8"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add main.rs if not provided
|
|
167
|
+
const hasMainRs = projectFiles.some(
|
|
168
|
+
(f) => f.path === "src/main.rs" || f.path === "./src/main.rs"
|
|
169
|
+
);
|
|
170
|
+
if (!hasMainRs) {
|
|
171
|
+
await fs.writeFile(
|
|
172
|
+
path.join(srcPath, "main.rs"),
|
|
173
|
+
MAIN_RS_TEMPLATE,
|
|
174
|
+
"utf-8"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Send output message about file count
|
|
179
|
+
const fileCountMsg: CompilationOutput = {
|
|
180
|
+
type: "stdout",
|
|
181
|
+
data: `📁 Compiling project with ${projectFiles.length} file(s)`,
|
|
182
|
+
timestamp: 0,
|
|
183
|
+
};
|
|
184
|
+
onOutput?.(fileCountMsg);
|
|
185
|
+
} else {
|
|
186
|
+
// Legacy single-file mode (backward compatible)
|
|
187
|
+
await writeProjectFiles(
|
|
188
|
+
projectPath,
|
|
189
|
+
srcPath,
|
|
190
|
+
code,
|
|
191
|
+
CARGO_TOML_TEMPLATE,
|
|
192
|
+
RUST_TOOLCHAIN_TOML,
|
|
193
|
+
MAIN_RS_TEMPLATE,
|
|
194
|
+
GITIGNORE_TEMPLATE
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// STEP 3: Ensure wasm target exists for the pinned toolchain
|
|
199
|
+
const wasmInstalled = await installWasmTarget(projectPath);
|
|
200
|
+
if (!wasmInstalled) {
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
exitCode: -1,
|
|
204
|
+
output: [{ type: "error", data: ERROR_MESSAGES.WASM_TARGET_MISSING }],
|
|
205
|
+
error: ERROR_MESSAGES.WASM_TARGET_MISSING,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// STEP 4: Generate Cargo.lock (helps reproducibility)
|
|
210
|
+
const lockfileGenerated = await generateLockfile(projectPath);
|
|
211
|
+
if (!lockfileGenerated) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
exitCode: -1,
|
|
215
|
+
output: [{ type: "error", data: "Failed to generate Cargo.lock file" }],
|
|
216
|
+
error: "Failed to generate Cargo.lock file",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// STEP 5: Run cargo build (LOCAL COMPILATION - NO RPC NEEDED)
|
|
221
|
+
return await new Promise((resolve) => {
|
|
222
|
+
const output: CompilationOutput[] = [];
|
|
223
|
+
const startTime = Date.now();
|
|
224
|
+
|
|
225
|
+
const proc = spawn("cargo", COMPILATION_CONSTANTS.COMMANDS.BUILD, {
|
|
226
|
+
cwd: projectPath,
|
|
227
|
+
shell: false,
|
|
228
|
+
env: {
|
|
229
|
+
...process.env,
|
|
230
|
+
CARGO_TERM_COLOR: "always",
|
|
231
|
+
CARGO_TARGET_DIR: `${projectPath}/target`,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const timeout = setTimeout(() => {
|
|
236
|
+
proc.kill();
|
|
237
|
+
const msg: CompilationOutput = {
|
|
238
|
+
type: "error",
|
|
239
|
+
data: ERROR_MESSAGES.TIMEOUT,
|
|
240
|
+
timestamp: Date.now() - startTime,
|
|
241
|
+
};
|
|
242
|
+
output.push(msg);
|
|
243
|
+
onOutput?.(msg);
|
|
244
|
+
|
|
245
|
+
resolve({
|
|
246
|
+
success: false,
|
|
247
|
+
exitCode: -1,
|
|
248
|
+
output,
|
|
249
|
+
error: ERROR_MESSAGES.TIMEOUT,
|
|
250
|
+
});
|
|
251
|
+
}, COMPILATION_CONSTANTS.COMPILE_TIMEOUT);
|
|
252
|
+
|
|
253
|
+
proc.stdout.on("data", (data) => {
|
|
254
|
+
const msg: CompilationOutput = {
|
|
255
|
+
type: "stdout",
|
|
256
|
+
data: data.toString(),
|
|
257
|
+
timestamp: Date.now() - startTime,
|
|
258
|
+
};
|
|
259
|
+
output.push(msg);
|
|
260
|
+
onOutput?.(msg);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
proc.stderr.on("data", (data) => {
|
|
264
|
+
const msg: CompilationOutput = {
|
|
265
|
+
type: "stderr",
|
|
266
|
+
data: data.toString(),
|
|
267
|
+
timestamp: Date.now() - startTime,
|
|
268
|
+
};
|
|
269
|
+
output.push(msg);
|
|
270
|
+
onOutput?.(msg);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
proc.on("error", (error) => {
|
|
274
|
+
clearTimeout(timeout);
|
|
275
|
+
const msg: CompilationOutput = {
|
|
276
|
+
type: "error",
|
|
277
|
+
data: error.message,
|
|
278
|
+
timestamp: Date.now() - startTime,
|
|
279
|
+
};
|
|
280
|
+
output.push(msg);
|
|
281
|
+
onOutput?.(msg);
|
|
282
|
+
|
|
283
|
+
resolve({
|
|
284
|
+
success: false,
|
|
285
|
+
exitCode: -1,
|
|
286
|
+
output,
|
|
287
|
+
error: error.message,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
proc.on("close", async (code) => {
|
|
292
|
+
clearTimeout(timeout);
|
|
293
|
+
|
|
294
|
+
// Get WASM size if compilation succeeded
|
|
295
|
+
let wasmSize: number | undefined;
|
|
296
|
+
if (code === 0) {
|
|
297
|
+
const size = await getWasmSize(projectPath);
|
|
298
|
+
if (size !== null) {
|
|
299
|
+
wasmSize = size;
|
|
300
|
+
const sizeMB = (size / 1024).toFixed(2);
|
|
301
|
+
const maxMB = (COMPILATION_CONSTANTS.MAX_WASM_SIZE / 1024).toFixed(
|
|
302
|
+
2
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Add size info to output
|
|
306
|
+
const sizeMsg: CompilationOutput = {
|
|
307
|
+
type:
|
|
308
|
+
size > COMPILATION_CONSTANTS.MAX_WASM_SIZE ? "error" : "stdout",
|
|
309
|
+
data:
|
|
310
|
+
size > COMPILATION_CONSTANTS.MAX_WASM_SIZE
|
|
311
|
+
? `⚠️ WASM size: ${sizeMB} KB (exceeds ${maxMB} KB limit)`
|
|
312
|
+
: `✓ WASM size: ${sizeMB} KB (within ${maxMB} KB limit)`,
|
|
313
|
+
timestamp: Date.now() - startTime,
|
|
314
|
+
};
|
|
315
|
+
output.push(sizeMsg);
|
|
316
|
+
onOutput?.(sizeMsg);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const msg: CompilationOutput = {
|
|
321
|
+
type: "complete",
|
|
322
|
+
data:
|
|
323
|
+
code === 0 ? "✓ Compilation successful" : "✗ Compilation failed",
|
|
324
|
+
timestamp: Date.now() - startTime,
|
|
325
|
+
};
|
|
326
|
+
output.push(msg);
|
|
327
|
+
onOutput?.(msg);
|
|
328
|
+
|
|
329
|
+
resolve({
|
|
330
|
+
success: code === 0,
|
|
331
|
+
exitCode: code ?? -1,
|
|
332
|
+
output,
|
|
333
|
+
wasmSize,
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return {
|
|
339
|
+
success: false,
|
|
340
|
+
exitCode: -1,
|
|
341
|
+
output: [
|
|
342
|
+
{
|
|
343
|
+
type: "error",
|
|
344
|
+
data: error instanceof Error ? error.message : "Unknown error",
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function parseCompilationErrors(stderr: string): {
|
|
353
|
+
line: number;
|
|
354
|
+
column: number;
|
|
355
|
+
message: string;
|
|
356
|
+
}[] {
|
|
357
|
+
const errors: { line: number; column: number; message: string }[] = [];
|
|
358
|
+
const clean = stripAnsi(stderr);
|
|
359
|
+
|
|
360
|
+
// Matches:
|
|
361
|
+
// error[E0412]: message...
|
|
362
|
+
// --> src/lib.rs:16:31
|
|
363
|
+
const re =
|
|
364
|
+
/error(?:\[[A-Z0-9]+\])?:\s+(.+?)\n(?:.|\n)*?-->\s+src[\\/]+lib\.rs:(\d+):(\d+)/g;
|
|
365
|
+
|
|
366
|
+
let m: RegExpExecArray | null;
|
|
367
|
+
while ((m = re.exec(clean)) !== null) {
|
|
368
|
+
errors.push({
|
|
369
|
+
message: m[1].trim(),
|
|
370
|
+
line: parseInt(m[2], 10),
|
|
371
|
+
column: parseInt(m[3], 10),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return errors;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
type RunResult = {
|
|
379
|
+
code: number;
|
|
380
|
+
stdout: string;
|
|
381
|
+
stderr: string;
|
|
382
|
+
timedOut: boolean;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
function extractFromFirstInterface(text: string): string | null {
|
|
386
|
+
const idx = text.indexOf("interface ");
|
|
387
|
+
if (idx === -1) return null;
|
|
388
|
+
return text.slice(idx).trim();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function extractJsonArray(text: string): string | null {
|
|
392
|
+
const start = text.indexOf("[");
|
|
393
|
+
const end = text.lastIndexOf("]");
|
|
394
|
+
if (start === -1 || end === -1 || end <= start) return null;
|
|
395
|
+
return text.slice(start, end + 1);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function runCargo(
|
|
399
|
+
cwd: string,
|
|
400
|
+
args: string[],
|
|
401
|
+
timeoutMs: number,
|
|
402
|
+
onOutput?: (o: CompilationOutput) => void
|
|
403
|
+
): Promise<RunResult> {
|
|
404
|
+
return new Promise((resolve) => {
|
|
405
|
+
const start = Date.now();
|
|
406
|
+
const proc = spawn("cargo", args, {
|
|
407
|
+
cwd,
|
|
408
|
+
shell: false,
|
|
409
|
+
env: {
|
|
410
|
+
...process.env,
|
|
411
|
+
// ✅ don't pollute parsing with colors
|
|
412
|
+
CARGO_TERM_COLOR: "never",
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
let stdout = "";
|
|
417
|
+
let stderr = "";
|
|
418
|
+
let timedOut = false;
|
|
419
|
+
|
|
420
|
+
const timeout = setTimeout(() => {
|
|
421
|
+
timedOut = true;
|
|
422
|
+
try {
|
|
423
|
+
proc.kill("SIGKILL");
|
|
424
|
+
} catch {}
|
|
425
|
+
}, timeoutMs);
|
|
426
|
+
|
|
427
|
+
proc.stdout.on("data", (d) => {
|
|
428
|
+
const s = d.toString();
|
|
429
|
+
stdout += s;
|
|
430
|
+
onOutput?.({ type: "stdout", data: s, timestamp: Date.now() - start });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
proc.stderr.on("data", (d) => {
|
|
434
|
+
const s = d.toString();
|
|
435
|
+
stderr += s;
|
|
436
|
+
onOutput?.({ type: "stderr", data: s, timestamp: Date.now() - start });
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
proc.on("error", (e) => {
|
|
440
|
+
clearTimeout(timeout);
|
|
441
|
+
resolve({
|
|
442
|
+
code: -1,
|
|
443
|
+
stdout,
|
|
444
|
+
stderr: stderr + `\n${e.message}`,
|
|
445
|
+
timedOut,
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
proc.on("close", (code) => {
|
|
450
|
+
clearTimeout(timeout);
|
|
451
|
+
resolve({ code: code ?? -1, stdout, stderr, timedOut });
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export async function exportContractABI(
|
|
457
|
+
projectPath: string,
|
|
458
|
+
onOutput?: (output: CompilationOutput) => void
|
|
459
|
+
): Promise<{
|
|
460
|
+
success: boolean;
|
|
461
|
+
solidity?: string;
|
|
462
|
+
abi?: string;
|
|
463
|
+
error?: string;
|
|
464
|
+
details?: string;
|
|
465
|
+
}> {
|
|
466
|
+
const timeoutMs = Math.min(COMPILATION_CONSTANTS.COMPILE_TIMEOUT, 180_000);
|
|
467
|
+
|
|
468
|
+
// 1) Solidity interface
|
|
469
|
+
const solRes = await runCargo(
|
|
470
|
+
projectPath,
|
|
471
|
+
["stylus", "export-abi", "--rust-features=export-abi"],
|
|
472
|
+
timeoutMs,
|
|
473
|
+
onOutput
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const combinedSol = `${solRes.stdout}\n${solRes.stderr}`;
|
|
477
|
+
const solidity =
|
|
478
|
+
extractFromFirstInterface(solRes.stdout) ||
|
|
479
|
+
extractFromFirstInterface(solRes.stderr) ||
|
|
480
|
+
extractFromFirstInterface(combinedSol);
|
|
481
|
+
|
|
482
|
+
if (solRes.timedOut) {
|
|
483
|
+
return {
|
|
484
|
+
success: false,
|
|
485
|
+
error: "ABI export timed out",
|
|
486
|
+
details: `Timed out after ${timeoutMs}ms\nSTDERR:\n${solRes.stderr}\nSTDOUT:\n${solRes.stdout}`,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (solRes.code !== 0 || !solidity) {
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
error: "Failed to export Solidity interface",
|
|
494
|
+
details: `exit=${solRes.code}\nSTDERR:\n${solRes.stderr}\nSTDOUT:\n${solRes.stdout}`,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// 2) JSON ABI (optional; may require solc)
|
|
499
|
+
const jsonRes = await runCargo(
|
|
500
|
+
projectPath,
|
|
501
|
+
["stylus", "export-abi", "--json", "--rust-features=export-abi"],
|
|
502
|
+
timeoutMs,
|
|
503
|
+
onOutput
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
let abi: string | undefined;
|
|
507
|
+
|
|
508
|
+
if (jsonRes.code === 0) {
|
|
509
|
+
const mixed = `${jsonRes.stdout}\n${jsonRes.stderr}`;
|
|
510
|
+
const jsonPart =
|
|
511
|
+
extractJsonArray(jsonRes.stdout) ||
|
|
512
|
+
extractJsonArray(jsonRes.stderr) ||
|
|
513
|
+
extractJsonArray(mixed);
|
|
514
|
+
|
|
515
|
+
if (jsonPart) {
|
|
516
|
+
try {
|
|
517
|
+
abi = JSON.stringify(JSON.parse(jsonPart), null, 2);
|
|
518
|
+
} catch {
|
|
519
|
+
// ignore parse failures; still return solidity
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
solidity,
|
|
527
|
+
abi,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const COMPILATION_CONSTANTS = {
|
|
2
|
+
COMMANDS: {
|
|
3
|
+
BUILD: ["build", "--target", "wasm32-unknown-unknown", "--release"],
|
|
4
|
+
CHECK: ["stylus", "check", "--endpoint"], // For Phase 2
|
|
5
|
+
EXPORT_ABI: [
|
|
6
|
+
"run",
|
|
7
|
+
"--release",
|
|
8
|
+
"--features",
|
|
9
|
+
"export-abi",
|
|
10
|
+
"--bin",
|
|
11
|
+
"stylus-hello-world",
|
|
12
|
+
], // RUN the binary
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
RUST_TOOLCHAIN_CHANNEL: "1.87.0",
|
|
16
|
+
TEMP_BASE: ".stylus-temp",
|
|
17
|
+
COMPILE_TIMEOUT: 120000,
|
|
18
|
+
PROCESS_TIMEOUT: 180000,
|
|
19
|
+
MAX_WASM_SIZE: 24 * 1024,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const ERROR_MESSAGES = {
|
|
23
|
+
RUST_NOT_FOUND: "Rust is not installed. Run: npm run setup",
|
|
24
|
+
CARGO_STYLUS_NOT_FOUND:
|
|
25
|
+
"cargo-stylus not found. Run: cargo install cargo-stylus",
|
|
26
|
+
COMPILATION_FAILED: "Compilation failed. Check output for details.",
|
|
27
|
+
TIMEOUT: "Compilation timed out after 2 minutes.",
|
|
28
|
+
WASM_TARGET_MISSING: "WASM target not installed for Rust toolchain",
|
|
29
|
+
WASM_TOO_LARGE:
|
|
30
|
+
"WASM binary exceeds 24KB size limit. Try optimizing with opt-level = 's' or 'z'",
|
|
31
|
+
};
|