UNPKG

@consensys/create-web3-app

Version:

CLI tool for generating Web3 starter projects, streamlining the setup of monorepo structures with a frontend (Next.js or React) and blockchain tooling (HardHat or Foundry). It leverages the commander library for command-line interactions and guides users

583 lines (582 loc) 34.4 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { describe, it, expect, vi, beforeEach } from "vitest"; import inquirer from "inquirer"; import { promises as fsPromises } from "fs"; import path from "path"; import degit from "degit"; // Import the actual degit // Import functions to test and mocks import * as utils from "./index.js"; // Import all exports import { TEMPLATES, BLOCKCHAIN_TOOLING_CHOICES, PACAKGE_MANAGER_CHOICES, } from "../constants/index.js"; // --- Mocks --- vi.mock("inquirer"); // Mock fs promises vi.mock("fs", function (importOriginal) { return __awaiter(void 0, void 0, void 0, function () { var originalFs; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, importOriginal()]; case 1: originalFs = _a.sent(); return [2 /*return*/, __assign(__assign({}, originalFs), { promises: { mkdir: vi.fn(), writeFile: vi.fn(), readFile: vi.fn(), rm: vi.fn(), } })]; } }); }); }); // Mock execAsync vi.mock("./index", function (importOriginal) { return __awaiter(void 0, void 0, void 0, function () { var originalModule; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, importOriginal()]; case 1: originalModule = _a.sent(); return [2 /*return*/, __assign(__assign({}, originalModule), { execAsync: vi.fn() })]; } }); }); }); // --- Refined Degit Mock --- // 1. Define the structure the mock factory will return var singleDegitInstance = { clone: vi.fn() }; // 2. Mock the factory to *always* return this single instance vi.mock("degit", function () { return ({ default: vi.fn().mockImplementation(function () { return singleDegitInstance; }) }); }); // --- Access Mocks via Modules --- var mockedFsMkdir = vi.mocked(fsPromises.mkdir); var mockedFsWriteFile = vi.mocked(fsPromises.writeFile); var mockedFsReadFile = vi.mocked(fsPromises.readFile); var mockedFsRm = vi.mocked(fsPromises.rm); var mockedExecAsync = vi.mocked(utils.execAsync); // 3. Get a reference to the mocked factory var mockedDegitFactory = vi.mocked(degit); // 4. Get a direct reference to the clone method on our single instance var mockedDegitClone = vi.mocked(singleDegitInstance.clone); // Mock console logging vi.spyOn(console, "log").mockImplementation(function () { }); vi.spyOn(console, "error").mockImplementation(function () { }); vi.spyOn(console, "warn").mockImplementation(function () { }); describe("create-web3-app Utils", function () { beforeEach(function () { // Reset mocks using the direct references vi.clearAllMocks(); // Clears call history, reset spies mockedFsMkdir.mockReset(); mockedFsWriteFile.mockReset(); mockedFsReadFile.mockReset(); mockedFsRm.mockReset(); mockedExecAsync.mockReset(); mockedDegitFactory.mockClear(); // Clear calls to the factory itself mockedDegitClone.mockReset(); // Reset the clone method on the single instance vi.mocked(inquirer.prompt).mockReset(); }); // --- Test promptForOptions --- describe("promptForOptions", function () { it("should return correct options when all prompts are answered", function () { return __awaiter(void 0, void 0, void 0, function () { var mockArgs, mockTemplate, mockTooling, mockPackageManager, mockAnswers, options; return __generator(this, function (_a) { switch (_a.label) { case 0: mockArgs = "my-test-project"; mockTemplate = TEMPLATES[0]; mockTooling = BLOCKCHAIN_TOOLING_CHOICES[0]; mockPackageManager = PACAKGE_MANAGER_CHOICES[0]; mockAnswers = { frameworkName: mockTemplate.name, tooling: mockTooling.name, packageManager: mockPackageManager.name, }; // Set mock for this test only vi.mocked(inquirer.prompt).mockResolvedValue(mockAnswers); return [4 /*yield*/, utils.promptForOptions(mockArgs)]; case 1: options = _a.sent(); expect(inquirer.prompt).toHaveBeenCalledTimes(3); expect(options).toEqual({ projectName: mockArgs, templateId: mockTemplate.id, blockchain_tooling: mockTooling.value, packageManager: mockPackageManager.value, }); return [2 /*return*/]; } }); }); }); it("should prompt for projectName if args are empty", function () { return __awaiter(void 0, void 0, void 0, function () { var mockProjectName, mockTemplate, mockTooling, mockPackageManager, mockAnswers, options; return __generator(this, function (_a) { switch (_a.label) { case 0: mockProjectName = "prompted-project"; mockTemplate = TEMPLATES[1]; mockTooling = BLOCKCHAIN_TOOLING_CHOICES[1]; mockPackageManager = PACAKGE_MANAGER_CHOICES[1]; mockAnswers = { projectName: mockProjectName, frameworkName: mockTemplate.name, tooling: mockTooling.name, packageManager: mockPackageManager.name, }; // Chain mocks for this test vi.mocked(inquirer.prompt) .mockResolvedValueOnce({ projectName: mockProjectName }) .mockResolvedValueOnce({ frameworkName: mockAnswers.frameworkName }) .mockResolvedValueOnce({ tooling: mockAnswers.tooling }) .mockResolvedValueOnce({ packageManager: mockAnswers.packageManager }); return [4 /*yield*/, utils.promptForOptions("")]; case 1: options = _a.sent(); expect(inquirer.prompt).toHaveBeenCalledTimes(4); expect(options).toEqual({ projectName: mockProjectName, templateId: mockTemplate.id, blockchain_tooling: mockTooling.value, packageManager: mockPackageManager.value, }); return [2 /*return*/]; } }); }); }); // Updated test for invalid template name it("should throw error if template selection is invalid", function () { return __awaiter(void 0, void 0, void 0, function () { var mockArgs, mockAnswers; return __generator(this, function (_a) { switch (_a.label) { case 0: mockArgs = "my-test-project"; mockAnswers = { frameworkName: "NonExistentTemplate", // Invalid name tooling: "HardHat", packageManager: "npm", }; vi.mocked(inquirer.prompt).mockResolvedValue(mockAnswers); // Expect the error thrown by promptForFramework return [4 /*yield*/, expect(utils.promptForOptions(mockArgs)).rejects.toThrow('Internal error: Could not find template data for selected name "NonExistentTemplate"')]; case 1: // Expect the error thrown by promptForFramework _a.sent(); return [2 /*return*/]; } }); }); }); }); // --- Test cloneTemplate --- describe("cloneTemplate", function () { var degitTemplate = TEMPLATES.find(function (t) { return t.id === 'next-sdk-quickstart'; }); var gitTemplate = TEMPLATES.find(function (t) { return t.id === 'react-web3-starter'; }); var destinationPath = "/path/to/project"; var projectName = "my-project"; var gitPath = path.join(destinationPath, ".git"); var packageJsonPath = path.join(destinationPath, "package.json"); var options = { projectName: projectName, templateId: degitTemplate.id, blockchain_tooling: 'none', packageManager: 'yarn' }; beforeEach(function () { // Reset mocks used within cloneTemplate tests mockedExecAsync.mockResolvedValue({ stdout: "", stderr: "" }); mockedFsReadFile.mockResolvedValue(JSON.stringify({ name: "template-name", version: "1.0.0" })); mockedFsWriteFile.mockResolvedValue(undefined); mockedFsRm.mockResolvedValue(undefined); // Reset the clone mock specifically mockedDegitClone.mockReset().mockResolvedValue(undefined); // Ensure clone defaults to success }); // --- Degit Path Tests --- it("should call degit factory and clone method for DegitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); // Check the factory call expect(mockedDegitFactory).toHaveBeenCalledWith(degitTemplate.degitSource, expect.anything()); // Check the clone call on the instance expect(mockedDegitClone).toHaveBeenCalledWith(destinationPath); expect(mockedExecAsync).not.toHaveBeenCalled(); expect(mockedFsRm).not.toHaveBeenCalledWith(gitPath, expect.anything()); return [2 /*return*/]; } }); }); }); it("should read, update, and write package.json for DegitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { var expectedPackageJson; return __generator(this, function (_a) { switch (_a.label) { case 0: mockedFsReadFile.mockResolvedValue(JSON.stringify({ name: "old-name", version: "1.0.0" })); // Provide specific content return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); // Verify degit clone was called first expect(mockedDegitClone).toHaveBeenCalled(); // Verify package.json handling expect(mockedFsReadFile).toHaveBeenCalledWith(packageJsonPath, "utf-8"); expectedPackageJson = { name: projectName, version: "1.0.0" }; expect(mockedFsWriteFile).toHaveBeenCalledWith(packageJsonPath, JSON.stringify(expectedPackageJson, null, 2), "utf-8"); return [2 /*return*/]; } }); }); }); it("should handle errors during degit clone", function () { return __awaiter(void 0, void 0, void 0, function () { var cloneError; return __generator(this, function (_a) { switch (_a.label) { case 0: cloneError = new Error("Degit clone failed"); mockedDegitClone.mockRejectedValueOnce(cloneError); // Make degit fail return [4 /*yield*/, expect(utils.cloneTemplate(options, destinationPath)).rejects.toThrow(cloneError)]; case 1: _a.sent(); // Ensure subsequent steps didn't run expect(mockedFsReadFile).not.toHaveBeenCalled(); expect(mockedFsWriteFile).not.toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); it("should warn if package.json update fails for DegitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { var writeError; return __generator(this, function (_a) { switch (_a.label) { case 0: writeError = new Error("Failed to write file"); mockedFsWriteFile.mockRejectedValueOnce(writeError); // Fail writing package.json return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); expect(mockedDegitClone).toHaveBeenCalled(); // Ensure clone happened expect(mockedFsReadFile).toHaveBeenCalled(); // Read should have happened expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("Warning: Could not update package.json name"), expect.stringContaining(writeError.message)); return [2 /*return*/]; } }); }); }); // --- Git Clone Path Tests --- it("should call git clone with correct arguments for GitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); expect(mockedExecAsync).toHaveBeenCalledWith("git clone ".concat(gitTemplate.repo_url, " ").concat(destinationPath)); expect(mockedDegitFactory).not.toHaveBeenCalled(); // Ensure degit factory wasn't called expect(mockedDegitClone).not.toHaveBeenCalled(); // Ensure degit clone wasn't called return [2 /*return*/]; } }); }); }); it("should remove the .git directory after cloning for GitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); expect(mockedExecAsync).toHaveBeenCalled(); // Ensure clone happened expect(mockedFsRm).toHaveBeenCalledWith(gitPath, { recursive: true, force: true, }); return [2 /*return*/]; } }); }); }); it("should read, update, and write package.json for GitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { var expectedPackageJson; return __generator(this, function (_a) { switch (_a.label) { case 0: mockedFsReadFile.mockResolvedValue(JSON.stringify({ name: "old-name", version: "1.0.0" })); return [4 /*yield*/, utils.cloneTemplate(options, destinationPath)]; case 1: _a.sent(); // Verify clone happened first expect(mockedExecAsync).toHaveBeenCalled(); expect(mockedFsRm).toHaveBeenCalled(); // Verify package.json handling expect(mockedFsReadFile).toHaveBeenCalledWith(packageJsonPath, "utf-8"); expectedPackageJson = { name: projectName, version: "1.0.0" }; expect(mockedFsWriteFile).toHaveBeenCalledWith(packageJsonPath, JSON.stringify(expectedPackageJson, null, 2), "utf-8"); return [2 /*return*/]; } }); }); }); it("should handle errors during git clone for GitTemplate", function () { return __awaiter(void 0, void 0, void 0, function () { var cloneError; return __generator(this, function (_a) { switch (_a.label) { case 0: cloneError = new Error("Git clone failed"); mockedExecAsync.mockRejectedValueOnce(cloneError); // Make git clone fail return [4 /*yield*/, expect(utils.cloneTemplate(options, destinationPath)).rejects.toThrow(cloneError)]; case 1: _a.sent(); // Ensure subsequent steps didn't run expect(mockedFsRm).not.toHaveBeenCalled(); expect(mockedFsReadFile).not.toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); // --- Common Tests --- it("should throw error if templateId is not found", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, expect(utils.cloneTemplate(options, destinationPath)).rejects.toThrow('Template with id "invalid-id" not found.')]; case 1: _a.sent(); expect(mockedExecAsync).not.toHaveBeenCalled(); expect(mockedDegitClone).not.toHaveBeenCalled(); // Check clone mock here return [2 /*return*/]; } }); }); }); }); // --- Test initializeMonorepo --- describe("initializeMonorepo", function () { var projectName = "my-monorepo"; var packagesPath = path.join(projectName, "packages"); var blockchainPath = path.join(projectName, "packages", "blockchain"); var sitePath = path.join(projectName, "packages", "site"); var gitignorePath = path.join(projectName, ".gitignore"); var rootPackageJsonPath = path.join(projectName, "package.json"); var pnpmWorkspacePath = path.join(projectName, "pnpm-workspace.yaml"); it("should create base directories", function () { return __awaiter(void 0, void 0, void 0, function () { var options; return __generator(this, function (_a) { switch (_a.label) { case 0: options = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'npm' }; return [4 /*yield*/, utils.initializeMonorepo(options)]; case 1: _a.sent(); // Check that mkdir was called for all necessary paths expect(mockedFsMkdir).toHaveBeenCalledWith(packagesPath, { recursive: true }); expect(mockedFsMkdir).toHaveBeenCalledWith(blockchainPath, { recursive: true }); expect(mockedFsMkdir).toHaveBeenCalledWith(sitePath, { recursive: true }); return [2 /*return*/]; } }); }); }); it("should create .gitignore", function () { return __awaiter(void 0, void 0, void 0, function () { var options; return __generator(this, function (_a) { switch (_a.label) { case 0: options = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'npm' }; return [4 /*yield*/, utils.initializeMonorepo(options)]; case 1: _a.sent(); // Check that writeFile was called for .gitignore expect(mockedFsWriteFile).toHaveBeenCalledWith(gitignorePath, expect.stringContaining("node_modules")); return [2 /*return*/]; } }); }); }); it("should create root package.json", function () { return __awaiter(void 0, void 0, void 0, function () { var options, expectedPackageJson; return __generator(this, function (_a) { switch (_a.label) { case 0: options = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'yarn' }; return [4 /*yield*/, utils.initializeMonorepo(options)]; case 1: _a.sent(); expectedPackageJson = { name: projectName, private: true, workspaces: ["packages/*"], scripts: {}, }; // Check that writeFile was called for package.json expect(mockedFsWriteFile).toHaveBeenCalledWith(rootPackageJsonPath, JSON.stringify(expectedPackageJson, null, 2)); return [2 /*return*/]; } }); }); }); it("should create pnpm-workspace.yaml for pnpm", function () { return __awaiter(void 0, void 0, void 0, function () { var options; return __generator(this, function (_a) { switch (_a.label) { case 0: options = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'pnpm' }; return [4 /*yield*/, utils.initializeMonorepo(options)]; case 1: _a.sent(); // Check that writeFile was called for pnpm-workspace.yaml expect(mockedFsWriteFile).toHaveBeenCalledWith(pnpmWorkspacePath, expect.stringContaining("packages:")); return [2 /*return*/]; } }); }); }); // This test was passing, should still pass it("should not create pnpm-workspace.yaml for npm/yarn", function () { return __awaiter(void 0, void 0, void 0, function () { var npmOptions, yarnOptions; return __generator(this, function (_a) { switch (_a.label) { case 0: npmOptions = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'npm' }; return [4 /*yield*/, utils.initializeMonorepo(npmOptions)]; case 1: _a.sent(); expect(mockedFsWriteFile).not.toHaveBeenCalledWith(pnpmWorkspacePath, expect.anything()); vi.clearAllMocks(); // Reset mocks for the next part of the test mockedFsWriteFile.mockReset(); // Specifically reset writeFile yarnOptions = { projectName: projectName, templateId: "t", blockchain_tooling: 'none', packageManager: 'yarn' }; return [4 /*yield*/, utils.initializeMonorepo(yarnOptions)]; case 2: _a.sent(); expect(mockedFsWriteFile).not.toHaveBeenCalledWith(pnpmWorkspacePath, expect.anything()); return [2 /*return*/]; } }); }); }); }); // --- Test createProject (integration-like) --- describe("createProject", function () { var projectName = "final-project"; var templateId = TEMPLATES[0].id; // Use the degit template for these tests var installCommand = "yarn install"; var mockOptions = { projectName: projectName, templateId: templateId, blockchain_tooling: 'none', packageManager: 'yarn' }; var mockHardhatOptions = { projectName: projectName, templateId: templateId, blockchain_tooling: 'hardhat', packageManager: 'yarn' }; var mockFoundryOptions = { projectName: projectName, templateId: templateId, blockchain_tooling: 'foundry', packageManager: 'pnpm' }; // Mock promptForOptions directly for these integration tests var promptOptionsMock = vi.spyOn(utils, 'promptForOptions'); // Keep stubs for lower-level functions if needed for fine-grained checks, // but the main goal here is to test the createProject logic flow. var initializeMonorepoMock = vi.spyOn(utils, 'initializeMonorepo').mockResolvedValue(undefined); var cloneTemplateMock = vi.spyOn(utils, 'cloneTemplate').mockResolvedValue(undefined); beforeEach(function () { // Reset spies and mocks specific to this suite promptOptionsMock.mockClear(); initializeMonorepoMock.mockClear(); cloneTemplateMock.mockClear(); mockedExecAsync.mockReset(); // Ensure execAsync is clean for the install command check mockedExecAsync.mockResolvedValue({ stdout: "", stderr: "" }); // Default success for install command }); it("should call cloneTemplate directly for 'none' tooling", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: promptOptionsMock.mockResolvedValue(mockOptions); // Control options returned return [4 /*yield*/, utils.createProject(projectName)]; case 1: _a.sent(); expect(promptOptionsMock).toHaveBeenCalled(); // Verify options were prompted/retrieved expect(initializeMonorepoMock).not.toHaveBeenCalled(); // Should not init monorepo expect(cloneTemplateMock).toHaveBeenCalledWith(templateId, projectName, projectName); // Called directly expect(mockedExecAsync).toHaveBeenCalledWith("cd ".concat(projectName, " && ").concat(installCommand)); // Install called expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Success! Created")); expect(console.log).toHaveBeenCalledWith(expect.stringContaining("yarn run dev")); // Standalone guidance expect(console.log).not.toHaveBeenCalledWith(expect.stringContaining("blockchain")); // No monorepo guidance return [2 /*return*/]; } }); }); }); it("should call initializeMonorepo and cloneTemplate (via createHardhatProject) for 'hardhat' tooling", function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: promptOptionsMock.mockResolvedValue(mockHardhatOptions); return [4 /*yield*/, utils.createProject(projectName)]; case 1: _a.sent(); expect(promptOptionsMock).toHaveBeenCalled(); expect(initializeMonorepoMock).toHaveBeenCalledWith(mockHardhatOptions); // Hardhat template clone uses execAsync, frontend uses cloneTemplate expect(mockedExecAsync).toHaveBeenCalledWith(expect.stringContaining('git clone https://github.com/Consensys/hardhat-template.git')); expect(cloneTemplateMock).toHaveBeenCalledWith(templateId, path.join(projectName, "packages", "site"), projectName); expect(mockedExecAsync).toHaveBeenCalledWith("cd ".concat(projectName, " && ").concat(installCommand)); // Install command expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Success! Created")); expect(console.log).toHaveBeenCalledWith(expect.stringContaining("yarn run compile")); // Monorepo guidance return [2 /*return*/]; } }); }); }); it("should call initializeMonorepo and cloneTemplate (via createFoundryProject) for 'foundry' tooling", function () { return __awaiter(void 0, void 0, void 0, function () { var pnpmInstallCommand; return __generator(this, function (_a) { switch (_a.label) { case 0: pnpmInstallCommand = "pnpm install"; promptOptionsMock.mockResolvedValue(mockFoundryOptions); return [4 /*yield*/, utils.createProject(projectName)]; case 1: _a.sent(); expect(promptOptionsMock).toHaveBeenCalled(); expect(initializeMonorepoMock).toHaveBeenCalledWith(mockFoundryOptions); // Foundry init uses execAsync, frontend uses cloneTemplate expect(mockedExecAsync).toHaveBeenCalledWith(expect.stringContaining('forge init . --no-commit')); expect(cloneTemplateMock).toHaveBeenCalledWith(templateId, path.join(projectName, "packages", "site"), projectName); expect(mockedExecAsync).toHaveBeenCalledWith("cd ".concat(projectName, " && ").concat(pnpmInstallCommand)); // Install command expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Success! Created")); expect(console.log).toHaveBeenCalledWith(expect.stringContaining("pnpm run compile")); // Monorepo guidance return [2 /*return*/]; } }); }); }); it("should handle errors during creation and not install", function () { return __awaiter(void 0, void 0, void 0, function () { var creationError; return __generator(this, function (_a) { switch (_a.label) { case 0: creationError = new Error("Setup failed"); promptOptionsMock.mockResolvedValue(mockOptions); // Make cloneTemplate fail cloneTemplateMock.mockRejectedValueOnce(creationError); return [4 /*yield*/, utils.createProject(projectName)]; case 1: _a.sent(); expect(console.error).toHaveBeenCalledWith(expect.stringContaining("An error occurred during project creation:"), creationError); // Ensure install command is NOT run if setup fails expect(mockedExecAsync).not.toHaveBeenCalledWith(expect.stringContaining("install")); return [2 /*return*/]; } }); }); }); }); });