Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/c2wasm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@
isXRPL: boolean
): Promise<void> {
// Reading all files in the directory tree
let fileObjects: any[];

Check warning on line 28 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 28 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
try {
fileObjects = readFiles(dirPath);
fileObjects = readFiles(dirPath).filter((file) => file.type === "c");
} catch (error: any) {

Check warning on line 31 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 31 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
console.error(`Error reading files: ${error}`);
process.exit(1);
}

let headerObjects: any[] = [];

Check warning on line 36 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 36 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
if (headersPath) {
try {
headerObjects = readFiles(headersPath).filter(
(file) => file.type === "h"
);
} catch (error: any) {

Check warning on line 42 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 42 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
console.error(`Error reading header files: ${error}`);
process.exit(1);
}
Expand Down Expand Up @@ -82,11 +82,11 @@
name: filename,
src: fileContent,
};
let headerObjects: any[] = [];

Check warning on line 85 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 85 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
if (headerPath) {
try {
headerObjects = readFiles(headerPath).filter((file) => file.type === "h");
} catch (error: any) {

Check warning on line 89 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 89 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
console.error(`Error reading header files: ${error}`);
process.exit(1);
}
Expand All @@ -105,8 +105,8 @@
}

// Function to read all files in a directory tree
export function readFiles(dirPath: string): any[] {

Check warning on line 108 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 108 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
const files: any[] = [];

Check warning on line 109 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 109 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
const fileNames = fs.readdirSync(dirPath);
for (const fileName of fileNames) {
const filePath = path.join(dirPath, fileName);
Expand Down Expand Up @@ -174,8 +174,8 @@
}

export async function buildWasm(
fileObject: any,

Check warning on line 177 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 177 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
headerObjects: any[],

Check warning on line 178 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (16.x)

Unexpected any. Specify a different type

Check warning on line 178 in src/c2wasm/index.ts

View workflow job for this annotation

GitHub Actions / build-and-lint (20.x)

Unexpected any. Specify a different type
outDir: string,
isXRPL: boolean
) {
Expand Down
64 changes: 56 additions & 8 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,41 @@ const copyFiles = (source: string, destination: string) => {
});
};

const downloadCHeaderFiles = async (
compileHost: string,
projectDir: string
): Promise<void> => {
const headerNames = [
"error",
"extern",
"hookapi",
"macro",
"sfcodes",
"tts",
] as const;
const response = await axios.get(
`${compileHost.replace(/\/+$/, "")}/api/header-files`
);
const headerFiles = response.data;
if (!headerFiles || typeof headerFiles !== "object") {
throw Error("Invalid header files response from the compile server");
}

const files = headerNames.map((name) => {
const content = headerFiles[name];
if (typeof content !== "string" || content.length === 0) {
throw Error(`Missing or invalid header file: ${name}.h`);
}
return { filename: `${name}.h`, content };
});

const includeDir = path.join(projectDir, "contracts", "include");
fs.mkdirSync(includeDir, { recursive: true });
files.forEach(({ filename, content }) => {
fs.writeFileSync(path.join(includeDir, filename), content, "utf-8");
});
};

const clean = (filePath: string, outputPath?: string): string => {
const tsCode = fs.readFileSync(filePath, "utf-8");
const importPattern = /^\s*import\s+.*?;\s*$/gm;
Expand Down Expand Up @@ -76,15 +111,28 @@ export const initCommand = async (type: "c" | "js", folderName: string) => {
type === "c" ? "CHooks" : "JSHooks"
} project in ${newProjectDir}`
);
try {
const projectEnv = dotenv.config({
path: path.join(newProjectDir, ".env"),
});
if (!projectEnv.parsed) {
console.log("No .env file found in the project template.");
process.exit(1);
const projectEnv = dotenv.config({
path: path.join(newProjectDir, ".env"),
});
if (!projectEnv.parsed) {
console.log("No .env file found in the project template.");
process.exit(1);
}
const env = projectEnv.parsed;
if (type === "c") {
const compileHost = env.HOOKS_COMPILE_HOST;
if (!compileHost) {
throw Error("HOOKS_COMPILE_HOST is not set in the project template");
}
try {
await downloadCHeaderFiles(compileHost, newProjectDir);
console.log("Header files saved to contracts/include.");
} catch (error) {
console.error("Error downloading header files:", error);
throw error;
}
const env = projectEnv.parsed;
}
try {
const aliceResponse = await axios.post(
`https://${env.XRPLD_WSS.replace("wss://", "")}/newcreds`
);
Expand Down
2 changes: 1 addition & 1 deletion src/init/c/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "src/index.ts",
"scripts": {
"build": "hooks-cli compile-c contracts build/",
"build": "hooks-cli compile-c contracts build/ --headers contracts/include",
"deploy": "yarn run build && ts-node src/index.ts"
},
"author": {
Expand Down
49 changes: 49 additions & 0 deletions test/integration/c2wasm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import axios from "axios";
import * as fs from "fs";
import * as path from "path";
import { buildCDir } from "../../src/c2wasm";
import { decodeBinary } from "../../src/2bytes/decodeBinary";

jest.mock("axios");
jest.mock("../../src/2bytes/decodeBinary");

describe("C directory builds", () => {
const projectDir = path.join(process.cwd(), "test-c-build-project");
const contractsDir = path.join(projectDir, "contracts");
const includeDir = path.join(contractsDir, "include");
const outDir = path.join(projectDir, "build");

beforeEach(() => {
jest.clearAllMocks();
fs.mkdirSync(includeDir, { recursive: true });
fs.writeFileSync(path.join(contractsDir, "base.c"), "int64_t hook() {}\n");
fs.writeFileSync(path.join(includeDir, "macro.h"), "#define TEST 1\n");
});

afterEach(() => {
fs.rmSync(projectDir, { recursive: true, force: true });
});

it("does not compile headers found below the source directory", async () => {
(decodeBinary as jest.Mock).mockResolvedValue(new Uint8Array([0]));
(axios.post as jest.Mock).mockResolvedValue({
data: {
success: true,
message: "",
output: "binary",
tasks: [],
},
});

await buildCDir(contractsDir, outDir, includeDir, false);

expect(axios.post).toHaveBeenCalledTimes(1);
const body = JSON.parse((axios.post as jest.Mock).mock.calls[0][1]);
expect(body.files).toEqual([
expect.objectContaining({ name: "base.c", type: "c" }),
]);
expect(body.headers).toEqual([
expect.objectContaining({ name: "macro.h", type: "h" }),
]);
});
});
61 changes: 59 additions & 2 deletions test/integration/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ describe("Init Tests", () => {
const tempPathJS = path.join(__dirname, "..", "..", "src", "init", "js");

beforeEach(() => {
jest.clearAllMocks();

// Clean up any existing directories
if (fs.existsSync(projectPathC)) {
fs.rmSync(projectPathC, { recursive: true, force: true });
Expand All @@ -58,8 +60,16 @@ describe("Init Tests", () => {
});

it("should initialize a new C project", async () => {
// Mock axios response
jest.mock("axios");
const headerFiles = {
error: "#define INTERNAL_ERROR -2\n",
extern: "extern int64_t accept(uint32_t, uint32_t, int64_t);\n",
hookapi: '#include "macro.h"\n',
macro: "#define SBUF(str) (uint32_t)(str), sizeof(str)\n",
sfcodes: "#define sfAccount ((8U << 16U) + 1U)\n",
tts: "#define ttINVOKE 99\n",
};

(axios.get as jest.Mock).mockResolvedValue({ data: headerFiles });
(axios.post as jest.Mock).mockResolvedValue({
data: {
code: "tesSUCCESS",
Expand Down Expand Up @@ -97,6 +107,30 @@ describe("Init Tests", () => {
expect(env?.XRPLD_WSS).toBeDefined();
expect(env?.XRPLD_WSS).toEqual(originalEnv?.XRPLD_WSS);
expect(env?.ALICE_SEED).toBeDefined();

expect(axios.get).toHaveBeenCalledWith(
`${originalEnv?.HOOKS_COMPILE_HOST}/api/header-files`
);

const includePath = path.join(projectPathC, "contracts", "include");
expect(fs.statSync(includePath).isDirectory()).toBe(true);
expect(fs.readdirSync(includePath).sort()).toEqual(
Object.keys(headerFiles)
.map((name) => `${name}.h`)
.sort()
);
Object.entries(headerFiles).forEach(([name, content]) => {
expect(
fs.readFileSync(path.join(includePath, `${name}.h`), "utf-8")
).toBe(content);
});

const generatedPackage = JSON.parse(
fs.readFileSync(path.join(projectPathC, "package.json"), "utf-8")
);
expect(generatedPackage.scripts.build).toBe(
"hooks-cli compile-c contracts build/ --headers contracts/include"
);
});

it("should initialize a new JS project", async () => {
Expand All @@ -110,6 +144,8 @@ describe("Init Tests", () => {

await expect(initCommand("js", folderNameJS)).resolves.not.toThrow();

expect(axios.get).not.toHaveBeenCalled();

// Verify that the directory was created
expect(fs.existsSync(projectPathJS)).toBe(true);

Expand Down Expand Up @@ -140,6 +176,27 @@ describe("Init Tests", () => {
expect(env?.ALICE_SEED).toBeDefined();
});

it("should fail C initialization when a required header is missing", async () => {
(axios.get as jest.Mock).mockResolvedValue({
data: {
error: "error",
extern: "extern",
hookapi: "hookapi",
macro: "macro",
sfcodes: "sfcodes",
},
});

await expect(initCommand("c", folderNameC)).rejects.toThrow(
"Missing or invalid header file: tts.h"
);

expect(axios.post).not.toHaveBeenCalled();
expect(
fs.existsSync(path.join(projectPathC, "contracts", "include"))
).toBe(false);
});

it("should handle existing directory error", async () => {
// Create the directory beforehand to simulate existing directory
fs.mkdirSync(projectPathC, { recursive: true });
Expand Down
Loading