addons/isl-server/proxy/rmtree.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import fs from 'node:fs';
b69ab319import path from 'node:path';
b69ab3110
b69ab3111/**
b69ab3112 * fs.promises.rm() was introduced in Node v14.14.0, so to in order to run in
b69ab3113 * Node v10, we must provide our own implementation.
b69ab3114 *
b69ab3115 * This functions like `rm -rf <file>`.
b69ab3116 */
b69ab3117export default async function rmtree(file: string): Promise<void> {
b69ab3118 let stat;
b69ab3119 try {
b69ab3120 stat = await fs.promises.lstat(file);
b69ab3121 } catch (error) {
b69ab3122 if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
b69ab3123 // If the file does not exist, nothing to do!
b69ab3124 return;
b69ab3125 } else {
b69ab3126 throw error;
b69ab3127 }
b69ab3128 }
b69ab3129
b69ab3130 if (stat.isDirectory()) {
b69ab3131 const stack = [file];
b69ab3132 while (stack.length !== 0) {
b69ab3133 // eslint-disable-next-line no-await-in-loop
b69ab3134 await rmtreeIterative(stack);
b69ab3135 }
b69ab3136 } else {
b69ab3137 await fs.promises.unlink(file);
b69ab3138 }
b69ab3139}
b69ab3140
b69ab3141/**
b69ab3142 * fs.promises.rm() was introduced in Node v14.14.0, so to in order to run in
b69ab3143 * Node v10, we must provide our own implementation.
b69ab3144 *
b69ab3145 * @param stack a list of folders to remove recursively. Folders at the end of
b69ab3146 * the array will be removed before preceding folders.
b69ab3147 */
b69ab3148async function rmtreeIterative(stack: Array<string>): Promise<void> {
b69ab3149 // This is effectively a "peek" on the stack.
b69ab3150 const folder = stack[stack.length - 1];
b69ab3151 if (folder == null) {
b69ab3152 throw new Error(`invariant violation: empty stack`);
b69ab3153 }
b69ab3154
b69ab3155 // We rely on the caller to ensure `folder` is a path to a directory rather
b69ab3156 // than stat each argument that was passed in.
b69ab3157 const files = await fs.promises.readdir(folder);
b69ab3158
b69ab3159 const stackLength = stack.length;
b69ab3160 await Promise.all(
b69ab3161 files.map(async (file: string) => {
b69ab3162 const fullPath = path.join(folder, file);
b69ab3163 const stat = await fs.promises.lstat(fullPath);
b69ab3164 if (stat.isDirectory()) {
b69ab3165 stack.push(fullPath);
b69ab3166 } else {
b69ab3167 await fs.promises.unlink(fullPath);
b69ab3168 }
b69ab3169 }),
b69ab3170 );
b69ab3171
b69ab3172 // If nothing was pushed onto the stack, then we can assume this folder is
b69ab3173 // now empty and rmdir() will succeed.
b69ab3174 if (stack.length === stackLength) {
b69ab3175 await fs.promises.rmdir(folder);
b69ab3176 stack.pop();
b69ab3177 }
b69ab3178}