Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ts: Fix loading programs with numbers in their names using workspace #3450

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- lang: Deduplicate `zero` accounts against `init` accounts ([#3422](https://github.com/coral-xyz/anchor/pull/3422)).
- cli: Fix custom `provider.cluster` ([#3428](https://github.com/coral-xyz/anchor/pull/3428)).
- cli: Ignore non semver solana/agave releases to avoid panic ([#3432](https://github.com/coral-xyz/anchor/pull/3432)).
- ts: Fix loading programs with numbers in their names using `workspace` ([#3450](https://github.com/coral-xyz/anchor/pull/3450)).

### Breaking

Expand Down
47 changes: 26 additions & 21 deletions ts/packages/anchor/src/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as toml from "toml";
import camelcase from "camelcase";
import { snakeCase } from "snake-case";
import { Program } from "./program/index.js";
import { isBrowser } from "./utils/common.js";
Expand All @@ -19,27 +20,10 @@ const workspace = new Proxy(
throw new Error("Workspaces aren't available in the browser");
}

// Converting `programName` to snake_case enables the ability to use any
// Converting `programName` to camelCase enables the ability to use any
// of the following to access the workspace program:
// `workspace.myProgram`, `workspace.MyProgram`, `workspace["my-program"]`...
programName = snakeCase(programName);

// Check whether the program name contains any digits
if (/\d/.test(programName)) {
// Numbers cannot be properly converted from camelCase to snake_case,
// e.g. if the `programName` is `myProgram2`, the actual program name could
// be `my_program2` or `my_program_2`. This implementation assumes the
// latter as the default and always converts to `_numbers`.
//
// A solution to the conversion of program names with numbers in them
// would be to always convert the `programName` to camelCase instead of
// snake_case. The problem with this approach is that it would require
// converting everything else e.g. program names in Anchor.toml and IDL
// file names which are both snake_case.
programName = programName
.replace(/\d+/g, (match) => "_" + match)
.replace("__", "_");
}
programName = camelcase(programName);

// Return early if the program is in cache
if (workspaceCache[programName]) return workspaceCache[programName];
Expand All @@ -50,15 +34,36 @@ const workspace = new Proxy(
// Override the workspace programs if the user put them in the config.
const anchorToml = toml.parse(fs.readFileSync("Anchor.toml"));
const clusterId = anchorToml.provider.cluster;
const programEntry = anchorToml.programs?.[clusterId]?.[programName];
const programs = anchorToml.programs?.[clusterId];
let programEntry;
if (programs) {
programEntry = Object.entries(programs).find(
([key]) => camelcase(key) === programName
)?.[1];
}

let idlPath: string;
let programId;
if (typeof programEntry === "object" && programEntry.idl) {
idlPath = programEntry.idl;
programId = programEntry.address;
} else {
idlPath = path.join("target", "idl", `${programName}.json`);
// Assuming the IDL file's name to be the snake_case name of the
// `programName` with `.json` extension results in problems when
// numbers are involved due to the nature of case conversion from
// camelCase to snake_case being lossy.
//
// To avoid the above problem with numbers, read the `idl` directory and
// compare the camelCased version of both file names and `programName`.
const idlDirPath = path.join("target", "idl");
const fileName = fs
.readdirSync(idlDirPath)
.find((name) => camelcase(path.parse(name).name) === programName);
if (!fileName) {
throw new Error(`Failed to find IDL of program \`${programName}\``);
}

idlPath = path.join(idlDirPath, fileName);
}

if (!fs.existsSync(idlPath)) {
Expand Down
Loading