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
12924 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
import { debug } from "./log.ts";
13
14
export { ensureDir, ensureDirSync } from "fs/ensure-dir";
15
export { existsSync } from "fs/exists";
16
export { walk, walkSync } from "fs/walk";
17
export { expandGlob, expandGlobSync } from "fs/expand-glob";
18
export type { ExpandGlobOptions } from "fs/expand-glob";
19
export { EOL, format, LF } from "fs/eol";
20
export { copy, copySync } from "fs/copy";
21
export type { CopyOptions } from "fs/copy";
22
export { moveSync } from "fs/move";
23
export { emptyDirSync } from "fs/empty-dir";
24
export type { WalkEntry } from "fs/walk";
25
26
// It looks like these exports disappeared when Deno moved to JSR? :(
27
// from https://jsr.io/@std/fs/1.0.3/_get_file_info_type.ts
28
29
export type PathType = "file" | "dir" | "symlink";
30
export function getFileInfoType(fileInfo: Deno.FileInfo): PathType | undefined {
31
return fileInfo.isFile
32
? "file"
33
: fileInfo.isDirectory
34
? "dir"
35
: fileInfo.isSymlink
36
? "symlink"
37
: undefined;
38
}
39
40
// from https://jsr.io/@std/fs/1.0.3/_is_subdir.ts
41
/**
42
* Checks whether `path2` is a sub-directory of `path1`.
43
*
44
* The original function uses bad parameter names which are misleading.
45
*
46
* This function is such that, for all paths p:
47
*
48
* isSubdir(p, join(p, "foo")) === true
49
* isSubdir(p, p) === false
50
* isSubdir(join(p, "foo"), p) === false
51
*
52
* @param path1 First path, as a string or URL.
53
* @param path2 Second path, as a string or URL.
54
* @param sep Path separator. Defaults to `\\` for Windows and `/` for other
55
* platforms.
56
*
57
* @returns `true` if `path2` is a proper sub-directory of `path1`, `false` otherwise.
58
*/
59
export function isSubdir(
60
path1: string | URL,
61
path2: string | URL,
62
sep = SEPARATOR,
63
): boolean {
64
path1 = toPathString(path1);
65
path2 = toPathString(path2);
66
67
path1 = resolve(path1);
68
path2 = resolve(path2);
69
70
if (path1 === path2) {
71
return false;
72
}
73
74
const path1Array = path1.split(sep);
75
const path2Array = path2.split(sep);
76
77
// if path1Array is longer than path2Array, then at least one of the
78
// comparisons will return false, because it will compare a string to
79
// undefined
80
81
return path1Array.every((current, i) => path2Array[i] === current);
82
}
83
84
/**
85
* Convert a URL or string to a path.
86
*
87
* @param pathUrl A URL or string to be converted.
88
*
89
* @returns The path as a string.
90
*/
91
export function toPathString(
92
pathUrl: string | URL,
93
): string {
94
return pathUrl instanceof URL ? fromFileUrl(pathUrl) : pathUrl;
95
}
96
97
export function safeMoveSync(
98
src: string,
99
dest: string,
100
): void {
101
try {
102
Deno.renameSync(src, dest);
103
// deno-lint-ignore no-explicit-any
104
} catch (err: any) {
105
// code isn't part of the generic error object, which is why we use `: any`
106
if (err.code !== "EXDEV") {
107
throw err;
108
}
109
copySync(src, dest, { overwrite: true });
110
safeRemoveSync(src, { recursive: true });
111
}
112
}
113
114
export function safeRemoveSync(
115
file: string,
116
options: Deno.RemoveOptions = {},
117
) {
118
try {
119
Deno.removeSync(file, options);
120
} catch (e) {
121
if (existsSync(file)) {
122
throw e;
123
}
124
}
125
}
126
127
export class UnsafeRemovalError extends Error {
128
constructor(msg: string) {
129
super(msg);
130
}
131
}
132
133
export function safeRemoveDirSync(
134
path: string,
135
boundary: string,
136
) {
137
// Resolve symlinks to ensure consistent path comparison.
138
// This is needed because external tools (like knitr) may resolve symlinks
139
// while project.dir preserves them.
140
//
141
// We use the original Deno.realPathSync (saved before monkey-patching)
142
// because the monkey-patch replaces it with normalizePath which doesn't
143
// resolve symlinks.
144
//
145
// Note: The UNC path bug that motivated the monkey-patch was fixed in
146
// Deno v1.16 (see denoland/deno#12243), so this is safe on all platforms.
147
let resolvedPath = path;
148
let resolvedBoundary = boundary;
149
try {
150
resolvedPath = originalRealPathSync(path);
151
resolvedBoundary = originalRealPathSync(boundary);
152
} catch {
153
// If resolution fails (e.g., path doesn't exist), use original paths
154
}
155
156
if (resolvedPath === resolvedBoundary || !isSubdir(resolvedBoundary, resolvedPath)) {
157
throw new UnsafeRemovalError(
158
`Refusing to remove directory ${path} that isn't a subdirectory of ${boundary}`,
159
);
160
}
161
return safeRemoveSync(path, { recursive: true });
162
}
163
164
/**
165
* Obtain the mode of a file in a windows-safe way.
166
*
167
* @param path The path to the file.
168
*
169
* @returns The mode of the file, or `undefined` if the mode cannot be obtained.
170
*/
171
export function safeModeFromFile(path: string): number | undefined {
172
if (Deno.build.os !== "windows") {
173
const stat = Deno.statSync(path);
174
if (stat.mode !== null) {
175
return stat.mode;
176
}
177
}
178
}
179
180
/**
181
* Set file mode in a platform-safe way. No-op on Windows (where chmod
182
* is not supported). Swallows errors on other platforms since permission
183
* changes are often non-fatal (e.g., on filesystems that don't support it).
184
*/
185
export function safeChmodSync(path: string, mode: number): void {
186
if (Deno.build.os !== "windows") {
187
try {
188
Deno.chmodSync(path, mode);
189
} catch (e) {
190
debug(`safeChmodSync: failed to chmod ${path}: ${e}`);
191
}
192
}
193
}
194
195
/**
196
* Ensure a file has user write permission. Files copied from installed
197
* resources (e.g. system packages) may be read-only, but users expect
198
* to edit files created by `quarto create`. No-op on Windows.
199
*/
200
export function ensureUserWritable(path: string): void {
201
const mode = safeModeFromFile(path);
202
if (mode !== undefined && !(mode & 0o200)) {
203
safeChmodSync(path, mode | 0o200);
204
}
205
}
206
207