@better-sol/cli 0.1.0-alpha.12 → 0.1.0-alpha.13
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 +154 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
-
import path, { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import { execFile, execSync } from "node:child_process";
|
|
9
9
|
import { generateKeyPairSigner, getAddressDecoder } from "@solana/kit";
|
|
10
|
-
import { access, mkdir, mkdtemp, opendir, readFile, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { access, mkdir, mkdtemp, opendir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
11
11
|
import { parseSync } from "oxc-parser";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -3532,12 +3532,21 @@ function flattenAccountItems(items) {
|
|
|
3532
3532
|
return result;
|
|
3533
3533
|
}
|
|
3534
3534
|
function idlTypeToCode(type) {
|
|
3535
|
-
if (typeof type === "string")
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
if ("
|
|
3540
|
-
|
|
3535
|
+
if (typeof type === "string") {
|
|
3536
|
+
if (type === "publicKey") return "bs.pubkey()";
|
|
3537
|
+
return `bs.${type}()`;
|
|
3538
|
+
}
|
|
3539
|
+
if (typeof type !== "object" || type === null) return "bs.pubkey()";
|
|
3540
|
+
const record = type;
|
|
3541
|
+
const option = record.option;
|
|
3542
|
+
if (option !== void 0) return `bs.optional(${idlTypeToCode(option)})`;
|
|
3543
|
+
const coption = record.coption;
|
|
3544
|
+
if (coption !== void 0) return `bs.optional(${idlTypeToCode(coption)})`;
|
|
3545
|
+
const vec = record.vec;
|
|
3546
|
+
if (vec !== void 0) return `bs.vector(${idlTypeToCode(vec)})`;
|
|
3547
|
+
const array = record.array;
|
|
3548
|
+
if (Array.isArray(array) && array.length === 2) return `bs.array(${idlTypeToCode(array[0])}, ${array[1]})`;
|
|
3549
|
+
if (record.defined !== void 0) return "bs.pubkey()";
|
|
3541
3550
|
return "bs.pubkey()";
|
|
3542
3551
|
}
|
|
3543
3552
|
function escapeString(str) {
|
|
@@ -3553,18 +3562,35 @@ function getStructFields(typeDef) {
|
|
|
3553
3562
|
if (!Array.isArray(fields)) return [];
|
|
3554
3563
|
return fields;
|
|
3555
3564
|
}
|
|
3565
|
+
function getStructFieldsFromAccount(account) {
|
|
3566
|
+
if (typeof account !== "object" || account === null) return [];
|
|
3567
|
+
const accountType = account.type;
|
|
3568
|
+
if (typeof accountType !== "object" || accountType === null) return [];
|
|
3569
|
+
const typeRecord = accountType;
|
|
3570
|
+
if (typeRecord.kind !== "struct") return [];
|
|
3571
|
+
const fields = typeRecord.fields;
|
|
3572
|
+
if (!Array.isArray(fields)) return [];
|
|
3573
|
+
return fields;
|
|
3574
|
+
}
|
|
3575
|
+
function getIdlName$1(idl) {
|
|
3576
|
+
if (typeof idl.metadata?.name === "string") return idl.metadata.name;
|
|
3577
|
+
const record = idl;
|
|
3578
|
+
return typeof record.name === "string" ? record.name : void 0;
|
|
3579
|
+
}
|
|
3556
3580
|
function generateIdlProgram(idl, sourceLabel) {
|
|
3557
3581
|
const w = new CodeWriter();
|
|
3558
|
-
const programName = idl
|
|
3582
|
+
const programName = toCamel(getIdlName$1(idl) ?? "unknown");
|
|
3559
3583
|
const programAddress = idl.address ?? "";
|
|
3560
3584
|
const typesByName = /* @__PURE__ */ new Map();
|
|
3561
3585
|
for (const t of idl.types ?? []) typesByName.set(t.name, t);
|
|
3562
3586
|
const accountDefs = idl.accounts ?? [];
|
|
3563
3587
|
const resolvedAccounts = /* @__PURE__ */ new Map();
|
|
3564
3588
|
for (const acc of accountDefs) {
|
|
3565
|
-
const
|
|
3589
|
+
const fromTypes = getStructFields(typesByName.get(acc.name));
|
|
3590
|
+
const fromAccount = getStructFieldsFromAccount(acc);
|
|
3591
|
+
const fields = fromTypes.length > 0 ? fromTypes : fromAccount;
|
|
3566
3592
|
resolvedAccounts.set(acc.name, {
|
|
3567
|
-
|
|
3593
|
+
exportName: toPascal(acc.name),
|
|
3568
3594
|
fields
|
|
3569
3595
|
});
|
|
3570
3596
|
}
|
|
@@ -3573,11 +3599,11 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3573
3599
|
w.line(`// Generated by better-sol from ${sourceLabel}. Do not edit.`);
|
|
3574
3600
|
w.line(`import { bs } from "better-sol/program"`);
|
|
3575
3601
|
w.blank();
|
|
3576
|
-
for (const [
|
|
3602
|
+
for (const [, { exportName, fields }] of resolvedAccounts) {
|
|
3577
3603
|
if (fields.length === 0) continue;
|
|
3578
|
-
w.line(`const ${
|
|
3604
|
+
w.line(`const ${exportName} = bs.account({`);
|
|
3579
3605
|
w.indent(1);
|
|
3580
|
-
for (const field of fields) w.line(`${field.name}: ${idlTypeToCode(field.type)},`);
|
|
3606
|
+
for (const field of fields) w.line(`${toCamel(field.name)}: ${idlTypeToCode(field.type)},`);
|
|
3581
3607
|
w.indent(0);
|
|
3582
3608
|
w.line("})");
|
|
3583
3609
|
w.blank();
|
|
@@ -3590,10 +3616,9 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3590
3616
|
w.line(`address: "${escapeString(programAddress)}",`);
|
|
3591
3617
|
if (resolvedAccounts.size > 0) {
|
|
3592
3618
|
w.line(`accounts: {`);
|
|
3593
|
-
for (const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
w.line(` ${name},`);
|
|
3619
|
+
for (const [, { exportName, fields }] of resolvedAccounts) {
|
|
3620
|
+
if (fields.length === 0) continue;
|
|
3621
|
+
w.line(` ${exportName},`);
|
|
3597
3622
|
}
|
|
3598
3623
|
w.line(`},`);
|
|
3599
3624
|
}
|
|
@@ -3601,7 +3626,7 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3601
3626
|
w.line(`errors: {`);
|
|
3602
3627
|
for (const err of errorEntries) {
|
|
3603
3628
|
const msg = err.msg ?? err.name;
|
|
3604
|
-
w.line(` ${err.name}: "${escapeString(msg)}",`);
|
|
3629
|
+
w.line(` ${toPascal(err.name)}: "${escapeString(msg)}",`);
|
|
3605
3630
|
}
|
|
3606
3631
|
w.line(`},`);
|
|
3607
3632
|
}
|
|
@@ -3609,8 +3634,8 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3609
3634
|
w.line(`events: {`);
|
|
3610
3635
|
for (const ev of eventEntries) {
|
|
3611
3636
|
const fields = getStructFields(typesByName.get(ev.name));
|
|
3612
|
-
w.line(` ${ev.name}: {`);
|
|
3613
|
-
for (const f of fields) w.line(` ${f.name}: ${idlTypeToCode(f.type)},`);
|
|
3637
|
+
w.line(` ${toPascal(ev.name)}: {`);
|
|
3638
|
+
for (const f of fields) w.line(` ${toCamel(f.name)}: ${idlTypeToCode(f.type)},`);
|
|
3614
3639
|
w.line(` },`);
|
|
3615
3640
|
}
|
|
3616
3641
|
w.line(`},`);
|
|
@@ -3619,21 +3644,22 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3619
3644
|
w.line(`}, ix => ({`);
|
|
3620
3645
|
for (const instr of idl.instructions) {
|
|
3621
3646
|
const flatAccounts = flattenAccountItems(instr.accounts);
|
|
3647
|
+
const ixName = toCamel(instr.name);
|
|
3622
3648
|
w.blank();
|
|
3623
|
-
w.line(`${
|
|
3649
|
+
w.line(`${ixName}: ix({`);
|
|
3624
3650
|
if (flatAccounts.length > 0) {
|
|
3625
3651
|
w.line(` accounts: {`);
|
|
3626
3652
|
for (const acc of flatAccounts) {
|
|
3627
3653
|
if (acc.optional === true) continue;
|
|
3628
3654
|
const constraint = accountToConstraint(acc, resolvedAccounts);
|
|
3629
|
-
w.line(` ${acc.name}: ${constraint},`);
|
|
3655
|
+
w.line(` ${toCamel(acc.name)}: ${constraint},`);
|
|
3630
3656
|
}
|
|
3631
3657
|
w.line(` },`);
|
|
3632
3658
|
}
|
|
3633
3659
|
const args = instr.args ?? [];
|
|
3634
3660
|
if (args.length > 0) {
|
|
3635
3661
|
w.line(` args: {`);
|
|
3636
|
-
for (const arg of args) w.line(` ${arg.name}: ${idlTypeToCode(arg.type)},`);
|
|
3662
|
+
for (const arg of args) w.line(` ${toCamel(arg.name)}: ${idlTypeToCode(arg.type)},`);
|
|
3637
3663
|
w.line(` },`);
|
|
3638
3664
|
}
|
|
3639
3665
|
if (instr.returns !== void 0) w.line(` returns: ${idlTypeToCode(instr.returns)},`);
|
|
@@ -3649,15 +3675,22 @@ function generateIdlProgram(idl, sourceLabel) {
|
|
|
3649
3675
|
}
|
|
3650
3676
|
function accountToConstraint(acc, resolvedAccounts) {
|
|
3651
3677
|
if (acc.signer === true) return "bs.signer()";
|
|
3652
|
-
const
|
|
3678
|
+
const resolved = resolveAccountDefinition(acc.name, resolvedAccounts);
|
|
3679
|
+
const accountRef = resolved !== void 0 && resolved.fields.length > 0 ? resolved.exportName : "bs.account({})";
|
|
3653
3680
|
if (acc.writable === true) return `bs.mut(${accountRef})`;
|
|
3654
3681
|
return accountRef;
|
|
3655
3682
|
}
|
|
3683
|
+
function resolveAccountDefinition(accountName, resolvedAccounts) {
|
|
3684
|
+
const exact = resolvedAccounts.get(accountName);
|
|
3685
|
+
if (exact !== void 0) return exact;
|
|
3686
|
+
const pascalName = toPascal(accountName);
|
|
3687
|
+
for (const [idlName, account] of resolvedAccounts) if (idlName === pascalName || account.exportName === pascalName) return account;
|
|
3688
|
+
}
|
|
3656
3689
|
function runParams(accounts, args) {
|
|
3657
3690
|
const parts = [];
|
|
3658
|
-
const accountNames = accounts.filter((a) => a.optional !== true).map((a) => a.name);
|
|
3691
|
+
const accountNames = accounts.filter((a) => a.optional !== true).map((a) => toCamel(a.name));
|
|
3659
3692
|
if (accountNames.length > 0) parts.push(`{ ${accountNames.join(", ")} }`);
|
|
3660
|
-
const argNames = args.map((a) => a.name);
|
|
3693
|
+
const argNames = args.map((a) => toCamel(a.name));
|
|
3661
3694
|
if (argNames.length > 0) parts.push(`{ ${argNames.join(", ")} }`);
|
|
3662
3695
|
parts.push("ctx");
|
|
3663
3696
|
return parts.join(", ");
|
|
@@ -69245,7 +69278,7 @@ async function generateIdl(input, options) {
|
|
|
69245
69278
|
sourceLabel = basename(idlPath);
|
|
69246
69279
|
s.stop("IDL loaded");
|
|
69247
69280
|
}
|
|
69248
|
-
const programName = options.name ?? idl
|
|
69281
|
+
const programName = toCamel(options.name ?? getIdlName(idl) ?? "program");
|
|
69249
69282
|
const outPath = cwdPath(options.out ?? `generated/${programName}.ts`);
|
|
69250
69283
|
s.message("Generating program definition");
|
|
69251
69284
|
const code = generateIdlProgram(idl, sourceLabel);
|
|
@@ -69253,12 +69286,101 @@ async function generateIdl(input, options) {
|
|
|
69253
69286
|
await writeFile(outPath, code, "utf-8");
|
|
69254
69287
|
s.stop("Generated");
|
|
69255
69288
|
log.info(`Wrote ${resolve(outPath)}`);
|
|
69256
|
-
|
|
69257
|
-
log.step(`Import it in your app:\n\n import { ${programName} } from "./${importPath}"\n\nThen register it with the client:\n\n const sol = await betterSol({ programs: { ${programName} } })`);
|
|
69289
|
+
log.step(await buildImportGuide(outPath, programName));
|
|
69258
69290
|
outro("Done");
|
|
69259
69291
|
}
|
|
69292
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
69293
|
+
".better-sol",
|
|
69294
|
+
".git",
|
|
69295
|
+
".next",
|
|
69296
|
+
"dist",
|
|
69297
|
+
"build",
|
|
69298
|
+
"node_modules"
|
|
69299
|
+
]);
|
|
69300
|
+
async function buildImportGuide(outPath, programName) {
|
|
69301
|
+
const usage = await findBetterSolUsage(process.cwd());
|
|
69302
|
+
if (usage !== void 0) return `Import it in ${relativePath(process.cwd(), usage.filePath)}:\n\n import { ${programName} } from "${moduleSpecifier(dirname(usage.filePath), outPath)}"\n\n${usage.hasProgramsObject ? `Add it to the existing programs object:\n\n programs: { ..., ${programName} }` : `Register it with the client:\n\n const sol = await betterSol({ programs: { ${programName} } })`}`;
|
|
69303
|
+
return `Import it in your app:\n\n import { ${programName} } from "${moduleSpecifier(process.cwd(), outPath)}"\n\nThen register it with the client:\n\n const sol = await betterSol({ programs: { ${programName} } })`;
|
|
69304
|
+
}
|
|
69305
|
+
async function findBetterSolUsage(root) {
|
|
69306
|
+
const files = await collectTypeScriptFiles(root);
|
|
69307
|
+
let fallback;
|
|
69308
|
+
for (const filePath of files) {
|
|
69309
|
+
const usage = parseBetterSolUsage(await readFile(filePath, "utf-8"), filePath);
|
|
69310
|
+
if (usage === void 0) continue;
|
|
69311
|
+
if (usage.hasProgramsObject) return usage;
|
|
69312
|
+
fallback ??= usage;
|
|
69313
|
+
}
|
|
69314
|
+
return fallback;
|
|
69315
|
+
}
|
|
69316
|
+
async function collectTypeScriptFiles(root) {
|
|
69317
|
+
const results = [];
|
|
69318
|
+
await collectTypeScriptFilesInto(root, results);
|
|
69319
|
+
return results.sort();
|
|
69320
|
+
}
|
|
69321
|
+
async function collectTypeScriptFilesInto(directory, results) {
|
|
69322
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
69323
|
+
for (const entry of entries) {
|
|
69324
|
+
const fullPath = join(directory, entry.name);
|
|
69325
|
+
if (entry.isDirectory()) {
|
|
69326
|
+
if (!IGNORED_DIRECTORIES.has(entry.name)) await collectTypeScriptFilesInto(fullPath, results);
|
|
69327
|
+
continue;
|
|
69328
|
+
}
|
|
69329
|
+
if (!entry.isFile()) continue;
|
|
69330
|
+
const extension = extname(entry.name);
|
|
69331
|
+
if (extension === ".ts" || extension === ".tsx") results.push(fullPath);
|
|
69332
|
+
}
|
|
69333
|
+
}
|
|
69334
|
+
function parseBetterSolUsage(source, filePath) {
|
|
69335
|
+
let program;
|
|
69336
|
+
try {
|
|
69337
|
+
program = parseModule(filePath, source);
|
|
69338
|
+
} catch {
|
|
69339
|
+
return;
|
|
69340
|
+
}
|
|
69341
|
+
let hasCall = false;
|
|
69342
|
+
let hasProgramsObject = false;
|
|
69343
|
+
visitAst(program, (node) => {
|
|
69344
|
+
if (!isBetterSolCall(node)) return;
|
|
69345
|
+
hasCall = true;
|
|
69346
|
+
const firstArg = node.arguments[0];
|
|
69347
|
+
if (!isObjectExpression(firstArg)) return;
|
|
69348
|
+
if (isObjectExpression(getObjectProperty(firstArg, "programs"))) hasProgramsObject = true;
|
|
69349
|
+
});
|
|
69350
|
+
return hasCall ? {
|
|
69351
|
+
filePath,
|
|
69352
|
+
hasProgramsObject
|
|
69353
|
+
} : void 0;
|
|
69354
|
+
}
|
|
69355
|
+
function isBetterSolCall(node) {
|
|
69356
|
+
return isCallExpression(node) && isIdentifier(node.callee) && node.callee.name === "betterSol";
|
|
69357
|
+
}
|
|
69358
|
+
function visitAst(node, visit) {
|
|
69359
|
+
if (!isAstNode(node)) return;
|
|
69360
|
+
visit(node);
|
|
69361
|
+
for (const value of Object.values(node)) if (Array.isArray(value)) for (const item of value) visitAst(item, visit);
|
|
69362
|
+
else visitAst(value, visit);
|
|
69363
|
+
}
|
|
69364
|
+
function isAstNode(value) {
|
|
69365
|
+
if (typeof value !== "object" || value === null) return false;
|
|
69366
|
+
return typeof value.type === "string";
|
|
69367
|
+
}
|
|
69368
|
+
function moduleSpecifier(fromDirectory, toFile) {
|
|
69369
|
+
const normalized = removeExt(relative(fromDirectory, toFile)).split(sep).join("/");
|
|
69370
|
+
if (normalized.startsWith(".")) return normalized;
|
|
69371
|
+
return `./${normalized}`;
|
|
69372
|
+
}
|
|
69373
|
+
function relativePath(fromDirectory, toFile) {
|
|
69374
|
+
const value = relative(fromDirectory, toFile).split(sep).join("/");
|
|
69375
|
+
return value.length > 0 ? value : ".";
|
|
69376
|
+
}
|
|
69260
69377
|
function removeExt(path) {
|
|
69261
|
-
return path.replace(/\.
|
|
69378
|
+
return path.replace(/\.tsx?$/, "");
|
|
69379
|
+
}
|
|
69380
|
+
function getIdlName(idl) {
|
|
69381
|
+
if (typeof idl.metadata?.name === "string") return idl.metadata.name;
|
|
69382
|
+
const record = idl;
|
|
69383
|
+
return typeof record.name === "string" ? record.name : void 0;
|
|
69262
69384
|
}
|
|
69263
69385
|
//#endregion
|
|
69264
69386
|
//#region src/commands/login.ts
|
|
@@ -69360,7 +69482,7 @@ async function gitCommit() {
|
|
|
69360
69482
|
//#endregion
|
|
69361
69483
|
//#region src/index.ts
|
|
69362
69484
|
const cli = new Command();
|
|
69363
|
-
cli.name("better-sol").description("Write Solana programs in TypeScript. Run with npx @better-sol/cli@alpha").version("0.1.0-alpha.
|
|
69485
|
+
cli.name("better-sol").description("Write Solana programs in TypeScript. Run with npx @better-sol/cli@alpha").version("0.1.0-alpha.13");
|
|
69364
69486
|
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)));
|
|
69365
69487
|
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)));
|
|
69366
69488
|
cli.command("login").description("Save your compiler API key").argument("[apiKey]", "compiler API key").action((apiKey) => run(() => login(apiKey)));
|