Skip to content

Commit ccb3a00

Browse files
author
Lukas Holzer
authored
fix: fixes an issue where the cache could not save .dot directories (#4843)
Fixes https://github.com/netlify/pillar-workflow/issues/1017
1 parent e5f4c12 commit ccb3a00

File tree

14 files changed

+177
-35
lines changed

14 files changed

+177
-35
lines changed

packages/build/src/core/constants.js

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export const getConstants = async function ({
2121
}) {
2222
const isLocal = mode !== 'buildbot'
2323
const normalizedCacheDir = getCacheDir({ cacheDir, cwd: buildDir })
24-
2524
const constants = {
2625
// Path to the Netlify configuration file
2726
CONFIG_PATH: configPath,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { mkdir, readdir, writeFile } from 'fs/promises';
2+
import { existsSync } from 'fs';
3+
4+
5+
if (existsSync('dist/.dot')) {
6+
console.log('content inside dist/.dot:');
7+
console.log((await readdir('dist/.dot')).join('\n'));
8+
}
9+
if (existsSync('dist/other')) {
10+
console.log('content inside dist/other:');
11+
console.log((await readdir('dist/other')).join('\n'));
12+
}
13+
14+
console.log('Generate files');
15+
await mkdir('dist/.dot', { recursive: true });
16+
await mkdir('dist/other', { recursive: true });
17+
18+
await Promise.all([
19+
writeFile('dist/.dot/hello.txt', ''),
20+
writeFile('dist/other/index.html', '<h1>hello world</h1>'),
21+
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
onPreBuild: async ({ utils }) => {
3+
await utils.cache.restore('dist');
4+
},
5+
onPostBuild: async ({ utils }) => {
6+
await utils.cache.save('dist');
7+
},
8+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
name: custom-cache-plugin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[build]
2+
command = "node build.mjs"
3+
4+
[[plugins]]
5+
package = "/netlify-plugin-cache"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "commonjs"
3+
}

packages/build/tests/plugins/snapshots/tests.js.md

+74
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,80 @@ Generated by [AVA](https://avajs.dev).
19431943
(Netlify Build completed in 1ms)␊
19441944
Build step duration: Netlify Build completed in 1ms`
19451945

1946+
## Cache utils are caching .dot directories as well
1947+
1948+
> Snapshot 1
1949+
1950+
`␊
1951+
Netlify Build ␊
1952+
────────────────────────────────────────────────────────────────␊
1953+
1954+
> Version␊
1955+
@netlify/build 1.0.0␊
1956+
1957+
> Flags␊
1958+
debug: true␊
1959+
repositoryRoot: packages/build/tests/plugins/fixtures/cache_utils␊
1960+
testOpts:␊
1961+
pluginsListUrl: test␊
1962+
silentLingeringProcesses: true␊
1963+
1964+
> Current directory␊
1965+
packages/build/tests/plugins/fixtures/cache_utils␊
1966+
1967+
> Config file␊
1968+
packages/build/tests/plugins/fixtures/cache_utils/netlify.toml␊
1969+
1970+
> Resolved config␊
1971+
build:␊
1972+
command: node build.mjs␊
1973+
commandOrigin: config␊
1974+
publish: packages/build/tests/plugins/fixtures/cache_utils␊
1975+
publishOrigin: default␊
1976+
plugins:␊
1977+
- inputs: {}␊
1978+
origin: config␊
1979+
package: /external/path␊
1980+
1981+
> Context␊
1982+
production␊
1983+
1984+
> Loading plugins␊
1985+
- /external/path from netlify.toml␊
1986+
1987+
1. /external/path (onPreBuild event) ␊
1988+
────────────────────────────────────────────────────────────────␊
1989+
1990+
1991+
(/external/path onPreBuild completed in 1ms)␊
1992+
Build step duration: /external/path onPreBuild completed in 1ms␊
1993+
1994+
2. build.command from netlify.toml ␊
1995+
────────────────────────────────────────────────────────────────␊
1996+
1997+
$ node build.mjs␊
1998+
content inside dist/.dot:␊
1999+
hello.txt␊
2000+
content inside dist/other:␊
2001+
index.html␊
2002+
Generate files␊
2003+
2004+
(build.command completed in 1ms)␊
2005+
Build step duration: build.command completed in 1ms␊
2006+
2007+
3. /external/path (onPostBuild event) ␊
2008+
────────────────────────────────────────────────────────────────␊
2009+
2010+
2011+
(/external/path onPostBuild completed in 1ms)␊
2012+
Build step duration: /external/path onPostBuild completed in 1ms␊
2013+
2014+
Netlify Build Complete ␊
2015+
────────────────────────────────────────────────────────────────␊
2016+
2017+
(Netlify Build completed in 1ms)␊
2018+
Build step duration: Netlify Build completed in 1ms`
2019+
19462020
## Can run list util
19472021

19482022
> Snapshot 1
Binary file not shown.

packages/build/tests/plugins/tests.js

+10
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ test('Can run utils', async (t) => {
198198
}
199199
})
200200

201+
test('Cache utils are caching .dot directories as well', async (t) => {
202+
// cleanup cache first
203+
await removeDir([`${FIXTURES_DIR}/cache_utils/dist`, `${FIXTURES_DIR}/cache_utils/.netlify`])
204+
// generate cache
205+
await new Fixture('./fixtures/cache_utils').runWithBuild()
206+
// should have cached files in the output message
207+
const output = await new Fixture('./fixtures/cache_utils').runWithBuild()
208+
t.snapshot(normalizeOutput(output))
209+
})
210+
201211
test('Can run list util', async (t) => {
202212
const output = await new Fixture('./fixtures/functions_list').runWithBuild()
203213
t.snapshot(normalizeOutput(output))

packages/cache-utils/src/fs.ts

+30-17
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,80 @@ import { promises as fs } from 'fs'
22
import { basename, dirname } from 'path'
33

44
import cpy from 'cpy'
5-
import { globby } from 'globby'
5+
import { Options, globby } from 'globby'
66
import { isNotJunk } from 'junk'
77
import { moveFile } from 'move-file'
88

99
/**
1010
* Move or copy a cached file/directory from/to a local one
11+
* @param src The src directory or file to cache
12+
* @param dest The destination location
13+
* @param move If the file should be moved, moving is faster but removes the source files locally
1114
*/
12-
export const moveCacheFile = async function (src: string, dest: string, move: boolean) {
15+
export const moveCacheFile = async function (src: string, dest: string, move = false) {
1316
// Moving is faster but removes the source files locally
1417
if (move) {
1518
return moveFile(src, dest, { overwrite: false })
1619
}
1720

18-
const glob = await getSrcGlob(src)
19-
if (glob) {
20-
return cpy(glob.srcGlob, dirname(dest), { cwd: glob.cwd, parents: true, overwrite: false })
21+
const { srcGlob, ...options } = await getSrcGlob(src)
22+
if (srcGlob) {
23+
return cpy(srcGlob, dirname(dest), { ...options, parents: true, overwrite: false })
2124
}
2225
}
2326

2427
/**
2528
* Non-existing files and empty directories are always skipped
2629
*/
2730
export const hasFiles = async function (src: string): Promise<boolean> {
28-
const glob = await getSrcGlob(src)
29-
if (!glob) {
30-
return false
31-
}
32-
33-
return glob.srcGlob !== undefined && !(await isEmptyDir({ srcGlob: glob.srcGlob, cwd: glob.cwd, isDir: glob.isDir }))
31+
const { srcGlob, isDir, ...options } = await getSrcGlob(src)
32+
return srcGlob !== undefined && !(await isEmptyDir(srcGlob, isDir, options))
3433
}
3534

3635
/** Replicates what `cpy` is doing under the hood. */
37-
const isEmptyDir = async function ({ srcGlob, cwd, isDir }) {
36+
const isEmptyDir = async function (globPattern: string, isDir: boolean, options: Options) {
3837
if (!isDir) {
3938
return false
4039
}
4140

42-
const files = await globby(srcGlob, { cwd })
41+
const files = await globby(globPattern, options)
4342
const filteredFiles = files.filter((file) => isNotJunk(basename(file)))
4443
return filteredFiles.length === 0
4544
}
4645

46+
type GlobOptions = {
47+
srcGlob?: string
48+
isDir: boolean
49+
cwd: string
50+
dot?: boolean
51+
}
52+
4753
/**
4854
* Get globbing pattern with files to move/copy
4955
*/
50-
const getSrcGlob = async function (src: string): Promise<null | { srcGlob: string; cwd: string; isDir: boolean }> {
56+
const getSrcGlob = async function (src: string): Promise<GlobOptions> {
5157
const srcStat = await getStat(src)
5258

5359
if (srcStat === undefined) {
54-
return null
60+
return { srcGlob: undefined, isDir: false, cwd: '' }
5561
}
5662

5763
const isDir = srcStat.isDirectory()
5864
const srcBasename = basename(src)
5965
const cwd = dirname(src)
6066

67+
const baseOptions: GlobOptions = {
68+
srcGlob: srcBasename,
69+
isDir,
70+
cwd,
71+
dot: true, // collect .dot directories as well
72+
}
73+
6174
if (isDir) {
62-
return { srcGlob: `${srcBasename}/**`, cwd, isDir }
75+
return { ...baseOptions, srcGlob: `${srcBasename}/**` }
6376
}
6477

65-
return { srcGlob: srcBasename, cwd, isDir }
78+
return baseOptions
6679
}
6780

6881
const getStat = async (src: string) => {
+23-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
import { promises as fs } from 'fs'
22

33
import { pathExists } from 'path-exists'
4-
import { expect, test, vi } from 'vitest'
4+
import { SpyInstance, afterEach, beforeEach, expect, test, vi } from 'vitest'
55

66
import { restore, save } from '../src/main.js'
77

88
import { createTmpDir, removeFiles } from './helpers/main.js'
99

10-
test('Should allow changing the cache directory', async () => {
11-
const [cacheDir, srcDir] = await Promise.all([createTmpDir(), createTmpDir()])
12-
const cwdSpy = vi.spyOn(process, 'cwd').mockImplementation(() => srcDir)
13-
try {
14-
const srcFile = `${srcDir}/test`
15-
await fs.writeFile(srcFile, '')
16-
expect(await save(srcFile, { cacheDir })).toBe(true)
17-
const cachedFiles = await fs.readdir(cacheDir)
18-
expect(cachedFiles.length).toBe(1)
19-
await removeFiles(srcFile)
20-
expect(await restore(srcFile, { cacheDir })).toBe(true)
21-
expect(await pathExists(srcFile)).toBe(true)
22-
} finally {
23-
await removeFiles([cacheDir, srcDir])
24-
}
10+
let cacheDir, srcDir: string
11+
let cwdSpy: SpyInstance<[], string>
12+
13+
beforeEach(async () => {
14+
cacheDir = await createTmpDir()
15+
srcDir = await createTmpDir()
16+
cwdSpy = vi.spyOn(process, 'cwd').mockImplementation(() => srcDir)
17+
})
18+
19+
afterEach(async () => {
20+
await removeFiles([cacheDir, srcDir])
2521
cwdSpy.mockRestore()
2622
})
23+
24+
test('Should allow changing the cache directory', async () => {
25+
const srcFile = `${srcDir}/test`
26+
await fs.writeFile(srcFile, '')
27+
expect(await save(srcFile, { cacheDir })).toBe(true)
28+
const cachedFiles = await fs.readdir(cacheDir)
29+
expect(cachedFiles.length).toBe(1)
30+
await removeFiles(srcFile)
31+
expect(await restore(srcFile, { cacheDir })).toBe(true)
32+
expect(await pathExists(srcFile)).toBe(true)
33+
})

packages/testing/src/dir.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const createGit = (cwd: string) => {
2727
// Removing a directory sometimes fails on Windows in CI due to Windows
2828
// directory locking.
2929
// This results in `EBUSY: resource busy or locked, rmdir /path/to/dir`
30-
export const removeDir = async function (dir) {
30+
export const removeDir = async function (dir: string | string[]) {
3131
try {
3232
await del(dir, { force: true })
3333
} catch {

0 commit comments

Comments
 (0)