@better-sol/cli 0.1.0-alpha.6 → 0.1.0-alpha.7
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/dist/index.js +103 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,10 +4,11 @@ import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } f
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import path, { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { execSync } from "node:child_process";
|
|
7
|
+
import { homedir, tmpdir } from "node:os";
|
|
8
|
+
import { execFile, execSync } from "node:child_process";
|
|
9
9
|
import { generateKeyPairSigner, getAddressDecoder } from "@solana/kit";
|
|
10
|
-
import { access, mkdir, opendir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { access, mkdir, mkdtemp, opendir, readFile, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { promisify } from "node:util";
|
|
11
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
13
|
import { parseSync } from "oxc-parser";
|
|
13
14
|
import { AnchorProvider, Program } from "@coral-xyz/anchor";
|
|
@@ -460,11 +461,23 @@ async function compileProgram(params) {
|
|
|
460
461
|
})
|
|
461
462
|
});
|
|
462
463
|
if (!response.ok) {
|
|
463
|
-
const
|
|
464
|
-
throw new Error(`
|
|
464
|
+
const error = await readJson(response);
|
|
465
|
+
if (response.status === 429 && error.retryAfterSeconds !== void 0) throw new Error(`Rate limit exceeded. Try again in ${formatDuration(error.retryAfterSeconds)}.`);
|
|
466
|
+
throw new Error(`Compile failed (${response.status}): ${error.error}`);
|
|
465
467
|
}
|
|
468
|
+
return readJson(response);
|
|
469
|
+
}
|
|
470
|
+
async function readJson(response) {
|
|
466
471
|
return await response.json();
|
|
467
472
|
}
|
|
473
|
+
function formatDuration(totalSeconds) {
|
|
474
|
+
const seconds = Math.max(0, Math.ceil(totalSeconds));
|
|
475
|
+
const minutes = Math.floor(seconds / 60);
|
|
476
|
+
const remainder = seconds % 60;
|
|
477
|
+
if (minutes === 0) return `${remainder}s`;
|
|
478
|
+
if (remainder === 0) return `${minutes}m`;
|
|
479
|
+
return `${minutes}m ${remainder}s`;
|
|
480
|
+
}
|
|
468
481
|
//#endregion
|
|
469
482
|
//#region src/lib/solana-rpc.ts
|
|
470
483
|
const CLUSTER_URLS = {
|
|
@@ -3103,6 +3116,7 @@ async function discoverProgramsWithSpinner(src) {
|
|
|
3103
3116
|
}
|
|
3104
3117
|
//#endregion
|
|
3105
3118
|
//#region src/commands/deploy.ts
|
|
3119
|
+
const execFileAsync = promisify(execFile);
|
|
3106
3120
|
const DEFAULT_PAYER_PATH = "keypair.json";
|
|
3107
3121
|
const AIRDROP_LAMPORTS = 2000000000n;
|
|
3108
3122
|
const AIRDROP_RETRIES = 3;
|
|
@@ -3118,9 +3132,10 @@ async function deploy(options) {
|
|
|
3118
3132
|
const payerPath = resolvePayerPath(options.payer, config.payer);
|
|
3119
3133
|
const payer = await readKeypair(payerPath);
|
|
3120
3134
|
const rpcUrl = clusterUrl(cluster);
|
|
3135
|
+
const writesRust = options.verify || options.dryRun || options.output !== void 0;
|
|
3121
3136
|
log.step(`Cluster: ${cluster}`);
|
|
3122
3137
|
log.step(`Source: ${src}`);
|
|
3123
|
-
log.step(`Output: ${out}`);
|
|
3138
|
+
if (writesRust) log.step(`Output: ${out}`);
|
|
3124
3139
|
await ensureFunded(payer.publicKey, cluster, rpcUrl);
|
|
3125
3140
|
const programs = await discoverProgramsWithSpinner(src);
|
|
3126
3141
|
const s = spinner();
|
|
@@ -3131,7 +3146,7 @@ async function deploy(options) {
|
|
|
3131
3146
|
throw new Error(`No program named '${options.program}' found in ${src}.\nAvailable programs:\n${available}`);
|
|
3132
3147
|
}
|
|
3133
3148
|
const projects = matched.map((program) => generateAnchorProject(program));
|
|
3134
|
-
if (
|
|
3149
|
+
if (writesRust) {
|
|
3135
3150
|
s.message(`Writing generated Anchor projects`);
|
|
3136
3151
|
await ensureDirectory(outDir);
|
|
3137
3152
|
await Promise.all(projects.map(async (project) => {
|
|
@@ -3147,44 +3162,51 @@ async function deploy(options) {
|
|
|
3147
3162
|
outro(`Dry run complete — Rust written to ${out}/. No compilation or deployment performed.`);
|
|
3148
3163
|
return;
|
|
3149
3164
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3165
|
+
const compileSpinner = spinner();
|
|
3166
|
+
compileSpinner.start(`Compiling ${matched.length === 1 ? matched[0]?.name : matched.length + " programs"}`);
|
|
3167
|
+
let compileResults;
|
|
3168
|
+
try {
|
|
3169
|
+
compileResults = await Promise.all(projects.map((project) => compileProgram({
|
|
3170
|
+
apiKey,
|
|
3171
|
+
program: project.program,
|
|
3172
|
+
libRs: project.libRs,
|
|
3173
|
+
cargoToml: project.cargoToml,
|
|
3174
|
+
idl: project.idl
|
|
3175
|
+
})));
|
|
3176
|
+
compileSpinner.stop("Compilation completed");
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
compileSpinner.stop("Compilation failed");
|
|
3179
|
+
throw error;
|
|
3180
|
+
}
|
|
3159
3181
|
for (const [i, project] of projects.entries()) {
|
|
3160
3182
|
const result = compileResults[i];
|
|
3161
|
-
printProgramSummary(project.program, cluster, outDir, options.verify);
|
|
3162
|
-
const statusLabel = result.status === "success" ? "✓ Success" : `✗ ${result.status}`;
|
|
3163
3183
|
const compileTime = `${(result.compileTimeMs / 1e3).toFixed(1)}s`;
|
|
3164
|
-
log.info(
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
}
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3184
|
+
log.info(`Program: ${project.program.name}`);
|
|
3185
|
+
log.step(`Address: ${project.program.address}`);
|
|
3186
|
+
if (result.status === "failed" || result.bytecode === null) {
|
|
3187
|
+
const logs = result.logs !== void 0 && result.logs.length > 0 ? `\n${result.logs}` : "";
|
|
3188
|
+
throw new Error(`Compilation failed for ${project.program.name}.${logs}`);
|
|
3189
|
+
}
|
|
3190
|
+
log.step(`Compiled: ${compileTime}`);
|
|
3191
|
+
const programKeypairPath = cwdJoin(BETTER_SOL_DIR, `${project.program.name}.json`);
|
|
3192
|
+
const solanaPath = ensureSolanaCli();
|
|
3193
|
+
const deploySpinner = spinner();
|
|
3194
|
+
deploySpinner.start(`Deploying ${project.program.name} to ${cluster}`);
|
|
3195
|
+
try {
|
|
3196
|
+
const signature = await deployCompiledProgram({
|
|
3197
|
+
bytecode: result.bytecode,
|
|
3198
|
+
programName: project.program.name,
|
|
3199
|
+
programKeypairPath,
|
|
3200
|
+
payerPath,
|
|
3201
|
+
cluster,
|
|
3202
|
+
solanaPath
|
|
3203
|
+
});
|
|
3204
|
+
deploySpinner.stop("Deployment completed");
|
|
3205
|
+
log.step(`Signature: ${signature}`);
|
|
3206
|
+
log.step(`Explorer: https://explorer.solana.com/address/${project.program.address}?cluster=${cluster}`);
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
deploySpinner.stop("Deployment failed");
|
|
3209
|
+
throw new Error(`Deployment failed: ${extractProcessErrorMessage(error)}`, { cause: error });
|
|
3188
3210
|
}
|
|
3189
3211
|
}
|
|
3190
3212
|
if (options.verify) {
|
|
@@ -3194,6 +3216,44 @@ async function deploy(options) {
|
|
|
3194
3216
|
}
|
|
3195
3217
|
outro("Deploy complete.");
|
|
3196
3218
|
}
|
|
3219
|
+
async function deployCompiledProgram(params) {
|
|
3220
|
+
const deployDir = await mkdtemp(join(tmpdir(), "better-sol-deploy-"));
|
|
3221
|
+
const soPath = join(deployDir, `${params.programName}.so`);
|
|
3222
|
+
try {
|
|
3223
|
+
writeFileSync(soPath, Buffer.from(params.bytecode, "base64"));
|
|
3224
|
+
const { stdout } = await execFileAsync(params.solanaPath, [
|
|
3225
|
+
"program",
|
|
3226
|
+
"deploy",
|
|
3227
|
+
soPath,
|
|
3228
|
+
"--program-id",
|
|
3229
|
+
params.programKeypairPath,
|
|
3230
|
+
"--keypair",
|
|
3231
|
+
params.payerPath,
|
|
3232
|
+
"--url",
|
|
3233
|
+
params.cluster
|
|
3234
|
+
], {
|
|
3235
|
+
encoding: "utf8",
|
|
3236
|
+
timeout: 12e4
|
|
3237
|
+
});
|
|
3238
|
+
return extractDeploymentSignature(stdout);
|
|
3239
|
+
} finally {
|
|
3240
|
+
await rm(deployDir, {
|
|
3241
|
+
recursive: true,
|
|
3242
|
+
force: true
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
function extractDeploymentSignature(stdout) {
|
|
3247
|
+
return stdout.split("\n").map((line) => line.trim()).find((line) => line.startsWith("Signature:"))?.replace("Signature:", "").trim() ?? "submitted";
|
|
3248
|
+
}
|
|
3249
|
+
function extractProcessErrorMessage(error) {
|
|
3250
|
+
if (typeof error === "object" && error !== null) {
|
|
3251
|
+
const record = error;
|
|
3252
|
+
if (typeof record.stderr === "string" && record.stderr.trim().length > 0) return record.stderr.trim();
|
|
3253
|
+
if (typeof record.stdout === "string" && record.stdout.trim().length > 0) return record.stdout.trim();
|
|
3254
|
+
}
|
|
3255
|
+
return error instanceof Error ? error.message : String(error);
|
|
3256
|
+
}
|
|
3197
3257
|
function resolvePayerPath(payerFlag, configPayer) {
|
|
3198
3258
|
if (payerFlag !== void 0) return cwdPath(payerFlag);
|
|
3199
3259
|
if (configPayer !== void 0) return configPayer;
|
|
@@ -69260,7 +69320,7 @@ async function gitCommit() {
|
|
|
69260
69320
|
//#endregion
|
|
69261
69321
|
//#region src/index.ts
|
|
69262
69322
|
const cli = new Command();
|
|
69263
|
-
cli.name("better-sol").description("TypeScript-first Solana program tooling — run with npx @better-sol/cli").version("0.1.0-alpha.
|
|
69323
|
+
cli.name("better-sol").description("TypeScript-first Solana program tooling — run with npx @better-sol/cli").version("0.1.0-alpha.7");
|
|
69264
69324
|
cli.command("init").description("Initialize a better-sol project").option("--force", "overwrite existing files", false).option("--skip-install", "skip installing dependencies", false).action((options) => run(() => init(options)));
|
|
69265
69325
|
cli.command("create").description("Create a new better-sol program").argument("[name]", "program name").option("--dir <dir>", "program directory", "programs").option("--force", "overwrite existing files", false).action((name, options) => run(() => create(name, options)));
|
|
69266
69326
|
cli.command("login").description("Save your compiler API key").action(() => run(() => login()));
|