Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/deno_ral/fs.ts
6449 views
1
/*
2
* encoding.ts
3
*
4
* Copyright (C) 2020-2024 Posit Software, PBC
5
*/
6
7
import { fromFileUrl } from "./path.ts";
8
import { resolve, SEP as SEPARATOR } from "./path.ts";
9
import { copySync } from "fs/copy";
10
import { existsSync } from "fs/exists";
11
import { originalRealPathSync } from "./original-real-path.ts";
12
13
export { ensureDir, ensureDirSync } from "fs/ensure-dir";
14
export { existsSync } from "fs/exists";
15
export { walk, walkSync } from "fs/walk";
16
export { expandGlob, expandGlobSync } from "fs/expand-glob";
17
export type { ExpandGlobOptions } from "fs/expand-glob";
18
export { EOL, format, LF } from "fs/eol";
19
export { copy, copySync } from "fs/copy";
20
export type { CopyOptions } from "fs/copy";
21
export { moveSync } from "fs/move";
22
export { emptyDirSync } from "fs/empty-dir";
23
export type { WalkEntry } from "fs/walk";
24
25
// It looks like these exports disappeared when Deno moved to JSR? :(
26
// from https://jsr.io/@std/fs/1.0.3/_get_file_info_type.ts
27
28
export type PathType = "file" | "dir" | "symlink";
29
export function getFileInfoType(fileInfo: Deno.FileInfo): PathType | undefined {
30
return fileInfo.isFile
31
? "file"
32
: fileInfo.isDirectory
33
? "dir"
34
: fileInfo.isSymlink
35
? "symlink"
36
: undefined;
37
}
38
39
// from https://jsr.io/@std/fs/1.0.3/_is_subdir.ts
40
/**
41
* Checks whether `path2` is a sub-directory of `path1`.
42
*
43
* The original function uses bad parameter names which are misleading.
44
*
45
* This function is such that, for all paths p:
46
*
47
* isSubdir(p, join(p, "foo")) === true
48
* isSubdir(p, p) === false
49
* isSubdir(join(p, "foo"), p) === false
50
*
51
* @param path1 First path, as a string or URL.
52
* @param path2 Second path, as a string or URL.
53
* @param sep Path separator. Defaults to `\\` for Windows and `/` for other
54
* platforms.
55
*
56
* @returns `true` if `path2` is a proper sub-directory of `path1`, `false` otherwise.
57
*/
58
export function isSubdir(
59
path1: string | URL,
60
path2: string | URL,
61
sep = SEPARATOR,
62
): boolean {
63
path1 = toPathString(path1);
64
path2 = toPathString(path2);
65
66
path1 = resolve(path1);
67
path2 = resolve(path2);
68
69
if (path1 === path2) {
70
return false;
71
}
72
73
const path1Array = path1.split(sep);
74
const path2Array = path2.split(sep);
75
76
// if path1Array is longer than path2Array, then at least one of the
77
// comparisons will return false, because it will compare a string to
78
// undefined
79
80
return path1Array.every((current, i) => path2Array[i] === current);
81
}
82
83
/**
84
* Convert a URL or string to a path.
85
*
86
* @param pathUrl A URL or string to be converted.
87
*
88
* @returns The path as a string.
89
*/
90
export function toPathString(
91
pathUrl: string | URL,
92
): string {
93
return pathUrl instanceof URL ? fromFileUrl(pathUrl) : pathUrl;
94
}
95
96
export function safeMoveSync(
97
src: string,
98
dest: string,
99
): void {
100
try {
101
Deno.renameSync(src, dest);
102
// deno-lint-ignore no-explicit-any
103
} catch (err: any) {
104
// code isn't part of the generic error object, which is why we use `: any`
105
if (err.code !== "EXDEV") {
106
throw err;
107
}
108
copySync(src, dest, { overwrite: true });
109
safeRemoveSync(src, { recursive: true });
110
}
111
}
112
113
export function safeRemoveSync(
114
file: string,
115
options: Deno.RemoveOptions = {},
116
) {
117
try {
118
Deno.removeSync(file, options);
119
} catch (e) {
120
if (existsSync(file)) {
121
throw e;
122
}
123
}
124
}
125
126
export class UnsafeRemovalError extends Error {
127
constructor(msg: string) {
128
super(msg);
129
}
130
}
131
132
export function safeRemoveDirSync(
133
path: string,
134
boundary: string,
135
) {
136
// Resolve symlinks to ensure consistent path comparison.
137
// This is needed because external tools (like knitr) may resolve symlinks
138
// while project.dir preserves them.
139
//
140
// We use the original Deno.realPathSync (saved before monkey-patching)
141
// because the monkey-patch replaces it with normalizePath which doesn't
142
// resolve symlinks.
143
//
144
// Note: The UNC path bug that motivated the monkey-patch was fixed in
145
// Deno v1.16 (see denoland/deno#12243), so this is safe on all platforms.
146
let resolvedPath = path;
147
let resolvedBoundary = boundary;
148
try {
149
resolvedPath = originalRealPathSync(path);
150
resolvedBoundary = originalRealPathSync(boundary);
151
} catch {
152
// If resolution fails (e.g., path doesn't exist), use original paths
153
}
154
155
if (resolvedPath === resolvedBoundary || !isSubdir(resolvedBoundary, resolvedPath)) {
156
throw new UnsafeRemovalError(
157
`Refusing to remove directory ${path} that isn't a subdirectory of ${boundary}`,
158
);
159
}
160
return safeRemoveSync(path, { recursive: true });
161
}
162
163
/**
164
* Obtain the mode of a file in a windows-safe way.
165
*
166
* @param path The path to the file.
167
*
168
* @returns The mode of the file, or `undefined` if the mode cannot be obtained.
169
*/
170
export function safeModeFromFile(path: string): number | undefined {
171
if (Deno.build.os !== "windows") {
172
const stat = Deno.statSync(path);
173
if (stat.mode !== null) {
174
return stat.mode;
175
}
176
}
177
}
178
179