Repository: spotify/backstage
File: plugins/techdocs-node/src/stages/publish/awsS3.ts (TypeScript)

Code before:
                  return this.storageClient.send(
                    new PutObjectCommand(putParams),
                  );
                }
                // For files larger than MAX_SINGLE_UPLOAD_BYTES, use multipart upload
                const upload = new Upload({
                  client: this.storageClient,
                  params,
                  // Configure miltipart upload option for better reliability
                  partSize: MAX_SINGLE_UPLOAD_BYTES,
                  queueSize: 3,
                  leavePartsOnError: false,
                });
                return upload.done();
              },
              `Upload-${params.Key}`,
              this.maxAttempts,
            );
            return result;
          } catch (error) {
            const s3Error = error as any;
            const errorName = s3Error?.name || 'Unknown';

            // Check if this is a multipart upload failure that we can handle
            if (
              fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES &&
              (errorName === 'InvalidPart' || errorName === 'NoSuchUpload')
            ) {
              this.logger.warn(
                `Multipart upload failed for ${params.Key}, Attempting simple upload fallback.`,
              );
              try {
                // Attempt simple upload as a fallback
                const fileContent = await fs.readFile(absoluteFilePath);
                const simpleParams = { ...params, Body: fileContent };
                const fallbackResult = await this.storageClient.send(
                  new PutObjectCommand(simpleParams),
                );
                this.logger.info(
                  `Simple upload fallback succeeded for ${params.Key}`,
                );
                return fallbackResult;
              } catch (fallbackError) {
                this.logger.error(
                  `Both multipart and simple upload failed for ${params.Key}: ${
                    fallbackError instanceof Error
                      ? fallbackError.message
                      : String(fallbackError)
                  }`,
                );
                // Fall through to throw original error

Review comment (suggestion):
would probably a better structure to do this check just once outside it all

```ts
if (large upload) {
  try {
    do multipart upload
    return;
  } catch (e) {
    could not do large upload, log and fall through
  }
}

try {
  do single-part upload
  return;
} catch (e) {
  more single-part specific error handling; throw
}
```

Code after:
                  const upload = new Upload({
                    client: this.storageClient,
                    params: uploadParams,
                    partSize: MAX_SINGLE_UPLOAD_BYTES,
                    queueSize: 3,
                    leavePartsOnError: false,
                  });
                  return upload.done();
                },
                `Upload-${params.Key}`,
                this.maxAttempts,
              );
              return;
            } catch (multipartError) {
              const s3Error = multipartError as any;
              const errorName = s3Error?.name || 'Unknown';

              // For specific multipart errors, attempt simple upload fallback
              if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') {
                this.logger.warn(
                  `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`,
                );
              } else {
                // Non-recoverable multipart error, throw it
                this.logger.error(
                  `Multipart upload failed for ${params.Key}: ${
                    multipartError instanceof Error
                      ? multipartError.message
                      : String(multipartError)
                  }`,
                );
                throw multipartError;
              }
            }
          }

          // Use simple upload for small files or as fallback from multipart
          try {
            const fileContent = await fs.readFile(absoluteFilePath);
            const putParams = { ...params, Body: fileContent };
            await this.retryOperation(
              () => this.storageClient.send(new PutObjectCommand(putParams)),
              `Upload-${params.Key}`,
              this.maxAttempts,
            );

            if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) {
              this.logger.info(
                `Simple upload fallback succeeded for ${params.Key}`,
              );
            }