diff --git a/__tests__/cache.test.ts b/__tests__/cache.test.ts index a3c1e3da..c2b46aa1 100644 --- a/__tests__/cache.test.ts +++ b/__tests__/cache.test.ts @@ -52,7 +52,6 @@ describe('cache', () => { // Mock utils (utils.getCacheDirectory as jest.Mock).mockReturnValue(mockCacheDir); - (utils.copyDirRecursive as jest.Mock).mockResolvedValue(undefined); }); describe('restoreCache', () => { diff --git a/__tests__/utils-basic.test.ts b/__tests__/utils-basic.test.ts index 31c2c3f7..c30e3152 100644 --- a/__tests__/utils-basic.test.ts +++ b/__tests__/utils-basic.test.ts @@ -19,7 +19,6 @@ jest.mock('path'); function setupPathAndOSMocks(): void { jest.resetAllMocks(); (path.join as jest.Mock).mockImplementation((...parts) => parts.join('/')); - (path.dirname as jest.Mock).mockImplementation(p => p.substring(0, p.lastIndexOf('/'))); (os.tmpdir as jest.Mock).mockReturnValue('/tmp'); } @@ -36,41 +35,6 @@ describe('utils - Basic Functions', () => { }); }); - describe('getExecutableDirectoryPath', () => { - test('should return directory path when input is a file', () => { - const result = utils.getExecutableDirectoryPath('/usr/bin/task'); - expect(result).toBe('/usr/bin'); - expect(path.dirname).toHaveBeenCalledWith('/usr/bin/task'); - }); - }); - - describe('parseMultilineInput', () => { - test('should handle empty input', () => { - const result = utils.parseMultilineInput(''); - expect(result).toEqual([]); - }); - - test('should handle single line input', () => { - const result = utils.parseMultilineInput('line1'); - expect(result).toEqual(['line1']); - }); - - test('should handle multiline input', () => { - const result = utils.parseMultilineInput('line1\nline2\nline3'); - expect(result).toEqual(['line1', 'line2', 'line3']); - }); - - test('should trim whitespace', () => { - const result = utils.parseMultilineInput(' line1 \n line2 '); - expect(result).toEqual(['line1', 'line2']); - }); - - test('should skip empty lines', () => { - const result = utils.parseMultilineInput('line1\n\nline2'); - expect(result).toEqual(['line1', 'line2']); - }); - }); - describe('logAndFail', () => { test('should throw error with message', () => { expect(() => utils.logAndFail('test error')).toThrow('test error'); diff --git a/__tests__/utils-file.test.ts b/__tests__/utils-file.test.ts deleted file mode 100644 index 8598973e..00000000 --- a/__tests__/utils-file.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @license - * SPDX-License-Identifier: MIT - * - * Copyright (c) 2025-2026 Ryan Johnson - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import * as utils from '../src/utils'; - -jest.mock('fs'); -jest.mock('path'); - -function setupFileMocks(): void { - jest.resetAllMocks(); - - // Mock path operations. - (path.join as jest.Mock).mockImplementation((...parts) => parts.join('/')); - (path.dirname as jest.Mock).mockImplementation(p => p.substring(0, p.lastIndexOf('/'))); -} - -describe('utils - File Operations', () => { - beforeEach(() => { - setupFileMocks(); - }); - - describe('copyDirRecursive', () => { - test('should throw if source does not exist', async () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - - await expect(utils.copyDirRecursive('/nonexistent', '/dest')).rejects.toThrow( - 'Source directory does not exist: /nonexistent' - ); - }); - - test('should handle file source', async () => { - // Setup mocks - (fs.existsSync as jest.Mock).mockImplementation(path => { - if (path === '/src/file.txt') return true; - if (path === '/dest') return true; - return false; - }); - - (fs.statSync as jest.Mock).mockReturnValue({ - isDirectory: () => false, - isFile: () => true, - mode: 0o755 - }); - - // Execute - await utils.copyDirRecursive('/src/file.txt', '/dest/file.txt'); - - // Verify - expect(fs.copyFileSync).toHaveBeenCalledWith('/src/file.txt', '/dest/file.txt'); - expect(fs.chmodSync).toHaveBeenCalledWith('/dest/file.txt', 0o755); - }); - - test('should create destination directory if it does not exist', async () => { - // Setup mocks - (fs.existsSync as jest.Mock).mockImplementation(path => (path === '/src' ? true : false)); - (fs.statSync as jest.Mock).mockReturnValue({ - isDirectory: () => true, - isFile: () => false, - mode: 0o755 - }); - (fs.readdirSync as jest.Mock).mockReturnValue([]); - - await utils.copyDirRecursive('/src', '/dest'); - - expect(fs.mkdirSync).toHaveBeenCalledWith('/dest', { recursive: true }); - }); - - test('should recursively copy directories and files', async () => { - jest.clearAllMocks(); - - (fs.existsSync as jest.Mock).mockReturnValue(true); - (fs.statSync as jest.Mock).mockImplementation(() => ({ - isDirectory: jest.fn().mockReturnValue(true), - isFile: jest.fn().mockReturnValue(false), - mode: 0o755 - })); - (fs.readdirSync as jest.Mock).mockImplementation(() => []); - - await utils.copyDirRecursive('/src', '/dest'); - - expect(fs.existsSync).toHaveBeenCalled(); - expect(fs.statSync).toHaveBeenCalled(); - expect(fs.readdirSync).toHaveBeenCalled(); - }); - - test('should preserve file permissions', async () => { - jest.clearAllMocks(); - - (fs.existsSync as jest.Mock).mockReturnValue(true); - (fs.statSync as jest.Mock).mockImplementation(() => ({ - isDirectory: jest.fn().mockReturnValue(false), - isFile: jest.fn().mockReturnValue(true), - mode: 0o644 - })); - (fs.readdirSync as jest.Mock).mockReturnValue([]); - - expect(fs.existsSync).not.toThrow(); - expect(fs.statSync).not.toThrow(); - }); - }); -}); diff --git a/dist/index.js b/dist/index.js index 69e9d9ba..de7bee9f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -48318,7 +48318,6 @@ const RELEASES_URL = 'https://github.com/go-task/task/releases/download'; - /** * Get the cache directory for Task * @returns Path to cache directory @@ -48326,72 +48325,6 @@ const RELEASES_URL = 'https://github.com/go-task/task/releases/download'; function getCacheDirectory() { return external_path_namespaceObject.join(external_os_.tmpdir(), CACHE_DIR); } -/** - * Get the directory path containing the Task executable. - * @param taskPath Path to Task installation - * @returns Directory containing the Task executable - */ -function getExecutableDirectoryPath(taskPath) { - return path.dirname(taskPath); -} -/** - * Copy a directory recursively with improved handling of deep directories. - * @param src Source directory - * @param dest Destination directory - * @throws Error if source directory does not exist - */ -async function copyDirRecursive(src, dest) { - // Validate source exists. - if (!fs.existsSync(src)) { - throw new Error(`Source directory does not exist: ${src}`); - } - const srcStats = fs.statSync(src); - if (srcStats.isFile()) { - // Create destination directory, if required. - const destDir = path.dirname(dest); - if (!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - // For single file copy, use the file name if the destination is a directory. - const fileName = path.basename(src); - const destPath = fs.existsSync(dest) && fs.statSync(dest).isDirectory() ? path.join(dest, fileName) : dest; - // Copy with original permissions. - fs.copyFileSync(src, destPath); - fs.chmodSync(destPath, srcStats.mode); - return; - } - // Create destination directory, if required. - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest, { recursive: true }); - } - // Use a queue-based approach to avoid stack overflow with deep directory structures. - const queue = [{ src, dest }]; - // Process entries in breadth-first order. - while (queue.length > 0) { - const { src: currentSrc, dest: currentDest } = queue.shift(); - // Read directory entries. - const entries = fs.readdirSync(currentSrc, { withFileTypes: true }); - for (const entry of entries) { - const srcPath = path.join(currentSrc, entry.name); - const destPath = path.join(currentDest, entry.name); - if (entry.isDirectory()) { - // Create the destination directory. - if (!fs.existsSync(destPath)) { - fs.mkdirSync(destPath, { recursive: true }); - } - // Add to queue instead of recursive call. - queue.push({ src: srcPath, dest: destPath }); - } - else { - // Copy file directly and preserve permissions. - fs.copyFileSync(srcPath, destPath); - // Copy permissions from source. - const stats = fs.statSync(srcPath); - fs.chmodSync(destPath, stats.mode); - } - } - } -} /** * Extracts version from tag name by removing 'v' prefix if present * @param tagName The tag name from GitHub release @@ -48427,20 +48360,6 @@ async function fetchLatestRelease(githubToken) { throw new Error(`Failed to fetch release information from ${RELEASES_API_URL}: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } } -/** - * Parses a multiline input string into an array of strings - * @param input The multiline input string - * @returns Array of trimmed non-empty lines - */ -function parseMultilineInput(input) { - if (!input) { - return []; - } - return input - .split('\n') - .map((s) => s.trim()) - .filter((s) => s !== ''); -} /** * Validates and logs errors for requirements * @param message Error message to display diff --git a/dist/utils.js b/dist/utils.js index 3e6dee1f..cee96652 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -9,7 +9,6 @@ import * as core from '@actions/core'; import * as os from 'os'; import * as path from 'path'; -import * as fs from 'fs'; import { RELEASES_API_URL, CACHE_DIR } from './constants'; /** * Get the cache directory for Task @@ -18,72 +17,6 @@ import { RELEASES_API_URL, CACHE_DIR } from './constants'; export function getCacheDirectory() { return path.join(os.tmpdir(), CACHE_DIR); } -/** - * Get the directory path containing the Task executable. - * @param taskPath Path to Task installation - * @returns Directory containing the Task executable - */ -export function getExecutableDirectoryPath(taskPath) { - return path.dirname(taskPath); -} -/** - * Copy a directory recursively with improved handling of deep directories. - * @param src Source directory - * @param dest Destination directory - * @throws Error if source directory does not exist - */ -export async function copyDirRecursive(src, dest) { - // Validate source exists. - if (!fs.existsSync(src)) { - throw new Error(`Source directory does not exist: ${src}`); - } - const srcStats = fs.statSync(src); - if (srcStats.isFile()) { - // Create destination directory, if required. - const destDir = path.dirname(dest); - if (!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - // For single file copy, use the file name if the destination is a directory. - const fileName = path.basename(src); - const destPath = fs.existsSync(dest) && fs.statSync(dest).isDirectory() ? path.join(dest, fileName) : dest; - // Copy with original permissions. - fs.copyFileSync(src, destPath); - fs.chmodSync(destPath, srcStats.mode); - return; - } - // Create destination directory, if required. - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest, { recursive: true }); - } - // Use a queue-based approach to avoid stack overflow with deep directory structures. - const queue = [{ src, dest }]; - // Process entries in breadth-first order. - while (queue.length > 0) { - const { src: currentSrc, dest: currentDest } = queue.shift(); - // Read directory entries. - const entries = fs.readdirSync(currentSrc, { withFileTypes: true }); - for (const entry of entries) { - const srcPath = path.join(currentSrc, entry.name); - const destPath = path.join(currentDest, entry.name); - if (entry.isDirectory()) { - // Create the destination directory. - if (!fs.existsSync(destPath)) { - fs.mkdirSync(destPath, { recursive: true }); - } - // Add to queue instead of recursive call. - queue.push({ src: srcPath, dest: destPath }); - } - else { - // Copy file directly and preserve permissions. - fs.copyFileSync(srcPath, destPath); - // Copy permissions from source. - const stats = fs.statSync(srcPath); - fs.chmodSync(destPath, stats.mode); - } - } - } -} /** * Extracts version from tag name by removing 'v' prefix if present * @param tagName The tag name from GitHub release @@ -119,20 +52,6 @@ export async function fetchLatestRelease(githubToken) { throw new Error(`Failed to fetch release information from ${RELEASES_API_URL}: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); } } -/** - * Parses a multiline input string into an array of strings - * @param input The multiline input string - * @returns Array of trimmed non-empty lines - */ -export function parseMultilineInput(input) { - if (!input) { - return []; - } - return input - .split('\n') - .map((s) => s.trim()) - .filter((s) => s !== ''); -} /** * Validates and logs errors for requirements * @param message Error message to display diff --git a/src/utils.ts b/src/utils.ts index b4496fe3..91354742 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,7 +10,6 @@ import * as core from '@actions/core'; import * as os from 'os'; import * as path from 'path'; -import * as fs from 'fs'; import { RELEASES_API_URL, CACHE_DIR } from './constants'; /** @@ -21,84 +20,6 @@ export function getCacheDirectory(): string { return path.join(os.tmpdir(), CACHE_DIR); } -/** - * Get the directory path containing the Task executable. - * @param taskPath Path to Task installation - * @returns Directory containing the Task executable - */ -export function getExecutableDirectoryPath(taskPath: string): string { - return path.dirname(taskPath); -} - -/** - * Copy a directory recursively with improved handling of deep directories. - * @param src Source directory - * @param dest Destination directory - * @throws Error if source directory does not exist - */ -export async function copyDirRecursive(src: string, dest: string): Promise { - // Validate source exists. - if (!fs.existsSync(src)) { - throw new Error(`Source directory does not exist: ${src}`); - } - - const srcStats = fs.statSync(src); - if (srcStats.isFile()) { - // Create destination directory, if required. - const destDir = path.dirname(dest); - if (!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - - // For single file copy, use the file name if the destination is a directory. - const fileName = path.basename(src); - const destPath = - fs.existsSync(dest) && fs.statSync(dest).isDirectory() ? path.join(dest, fileName) : dest; - - // Copy with original permissions. - fs.copyFileSync(src, destPath); - fs.chmodSync(destPath, srcStats.mode); - return; - } - - // Create destination directory, if required. - if (!fs.existsSync(dest)) { - fs.mkdirSync(dest, { recursive: true }); - } - - // Use a queue-based approach to avoid stack overflow with deep directory structures. - const queue: Array<{ src: string; dest: string }> = [{ src, dest }]; - - // Process entries in breadth-first order. - while (queue.length > 0) { - const { src: currentSrc, dest: currentDest } = queue.shift()!; - - // Read directory entries. - const entries = fs.readdirSync(currentSrc, { withFileTypes: true }); - - for (const entry of entries) { - const srcPath = path.join(currentSrc, entry.name); - const destPath = path.join(currentDest, entry.name); - - if (entry.isDirectory()) { - // Create the destination directory. - if (!fs.existsSync(destPath)) { - fs.mkdirSync(destPath, { recursive: true }); - } - // Add to queue instead of recursive call. - queue.push({ src: srcPath, dest: destPath }); - } else { - // Copy file directly and preserve permissions. - fs.copyFileSync(srcPath, destPath); - - // Copy permissions from source. - const stats = fs.statSync(srcPath); - fs.chmodSync(destPath, stats.mode); - } - } - } -} - /** * Extracts version from tag name by removing 'v' prefix if present * @param tagName The tag name from GitHub release @@ -144,22 +65,6 @@ export async function fetchLatestRelease(githubToken?: string): Promise } } -/** - * Parses a multiline input string into an array of strings - * @param input The multiline input string - * @returns Array of trimmed non-empty lines - */ -export function parseMultilineInput(input: string): string[] { - if (!input) { - return []; - } - - return input - .split('\n') - .map((s: string) => s.trim()) - .filter((s: string) => s !== ''); -} - /** * Validates and logs errors for requirements * @param message Error message to display diff --git a/taskfile.yml b/taskfile.yml index eb898112..0e6331b0 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -60,7 +60,7 @@ tasks: JEST_OPTS: --runInBand --detectOpenHandles --forceExit cmds: - echo "==> Running unit tests..." - - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/cache.test.ts __tests__/installer.test.ts __tests__/utils-basic.test.ts __tests__/utils-file.test.ts + - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/cache.test.ts __tests__/installer.test.ts __tests__/utils-basic.test.ts test:components: desc: Run component tests. @@ -73,8 +73,6 @@ tasks: - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/installer.test.ts - echo "==> Running basic utility tests..." - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/utils-basic.test.ts - - echo "==> Running file operation tests..." - - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/utils-file.test.ts - echo "==> Running network operation tests..." - node --expose-gc --max-old-space-size={{.NODE_MEM}} node_modules/.bin/jest {{.JEST_OPTS}} __tests__/utils-network.test.ts