diff --git a/app.tests.ts b/app.tests.ts index 2b12115..dd7e9fc 100644 --- a/app.tests.ts +++ b/app.tests.ts @@ -1,6 +1,8 @@ import { red } from "ansicolor"; -import { main } from "./app"; +import { getManifestFileName, main } from "./app"; import { version } from "./package.json"; +import { resolve } from "path"; +import fs from "fs"; describe("Command-line arguments handling", () => { const initialArgv = process.argv; @@ -366,3 +368,39 @@ describe("Command-line arguments handling", () => { } ); }); + +describe("Custom manifest detection", () => { + it("returns the default manifest file name for the example project", () => { + // Arrange + const manifestDir = "example"; + const targetContractName = "counter"; + + // Act + const actual = getManifestFileName(manifestDir, targetContractName); + + // Assert + expect(actual).toBe("Clarinet.toml"); + }); + + it("returns the custom manifest file name when it exists", () => { + // Setup + const manifestDir = "d290f1ee-6c54-4b01-90e6-d701748f0851"; + const targetContractName = "7c9e6679-7425-40de-944b-e07fc1f90ae7"; + + const expected = `Clarinet-${targetContractName}.toml`; + const expectedPath = resolve(manifestDir, expected); + + jest + .spyOn(fs, "existsSync") + .mockImplementation((p: fs.PathLike) => p.toString() === expectedPath); + + // Exercise + const actual = getManifestFileName(manifestDir, targetContractName); + + // Verify + expect(actual).toBe(expected); + + // Teardown + jest.restoreAllMocks(); + }); +}); diff --git a/app.ts b/app.ts index 71cadb0..5528b62 100644 --- a/app.ts +++ b/app.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { join } from "path"; +import { join, resolve } from "path"; import { EventEmitter } from "events"; import { checkProperties } from "./property"; import { checkInvariants } from "./invariant"; @@ -11,11 +11,35 @@ import { import { issueFirstClassCitizenship } from "./citizen"; import { version } from "./package.json"; import { red } from "ansicolor"; +import { existsSync } from "fs"; const logger = (log: string, logLevel: "log" | "error" | "info" = "log") => { console[logLevel](log); }; +/** + * Gets the manifest file name for a Clarinet project. + * If a custom manifest exists (`Clarinet-.toml`), it is used. + * Otherwise, the default `Clarinet.toml` is returned. + * @param manifestDir The relative path to the Clarinet project directory. + * @param targetContractName The target contract name. + * @returns The manifest file name. + */ +export const getManifestFileName = ( + manifestDir: string, + targetContractName: string +) => { + const isCustomManifest = existsSync( + resolve(manifestDir, `Clarinet-${targetContractName}.toml`) + ); + + if (isCustomManifest) { + return `Clarinet-${targetContractName}.toml`; + } + + return "Clarinet.toml"; +}; + const helpMessage = ` rv v${version} @@ -90,8 +114,14 @@ export async function main() { return; } - /** The relative path to `Clarinet.toml`. */ - const manifestPath = join(manifestDir, "Clarinet.toml"); + /** + * The relative path to the manifest file, either `Clarinet.toml` or + * `Clarinet-.toml`. If the latter exists, it is used. + */ + const manifestPath = join( + manifestDir, + getManifestFileName(manifestDir, sutContractName) + ); radio.emit("logMessage", `Using manifest path: ${manifestPath}`); radio.emit("logMessage", `Target contract: ${sutContractName}`); @@ -110,7 +140,11 @@ export async function main() { radio.emit("logMessage", `Using runs: ${runs}`); } - const simnet = await issueFirstClassCitizenship(manifestDir, sutContractName); + const simnet = await issueFirstClassCitizenship( + manifestDir, + manifestPath, + sutContractName + ); /** * The list of contract IDs for the SUT contract names, as per the simnet. diff --git a/citizen.tests.ts b/citizen.tests.ts index 210574c..28d9f77 100644 --- a/citizen.tests.ts +++ b/citizen.tests.ts @@ -12,6 +12,7 @@ import { join } from "path"; import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync } from "fs"; import { tmpdir } from "os"; import yaml from "yaml"; +import { getManifestFileName } from "./app"; describe("Simnet deployment plan operations", () => { const manifestDir = "example"; @@ -337,7 +338,11 @@ describe("Simnet deployment plan operations", () => { cpSync(manifestDir, tempDir, { recursive: true }); // Exercise - const firstClassSimnet = await issueFirstClassCitizenship(tempDir, "cargo"); + const firstClassSimnet = await issueFirstClassCitizenship( + tempDir, + join(tempDir, getManifestFileName(tempDir, "cargo")), + "cargo" + ); const actual = firstClassSimnet.getContractSource("cargo"); // Verify diff --git a/citizen.ts b/citizen.ts index 3ae98d1..c008de5 100644 --- a/citizen.ts +++ b/citizen.ts @@ -20,16 +20,16 @@ import { * to the simnet. * * @param manifestDir The relative path to the manifest directory. + * @param manifestPath The absolute path to the manifest file. * @param sutContractName The target contract name. * @returns The initialized simnet instance with all contracts deployed, with * the test contract treated as a first-class citizen of the target contract. */ export const issueFirstClassCitizenship = async ( manifestDir: string, + manifestPath: string, sutContractName: string ): Promise => { - const manifestPath = join(manifestDir, "Clarinet.toml"); - // Initialize the simnet, to generate the simnet plan and instance. The empty // session will be set up, and contracts will be deployed in the correct // order based on the simnet plan a few lines below. diff --git a/invariant.tests.ts b/invariant.tests.ts index 058d27b..c1243cd 100644 --- a/invariant.tests.ts +++ b/invariant.tests.ts @@ -8,6 +8,7 @@ import { import { join } from "path"; import { issueFirstClassCitizenship } from "./citizen"; import { Cl } from "@stacks/transactions"; +import { getManifestFileName } from "./app"; describe("Simnet contracts operations", () => { it("correctly initializes the local context for a given functions map", async () => { @@ -39,7 +40,11 @@ describe("Simnet contracts operations", () => { it("correctly initializes the Clarity context", async () => { // Arrange - const simnet = await issueFirstClassCitizenship("example", "counter"); + const simnet = await issueFirstClassCitizenship( + "example", + join("example", getManifestFileName("example", "counter")), + "counter" + ); const rendezvousList = Array.from( getSimnetDeployerContractsInterfaces(simnet).keys()