diff --git a/packages/build/src/core/constants.js b/packages/build/src/core/constants.js index 83e92b4802..4aec0e12ba 100644 --- a/packages/build/src/core/constants.js +++ b/packages/build/src/core/constants.js @@ -21,7 +21,6 @@ export const getConstants = async function ({ }) { const isLocal = mode !== 'buildbot' const normalizedCacheDir = getCacheDir({ cacheDir, cwd: buildDir }) - const constants = { // Path to the Netlify configuration file CONFIG_PATH: configPath, diff --git a/packages/build/tests/plugins/fixtures/cache_utils/.gitignore b/packages/build/tests/plugins/fixtures/cache_utils/.gitignore new file mode 100644 index 0000000000..1521c8b765 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/build/tests/plugins/fixtures/cache_utils/build.mjs b/packages/build/tests/plugins/fixtures/cache_utils/build.mjs new file mode 100644 index 0000000000..c4d2b2e01b --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/build.mjs @@ -0,0 +1,21 @@ +import { mkdir, readdir, writeFile } from 'fs/promises'; +import { existsSync } from 'fs'; + + +if (existsSync('dist/.dot')) { + console.log('content inside dist/.dot:'); + console.log((await readdir('dist/.dot')).join('\n')); +} +if (existsSync('dist/other')) { + console.log('content inside dist/other:'); + console.log((await readdir('dist/other')).join('\n')); +} + +console.log('Generate files'); +await mkdir('dist/.dot', { recursive: true }); +await mkdir('dist/other', { recursive: true }); + +await Promise.all([ + writeFile('dist/.dot/hello.txt', ''), + writeFile('dist/other/index.html', '

hello world

'), +]); diff --git a/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/index.js b/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/index.js new file mode 100644 index 0000000000..83f7586e29 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/index.js @@ -0,0 +1,8 @@ +module.exports = { + onPreBuild: async ({ utils }) => { + await utils.cache.restore('dist'); + }, + onPostBuild: async ({ utils }) => { + await utils.cache.save('dist'); + }, +}; diff --git a/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/manifest.yaml b/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/manifest.yaml new file mode 100644 index 0000000000..c6d701f902 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/netlify-plugin-cache/manifest.yaml @@ -0,0 +1 @@ +name: custom-cache-plugin diff --git a/packages/build/tests/plugins/fixtures/cache_utils/netlify.toml b/packages/build/tests/plugins/fixtures/cache_utils/netlify.toml new file mode 100644 index 0000000000..0f2545e472 --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/netlify.toml @@ -0,0 +1,5 @@ +[build] +command = "node build.mjs" + +[[plugins]] +package = "/netlify-plugin-cache" diff --git a/packages/build/tests/plugins/fixtures/cache_utils/package.json b/packages/build/tests/plugins/fixtures/cache_utils/package.json new file mode 100644 index 0000000000..5bbefffbab --- /dev/null +++ b/packages/build/tests/plugins/fixtures/cache_utils/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/build/tests/plugins/snapshots/tests.js.md b/packages/build/tests/plugins/snapshots/tests.js.md index 2fd1cb31d6..4aea52959e 100644 --- a/packages/build/tests/plugins/snapshots/tests.js.md +++ b/packages/build/tests/plugins/snapshots/tests.js.md @@ -1943,6 +1943,80 @@ Generated by [AVA](https://avajs.dev). (Netlify Build completed in 1ms)␊ Build step duration: Netlify Build completed in 1ms` +## Cache utils are caching .dot directories as well + +> Snapshot 1 + + `␊ + Netlify Build ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + > Version␊ + @netlify/build 1.0.0␊ + ␊ + > Flags␊ + debug: true␊ + repositoryRoot: packages/build/tests/plugins/fixtures/cache_utils␊ + testOpts:␊ + pluginsListUrl: test␊ + silentLingeringProcesses: true␊ + ␊ + > Current directory␊ + packages/build/tests/plugins/fixtures/cache_utils␊ + ␊ + > Config file␊ + packages/build/tests/plugins/fixtures/cache_utils/netlify.toml␊ + ␊ + > Resolved config␊ + build:␊ + command: node build.mjs␊ + commandOrigin: config␊ + publish: packages/build/tests/plugins/fixtures/cache_utils␊ + publishOrigin: default␊ + plugins:␊ + - inputs: {}␊ + origin: config␊ + package: /external/path␊ + ␊ + > Context␊ + production␊ + ␊ + > Loading plugins␊ + - /external/path from netlify.toml␊ + ␊ + 1. /external/path (onPreBuild event) ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + ␊ + (/external/path onPreBuild completed in 1ms)␊ + Build step duration: /external/path onPreBuild completed in 1ms␊ + ␊ + 2. build.command from netlify.toml ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + $ node build.mjs␊ + content inside dist/.dot:␊ + hello.txt␊ + content inside dist/other:␊ + index.html␊ + Generate files␊ + ␊ + (build.command completed in 1ms)␊ + Build step duration: build.command completed in 1ms␊ + ␊ + 3. /external/path (onPostBuild event) ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + ␊ + (/external/path onPostBuild completed in 1ms)␊ + Build step duration: /external/path onPostBuild completed in 1ms␊ + ␊ + Netlify Build Complete ␊ + ────────────────────────────────────────────────────────────────␊ + ␊ + (Netlify Build completed in 1ms)␊ + Build step duration: Netlify Build completed in 1ms` + ## Can run list util > Snapshot 1 diff --git a/packages/build/tests/plugins/snapshots/tests.js.snap b/packages/build/tests/plugins/snapshots/tests.js.snap index 3983011b5d..46d468e7c9 100644 Binary files a/packages/build/tests/plugins/snapshots/tests.js.snap and b/packages/build/tests/plugins/snapshots/tests.js.snap differ diff --git a/packages/build/tests/plugins/tests.js b/packages/build/tests/plugins/tests.js index 706c56cad1..9c8de133ce 100644 --- a/packages/build/tests/plugins/tests.js +++ b/packages/build/tests/plugins/tests.js @@ -198,6 +198,16 @@ test('Can run utils', async (t) => { } }) +test('Cache utils are caching .dot directories as well', async (t) => { + // cleanup cache first + await removeDir([`${FIXTURES_DIR}/cache_utils/dist`, `${FIXTURES_DIR}/cache_utils/.netlify`]) + // generate cache + await new Fixture('./fixtures/cache_utils').runWithBuild() + // should have cached files in the output message + const output = await new Fixture('./fixtures/cache_utils').runWithBuild() + t.snapshot(normalizeOutput(output)) +}) + test('Can run list util', async (t) => { const output = await new Fixture('./fixtures/functions_list').runWithBuild() t.snapshot(normalizeOutput(output)) diff --git a/packages/cache-utils/src/fs.ts b/packages/cache-utils/src/fs.ts index c99087eaa6..6f62bd34a3 100644 --- a/packages/cache-utils/src/fs.ts +++ b/packages/cache-utils/src/fs.ts @@ -2,22 +2,25 @@ import { promises as fs } from 'fs' import { basename, dirname } from 'path' import cpy from 'cpy' -import { globby } from 'globby' +import { Options, globby } from 'globby' import { isNotJunk } from 'junk' import { moveFile } from 'move-file' /** * Move or copy a cached file/directory from/to a local one + * @param src The src directory or file to cache + * @param dest The destination location + * @param move If the file should be moved, moving is faster but removes the source files locally */ -export const moveCacheFile = async function (src: string, dest: string, move: boolean) { +export const moveCacheFile = async function (src: string, dest: string, move = false) { // Moving is faster but removes the source files locally if (move) { return moveFile(src, dest, { overwrite: false }) } - const glob = await getSrcGlob(src) - if (glob) { - return cpy(glob.srcGlob, dirname(dest), { cwd: glob.cwd, parents: true, overwrite: false }) + const { srcGlob, ...options } = await getSrcGlob(src) + if (srcGlob) { + return cpy(srcGlob, dirname(dest), { ...options, parents: true, overwrite: false }) } } @@ -25,44 +28,54 @@ export const moveCacheFile = async function (src: string, dest: string, move: bo * Non-existing files and empty directories are always skipped */ export const hasFiles = async function (src: string): Promise { - const glob = await getSrcGlob(src) - if (!glob) { - return false - } - - return glob.srcGlob !== undefined && !(await isEmptyDir({ srcGlob: glob.srcGlob, cwd: glob.cwd, isDir: glob.isDir })) + const { srcGlob, isDir, ...options } = await getSrcGlob(src) + return srcGlob !== undefined && !(await isEmptyDir(srcGlob, isDir, options)) } /** Replicates what `cpy` is doing under the hood. */ -const isEmptyDir = async function ({ srcGlob, cwd, isDir }) { +const isEmptyDir = async function (globPattern: string, isDir: boolean, options: Options) { if (!isDir) { return false } - const files = await globby(srcGlob, { cwd }) + const files = await globby(globPattern, options) const filteredFiles = files.filter((file) => isNotJunk(basename(file))) return filteredFiles.length === 0 } +type GlobOptions = { + srcGlob?: string + isDir: boolean + cwd: string + dot?: boolean +} + /** * Get globbing pattern with files to move/copy */ -const getSrcGlob = async function (src: string): Promise { +const getSrcGlob = async function (src: string): Promise { const srcStat = await getStat(src) if (srcStat === undefined) { - return null + return { srcGlob: undefined, isDir: false, cwd: '' } } const isDir = srcStat.isDirectory() const srcBasename = basename(src) const cwd = dirname(src) + const baseOptions: GlobOptions = { + srcGlob: srcBasename, + isDir, + cwd, + dot: true, // collect .dot directories as well + } + if (isDir) { - return { srcGlob: `${srcBasename}/**`, cwd, isDir } + return { ...baseOptions, srcGlob: `${srcBasename}/**` } } - return { srcGlob: srcBasename, cwd, isDir } + return baseOptions } const getStat = async (src: string) => { diff --git a/packages/cache-utils/tests/digests.js b/packages/cache-utils/tests/digests.test.ts similarity index 100% rename from packages/cache-utils/tests/digests.js rename to packages/cache-utils/tests/digests.test.ts diff --git a/packages/cache-utils/tests/dir.test.ts b/packages/cache-utils/tests/dir.test.ts index 583a67af97..c6ebae7602 100644 --- a/packages/cache-utils/tests/dir.test.ts +++ b/packages/cache-utils/tests/dir.test.ts @@ -1,26 +1,33 @@ import { promises as fs } from 'fs' import { pathExists } from 'path-exists' -import { expect, test, vi } from 'vitest' +import { SpyInstance, afterEach, beforeEach, expect, test, vi } from 'vitest' import { restore, save } from '../src/main.js' import { createTmpDir, removeFiles } from './helpers/main.js' -test('Should allow changing the cache directory', async () => { - const [cacheDir, srcDir] = await Promise.all([createTmpDir(), createTmpDir()]) - const cwdSpy = vi.spyOn(process, 'cwd').mockImplementation(() => srcDir) - try { - const srcFile = `${srcDir}/test` - await fs.writeFile(srcFile, '') - expect(await save(srcFile, { cacheDir })).toBe(true) - const cachedFiles = await fs.readdir(cacheDir) - expect(cachedFiles.length).toBe(1) - await removeFiles(srcFile) - expect(await restore(srcFile, { cacheDir })).toBe(true) - expect(await pathExists(srcFile)).toBe(true) - } finally { - await removeFiles([cacheDir, srcDir]) - } +let cacheDir, srcDir: string +let cwdSpy: SpyInstance<[], string> + +beforeEach(async () => { + cacheDir = await createTmpDir() + srcDir = await createTmpDir() + cwdSpy = vi.spyOn(process, 'cwd').mockImplementation(() => srcDir) +}) + +afterEach(async () => { + await removeFiles([cacheDir, srcDir]) cwdSpy.mockRestore() }) + +test('Should allow changing the cache directory', async () => { + const srcFile = `${srcDir}/test` + await fs.writeFile(srcFile, '') + expect(await save(srcFile, { cacheDir })).toBe(true) + const cachedFiles = await fs.readdir(cacheDir) + expect(cachedFiles.length).toBe(1) + await removeFiles(srcFile) + expect(await restore(srcFile, { cacheDir })).toBe(true) + expect(await pathExists(srcFile)).toBe(true) +}) diff --git a/packages/testing/src/dir.ts b/packages/testing/src/dir.ts index 7686c9148f..a252c022e3 100644 --- a/packages/testing/src/dir.ts +++ b/packages/testing/src/dir.ts @@ -27,7 +27,7 @@ const createGit = (cwd: string) => { // Removing a directory sometimes fails on Windows in CI due to Windows // directory locking. // This results in `EBUSY: resource busy or locked, rmdir /path/to/dir` -export const removeDir = async function (dir) { +export const removeDir = async function (dir: string | string[]) { try { await del(dir, { force: true }) } catch {