async uploadDir(srcDir, dstDir, options) {
    const getRemoteStatus = async (dstDir) => {
      const absDstDir = await normalizeRemotePath(this, dstDir);
      const status = await this.exists(absDstDir);
      if (status && status !== 'd') {
        throw this.fmtError(
          `Bad path ${absDstDir} Not a directory`,
          'getRemoteStatus',
          errorCode.badPath,
        );
      }
      return { remoteDir: absDstDir, remoteStatus: status };
    };

    const checkLocalStatus = (srcDir) => {
      const srcType = localExists(srcDir);
      if (!srcType) {
        throw this.fmtError(
          `Bad path: ${srcDir} not exist`,
          'getLocalStatus',
          errorCode.badPath,
        );
      }
      if (srcType !== 'd') {
        throw this.fmtError(
          `Bad path: ${srcDir}: not a directory`,
          'getLocalStatus',
          errorCode.badPath,
        );
      }
      return srcType;
    };

    const uploadFiles = async (srcDir, dstDir, fileList, useFastput) => {
      let listeners = addTempListeners(this, 'uploadFiles');

      try {
        const uploadList = [];
        for (const f of fileList) {
          const src = join(srcDir, f.name);
          const dst = `${dstDir}/${f.name}`;
          uploadList.push([src, dst]);
        }
        const uploadGroups = partition(uploadList, this.promiseLimit);
        const func = useFastput ? this._fastPut.bind(this) : this._put.bind(this);
        const uploadResults = [];
        for (const group of uploadGroups) {
          const pList = [];
          for (const [src, dst] of group) {
            pList.push(func(src, dst, null, false));
            this.client.emit('upload', { source: src, destination: dst });
          }
          const groupResults = await Promise.all(pList);
          for (const r of groupResults) {
            uploadResults.push(r);
          }
        }
        return uploadResults;
      } catch (e) {
        throw this.fmtError(`${e.message} ${srcDir} to ${dstDir}`, 'uploadFiles', e.code);
      } finally {
        removeTempListeners(this, listeners, uploadFiles);
      }
    };

    try {
      haveConnection(this, 'uploadDir');
      const { remoteDir, remoteStatus } = await getRemoteStatus(dstDir);
      checkLocalStatus(srcDir);
      if (!remoteStatus) {
        await this._mkdir(remoteDir, true);
      }
      let dirEntries = fs.readdirSync(srcDir, {
        encoding: 'utf8',
        withFileTypes: true,
      });
      if (options?.filter) {
        dirEntries = dirEntries.filter((item) =>
          options.filter(join(srcDir, item.name), item.isDirectory()),
        );
      }
      const dirUploads = dirEntries.filter((item) => item.isDirectory());
      const fileUploads = dirEntries.filter((item) => !item.isDirectory());
      await uploadFiles(srcDir, remoteDir, fileUploads, options?.useFastput);
      for (const d of dirUploads) {
        const src = join(srcDir, d.name);
        const dst = `${remoteDir}/${d.name}`;
        await this.uploadDir(src, dst, options);
      }
      return `${srcDir} uploaded to ${dstDir}`;
    } catch (err) {
      throw err.custom
        ? err
        : this.fmtError(`${err.message} ${srcDir}`, 'uploadDir', err.code);
    }
  }
@async

Upload the specified source directory to the specified destination
directory. All regular files and sub-directories are uploaded to the remote
server.
@param {String} srcDir - local source directory
@param {String} dstDir - remote destination directory
@param {Object} options - (Optional) An object with 2 supported properties,
'filter' and 'useFastput'. Filter is a function of two arguments.
The first argument is the full path of a directory entry from the directory
to be uploaded and the second argument is a boolean, which will be true if
the target path is for a directory. If the function returns true, this item
will be uploaded and excluded when it returns false. The 'useFastput' property is a
boolean value. When true, the 'fastPut()' method will be used to upload files. Default
is to use the slower, but more supported 'put()' method.

@returns {Promise<Array>}