From 5a0c01a07c2cd5a892f5db3040b8dca364139385 Mon Sep 17 00:00:00 2001 From: outslept Date: Sat, 12 Jul 2025 17:45:51 +0300 Subject: [PATCH 1/4] chore: remove ESLint suppressions for functions-utils Remove temporary TypeScript ESLint rule suppressions for: - packages/functions-utils/src/main.ts - packages/functions-utils/tests/main.test.ts --- eslint_temporary_suppressions.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/eslint_temporary_suppressions.js b/eslint_temporary_suppressions.js index dbac8d090d..15ff2f0399 100644 --- a/eslint_temporary_suppressions.js +++ b/eslint_temporary_suppressions.js @@ -2228,26 +2228,6 @@ export default [ '@typescript-eslint/no-unsafe-return': 'off', }, }, - { - files: ['packages/functions-utils/src/main.ts'], - rules: { - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unsafe-member-access': 'off', - '@typescript-eslint/no-unsafe-return': 'off', - '@typescript-eslint/no-unsafe-call': 'off', - '@typescript-eslint/no-unsafe-argument': 'off', - }, - }, - { - files: ['packages/functions-utils/tests/main.test.ts'], - rules: { - '@typescript-eslint/no-unsafe-argument': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-unsafe-assignment': 'off', - '@typescript-eslint/restrict-template-expressions': 'off', - }, - }, { files: ['packages/git-utils/src/commits.ts'], rules: { From dbca3d993a86782455160c969fb09bc3bce0bd3e Mon Sep 17 00:00:00 2001 From: outslept Date: Sat, 12 Jul 2025 17:48:35 +0300 Subject: [PATCH 2/4] refactor: improve TypeScript types and replace path-exists Replace the external `path-exists` dependency with native Node.js fs/promises access() function to eliminate external dependency. Add FailOptions interface to eliminate all unsafe type assignments. Updated import statements to use specific named imports instead of namespace imports. Update error patterns to use return statements after fail() calls. --- packages/functions-utils/src/main.ts | 44 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/functions-utils/src/main.ts b/packages/functions-utils/src/main.ts index 7ce0eb52bd..a2e4b9ec8a 100644 --- a/packages/functions-utils/src/main.ts +++ b/packages/functions-utils/src/main.ts @@ -1,23 +1,35 @@ -import { promises as fs } from 'fs' import { basename, dirname, join } from 'path' +import { access, stat } from 'fs/promises' import { listFunctions, listFunctionsFiles } from '@netlify/zip-it-and-ship-it' import cpy from 'cpy' -import { pathExists } from 'path-exists' + +interface FailOptions { + fail?: (message: string, options?: { error?: unknown }) => void +} // Add a Netlify Function file to the `functions` directory, so it is processed // by `@netlify/plugin-functions-core` -export const add = async function (src?: string, dist?: string, { fail = defaultFail } = {}): Promise { +export const add = async function ( + src?: string, + dist?: string, + { fail = defaultFail }: FailOptions = {}, +): Promise { if (src === undefined) { - return fail('No function source directory was specified') + fail('No function source directory was specified') + return } - if (!(await pathExists(src))) { - return fail(`No function file or directory found at "${src}"`) + try { + await access(src) + } catch { + fail(`No function file or directory found at "${src}"`) + return } if (dist === undefined) { - return fail('No function directory was specified') + fail('No function directory was specified') + return } const srcBasename = basename(src) @@ -26,7 +38,7 @@ export const add = async function (src?: string, dist?: string, { fail = default } const getSrcAndDest = async function (src: string, srcBasename: string, dist: string): Promise<[string, string]> { - const srcStat = await fs.stat(src) + const srcStat = await stat(src) if (srcStat.isDirectory()) { return [`${srcBasename}/**`, join(dist, srcBasename)] @@ -35,9 +47,10 @@ const getSrcAndDest = async function (src: string, srcBasename: string, dist: st return [srcBasename, dist] } -export const list = async function (functionsSrc, { fail = defaultFail } = {} as any) { - if (functionsSrc === undefined || functionsSrc.length === 0) { - return fail('No function directory was specified') +export const list = async function (functionsSrc: string | string[], { fail = defaultFail }: FailOptions = {}) { + if (Array.isArray(functionsSrc) ? functionsSrc.length === 0 : !functionsSrc) { + fail('No function directory was specified') + return } try { @@ -47,9 +60,10 @@ export const list = async function (functionsSrc, { fail = defaultFail } = {} as } } -export const listAll = async function (functionsSrc, { fail = defaultFail } = {} as any) { - if (functionsSrc === undefined || functionsSrc.length === 0) { - return fail('No function directory was specified') +export const listAll = async function (functionsSrc: string | string[], { fail = defaultFail }: FailOptions = {}) { + if (Array.isArray(functionsSrc) ? functionsSrc.length === 0 : !functionsSrc) { + fail('No function directory was specified') + return } try { @@ -59,6 +73,6 @@ export const listAll = async function (functionsSrc, { fail = defaultFail } = {} } } -const defaultFail = function (message) { +const defaultFail = function (message: string): never { throw new Error(message) } From c7181ca7fe866ae15c4777b9dc810f65e9210401 Mon Sep 17 00:00:00 2001 From: outslept Date: Sat, 12 Jul 2025 17:51:04 +0300 Subject: [PATCH 3/4] refactor: improve test TypeScript types and remove path-exists Replace path-exists dependency in tests with native fs/promises access(). Add TypeScript interface for the normalizeFiles function to ensure type safety for params. Replace unsafe type assertions with TypeScript error suppression comment. Add null checks and type guards for list/listAll function results. --- packages/functions-utils/tests/main.test.ts | 38 +++++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/functions-utils/tests/main.test.ts b/packages/functions-utils/tests/main.test.ts index 692b0b0ae9..c92719b249 100644 --- a/packages/functions-utils/tests/main.test.ts +++ b/packages/functions-utils/tests/main.test.ts @@ -1,9 +1,8 @@ -import { readFile, rm } from 'fs/promises' +import { readFile, rm, access } from 'fs/promises' import { normalize, resolve } from 'path' import { fileURLToPath } from 'url' import cpy from 'cpy' -import { pathExists } from 'path-exists' import sortOn from 'sort-on' import { expect, test, vi } from 'vitest' @@ -13,6 +12,15 @@ import { getDist, createDist } from './helpers/main.js' const FIXTURES_DIR = fileURLToPath(new URL('fixtures', import.meta.url)) +const pathExists = async (path: string): Promise => { + try { + await access(path) + return true + } catch { + return false + } +} + test('Should copy a source file to a dist directory', async () => { const dist = await getDist() try { @@ -45,7 +53,8 @@ test('Should throw when source is undefined', async () => { test('Should throw when source is empty array', async () => { const dist = await getDist() try { - await expect(() => add([] as any, dist)).rejects.toThrow() + // @ts-expect-error testing invalid input + await expect(() => add([], dist)).rejects.toThrow() } finally { await rm(dist, { force: true, recursive: true }) } @@ -101,13 +110,26 @@ test('Should overwrite dist file if it already exists', async () => { }) test('Should allow "fail" option to customize failures', async () => { - const fail = vi.fn() as any + const fail = vi.fn() await add(undefined, undefined, { fail }) expect(fail).toHaveBeenCalledOnce() expect(fail).toHaveBeenCalledWith('No function source directory was specified') }) -const normalizeFiles = function (fixtureDir, { name, mainFile, runtime, extension, srcDir, srcFile, schedule }) { +interface FileData { + name: string + mainFile: string + runtime: string + extension: string + srcDir?: string + srcFile?: string + schedule?: string +} + +const normalizeFiles = function ( + fixtureDir: string, + { name, mainFile, runtime, extension, srcDir, srcFile, schedule }: FileData, +) { const mainFileA = normalize(`${fixtureDir}/${mainFile}`) const srcFileA = srcFile === undefined ? {} : { srcFile: normalize(`${fixtureDir}/${srcFile}`) } const srcDirA = srcDir ? { srcDir: resolve(fixtureDir, srcDir) } : {} @@ -117,7 +139,8 @@ const normalizeFiles = function (fixtureDir, { name, mainFile, runtime, extensio test('Can list function main files with list()', async () => { const fixtureDir = `${FIXTURES_DIR}/list` const functions = await list(fixtureDir) - expect(sortOn(functions, ['mainFile', 'extension'])).toEqual( + expect(functions).toBeDefined() + expect(sortOn(functions!, ['mainFile', 'extension'])).toEqual( [ { name: 'four', mainFile: 'four.js/four.js.js', runtime: 'js', extension: '.js', srcDir: 'four.js' }, { @@ -138,7 +161,8 @@ test('Can list function main files with list()', async () => { test('Can list all function files with listAll()', async () => { const fixtureDir = `${FIXTURES_DIR}/list` const functions = await listAll(fixtureDir) - expect(sortOn(functions, ['mainFile', 'extension'])).toEqual( + expect(functions).toBeDefined() + expect(sortOn(functions!, ['mainFile', 'extension'])).toEqual( [ { name: 'four', From 8138bce3eaada7244e9e6fe45e28751d6fddc273 Mon Sep 17 00:00:00 2001 From: outslept Date: Sat, 12 Jul 2025 17:51:41 +0300 Subject: [PATCH 4/4] fix: remove path-exists dependency from functions-utils --- package-lock.json | 3 +-- packages/functions-utils/package.json | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5ddbda5a9a..7427817204 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24822,8 +24822,7 @@ "license": "MIT", "dependencies": { "@netlify/zip-it-and-ship-it": "13.2.0", - "cpy": "^11.0.0", - "path-exists": "^5.0.0" + "cpy": "^11.0.0" }, "devDependencies": { "@types/node": "^18.19.111", diff --git a/packages/functions-utils/package.json b/packages/functions-utils/package.json index 92606baf36..cbab4c1988 100644 --- a/packages/functions-utils/package.json +++ b/packages/functions-utils/package.json @@ -51,8 +51,7 @@ "license": "MIT", "dependencies": { "@netlify/zip-it-and-ship-it": "13.2.0", - "cpy": "^11.0.0", - "path-exists": "^5.0.0" + "cpy": "^11.0.0" }, "devDependencies": { "@types/node": "^18.19.111",