Skip to content

Commit 48853d6

Browse files
iclantonCopilotclaude
authored
[node-core-library] Add createReadStream/createWriteStream to FileSystem (#5757)
* Add createReadStream/createWriteStream to FileSystem in node-core-library and update consumers to use FileSystem instead of fs directly Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/5ad20d4d-c9a4-4855-bb13-8dd9e2c1350b Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Add ensureFolderExists option to FileSystem.createWriteStream Agent-Logs-Url: https://github.com/microsoft/rushstack/sessions/199d4b3e-1f3f-44e1-9fc6-7b4a0e027c7e Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> * Expand FileSystem.createWriteStream. * fixup! Add createReadStream/createWriteStream to FileSystem in node-core-library and update consumers to use FileSystem instead of fs directly Wrap FileSystem stream methods in _wrapException for consistent error handling * fixup! Add createReadStream/createWriteStream to FileSystem in node-core-library and update consumers to use FileSystem instead of fs directly * Add unit tests for FileSystem.createReadStream/createWriteStream Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address PR feedback: use async APIs, deterministic paths, and async iteration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f0d5171 commit 48853d6

5 files changed

Lines changed: 269 additions & 18 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Add `FileSystem.createReadStream`, `FileSystem.createWriteStream`, and `FileSystem.createWriteStreamAsync` APIs for creating read and write filesystem streams.",
5+
"type": "minor",
6+
"packageName": "@rushstack/node-core-library"
7+
}
8+
],
9+
"packageName": "@rushstack/node-core-library",
10+
"email": "198982749+Copilot@users.noreply.github.com"
11+
}

common/reviews/api/node-core-library.api.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,15 @@ export class FileSystem {
166166
static copyFilesAsync(options: IFileSystemCopyFilesAsyncOptions): Promise<void>;
167167
static createHardLink(options: IFileSystemCreateLinkOptions): void;
168168
static createHardLinkAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
169+
static createReadStream(filePath: string): FileSystemReadStream;
169170
static createSymbolicLinkFile(options: IFileSystemCreateLinkOptions): void;
170171
static createSymbolicLinkFileAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
171172
static createSymbolicLinkFolder(options: IFileSystemCreateLinkOptions): void;
172173
static createSymbolicLinkFolderAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
173174
static createSymbolicLinkJunction(options: IFileSystemCreateLinkOptions): void;
174175
static createSymbolicLinkJunctionAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
176+
static createWriteStream(filePath: string, options?: IFileSystemCreateWriteStreamOptions): FileSystemWriteStream;
177+
static createWriteStreamAsync(filePath: string, options?: IFileSystemCreateWriteStreamOptions): Promise<FileSystemWriteStream>;
175178
static deleteFile(filePath: string, options?: IFileSystemDeleteFileOptions): void;
176179
static deleteFileAsync(filePath: string, options?: IFileSystemDeleteFileOptions): Promise<void>;
177180
static deleteFolder(folderPath: string): void;
@@ -225,9 +228,15 @@ export type FileSystemCopyFilesAsyncFilter = (sourcePath: string, destinationPat
225228
// @public
226229
export type FileSystemCopyFilesFilter = (sourcePath: string, destinationPath: string) => boolean;
227230

231+
// @public
232+
export type FileSystemReadStream = fs.ReadStream;
233+
228234
// @public
229235
export type FileSystemStats = fs.Stats;
230236

237+
// @public
238+
export type FileSystemWriteStream = fs.WriteStream;
239+
231240
// @public
232241
export class FileWriter {
233242
close(): void;
@@ -336,15 +345,18 @@ export interface IFileSystemCreateLinkOptions {
336345
newLinkPath: string;
337346
}
338347

348+
// @public
349+
export interface IFileSystemCreateWriteStreamOptions extends IFileSystemWriteFileOptionsBase {
350+
}
351+
339352
// @public
340353
export interface IFileSystemDeleteFileOptions {
341354
throwIfNotExists?: boolean;
342355
}
343356

344357
// @public
345-
export interface IFileSystemMoveOptions {
358+
export interface IFileSystemMoveOptions extends IFileSystemWriteFileOptionsBase {
346359
destinationPath: string;
347-
ensureFolderExists?: boolean;
348360
overwrite?: boolean;
349361
sourcePath: string;
350362
}
@@ -367,8 +379,7 @@ export interface IFileSystemUpdateTimeParameters {
367379
}
368380

369381
// @public
370-
export interface IFileSystemWriteBinaryFileOptions {
371-
ensureFolderExists?: boolean;
382+
export interface IFileSystemWriteBinaryFileOptions extends IFileSystemWriteFileOptionsBase {
372383
}
373384

374385
// @public
@@ -377,6 +388,11 @@ export interface IFileSystemWriteFileOptions extends IFileSystemWriteBinaryFileO
377388
encoding?: Encoding;
378389
}
379390

391+
// @public (undocumented)
392+
export interface IFileSystemWriteFileOptionsBase {
393+
ensureFolderExists?: boolean;
394+
}
395+
380396
// @public
381397
export interface IFileWriterFlags {
382398
append?: boolean;

libraries/node-core-library/src/FileSystem.ts

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ export type FileSystemStats = fs.Stats;
2828
*/
2929
export type FolderItem = fs.Dirent;
3030

31+
/**
32+
* An alias for the Node.js `fs.ReadStream` object.
33+
*
34+
* @remarks
35+
* This avoids the need to import the `fs` package when using the {@link FileSystem} API.
36+
* @public
37+
*/
38+
export type FileSystemReadStream = fs.ReadStream;
39+
40+
/**
41+
* An alias for the Node.js `fs.WriteStream` object.
42+
*
43+
* @remarks
44+
* This avoids the need to import the `fs` package when using the {@link FileSystem} API.
45+
* @public
46+
*/
47+
export type FileSystemWriteStream = fs.WriteStream;
48+
3149
// The PosixModeBits are intended to be used with bitwise operations.
3250
/* eslint-disable no-bitwise */
3351

@@ -44,17 +62,22 @@ export interface IFileSystemReadFolderOptions {
4462
}
4563

4664
/**
47-
* The options for {@link FileSystem.writeBuffersToFile}
4865
* @public
4966
*/
50-
export interface IFileSystemWriteBinaryFileOptions {
67+
export interface IFileSystemWriteFileOptionsBase {
5168
/**
5269
* If true, will ensure the folder is created before writing the file.
5370
* @defaultValue false
5471
*/
5572
ensureFolderExists?: boolean;
5673
}
5774

75+
/**
76+
* The options for {@link FileSystem.writeBuffersToFile}
77+
* @public
78+
*/
79+
export interface IFileSystemWriteBinaryFileOptions extends IFileSystemWriteFileOptionsBase {}
80+
5881
/**
5982
* The options for {@link FileSystem.writeFile}
6083
* @public
@@ -95,7 +118,7 @@ export interface IFileSystemReadFileOptions {
95118
* The options for {@link FileSystem.move}
96119
* @public
97120
*/
98-
export interface IFileSystemMoveOptions {
121+
export interface IFileSystemMoveOptions extends IFileSystemWriteFileOptionsBase {
99122
/**
100123
* The path of the existing object to be moved.
101124
* The path may be absolute or relative.
@@ -113,12 +136,6 @@ export interface IFileSystemMoveOptions {
113136
* @defaultValue true
114137
*/
115138
overwrite?: boolean;
116-
117-
/**
118-
* If true, will ensure the folder is created before writing the file.
119-
* @defaultValue false
120-
*/
121-
ensureFolderExists?: boolean;
122139
}
123140

124141
/**
@@ -258,6 +275,12 @@ export interface IFileSystemCopyFilesOptions extends IFileSystemCopyFilesAsyncOp
258275
filter?: FileSystemCopyFilesFilter; // narrow the type to exclude FileSystemCopyFilesAsyncFilter
259276
}
260277

278+
/**
279+
* The options for {@link FileSystem.createWriteStream}
280+
* @public
281+
*/
282+
export interface IFileSystemCreateWriteStreamOptions extends IFileSystemWriteFileOptionsBase {}
283+
261284
/**
262285
* The options for {@link FileSystem.deleteFile}
263286
* @public
@@ -750,10 +773,11 @@ export class FileSystem {
750773
* Writes a text string to a file on disk, overwriting the file if it already exists.
751774
* Behind the scenes it uses `fs.writeFileSync()`.
752775
* @remarks
753-
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
776+
* Throws an error if the folder doesn't exist, unless {@link IFileSystemWriteFileOptionsBase.ensureFolderExists}
777+
* is set to `true`.
754778
* @param filePath - The absolute or relative path of the file.
755779
* @param contents - The text that should be written to the file.
756-
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
780+
* @param options - Optional settings that can change the behavior.
757781
*/
758782
public static writeFile(
759783
filePath: string,
@@ -796,7 +820,8 @@ export class FileSystem {
796820
* multiple sources.
797821
*
798822
* @remarks
799-
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
823+
* Throws an error if the folder doesn't exist, unless {@link IFileSystemWriteFileOptionsBase.ensureFolderExists}
824+
* is set to `true`.
800825
* @param filePath - The absolute or relative path of the file.
801826
* @param contents - The content that should be written to the file.
802827
* @param options - Optional settings that can change the behavior.
@@ -956,10 +981,11 @@ export class FileSystem {
956981
* Writes a text string to a file on disk, appending to the file if it already exists.
957982
* Behind the scenes it uses `fs.appendFileSync()`.
958983
* @remarks
959-
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
984+
* Throws an error if the folder doesn't exist, unless {@link IFileSystemWriteFileOptionsBase.ensureFolderExists}
985+
* is set to `true`.
960986
* @param filePath - The absolute or relative path of the file.
961987
* @param contents - The text that should be written to the file.
962-
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
988+
* @param options - Optional settings that can change the behavior.
963989
*/
964990
public static appendToFile(
965991
filePath: string,
@@ -1237,6 +1263,61 @@ export class FileSystem {
12371263
});
12381264
}
12391265

1266+
/**
1267+
* Creates a readable stream for an existing file.
1268+
* Behind the scenes it uses `fs.createReadStream()`.
1269+
*
1270+
* @param filePath - The path to the file. The path may be absolute or relative.
1271+
* @returns A new readable stream for the file.
1272+
*/
1273+
public static createReadStream(filePath: string): FileSystemReadStream {
1274+
return FileSystem._wrapException(() => {
1275+
return fs.createReadStream(filePath);
1276+
});
1277+
}
1278+
1279+
/**
1280+
* Creates a writable stream for writing to a file.
1281+
* Behind the scenes it uses `fs.createWriteStream()`.
1282+
*
1283+
* @remarks
1284+
* Throws an error if the folder doesn't exist, unless {@link IFileSystemWriteFileOptionsBase.ensureFolderExists}
1285+
* is set to `true`.
1286+
* @param filePath - The path to the file. The path may be absolute or relative.
1287+
* @param options - Optional settings that can change the behavior.
1288+
* @returns A new writable stream for the file.
1289+
*/
1290+
public static createWriteStream(
1291+
filePath: string,
1292+
options?: IFileSystemCreateWriteStreamOptions
1293+
): FileSystemWriteStream {
1294+
return FileSystem._wrapException(() => {
1295+
if (options?.ensureFolderExists) {
1296+
const folderPath: string = nodeJsPath.dirname(filePath);
1297+
FileSystem.ensureFolder(folderPath);
1298+
}
1299+
1300+
return fs.createWriteStream(filePath);
1301+
});
1302+
}
1303+
1304+
/**
1305+
* An async version of {@link FileSystem.createWriteStream}.
1306+
*/
1307+
public static async createWriteStreamAsync(
1308+
filePath: string,
1309+
options?: IFileSystemCreateWriteStreamOptions
1310+
): Promise<FileSystemWriteStream> {
1311+
return await FileSystem._wrapExceptionAsync(async () => {
1312+
if (options?.ensureFolderExists) {
1313+
const folderPath: string = nodeJsPath.dirname(filePath);
1314+
await FileSystem.ensureFolderAsync(folderPath);
1315+
}
1316+
1317+
return fs.createWriteStream(filePath);
1318+
});
1319+
}
1320+
12401321
// ===============
12411322
// LINK OPERATIONS
12421323
// ===============

libraries/node-core-library/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,19 @@ export { type IFileErrorOptions, type IFileErrorFormattingOptions, FileError } f
5050
export {
5151
AlreadyExistsBehavior,
5252
FileSystem,
53+
type IFileSystemWriteFileOptionsBase,
5354
type FileSystemCopyFilesAsyncFilter,
5455
type FileSystemCopyFilesFilter,
56+
type FileSystemReadStream,
57+
type FileSystemWriteStream,
5558
type FolderItem,
5659
type FileSystemStats,
5760
type IFileSystemCopyFileBaseOptions,
5861
type IFileSystemCopyFileOptions,
5962
type IFileSystemCopyFilesAsyncOptions,
6063
type IFileSystemCopyFilesOptions,
6164
type IFileSystemCreateLinkOptions,
65+
type IFileSystemCreateWriteStreamOptions,
6266
type IFileSystemDeleteFileOptions,
6367
type IFileSystemMoveOptions,
6468
type IFileSystemReadFileOptions,

0 commit comments

Comments
 (0)