Skip to content

Commit 0c11718

Browse files
committed
chore: a tiny bit safer approach
1 parent 179ca04 commit 0c11718

File tree

3 files changed

+133
-11
lines changed

3 files changed

+133
-11
lines changed

src/generators/legacy-html/index.mjs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
import { readFile, rm, writeFile, mkdir } from 'node:fs/promises';
3+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
44
import { join } from 'node:path';
55

66
import HTMLMinifier from '@minify-html/node';
@@ -176,11 +176,6 @@ export default {
176176
// Define the output folder for API docs assets
177177
const assetsFolder = join(output, 'assets');
178178

179-
// Removes the current assets directory to copy the new assets
180-
// and prevent stale assets from existing in the output directory
181-
// If the path does not exists, it will simply ignore and continue
182-
await rm(assetsFolder, { recursive: true, force: true, maxRetries: 10 });
183-
184179
// Creates the assets folder if it does not exist
185180
await mkdir(assetsFolder, { recursive: true });
186181

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
'use strict';
2+
3+
import assert from 'node:assert';
4+
import { mkdir, readFile, rm, utimes, writeFile } from 'node:fs/promises';
5+
import { join } from 'node:path';
6+
import { afterEach, beforeEach, describe, it } from 'node:test';
7+
8+
import { safeCopy } from '../safeCopy.mjs';
9+
10+
describe('safeCopy', () => {
11+
const testDir = join(import.meta.dirname, 'test-safe-copy');
12+
const srcDir = join(testDir, 'src');
13+
const targetDir = join(testDir, 'target');
14+
15+
beforeEach(async () => {
16+
// Create test directories
17+
await mkdir(srcDir, { recursive: true });
18+
await mkdir(targetDir, { recursive: true });
19+
});
20+
21+
afterEach(async () => {
22+
// Clean up test directories
23+
await rm(testDir, { recursive: true, force: true });
24+
});
25+
26+
it('should copy new files that do not exist in target', async () => {
27+
// Create a file in source
28+
await writeFile(join(srcDir, 'file1.txt'), 'content1');
29+
30+
await safeCopy(srcDir, targetDir);
31+
32+
// Verify file was copied
33+
const content = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
34+
assert.strictEqual(content, 'content1');
35+
});
36+
37+
it('should copy multiple files', async () => {
38+
// Create multiple files in source
39+
await writeFile(join(srcDir, 'file1.txt'), 'content1');
40+
await writeFile(join(srcDir, 'file2.txt'), 'content2');
41+
await writeFile(join(srcDir, 'file3.txt'), 'content3');
42+
43+
await safeCopy(srcDir, targetDir);
44+
45+
// Verify all files were copied
46+
const content1 = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
47+
const content2 = await readFile(join(targetDir, 'file2.txt'), 'utf-8');
48+
const content3 = await readFile(join(targetDir, 'file3.txt'), 'utf-8');
49+
50+
assert.strictEqual(content1, 'content1');
51+
assert.strictEqual(content2, 'content2');
52+
assert.strictEqual(content3, 'content3');
53+
});
54+
55+
it('should skip files with same size and older modification time', async () => {
56+
// Create file in source with specific size
57+
const content = 'same content';
58+
await writeFile(join(srcDir, 'file1.txt'), content);
59+
60+
// Make source file old
61+
const oldTime = new Date(Date.now() - 10000);
62+
await utimes(join(srcDir, 'file1.txt'), oldTime, oldTime);
63+
64+
// Create target file with same size but different content and newer timestamp
65+
await writeFile(join(targetDir, 'file1.txt'), 'other things');
66+
67+
await safeCopy(srcDir, targetDir);
68+
69+
// Verify file was not overwritten (source is older)
70+
const targetContent = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
71+
assert.strictEqual(targetContent, 'other things');
72+
});
73+
74+
it('should copy files when source has newer modification time', async () => {
75+
// Create files in both directories
76+
await writeFile(join(srcDir, 'file1.txt'), 'new content');
77+
await writeFile(join(targetDir, 'file1.txt'), 'old content');
78+
79+
// Make target file older
80+
const oldTime = new Date(Date.now() - 10000);
81+
await utimes(join(targetDir, 'file1.txt'), oldTime, oldTime);
82+
83+
await safeCopy(srcDir, targetDir);
84+
85+
// Verify file was updated
86+
const content = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
87+
assert.strictEqual(content, 'new content');
88+
});
89+
90+
it('should copy files when sizes differ', async () => {
91+
// Create files with different sizes
92+
await writeFile(join(srcDir, 'file1.txt'), 'short');
93+
await writeFile(join(targetDir, 'file1.txt'), 'much longer content');
94+
95+
await safeCopy(srcDir, targetDir);
96+
97+
// Verify file was updated
98+
const content = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
99+
assert.strictEqual(content, 'short');
100+
});
101+
102+
it('should handle empty source directory', async () => {
103+
// Don't create any files in source
104+
await safeCopy(srcDir, targetDir);
105+
106+
// Verify no error occurred and target is still empty
107+
const files = await readFile(targetDir).catch(() => []);
108+
assert.ok(Array.isArray(files) || files === undefined);
109+
});
110+
111+
it('should copy files with same size but different content when mtime is newer', async () => {
112+
// Create files with same size but different content
113+
await writeFile(join(srcDir, 'file1.txt'), 'abcde');
114+
await writeFile(join(targetDir, 'file1.txt'), 'fghij');
115+
116+
// Make target older
117+
const oldTime = new Date(Date.now() - 10000);
118+
await utimes(join(targetDir, 'file1.txt'), oldTime, oldTime);
119+
120+
await safeCopy(srcDir, targetDir);
121+
122+
// Verify file was updated with source content
123+
const content = await readFile(join(targetDir, 'file1.txt'), 'utf-8');
124+
assert.strictEqual(content, 'abcde');
125+
});
126+
});

src/generators/legacy-html/utils/safeCopy.mjs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
'use strict';
22

3-
import { readFile, writeFile, stat, readdir } from 'node:fs/promises';
3+
import { copyFile, readdir, stat } from 'node:fs/promises';
44
import { join } from 'node:path';
55

66
/**
77
* Safely copies files from source to target directory, skipping files that haven't changed
8-
* based on file stats (size and modification time)
8+
* based on file stats (size and modification time). Uses native fs.copyFile which handles
9+
* concurrent operations gracefully.
910
*
1011
* @param {string} srcDir - Source directory path
1112
* @param {string} targetDir - Target directory path
@@ -31,8 +32,8 @@ export async function safeCopy(srcDir, targetDir) {
3132
continue;
3233
}
3334

34-
const fileContent = await readFile(sourcePath);
35-
36-
await writeFile(targetPath, fileContent);
35+
// Use copyFile with COPYFILE_FICLONE flag for efficient copying
36+
// This is atomic and handles concurrent operations better than manual read/write
37+
await copyFile(sourcePath, targetPath);
3738
}
3839
}

0 commit comments

Comments
 (0)