From 7ee8dd4ac69cb608be32050383bffadbd8c13358 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:43:44 +0100 Subject: [PATCH] refactor(functions-utils): replace dependencies with native APIs This replaces the following in `functions-utils`: - `cpy` with `fs.cp`, still filtering out `junk` - `path-exists` with `fs.access` and, in tests, `existsSync` - `sort-on` with a basic sort function - `tmp-promise` with `fs.mkdtemp` and `randomUUID` --- package-lock.json | 19 +----------- packages/functions-utils/package.json | 5 +--- packages/functions-utils/src/main.ts | 27 +++++++---------- .../functions-utils/tests/helpers/main.ts | 13 +++++---- packages/functions-utils/tests/main.test.ts | 29 +++++++++++-------- 5 files changed, 37 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0fb618770..e7b9acd067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20100,20 +20100,6 @@ "node": ">= 14" } }, - "node_modules/sort-on": { - "version": "6.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-prop": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/source-map": { "version": "0.6.1", "devOptional": true, @@ -23275,13 +23261,10 @@ "license": "MIT", "dependencies": { "@netlify/zip-it-and-ship-it": "15.3.0", - "cpy": "^11.0.0", - "path-exists": "^5.0.0" + "junk": "^4.0.0" }, "devDependencies": { "@types/node": "^22.12.0", - "sort-on": "^6.0.0", - "tmp-promise": "^3.0.0", "typescript": "^5.0.0", "vitest": "^3.0.0" }, diff --git a/packages/functions-utils/package.json b/packages/functions-utils/package.json index ae15b6ef56..4770911454 100644 --- a/packages/functions-utils/package.json +++ b/packages/functions-utils/package.json @@ -51,13 +51,10 @@ "license": "MIT", "dependencies": { "@netlify/zip-it-and-ship-it": "15.3.0", - "cpy": "^11.0.0", - "path-exists": "^5.0.0" + "junk": "^4.0.0" }, "devDependencies": { "@types/node": "^22.12.0", - "sort-on": "^6.0.0", - "tmp-promise": "^3.0.0", "typescript": "^5.0.0", "vitest": "^3.0.0" }, diff --git a/packages/functions-utils/src/main.ts b/packages/functions-utils/src/main.ts index 7ce0eb52bd..0346f0b86e 100644 --- a/packages/functions-utils/src/main.ts +++ b/packages/functions-utils/src/main.ts @@ -1,9 +1,8 @@ import { promises as fs } from 'fs' -import { basename, dirname, join } from 'path' +import { basename, join } from 'path' import { listFunctions, listFunctionsFiles } from '@netlify/zip-it-and-ship-it' -import cpy from 'cpy' -import { pathExists } from 'path-exists' +import { isNotJunk } from 'junk' // Add a Netlify Function file to the `functions` directory, so it is processed // by `@netlify/plugin-functions-core` @@ -12,7 +11,9 @@ export const add = async function (src?: string, dist?: string, { fail = default return fail('No function source directory was specified') } - if (!(await pathExists(src))) { + try { + await fs.access(src) + } catch { return fail(`No function file or directory found at "${src}"`) } @@ -20,19 +21,11 @@ export const add = async function (src?: string, dist?: string, { fail = default return fail('No function directory was specified') } - const srcBasename = basename(src) - const [srcGlob, dest] = await getSrcAndDest(src, srcBasename, dist) - await cpy(srcGlob, dest, { cwd: dirname(src), overwrite: true }) -} - -const getSrcAndDest = async function (src: string, srcBasename: string, dist: string): Promise<[string, string]> { - const srcStat = await fs.stat(src) - - if (srcStat.isDirectory()) { - return [`${srcBasename}/**`, join(dist, srcBasename)] - } - - return [srcBasename, dist] + await fs.cp(src, join(dist, basename(src)), { + recursive: true, + force: true, + filter: (source) => isNotJunk(basename(source)), + }) } export const list = async function (functionsSrc, { fail = defaultFail } = {} as any) { diff --git a/packages/functions-utils/tests/helpers/main.ts b/packages/functions-utils/tests/helpers/main.ts index dfc9c2da3d..7b0d1a9430 100644 --- a/packages/functions-utils/tests/helpers/main.ts +++ b/packages/functions-utils/tests/helpers/main.ts @@ -1,13 +1,16 @@ -import { tmpName, dir as tmpDir } from 'tmp-promise' +import { randomUUID } from 'crypto' +import { mkdtemp } from 'fs/promises' +import { tmpdir } from 'os' +import { join } from 'path' + const PREFIX = 'test-functions-utils-' // Retrieve name of a temporary directory -export const getDist = function () { - return tmpName({ prefix: PREFIX }) +export const getDist = async function () { + return join(tmpdir(), `${PREFIX}${randomUUID()}`) } // Create temporary directory export const createDist = async function () { - const { path } = await tmpDir({ prefix: PREFIX }) - return path + return await mkdtemp(join(tmpdir(), PREFIX)) } diff --git a/packages/functions-utils/tests/main.test.ts b/packages/functions-utils/tests/main.test.ts index bb0b94ed13..3cf56f5e60 100644 --- a/packages/functions-utils/tests/main.test.ts +++ b/packages/functions-utils/tests/main.test.ts @@ -1,10 +1,8 @@ -import { readFile, rm } from 'fs/promises' +import { existsSync } from 'fs' +import { copyFile, readFile, rm } 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' import { add, list, listAll } from '../src/main.js' @@ -17,7 +15,7 @@ test('Should copy a source file to a dist directory', async () => { const dist = await getDist() try { await add(`${FIXTURES_DIR}/file/test.mjs`, dist) - expect(await pathExists(`${dist}/test.mjs`)).toBe(true) + expect(existsSync(`${dist}/test.mjs`)).toBe(true) } finally { await rm(dist, { force: true, recursive: true }) } @@ -27,7 +25,7 @@ test('Should copy a source directory to a dist directory', async () => { const dist = await getDist() try { await add(`${FIXTURES_DIR}/directory/test`, dist) - expect(await pathExists(`${dist}/test/index.js`)).toBe(true) + expect(existsSync(`${dist}/test/index.js`)).toBe(true) } finally { await rm(dist, { force: true, recursive: true }) } @@ -68,7 +66,7 @@ test('Should copy a source file even if dist directory already exists', async () const dist = await createDist() try { await add(`${FIXTURES_DIR}/file/test.mjs`, dist) - expect(await pathExists(`${dist}/test.mjs`)).toBe(true) + expect(existsSync(`${dist}/test.mjs`)).toBe(true) } finally { await rm(dist, { force: true, recursive: true }) } @@ -79,14 +77,14 @@ test('Should overwrite dist file if it already exists', async () => { const fixtureDir = `${FIXTURES_DIR}/file` const testModule = `${dist}/test.mjs` - await cpy(`${fixtureDir}/test.mjs`, fixtureDir, { rename: 'test.mjs.backup' }) + await copyFile(`${fixtureDir}/test.mjs`, `${fixtureDir}/test.mjs.backup`) try { await add(`${fixtureDir}/test.mjs`, dist) const file1 = await readFile(testModule, 'utf8') - await cpy(`${fixtureDir}/test_2.mjs`, fixtureDir, { rename: 'test.mjs' }) + await copyFile(`${fixtureDir}/test_2.mjs`, `${fixtureDir}/test.mjs`) await add(`${fixtureDir}/test.mjs`, dist) const file2 = await readFile(testModule, 'utf8') @@ -94,7 +92,7 @@ test('Should overwrite dist file if it already exists', async () => { expect(file1).toContain('one') expect(file2).toContain('two') } finally { - await cpy(`${fixtureDir}/test.mjs.backup`, fixtureDir, { rename: 'test.mjs' }) + await copyFile(`${fixtureDir}/test.mjs.backup`, `${fixtureDir}/test.mjs`) await rm(`${fixtureDir}/test.mjs.backup`, { force: true }) await rm(dist, { force: true, recursive: true }) } @@ -106,6 +104,13 @@ test('Should allow "fail" option to customize failures', async () => { expect(fail).toHaveBeenCalledExactlyOnceWith('No function source directory was specified') }) +// Sort by `mainFile`, then `extension` +const sortFunctions = function (functions) { + return [...functions].sort( + (a, b) => a.mainFile.localeCompare(b.mainFile) || a.extension.localeCompare(b.extension), + ) +} + const normalizeFiles = function ( fixtureDir, { name, mainFile, runtime, extension, srcDir, srcFile, srcPath, schedule }, @@ -120,7 +125,7 @@ const normalizeFiles = function ( 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(sortFunctions(functions)).toEqual( [ { name: 'four', @@ -149,7 +154,7 @@ 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(sortFunctions(functions)).toEqual( [ { name: 'four',