Skip to content

Commit b15e90d

Browse files
committed
refactor: update type definitions for file system API functions to return Promise<unknown> instead of Promise<void>; enhance CI workflow for testing with improved Docker run command; refine timestamp handling in stat functions for better precision and compatibility
1 parent 2c0214d commit b15e90d

File tree

10 files changed

+194
-89
lines changed

10 files changed

+194
-89
lines changed

.github/workflows/CI.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,15 @@ jobs:
223223
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
224224
if: ${{ contains(matrix.target, 'armv7') }}
225225
- name: Test bindings
226-
uses: addnab/docker-run-action@v3
227-
with:
228-
image: ${{ steps.docker.outputs.IMAGE }}
229-
options: '-v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }} --platform ${{ steps.docker.outputs.PLATFORM }}'
230-
run: corepack enable && pnpm test
226+
run: |
227+
docker run --rm \
228+
-e CI=true \
229+
-e GITHUB_ACTIONS=true \
230+
-v "${{ github.workspace }}:${{ github.workspace }}" \
231+
-w "${{ github.workspace }}" \
232+
--platform "${{ steps.docker.outputs.PLATFORM }}" \
233+
"${{ steps.docker.outputs.IMAGE }}" \
234+
sh -lc "corepack enable && pnpm test"
231235
publish:
232236
name: Publish
233237
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ walkdir = "2.5.0"
2222
[target.'cfg(unix)'.dependencies]
2323
libc = "0.2"
2424

25+
[target.'cfg(windows)'.dependencies]
26+
windows-sys = { version = "0.59.0", features = [
27+
"Win32_Foundation",
28+
"Win32_Storage_FileSystem",
29+
] }
30+
2531
[build-dependencies]
2632
napi-build = "2"
2733

__test__/glob.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,11 @@ test('dual-run: globSync "src/*" should match node:fs.globSync behavior for dire
161161
try {
162162
// @ts-ignore - globSync 在旧版 Node 可能不存在
163163
const nodeGlob = nodeFs.globSync as ((p: string, o: object) => string[]) | undefined
164-
if (typeof nodeGlob === 'function') {
165-
nodeResults.push(...nodeGlob('src/*', { cwd: base }))
164+
if (typeof nodeGlob !== 'function') {
165+
t.pass('node:fs.globSync not available, skipping dual-run comparison')
166+
return
166167
}
168+
nodeResults.push(...nodeGlob('src/*', { cwd: base }))
167169
} catch {
168170
// 旧版 Node.js 不支持 fs.globSync,跳过对比
169171
t.pass('node:fs.globSync not available, skipping dual-run comparison')

__test__/stat.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,11 @@ test('statSync: atime Date should be correct for pre-epoch (negative ms) timesta
113113
const file = join(dir, 'pre-epoch.txt')
114114
nodeFs.writeFileSync(file, 'x')
115115
// -500 ms = 1969-12-31T23:59:59.500Z
116-
const preEpochSecs = -0.5
117-
nodeFs.utimesSync(file, preEpochSecs, preEpochSecs)
116+
// NOTE: Passing a negative number to utimesSync is not reliably supported across
117+
// platforms/Node versions. Use Date objects to ensure the pre-epoch timestamp
118+
// is actually applied.
119+
const preEpoch = new Date(-500)
120+
nodeFs.utimesSync(file, preEpoch, preEpoch)
118121

119122
const hyperStat = statSync(file)
120123
const nodeStat = nodeFs.statSync(file)

index.d.ts

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,35 @@ export declare class Stats {
4545
get birthtime(): Date
4646
}
4747

48-
export declare function access(path: string, mode?: number | undefined | null): Promise<void>
48+
export declare function access(path: string, mode?: number | undefined | null): Promise<unknown>
4949

5050
export declare function accessSync(path: string, mode?: number | undefined | null): void
5151

5252
export declare function appendFile(
5353
path: string,
5454
data: string | Buffer,
5555
options?: WriteFileOptions | undefined | null,
56-
): Promise<void>
56+
): Promise<unknown>
5757

5858
export declare function appendFileSync(
5959
path: string,
6060
data: string | Buffer,
6161
options?: WriteFileOptions | undefined | null,
6262
): void
6363

64-
export declare function chmod(path: string, mode: number): Promise<void>
64+
export declare function chmod(path: string, mode: number): Promise<unknown>
6565

6666
export declare function chmodSync(path: string, mode: number): void
6767

68-
export declare function chown(path: string, uid: number, gid: number): Promise<void>
68+
export declare function chown(path: string, uid: number, gid: number): Promise<unknown>
6969

7070
export declare function chownSync(path: string, uid: number, gid: number): void
7171

72-
export declare function copyFile(src: string, dest: string, mode?: number | undefined | null): Promise<void>
72+
export declare function copyFile(src: string, dest: string, mode?: number | undefined | null): Promise<unknown>
7373

7474
export declare function copyFileSync(src: string, dest: string, mode?: number | undefined | null): void
7575

76-
export declare function cp(src: string, dest: string, options?: CpOptions | undefined | null): Promise<void>
76+
export declare function cp(src: string, dest: string, options?: CpOptions | undefined | null): Promise<unknown>
7777

7878
export interface CpOptions {
7979
recursive?: boolean
@@ -91,21 +91,17 @@ export interface CpOptions {
9191

9292
export declare function cpSync(src: string, dest: string, options?: CpOptions | undefined | null): void
9393

94-
export declare function exists(path: string): Promise<boolean>
94+
export declare function exists(path: string): Promise<unknown>
9595

9696
export declare function existsSync(path: string): boolean
9797

98-
export declare function glob(
99-
pattern: string,
100-
options?: GlobOptions | undefined | null,
101-
): Promise<Array<string> | Array<Dirent>>
98+
export declare function glob(pattern: string, options?: GlobOptions | undefined | null): Promise<unknown>
10299

103100
export interface GlobOptions {
104101
cwd?: string
105102
withFileTypes?: boolean
106103
exclude?: Array<string>
107104
concurrency?: number
108-
/** Respect .gitignore / .ignore files (default: true) */
109105
gitIgnore?: boolean
110106
}
111107

@@ -114,15 +110,15 @@ export declare function globSync(
114110
options?: GlobOptions | undefined | null,
115111
): Array<string> | Array<Dirent>
116112

117-
export declare function link(existingPath: string, newPath: string): Promise<void>
113+
export declare function link(existingPath: string, newPath: string): Promise<unknown>
118114

119115
export declare function linkSync(existingPath: string, newPath: string): void
120116

121-
export declare function lstat(path: string): Promise<Stats>
117+
export declare function lstat(path: string): Promise<unknown>
122118

123119
export declare function lstatSync(path: string): Stats
124120

125-
export declare function mkdir(path: string, options?: MkdirOptions | undefined | null): Promise<string | null>
121+
export declare function mkdir(path: string, options?: MkdirOptions | undefined | null): Promise<unknown>
126122

127123
export interface MkdirOptions {
128124
recursive?: boolean
@@ -131,19 +127,29 @@ export interface MkdirOptions {
131127

132128
export declare function mkdirSync(path: string, options?: MkdirOptions | undefined | null): string | null
133129

134-
export declare function mkdtemp(prefix: string): Promise<string>
130+
export declare function mkdtemp(prefix: string): Promise<unknown>
135131

136132
export declare function mkdtempSync(prefix: string): string
137133

138-
export declare function readdir(
139-
path: string,
140-
options?: ReaddirOptions | undefined | null,
141-
): Promise<Array<string> | Array<Dirent>>
142-
134+
export declare function readdir(path: string, options?: ReaddirOptions | undefined | null): Promise<unknown>
135+
136+
/** * Reads the contents of a directory.
137+
* @param {string | Buffer | URL} path
138+
* @param {string | {
139+
* encoding?: string;
140+
* withFileTypes?: boolean;
141+
* recursive?: boolean;
142+
* }} [options]
143+
* @param {(
144+
* err?: Error,
145+
* files?: string[] | Buffer[] | Dirent[]
146+
* ) => any} callback
147+
* @returns {void}
148+
*/
143149
export interface ReaddirOptions {
144150
/**
145151
* File name encoding. 'utf8' (default) returns strings.
146-
* 'buffer' returns Buffer objects for each name (not yet supported, treated as 'utf8').
152+
* 'buffer' returns Buffer objects for each name.
147153
* Other values are treated as 'utf8'.
148154
*/
149155
encoding?: string
@@ -158,7 +164,7 @@ export declare function readdirSync(
158164
options?: ReaddirOptions | undefined | null,
159165
): Array<string> | Array<Dirent>
160166

161-
export declare function readFile(path: string, options?: ReadFileOptions | undefined | null): Promise<string | Buffer>
167+
export declare function readFile(path: string, options?: ReadFileOptions | undefined | null): Promise<unknown>
162168

163169
export interface ReadFileOptions {
164170
encoding?: string
@@ -167,21 +173,21 @@ export interface ReadFileOptions {
167173

168174
export declare function readFileSync(path: string, options?: ReadFileOptions | undefined | null): string | Buffer
169175

170-
export declare function readlink(path: string): Promise<string>
176+
export declare function readlink(path: string): Promise<unknown>
171177

172178
export declare function readlinkSync(path: string): string
173179

174-
export declare function realpath(path: string): Promise<string>
180+
export declare function realpath(path: string): Promise<unknown>
175181

176182
export declare function realpathSync(path: string): string
177183

178-
export declare function rename(oldPath: string, newPath: string): Promise<void>
184+
export declare function rename(oldPath: string, newPath: string): Promise<unknown>
179185

180186
export declare function renameSync(oldPath: string, newPath: string): void
181187

182-
export declare function rm(path: string, options?: RmOptions | undefined | null): Promise<void>
188+
export declare function rm(path: string, options?: RmOptions | undefined | null): Promise<unknown>
183189

184-
export declare function rmdir(path: string): Promise<void>
190+
export declare function rmdir(path: string): Promise<unknown>
185191

186192
export declare function rmdirSync(path: string): void
187193

@@ -191,7 +197,8 @@ export declare function rmdirSync(path: string): void
191197
* - `force`: When true, silently ignore errors when path does not exist.
192198
* - `recursive`: When true, remove directory and all its contents.
193199
* - `maxRetries`: If an `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM` error is
194-
* encountered, retries with a linear backoff of `retryDelay` ms on each try.
200+
* encountered, Node.js retries the operation with a linear backoff of `retryDelay` ms longer on
201+
* each try. This option represents the number of retries.
195202
* - `retryDelay`: The amount of time in milliseconds to wait between retries (default 100ms).
196203
* - `concurrency` (hyper-fs extension): Number of parallel threads for recursive removal.
197204
*/
@@ -205,41 +212,31 @@ export interface RmOptions {
205212

206213
export declare function rmSync(path: string, options?: RmOptions | undefined | null): void
207214

208-
export declare function stat(path: string): Promise<Stats>
215+
export declare function stat(path: string): Promise<unknown>
209216

210217
export declare function statSync(path: string): Stats
211218

212-
export declare function symlink(
213-
target: string,
214-
path: string,
215-
/** On Windows: 'file' | 'dir' | 'junction'. Ignored on Unix. */
216-
symlinkType?: string | undefined | null,
217-
): Promise<void>
219+
export declare function symlink(target: string, path: string, symlinkType?: string | undefined | null): Promise<unknown>
218220

219-
export declare function symlinkSync(
220-
target: string,
221-
path: string,
222-
/** On Windows: 'file' | 'dir' | 'junction'. Ignored on Unix. */
223-
symlinkType?: string | undefined | null,
224-
): void
221+
export declare function symlinkSync(target: string, path: string, symlinkType?: string | undefined | null): void
225222

226-
export declare function truncate(path: string, len?: number | undefined | null): Promise<void>
223+
export declare function truncate(path: string, len?: number | undefined | null): Promise<unknown>
227224

228225
export declare function truncateSync(path: string, len?: number | undefined | null): void
229226

230-
export declare function unlink(path: string): Promise<void>
227+
export declare function unlink(path: string): Promise<unknown>
231228

232229
export declare function unlinkSync(path: string): void
233230

234-
export declare function utimes(path: string, atime: number, mtime: number): Promise<void>
231+
export declare function utimes(path: string, atime: number, mtime: number): Promise<unknown>
235232

236233
export declare function utimesSync(path: string, atime: number, mtime: number): void
237234

238235
export declare function writeFile(
239236
path: string,
240237
data: string | Buffer,
241238
options?: WriteFileOptions | undefined | null,
242-
): Promise<void>
239+
): Promise<unknown>
243240

244241
export interface WriteFileOptions {
245242
encoding?: string

src/realpath.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ use napi_derive::napi;
44
use std::fs;
55
use std::path::Path;
66

7+
#[cfg(windows)]
8+
fn strip_verbatim_prefix(s: String) -> String {
9+
if let Some(rest) = s.strip_prefix(r"\\?\UNC\") {
10+
// \\?\UNC\server\share\path -> \\server\share\path
11+
return format!(r"\\{}", rest);
12+
}
13+
if let Some(rest) = s.strip_prefix(r"\\?\") {
14+
return rest.to_string();
15+
}
16+
s
17+
}
18+
719
fn realpath_impl(path_str: String) -> Result<String> {
820
let path = Path::new(&path_str);
921
let resolved = fs::canonicalize(path).map_err(|e| {
@@ -16,6 +28,33 @@ fn realpath_impl(path_str: String) -> Result<String> {
1628
Error::from_reason(e.to_string())
1729
}
1830
})?;
31+
32+
#[cfg(windows)]
33+
{
34+
use std::ffi::OsString;
35+
use std::os::windows::ffi::OsStrExt;
36+
use std::os::windows::ffi::OsStringExt;
37+
use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW;
38+
39+
let wide: Vec<u16> = resolved
40+
.as_os_str()
41+
.encode_wide()
42+
.chain(std::iter::once(0))
43+
.collect();
44+
let mut buf: Vec<u16> = vec![0; 32768];
45+
let len = unsafe { GetShortPathNameW(wide.as_ptr(), buf.as_mut_ptr(), buf.len() as u32) };
46+
if len > 0 && (len as usize) < buf.len() {
47+
buf.truncate(len as usize);
48+
let short = OsString::from_wide(&buf);
49+
let s = short.to_string_lossy().to_string();
50+
return Ok(strip_verbatim_prefix(s));
51+
}
52+
53+
let s = resolved.to_string_lossy().to_string();
54+
return Ok(strip_verbatim_prefix(s));
55+
}
56+
57+
#[cfg(not(windows))]
1958
Ok(resolved.to_string_lossy().to_string())
2059
}
2160

0 commit comments

Comments
 (0)