From 66f432e41fb702bbe8290a1b3a0e928958d12505 Mon Sep 17 00:00:00 2001 From: nshiab Date: Mon, 11 May 2026 15:58:06 -0400 Subject: [PATCH] Improved for nested folder structures --- src/helpers/updateProjectConfig.ts | 18 ++- test/updateProjectConfig.test.ts | 176 ++++++++++++++++++++++++++++- 2 files changed, 190 insertions(+), 4 deletions(-) diff --git a/src/helpers/updateProjectConfig.ts b/src/helpers/updateProjectConfig.ts index 904d05d..1e5a31f 100644 --- a/src/helpers/updateProjectConfig.ts +++ b/src/helpers/updateProjectConfig.ts @@ -9,12 +9,24 @@ export function updateProjectConfig(tasks: Record) { if (existsSync("deno.json")) { configFile = "deno.json"; key = "tasks"; - } else if (!existsSync("package.json") && runtime === "deno") { + } else if (existsSync("package.json")) { + configFile = "package.json"; + key = "scripts"; + } else if (runtime === "deno") { configFile = "deno.json"; key = "tasks"; writeFileSync(configFile, JSON.stringify({ [key]: {} }, null, 2)); - } else if (!existsSync("package.json")) { - return; + } else { + configFile = "package.json"; + key = "scripts"; + writeFileSync( + configFile, + JSON.stringify( + { name: "data-project", type: "module", [key]: {} }, + null, + 2, + ), + ); } try { diff --git a/test/updateProjectConfig.test.ts b/test/updateProjectConfig.test.ts index 2a42eee..893d6d7 100644 --- a/test/updateProjectConfig.test.ts +++ b/test/updateProjectConfig.test.ts @@ -1,7 +1,14 @@ import { assertEquals } from "@std/assert"; -import { readFileSync, writeFileSync } from "node:fs"; +import { stub } from "@std/testing/mock"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; import { updateProjectConfig } from "../src/helpers/updateProjectConfig.ts"; import { createTestDir } from "./helpers/utils.ts"; +import { runtimeConfig } from "../src/helpers/getRuntime.ts"; +import { + commandRunner, + installPackagesAndFetchDocs, +} from "../src/helpers/installPackagesAndFetchDocs.ts"; Deno.test("updateProjectConfig - should update deno.json if it exists", () => { const { tempDir, cleanup } = createTestDir(); @@ -44,3 +51,170 @@ Deno.test("updateProjectConfig - should update package.json if it exists and no cleanup(); } }); + +Deno.test("updateProjectConfig - should create config in nested folder if it doesn't exist", () => { + const { tempDir, cleanup } = createTestDir(); + const originalCwd = Deno.cwd(); + + // Clean up any existing stub + // @ts-ignore: Mocking for testing + if (runtimeConfig.getRuntime.restore) { + // @ts-ignore: Mocking for testing + runtimeConfig.getRuntime.restore(); + } + + try { + // 1. Run in an empty folder (tempDir) + Deno.chdir(tempDir); + // Use Deno by default for this test + const denoStub = stub(runtimeConfig, "getRuntime", () => "deno" as const); + + try { + updateProjectConfig({ "task1": "deno run main.ts" }); + assertEquals( + existsSync(join(tempDir, "deno.json")), + true, + "deno.json should be created in root", + ); + + // 2. Create a new folder in it. + const nestedDir = join(tempDir, "nested"); + mkdirSync(nestedDir); + + // 3. cd in it. + Deno.chdir(nestedDir); + + // 4. Call the script again. + updateProjectConfig({ "task2": "deno run main.ts" }); + + // Check if deno.json was created in nested folder + assertEquals( + existsSync(join(nestedDir, "deno.json")), + true, + "deno.json should be created in nested folder", + ); + } finally { + if (!denoStub.restored) denoStub.restore(); + } + } finally { + Deno.chdir(originalCwd); + cleanup(); + } +}); + +Deno.test("updateProjectConfig - Node runtime simulation in nested folder", async () => { + const { tempDir, cleanup } = createTestDir(); + const originalCwd = Deno.cwd(); + + // Check if runtimeConfig is already stubbed + // @ts-ignore: Mocking for testing + if (runtimeConfig.getRuntime.restore) { + // @ts-ignore: Mocking for testing + runtimeConfig.getRuntime.restore(); + } + + try { + Deno.chdir(tempDir); + // Mock getRuntime to return "node" + const runtimeStub = stub( + runtimeConfig, + "getRuntime", + () => "node" as const, + ); + + // Mock commandRunner.exec to simulate package installation + const execStub = stub( + commandRunner, + "exec", + ((_cmd: string, callback: unknown) => { + // Simulate npm behavior: if package.json exists in root/parent, it doesn't create one here. + // BUT wait, in our test we want to see if our fix works. + // Let's NOT create it automatically in exec anymore, to see if updateProjectConfig does it. + if (typeof callback === "function") { + // @ts-ignore: Mocking Node.js callback + callback(null, "", ""); + } + // @ts-ignore: Mocking ChildProcess return + return {}; + }) as unknown as typeof commandRunner.exec, + ); + + // Mock fetch for docs + const originalFetch = globalThis.fetch; + globalThis.fetch = ((() => + Promise.resolve({ + ok: true, + text: () => Promise.resolve("# Docs"), + })) as unknown) as typeof fetch; + + try { + // 1. Run in root (an empty folder) + // In reality, if it's an empty folder, npm install will look for a package.json upwards. + // If it finds one in a parent directory, it will install there and NOT create one in the current folder. + // This is likely the "bug" or behavior causing issues in nested folders. + + const pkg = "@nshiab/simple-data-analysis"; + + // Simulate running the script in root (tempDir) + await installPackagesAndFetchDocs([pkg], { silent: true }); + updateProjectConfig({ "sda": "node sda/main.ts" }); + + // In root, package.json should exist because it's the "start" + assertEquals( + existsSync(join(tempDir, "package.json")), + true, + "package.json should exist in root", + ); + + // 2. Create a new folder in it. + const nestedDir = join(tempDir, "nested"); + mkdirSync(nestedDir); + + // 3. cd in it. + Deno.chdir(nestedDir); + + // 4. Call the script again. + // We simulate a behavior where npm install DOES NOT create a package.json because it found one in the parent. + // In our mock, we need to reflect that. + + // Redefine execStub to simulate "finding parent package.json" behavior + execStub.restore(); + const execStubNested = stub( + commandRunner, + "exec", + ((_cmd: string, callback: unknown) => { + // Simulate npm behavior: if package.json exists in parent, it doesn't create one here. + // We don't create it here. + if (typeof callback === "function") { + // @ts-ignore: Mocking Node.js callback + callback(null, "", ""); + } + // @ts-ignore: Mocking ChildProcess return + return {}; + }) as unknown as typeof commandRunner.exec, + ); + + try { + await installPackagesAndFetchDocs([pkg], { silent: true }); + updateProjectConfig({ "sda": "node sda/main.ts" }); + + // This is where the failure should happen if the user's report is correct. + // Our current updateProjectConfig returns early if package.json doesn't exist. + assertEquals( + existsSync(join(nestedDir, "package.json")), + true, + "package.json should be created in nested folder even if npm didn't create it", + ); + } finally { + execStubNested.restore(); + } + } finally { + runtimeStub.restore(); + // execStub.restore(); // This is the duplicate one + globalThis.fetch = originalFetch; + } + } finally { + Deno.chdir(originalCwd); + cleanup(); + } +});