import { expect, test } from "bun:test"; import { withTestBackend } from "./test_backend.ts"; import { addWorkspace } from "../workspace.ts"; import * as path from "node:path"; import { writeFile, readdir, stat, rm } from "node:fs/promises"; import { getFolderSuffix, getMetadataFileName } from "../src/utils/resource_folders.ts"; // ============================================================================= // APP INLINE SCRIPT DELETION TESTS // Regression tests for: deleting inline script files within .app/ folders // during sync push should re-push the app, not crash with TypeError. // ============================================================================= async function fileExists(filePath: string): Promise { try { await stat(filePath); return true; } catch { return false; } } test("App: delete inline script file and push does not crash", async () => { await withTestBackend(async (backend, tempDir) => { const testWorkspace = { remote: backend.baseUrl, workspaceId: backend.workspace, name: "app_inline_delete_test", token: backend.token, }; await addWorkspace(testWorkspace, { force: true, configDir: backend.testConfigDir, }); await writeFile( `${tempDir}/wmill.yaml`, `defaultTs: bun\nincludes:\n - "**"\nexcludes: []`, "utf-8" ); // Create an app with an inline script via the API const appPath = "f/test/inline_delete_app"; const inlineContent = `export async function main() {\n return "hello";\n}`; // Create the folder first await backend.apiRequest!( `/api/w/${backend.workspace}/folders/create`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "test" }), } ).then((r) => r.text()); await backend.createAppWithInlineScript!(appPath, inlineContent, "bun"); // ========================================================================= // STEP 1: Pull — get the app folder with inline script files // ========================================================================= const pullResult1 = await backend.runCLICommand( ["sync", "pull", "--yes"], tempDir, "app_inline_delete_test" ); expect(pullResult1.code).toEqual(0); // Find the app folder and its inline script files const appSuffix = getFolderSuffix("app"); const appDir = path.join(tempDir, appPath + appSuffix); expect(await fileExists(appDir)).toBeTruthy(); // List files in the app folder to find inline script files const appFiles = await readdir(appDir); const inlineScriptFiles = appFiles.filter( (f) => f.endsWith(".ts") || f.endsWith(".js") ); expect(inlineScriptFiles.length).toBeGreaterThan(0); const inlineScriptPath = path.join(appDir, inlineScriptFiles[0]); expect(await fileExists(inlineScriptPath)).toBeTruthy(); const metadataFile = getMetadataFileName("app", "yaml"); const appYamlPath = path.join(appDir, metadataFile); // ========================================================================= // STEP 2: Remove the inline script from app.yaml and delete the .ts file // ========================================================================= // Replace the inline script with a static text component (no inline scripts) const updatedAppYaml = `summary: Test app with inline script value: type: app grid: - id: text1 data: type: textcomponent componentInput: type: static value: hello world hiddenInlineScripts: [] css: {} norefreshbar: false policy: on_behalf_of: null on_behalf_of_email: null triggerables: {} execution_mode: viewer `; await writeFile(appYamlPath, updatedAppYaml, "utf-8"); // Delete the inline script file await rm(inlineScriptPath); expect(await fileExists(inlineScriptPath)).toBeFalsy(); // Also delete any lock files for the inline script for (const f of appFiles) { if (f.endsWith(".lock")) { await rm(path.join(appDir, f)); } } // ========================================================================= // STEP 3: Push — should succeed, NOT crash with TypeError // ========================================================================= const pushResult = await backend.runCLICommand( ["sync", "push", "--yes"], tempDir, "app_inline_delete_test" ); // The critical assertion: push should not crash expect(pushResult.code).toEqual(0); // ========================================================================= // STEP 4: Verify by pulling again — inline script should be gone // ========================================================================= await rm(appDir, { recursive: true }); const pullResult2 = await backend.runCLICommand( ["sync", "pull", "--yes"], tempDir, "app_inline_delete_test" ); expect(pullResult2.code).toEqual(0); // App should still exist expect(await fileExists(appDir)).toBeTruthy(); // But no inline script files should be present const finalFiles = await readdir(appDir); const finalInlineScripts = finalFiles.filter( (f) => (f.endsWith(".ts") || f.endsWith(".js")) && f.includes("inline_script") ); expect(finalInlineScripts.length).toEqual(0); }); });