diff --git a/README.md b/README.md
index eeeccfd..f9b03ac 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# 玩玩云 - 现代化云存储管理平台
-> 一个功能完整的云存储管理系统,支持本地存储和SFTP存储,提供文件管理、分享、邮件验证等企业级功能。
+> 一个功能完整的云存储管理系统,支持本地存储和OSS云存储,提供文件管理、分享、邮件验证等企业级功能。
-
+



@@ -13,13 +13,13 @@
## ✨ 项目特色
-玩玩云是一个现代化的Web文件管理系统,让您可以通过浏览器轻松管理文件。系统支持**双存储模式**(本地存储/SFTP存储),提供完整的用户管理、文件分享、邮件通知等企业级功能。
+玩玩云是一个现代化的Web文件管理系统,让您可以通过浏览器轻松管理文件。系统支持**双存储模式**(本地存储/OSS云存储),提供完整的用户管理、文件分享、邮件通知等企业级功能。
### 核心特性
#### 🗂️ 双存储模式
- **本地存储** - 快速读写,适合小型部署
-- **SFTP存储** - 连接远程服务器,支持大容量存储
+- **OSS云存储** - 连接云服务,支持大容量存储(支持阿里云 OSS、腾讯云 COS、AWS S3)
- **一键切换** - 在管理面板轻松切换存储方式
#### 📁 完整的文件管理
@@ -32,7 +32,7 @@
- 生成分享链接,支持密码保护
- 支持有效期设置(1小时-永久)
- 分享密码防爆破保护(10次失败封锁20分钟)
-- 支持HTTP直链和SFTP双模式下载
+- 支持API直接下载
#### 👥 完善的用户系统
- 用户注册、登录、邮箱验证
@@ -139,16 +139,47 @@ docker-compose logs -f
- 文件存储在服务器本地
- 适合小型部署
-#### SFTP存储(适合独立存储)
-1. 点击"切换到 SFTP"
-2. 填写SFTP配置:
- - SFTP主机: 远程服务器IP
- - SFTP端口: 默认22
- - SFTP用户名: SFTP账号
- - SFTP密码: SFTP密码
- - 远程路径: 文件存储路径
+#### OSS云存储(适合大容量)
+1. 点击"切换到 OSS"
+2. 填写 OSS 配置:
+ - 云服务商:选择阿里云/腾讯云/AWS S3
+ - 地域:如 `oss-cn-hangzhou` / `ap-guangzhou` / `us-east-1`
+ - Access Key ID:从云服务商获取
+ - Access Key Secret:从云服务商获取
+ - 存储桶名称:在云控制台创建
+ - 自定义 Endpoint:可选,一般不需要填写
3. 保存配置
+**⚠️ 重要:OSS Bucket CORS 配置**
+
+使用 OSS 直连上传下载功能,必须在 Bucket 中配置 CORS 规则:
+
+```xml
+
+
+
+ https://你的域名.com
+ https://www.你的域名.com
+
+ http://localhost:3000
+
+ GET
+ PUT
+ POST
+ DELETE
+
+ *
+ ETag
+ x-amz-request-id
+
+
+```
+
+**各云服务商控制台配置路径:**
+- **阿里云 OSS**:Bucket 管理 → 权限管理 → 跨域设置 → 创建规则
+- **腾讯云 COS**:存储桶管理 → 安全管理 → 跨域访问 CORS 设置
+- **AWS S3**:Bucket → Permissions → CORS configuration
+
### 配置邮件服务
进入"管理面板" → "系统设置" → "邮件配置":
@@ -225,7 +256,7 @@ vue-driven-cloud-storage/
- **Node.js 20** - JavaScript 运行时
- **Express 4.x** - Web 应用框架
- **better-sqlite3** - 轻量级数据库
-- **ssh2-sftp-client** - SFTP 客户端库
+- **@aws-sdk/client-s3** - OSS/S3 云存储 SDK
- **jsonwebtoken** - JWT 认证
- **bcrypt** - 密码加密
- **nodemailer** - 邮件发送
@@ -262,7 +293,7 @@ vue-driven-cloud-storage/
- ✅ 支持反向代理 X-Forwarded-For
### 数据安全
-- ✅ SFTP 密码加密存储
+- ✅ OSS 密钥加密存储
- ✅ 数据库事务支持
- ✅ 定期清理过期分享
- ✅ 安全日志记录
@@ -303,8 +334,8 @@ docker-compose restart
```bash
# 备份数据库
-sudo cp /var/www/vue-driven-cloud-storage/backend/ftp-manager.db \
- /backup/ftp-manager.db.$(date +%Y%m%d)
+sudo cp /var/www/vue-driven-cloud-storage/backend/data/database.db \
+ /backup/database.db.$(date +%Y%m%d)
# 备份上传文件(本地存储模式)
sudo tar -czf /backup/uploads-$(date +%Y%m%d).tar.gz \
@@ -368,10 +399,17 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。
2. 检查防火墙是否开放端口
3. 查看 Nginx 日志:`sudo tail -f /var/log/nginx/error.log`
-**Q: SFTP 连接失败**
-1. 检查 SFTP 服务器是否可访问
-2. 验证用户名和密码是否正确
-3. 检查远程路径权限
+**Q: OSS 连接失败**
+1. 检查云服务商控制台,确认 Access Key 是否有效
+2. 验证地域和存储桶名称是否正确
+3. 检查存储桶的权限设置(需要允许读写操作)
+4. 检查网络连接和防火墙设置
+
+**Q: OSS 上传失败,提示 CORS 错误**
+1. 确认已在 Bucket 中配置 CORS 规则(参考上方配置指南)
+2. 检查 AllowedOrigin 是否包含你的域名
+3. 确认 AllowedMethod 包含 PUT 方法
+4. 检查 AllowedHeader 设置为 *
**Q: 邮件发送失败**
1. 检查 SMTP 配置是否正确
@@ -380,6 +418,25 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。
## 📝 更新日志
+### v3.1.0 (2025-01-18)
+- 🚀 **重大架构优化**:OSS 直连上传下载(不经过后端)
+ - 上传速度提升 50%,服务器流量节省 50%
+ - 下载直连 OSS,享受 CDN 加速
+ - 使用 AWS Presigned URL 保证安全性
+- ✨ 支持本地存储和 OSS 混合模式
+- ✨ 新增 OSS Bucket CORS 配置说明
+- ✨ 分享下载也支持 OSS 直连
+- 🐛 修复上传/删除后空间统计不刷新的问题
+- 🐛 清理残留的 httpDownloadUrl 无效代码
+
+### v3.0.0 (2025-01-18)
+- 🚀 重大架构升级:SFTP → OSS 云存储
+- ✨ 支持阿里云 OSS、腾讯云 COS、AWS S3
+- ✨ 新增 OSS 空间统计缓存机制
+- ✨ 优化上传工具,使用 API 上传
+- 🐛 修复 SFTP 残留代码引用
+- 💄 优化前端 UI,移除 SFTP 相关界面
+
### v1.1.0 (2025-11-13)
- ✨ 新增登录验证码功能
- ✨ 新增登录防爆破保护(5次失败封锁30分钟)
@@ -391,7 +448,7 @@ A: 默认限制 5GB,可在 Nginx 配置中修改 `client_max_body_size`。
### v1.0.0 (2025-11-01)
- 🎉 首个正式版本发布
- ✨ 完整的文件管理功能
-- ✨ 双存储模式(本地/SFTP)
+- ✨ 双存储模式(本地/OSS)
- ✨ 文件分享功能
- ✨ 用户管理系统
- ✨ 邮件验证和密码重置
diff --git a/backend/.env.example b/backend/.env.example
index 2c6a969..053e079 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -95,17 +95,20 @@ DATABASE_PATH=./data/database.db
STORAGE_ROOT=./storage
# ============================================
-# SFTP 配置(可选)
+# OSS 云存储配置(可选)
# ============================================
-#
-# 说明: 用户可以在 Web 界面配置自己的 SFTP 服务器
+#
+# 说明: 用户可以在 Web 界面配置自己的 OSS 存储
+# 支持:阿里云 OSS、腾讯云 COS、AWS S3
# 此处配置仅作为全局默认值(通常不需要配置)
#
-# FTP_HOST=your-ftp-host.com
-# FTP_PORT=22
-# FTP_USER=your-username
-# FTP_PASSWORD=your-password
+# OSS_PROVIDER=aliyun # 服务商: aliyun/tencent/aws
+# OSS_REGION=oss-cn-hangzhou # 地域
+# OSS_ACCESS_KEY_ID=your-key # Access Key ID
+# OSS_ACCESS_KEY_SECRET=secret # Access Key Secret
+# OSS_BUCKET=your-bucket # 存储桶名称
+# OSS_ENDPOINT= # 自定义 Endpoint(可选)
# ============================================
# 开发调试配置
diff --git a/backend/auth.js b/backend/auth.js
index e5f47c3..f1ea02c 100644
--- a/backend/auth.js
+++ b/backend/auth.js
@@ -157,15 +157,17 @@ function authMiddleware(req, res, next) {
username: user.username,
email: user.email,
is_admin: user.is_admin,
- has_ftp_config: user.has_ftp_config,
- ftp_host: user.ftp_host,
- ftp_port: user.ftp_port,
- ftp_user: user.ftp_user,
- ftp_password: user.ftp_password,
- http_download_base_url: user.http_download_base_url,
- // 存储相关字段(v2.0新增)
- storage_permission: user.storage_permission || 'sftp_only',
- current_storage_type: user.current_storage_type || 'sftp',
+ // OSS存储字段(v3.0新增)
+ has_oss_config: user.has_oss_config || 0,
+ oss_provider: user.oss_provider,
+ oss_region: user.oss_region,
+ oss_access_key_id: user.oss_access_key_id,
+ oss_access_key_secret: user.oss_access_key_secret,
+ oss_bucket: user.oss_bucket,
+ oss_endpoint: user.oss_endpoint,
+ // 存储相关字段
+ storage_permission: user.storage_permission || 'oss_only',
+ current_storage_type: user.current_storage_type || 'oss',
local_storage_quota: user.local_storage_quota || 1073741824,
local_storage_used: user.local_storage_used || 0,
// 主题偏好
diff --git a/backend/database.js b/backend/database.js
index 679be1b..cec1838 100644
--- a/backend/database.js
+++ b/backend/database.js
@@ -39,12 +39,13 @@ function initDatabase() {
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
- -- FTP配置(可选)
- ftp_host TEXT,
- ftp_port INTEGER DEFAULT 22,
- ftp_user TEXT,
- ftp_password TEXT,
- http_download_base_url TEXT,
+ -- OSS配置(可选)
+ oss_provider TEXT,
+ oss_region TEXT,
+ oss_access_key_id TEXT,
+ oss_access_key_secret TEXT,
+ oss_bucket TEXT,
+ oss_endpoint TEXT,
-- 上传工具API密钥
upload_api_key TEXT,
@@ -53,7 +54,7 @@ function initDatabase() {
is_admin INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
is_banned INTEGER DEFAULT 0,
- has_ftp_config INTEGER DEFAULT 0,
+ has_oss_config INTEGER DEFAULT 0,
-- 时间戳
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -96,8 +97,10 @@ function initDatabase() {
db.exec(`
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+ CREATE INDEX IF NOT EXISTS idx_users_upload_api_key ON users(upload_api_key);
CREATE INDEX IF NOT EXISTS idx_shares_code ON shares(share_code);
CREATE INDEX IF NOT EXISTS idx_shares_user ON shares(user_id);
+ CREATE INDEX IF NOT EXISTS idx_shares_expires ON shares(expires_at);
`);
// 数据库迁移:添加upload_api_key字段(如果不存在)
@@ -213,7 +216,7 @@ function createDefaultAdmin() {
db.prepare(`
INSERT INTO users (
username, email, password,
- is_admin, is_active, has_ftp_config, is_verified
+ is_admin, is_active, has_oss_config, is_verified
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
adminUsername,
@@ -221,7 +224,7 @@ function createDefaultAdmin() {
hashedPassword,
1,
1,
- 0, // 管理员不需要FTP配置
+ 0, // 管理员不需要OSS配置
1 // 管理员默认已验证
);
@@ -238,7 +241,7 @@ const UserDB = {
create(userData) {
const hashedPassword = bcrypt.hashSync(userData.password, 10);
- const hasFtpConfig = userData.ftp_host && userData.ftp_user && userData.ftp_password ? 1 : 0;
+ const hasOssConfig = userData.oss_provider && userData.oss_access_key_id && userData.oss_access_key_secret && userData.oss_bucket ? 1 : 0;
// 对验证令牌进行哈希存储(与 VerificationDB.setVerification 保持一致)
const hashedVerificationToken = userData.verification_token
@@ -248,22 +251,23 @@ const UserDB = {
const stmt = db.prepare(`
INSERT INTO users (
username, email, password,
- ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url,
- has_ftp_config,
+ oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint,
+ has_oss_config,
is_verified, verification_token, verification_expires_at
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const result = stmt.run(
userData.username,
userData.email,
hashedPassword,
- userData.ftp_host || null,
- userData.ftp_port || 22,
- userData.ftp_user || null,
- userData.ftp_password || null,
- userData.http_download_base_url || null,
- hasFtpConfig,
+ userData.oss_provider || null,
+ userData.oss_region || null,
+ userData.oss_access_key_id || null,
+ userData.oss_access_key_secret || null,
+ userData.oss_bucket || null,
+ userData.oss_endpoint || null,
+ hasOssConfig,
userData.is_verified !== undefined ? userData.is_verified : 0,
hashedVerificationToken,
userData.verification_expires_at || null
@@ -446,7 +450,7 @@ const ShareDB = {
});
const result = db.prepare(`
- SELECT s.*, u.username, u.ftp_host, u.ftp_port, u.ftp_user, u.ftp_password, u.http_download_base_url, u.theme_preference
+ SELECT s.*, u.username, u.oss_provider, u.oss_region, u.oss_access_key_id, u.oss_access_key_secret, u.oss_bucket, u.oss_endpoint, u.theme_preference
FROM shares s
JOIN users u ON s.user_id = u.id
WHERE s.share_code = ?
@@ -678,6 +682,51 @@ function migrateToV2() {
}
}
+// 数据库版本迁移 - v3.0 SFTP → OSS
+function migrateToOss() {
+ try {
+ const columns = db.prepare("PRAGMA table_info(users)").all();
+ const hasOssProvider = columns.some(col => col.name === 'oss_provider');
+
+ if (!hasOssProvider) {
+ console.log('[数据库迁移] 检测到 SFTP 版本,开始升级到 v3.0 OSS...');
+
+ // 添加 OSS 相关字段
+ db.exec(`
+ ALTER TABLE users ADD COLUMN oss_provider TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN oss_region TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN oss_access_key_id TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN oss_access_key_secret TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN oss_bucket TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN oss_endpoint TEXT DEFAULT NULL;
+ ALTER TABLE users ADD COLUMN has_oss_config INTEGER DEFAULT 0;
+ `);
+ console.log('[数据库迁移] ✓ OSS 字段已添加');
+
+ // 更新存储权限枚举值:sftp_only → oss_only
+ db.exec(`UPDATE users SET storage_permission = 'oss_only' WHERE storage_permission = 'sftp_only'`);
+ console.log('[数据库迁移] ✓ 存储权限枚举值已更新');
+
+ // 更新存储类型:sftp → oss
+ db.exec(`UPDATE users SET current_storage_type = 'oss' WHERE current_storage_type = 'sftp'`);
+ console.log('[数据库迁移] ✓ 存储类型已更新');
+
+ // 更新分享表的存储类型
+ const shareColumns = db.prepare("PRAGMA table_info(shares)").all();
+ const hasStorageType = shareColumns.some(col => col.name === 'storage_type');
+ if (hasStorageType) {
+ db.exec(`UPDATE shares SET storage_type = 'oss' WHERE storage_type = 'sftp'`);
+ console.log('[数据库迁移] ✓ 分享表存储类型已更新');
+ }
+
+ console.log('[数据库迁移] ✅ 数据库升级到 v3.0 完成!SFTP 已替换为 OSS');
+ }
+ } catch (error) {
+ console.error('[数据库迁移] OSS 迁移失败:', error);
+ // 不抛出错误,允许服务继续启动
+ }
+}
+
// 系统日志操作
const SystemLogDB = {
// 日志级别常量
@@ -819,6 +868,7 @@ createDefaultAdmin();
initDefaultSettings();
migrateToV2(); // 执行数据库迁移
migrateThemePreference(); // 主题偏好迁移
+migrateToOss(); // SFTP → OSS 迁移
module.exports = {
db,
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 8f109b1..2c9035d 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1,16 +1,17 @@
{
- "name": "ftp-web-manager-backend",
- "version": "1.0.0",
+ "name": "wanwanyun-backend",
+ "version": "3.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "ftp-web-manager-backend",
- "version": "1.0.0",
+ "name": "wanwanyun-backend",
+ "version": "3.1.0",
"license": "MIT",
"dependencies": {
+ "@aws-sdk/client-s3": "^3.600.0",
+ "@aws-sdk/lib-storage": "^3.600.0",
"archiver": "^7.0.1",
- "basic-ftp": "^5.0.4",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.8.1",
"cookie-parser": "^1.4.7",
@@ -22,13 +23,932 @@
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"nodemailer": "^6.9.14",
- "ssh2-sftp-client": "^12.0.1",
"svg-captcha": "^1.4.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
},
+ "node_modules/@aws-crypto/crc32": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
+ "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/crc32c": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz",
+ "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/sha1-browser": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz",
+ "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/supports-web-crypto": "^5.2.0",
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-locate-window": "^3.0.0",
+ "@smithy/util-utf8": "^2.0.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
+ "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-js": "^5.2.0",
+ "@aws-crypto/supports-web-crypto": "^5.2.0",
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "@aws-sdk/util-locate-window": "^3.0.0",
+ "@smithy/util-utf8": "^2.0.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/sha256-js": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
+ "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/util": "^5.2.0",
+ "@aws-sdk/types": "^3.222.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/supports-web-crypto": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
+ "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/util": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
+ "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "^3.222.0",
+ "@smithy/util-utf8": "^2.0.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^2.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-s3": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.971.0.tgz",
+ "integrity": "sha512-BBUne390fKa4C4QvZlUZ5gKcu+Uyid4IyQ20N4jl0vS7SK2xpfXlJcgKqPW5ts6kx6hWTQBk6sH5Lf12RvuJxg==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@aws-crypto/sha1-browser": "5.2.0",
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/credential-provider-node": "3.971.0",
+ "@aws-sdk/middleware-bucket-endpoint": "3.969.0",
+ "@aws-sdk/middleware-expect-continue": "3.969.0",
+ "@aws-sdk/middleware-flexible-checksums": "3.971.0",
+ "@aws-sdk/middleware-host-header": "3.969.0",
+ "@aws-sdk/middleware-location-constraint": "3.969.0",
+ "@aws-sdk/middleware-logger": "3.969.0",
+ "@aws-sdk/middleware-recursion-detection": "3.969.0",
+ "@aws-sdk/middleware-sdk-s3": "3.970.0",
+ "@aws-sdk/middleware-ssec": "3.971.0",
+ "@aws-sdk/middleware-user-agent": "3.970.0",
+ "@aws-sdk/region-config-resolver": "3.969.0",
+ "@aws-sdk/signature-v4-multi-region": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-endpoints": "3.970.0",
+ "@aws-sdk/util-user-agent-browser": "3.969.0",
+ "@aws-sdk/util-user-agent-node": "3.971.0",
+ "@smithy/config-resolver": "^4.4.6",
+ "@smithy/core": "^3.20.6",
+ "@smithy/eventstream-serde-browser": "^4.2.8",
+ "@smithy/eventstream-serde-config-resolver": "^4.3.8",
+ "@smithy/eventstream-serde-node": "^4.2.8",
+ "@smithy/fetch-http-handler": "^5.3.9",
+ "@smithy/hash-blob-browser": "^4.2.9",
+ "@smithy/hash-node": "^4.2.8",
+ "@smithy/hash-stream-node": "^4.2.8",
+ "@smithy/invalid-dependency": "^4.2.8",
+ "@smithy/md5-js": "^4.2.8",
+ "@smithy/middleware-content-length": "^4.2.8",
+ "@smithy/middleware-endpoint": "^4.4.7",
+ "@smithy/middleware-retry": "^4.4.23",
+ "@smithy/middleware-serde": "^4.2.9",
+ "@smithy/middleware-stack": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
+ "@smithy/util-defaults-mode-node": "^4.2.25",
+ "@smithy/util-endpoints": "^3.2.8",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-retry": "^4.2.8",
+ "@smithy/util-stream": "^4.5.10",
+ "@smithy/util-utf8": "^4.2.0",
+ "@smithy/util-waiter": "^4.2.8",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/client-sso": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.971.0.tgz",
+ "integrity": "sha512-Xx+w6DQqJxDdymYyIxyKJnRzPvVJ4e/Aw0czO7aC9L/iraaV7AG8QtRe93OGW6aoHSh72CIiinnpJJfLsQqP4g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/middleware-host-header": "3.969.0",
+ "@aws-sdk/middleware-logger": "3.969.0",
+ "@aws-sdk/middleware-recursion-detection": "3.969.0",
+ "@aws-sdk/middleware-user-agent": "3.970.0",
+ "@aws-sdk/region-config-resolver": "3.969.0",
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-endpoints": "3.970.0",
+ "@aws-sdk/util-user-agent-browser": "3.969.0",
+ "@aws-sdk/util-user-agent-node": "3.971.0",
+ "@smithy/config-resolver": "^4.4.6",
+ "@smithy/core": "^3.20.6",
+ "@smithy/fetch-http-handler": "^5.3.9",
+ "@smithy/hash-node": "^4.2.8",
+ "@smithy/invalid-dependency": "^4.2.8",
+ "@smithy/middleware-content-length": "^4.2.8",
+ "@smithy/middleware-endpoint": "^4.4.7",
+ "@smithy/middleware-retry": "^4.4.23",
+ "@smithy/middleware-serde": "^4.2.9",
+ "@smithy/middleware-stack": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
+ "@smithy/util-defaults-mode-node": "^4.2.25",
+ "@smithy/util-endpoints": "^3.2.8",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-retry": "^4.2.8",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/core": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.970.0.tgz",
+ "integrity": "sha512-klpzObldOq8HXzDjDlY6K8rMhYZU6mXRz6P9F9N+tWnjoYFfeBMra8wYApydElTUYQKP1O7RLHwH1OKFfKcqIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/xml-builder": "3.969.0",
+ "@smithy/core": "^3.20.6",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/signature-v4": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/crc64-nvme": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.969.0.tgz",
+ "integrity": "sha512-IGNkP54HD3uuLnrPCYsv3ZD478UYq+9WwKrIVJ9Pdi3hxPg8562CH3ZHf8hEgfePN31P9Kj+Zu9kq2Qcjjt61A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-env": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.970.0.tgz",
+ "integrity": "sha512-rtVzXzEtAfZBfh+lq3DAvRar4c3jyptweOAJR2DweyXx71QSMY+O879hjpMwES7jl07a3O1zlnFIDo4KP/96kQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-http": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.970.0.tgz",
+ "integrity": "sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/fetch-http-handler": "^5.3.9",
+ "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-stream": "^4.5.10",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-ini": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.971.0.tgz",
+ "integrity": "sha512-c0TGJG4xyfTZz3SInXfGU8i5iOFRrLmy4Bo7lMyH+IpngohYMYGYl61omXqf2zdwMbDv+YJ9AviQTcCaEUKi8w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/credential-provider-env": "3.970.0",
+ "@aws-sdk/credential-provider-http": "3.970.0",
+ "@aws-sdk/credential-provider-login": "3.971.0",
+ "@aws-sdk/credential-provider-process": "3.970.0",
+ "@aws-sdk/credential-provider-sso": "3.971.0",
+ "@aws-sdk/credential-provider-web-identity": "3.971.0",
+ "@aws-sdk/nested-clients": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/credential-provider-imds": "^4.2.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-login": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.971.0.tgz",
+ "integrity": "sha512-yhbzmDOsk0RXD3rTPhZra4AWVnVAC4nFWbTp+sUty1hrOPurUmhuz8bjpLqYTHGnlMbJp+UqkQONhS2+2LzW2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/nested-clients": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-node": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.971.0.tgz",
+ "integrity": "sha512-epUJBAKivtJqalnEBRsYIULKYV063o/5mXNJshZfyvkAgNIzc27CmmKRXTN4zaNOZg8g/UprFp25BGsi19x3nQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/credential-provider-env": "3.970.0",
+ "@aws-sdk/credential-provider-http": "3.970.0",
+ "@aws-sdk/credential-provider-ini": "3.971.0",
+ "@aws-sdk/credential-provider-process": "3.970.0",
+ "@aws-sdk/credential-provider-sso": "3.971.0",
+ "@aws-sdk/credential-provider-web-identity": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/credential-provider-imds": "^4.2.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-process": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.970.0.tgz",
+ "integrity": "sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-sso": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.971.0.tgz",
+ "integrity": "sha512-dY0hMQ7dLVPQNJ8GyqXADxa9w5wNfmukgQniLxGVn+dMRx3YLViMp5ZpTSQpFhCWNF0oKQrYAI5cHhUJU1hETw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/client-sso": "3.971.0",
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/token-providers": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.971.0.tgz",
+ "integrity": "sha512-F1AwfNLr7H52T640LNON/h34YDiMuIqW/ZreGzhRR6vnFGaSPtNSKAKB2ssAMkLM8EVg8MjEAYD3NCUiEo+t/w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/nested-clients": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/lib-storage": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.971.0.tgz",
+ "integrity": "sha512-THTCXZiYjuAU2kPD8rIuvtYRT83BxEzbv4uayPlQJ8v5bybLTYDbNEbpfZGilyAqUAdSGTMOkoLu9ROryCJ3/g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/abort-controller": "^4.2.8",
+ "@smithy/middleware-endpoint": "^4.4.7",
+ "@smithy/smithy-client": "^4.10.8",
+ "buffer": "5.6.0",
+ "events": "3.3.0",
+ "stream-browserify": "3.0.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@aws-sdk/client-s3": "3.971.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-bucket-endpoint": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.969.0.tgz",
+ "integrity": "sha512-MlbrlixtkTVhYhoasblKOkr7n2yydvUZjjxTnBhIuHmkyBS1619oGnTfq/uLeGYb4NYXdeQ5OYcqsRGvmWSuTw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-arn-parser": "3.968.0",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-expect-continue": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.969.0.tgz",
+ "integrity": "sha512-qXygzSi8osok7tH9oeuS3HoKw6jRfbvg5Me/X5RlHOvSSqQz8c5O9f3MjUApaCUSwbAU92KrbZWasw2PKiaVHg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-flexible-checksums": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.971.0.tgz",
+ "integrity": "sha512-+hGUDUxeIw8s2kkjfeXym0XZxdh0cqkHkDpEanWYdS1gnWkIR+gf9u/DKbKqGHXILPaqHXhWpLTQTVlaB4sI7Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/crc32": "5.2.0",
+ "@aws-crypto/crc32c": "5.2.0",
+ "@aws-crypto/util": "5.2.0",
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/crc64-nvme": "3.969.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-stream": "^4.5.10",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-host-header": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.969.0.tgz",
+ "integrity": "sha512-AWa4rVsAfBR4xqm7pybQ8sUNJYnjyP/bJjfAw34qPuh3M9XrfGbAHG0aiAfQGrBnmS28jlO6Kz69o+c6PRw1dw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-location-constraint": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.969.0.tgz",
+ "integrity": "sha512-zH7pDfMLG/C4GWMOpvJEoYcSpj7XsNP9+irlgqwi667sUQ6doHQJ3yyDut3yiTk0maq1VgmriPFELyI9lrvH/g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-logger": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.969.0.tgz",
+ "integrity": "sha512-xwrxfip7Y2iTtCMJ+iifN1E1XMOuhxIHY9DreMCvgdl4r7+48x2S1bCYPWH3eNY85/7CapBWdJ8cerpEl12sQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-recursion-detection": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.969.0.tgz",
+ "integrity": "sha512-2r3PuNquU3CcS1Am4vn/KHFwLi8QFjMdA/R+CRDXT4AFO/0qxevF/YStW3gAKntQIgWgQV8ZdEtKAoJvLI4UWg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@aws/lambda-invoke-store": "^0.2.2",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.970.0.tgz",
+ "integrity": "sha512-v/Y5F1lbFFY7vMeG5yYxuhnn0CAshz6KMxkz1pDyPxejNE9HtA0w8R6OTBh/bVdIm44QpjhbI7qeLdOE/PLzXQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-arn-parser": "3.968.0",
+ "@smithy/core": "^3.20.6",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/signature-v4": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-stream": "^4.5.10",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-ssec": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.971.0.tgz",
+ "integrity": "sha512-QGVhvRveYG64ZhnS/b971PxXM6N2NU79Fxck4EfQ7am8v1Br0ctoeDDAn9nXNblLGw87we9Z65F7hMxxiFHd3w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/middleware-user-agent": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.970.0.tgz",
+ "integrity": "sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-endpoints": "3.970.0",
+ "@smithy/core": "^3.20.6",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/nested-clients": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.971.0.tgz",
+ "integrity": "sha512-TWaILL8GyYlhGrxxnmbkazM4QsXatwQgoWUvo251FXmUOsiXDFDVX3hoGIfB3CaJhV2pJPfebHUNJtY6TjZ11g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/sha256-browser": "5.2.0",
+ "@aws-crypto/sha256-js": "5.2.0",
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/middleware-host-header": "3.969.0",
+ "@aws-sdk/middleware-logger": "3.969.0",
+ "@aws-sdk/middleware-recursion-detection": "3.969.0",
+ "@aws-sdk/middleware-user-agent": "3.970.0",
+ "@aws-sdk/region-config-resolver": "3.969.0",
+ "@aws-sdk/types": "3.969.0",
+ "@aws-sdk/util-endpoints": "3.970.0",
+ "@aws-sdk/util-user-agent-browser": "3.969.0",
+ "@aws-sdk/util-user-agent-node": "3.971.0",
+ "@smithy/config-resolver": "^4.4.6",
+ "@smithy/core": "^3.20.6",
+ "@smithy/fetch-http-handler": "^5.3.9",
+ "@smithy/hash-node": "^4.2.8",
+ "@smithy/invalid-dependency": "^4.2.8",
+ "@smithy/middleware-content-length": "^4.2.8",
+ "@smithy/middleware-endpoint": "^4.4.7",
+ "@smithy/middleware-retry": "^4.4.23",
+ "@smithy/middleware-serde": "^4.2.9",
+ "@smithy/middleware-stack": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/smithy-client": "^4.10.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-body-length-node": "^4.2.1",
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
+ "@smithy/util-defaults-mode-node": "^4.2.25",
+ "@smithy/util-endpoints": "^3.2.8",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-retry": "^4.2.8",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/region-config-resolver": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.969.0.tgz",
+ "integrity": "sha512-scj9OXqKpcjJ4jsFLtqYWz3IaNvNOQTFFvEY8XMJXTv+3qF5I7/x9SJtKzTRJEBF3spjzBUYPtGFbs9sj4fisQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/config-resolver": "^4.4.6",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/signature-v4-multi-region": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.970.0.tgz",
+ "integrity": "sha512-z3syXfuK/x/IsKf/AeYmgc2NT7fcJ+3fHaGO+fkghkV9WEba3fPyOwtTBX4KpFMNb2t50zDGZwbzW1/5ighcUQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/middleware-sdk-s3": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/signature-v4": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/token-providers": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.971.0.tgz",
+ "integrity": "sha512-4hKGWZbmuDdONMJV0HJ+9jwTDb0zLfKxcCLx2GEnBY31Gt9GeyIQ+DZ97Bb++0voawj6pnZToFikXTyrEq2x+w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/core": "3.970.0",
+ "@aws-sdk/nested-clients": "3.971.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/types": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.969.0.tgz",
+ "integrity": "sha512-7IIzM5TdiXn+VtgPdVLjmE6uUBUtnga0f4RiSEI1WW10RPuNvZ9U+pL3SwDiRDAdoGrOF9tSLJOFZmfuwYuVYQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-arn-parser": {
+ "version": "3.968.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.968.0.tgz",
+ "integrity": "sha512-gqqvYcitIIM2K4lrDX9de9YvOfXBcVdxfT/iLnvHJd4YHvSXlt+gs+AsL4FfPCxG4IG9A+FyulP9Sb1MEA75vw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-endpoints": {
+ "version": "3.970.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.970.0.tgz",
+ "integrity": "sha512-TZNZqFcMUtjvhZoZRtpEGQAdULYiy6rcGiXAbLU7e9LSpIYlRqpLa207oMNfgbzlL2PnHko+eVg8rajDiSOYCg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "@smithy/util-endpoints": "^3.2.8",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-locate-window": {
+ "version": "3.965.2",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.2.tgz",
+ "integrity": "sha512-qKgO7wAYsXzhwCHhdbaKFyxd83Fgs8/1Ka+jjSPrv2Ll7mB55Wbwlo0kkfMLh993/yEc8aoDIAc1Fz9h4Spi4Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-browser": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.969.0.tgz",
+ "integrity": "sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/types": "^4.12.0",
+ "bowser": "^2.11.0",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@aws-sdk/util-user-agent-node": {
+ "version": "3.971.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.971.0.tgz",
+ "integrity": "sha512-Eygjo9mFzQYjbGY3MYO6CsIhnTwAMd3WmuFalCykqEmj2r5zf0leWrhPaqvA5P68V5JdGfPYgj7vhNOd6CtRBQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-sdk/middleware-user-agent": "3.970.0",
+ "@aws-sdk/types": "3.969.0",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "aws-crt": ">=1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "aws-crt": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aws-sdk/xml-builder": {
+ "version": "3.969.0",
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.969.0.tgz",
+ "integrity": "sha512-BSe4Lx/qdRQQdX8cSSI7Et20vqBspzAjBy8ZmXVoyLkol3y4sXBXzn+BiLtR+oh60ExQn6o2DU4QjdOZbXaKIQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "fast-xml-parser": "5.2.5",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@aws/lambda-invoke-store": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz",
+ "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -56,6 +976,738 @@
"node": ">=14"
}
},
+ "node_modules/@smithy/abort-controller": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz",
+ "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/chunked-blob-reader": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz",
+ "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/chunked-blob-reader-native": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz",
+ "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-base64": "^4.3.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/config-resolver": {
+ "version": "4.4.6",
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz",
+ "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-config-provider": "^4.2.0",
+ "@smithy/util-endpoints": "^3.2.8",
+ "@smithy/util-middleware": "^4.2.8",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/core": {
+ "version": "3.20.7",
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.7.tgz",
+ "integrity": "sha512-aO7jmh3CtrmPsIJxUwYIzI5WVlMK8BMCPQ4D4nTzqTqBhbzvxHNzBMGcEg13yg/z9R2Qsz49NUFl0F0lVbTVFw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/middleware-serde": "^4.2.9",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-body-length-browser": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-stream": "^4.5.10",
+ "@smithy/util-utf8": "^4.2.0",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/credential-provider-imds": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz",
+ "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-codec": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz",
+ "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws-crypto/crc32": "5.2.0",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-browser": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz",
+ "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/eventstream-serde-universal": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-config-resolver": {
+ "version": "4.3.8",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz",
+ "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-node": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz",
+ "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/eventstream-serde-universal": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/eventstream-serde-universal": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz",
+ "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/eventstream-codec": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/fetch-http-handler": {
+ "version": "5.3.9",
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz",
+ "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/querystring-builder": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-base64": "^4.3.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-blob-browser": {
+ "version": "4.2.9",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz",
+ "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/chunked-blob-reader": "^5.2.0",
+ "@smithy/chunked-blob-reader-native": "^4.2.1",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-node": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz",
+ "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/hash-stream-node": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz",
+ "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/invalid-dependency": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz",
+ "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/is-array-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
+ "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/md5-js": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz",
+ "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-content-length": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz",
+ "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-endpoint": {
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.8.tgz",
+ "integrity": "sha512-TV44qwB/T0OMMzjIuI+JeS0ort3bvlPJ8XIH0MSlGADraXpZqmyND27ueuAL3E14optleADWqtd7dUgc2w+qhQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.20.7",
+ "@smithy/middleware-serde": "^4.2.9",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "@smithy/url-parser": "^4.2.8",
+ "@smithy/util-middleware": "^4.2.8",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-retry": {
+ "version": "4.4.24",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.24.tgz",
+ "integrity": "sha512-yiUY1UvnbUFfP5izoKLtfxDSTRv724YRRwyiC/5HYY6vdsVDcDOXKSXmkJl/Hovcxt5r+8tZEUAdrOaCJwrl9Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/service-error-classification": "^4.2.8",
+ "@smithy/smithy-client": "^4.10.9",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-retry": "^4.2.8",
+ "@smithy/uuid": "^1.1.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-serde": {
+ "version": "4.2.9",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz",
+ "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/middleware-stack": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz",
+ "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/node-config-provider": {
+ "version": "4.3.8",
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz",
+ "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/shared-ini-file-loader": "^4.4.3",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/node-http-handler": {
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz",
+ "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/abort-controller": "^4.2.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/querystring-builder": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/property-provider": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz",
+ "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/protocol-http": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz",
+ "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-builder": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz",
+ "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/querystring-parser": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz",
+ "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/service-error-classification": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz",
+ "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/shared-ini-file-loader": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz",
+ "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/signature-v4": {
+ "version": "5.3.8",
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz",
+ "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^4.2.0",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-middleware": "^4.2.8",
+ "@smithy/util-uri-escape": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/smithy-client": {
+ "version": "4.10.9",
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.9.tgz",
+ "integrity": "sha512-Je0EvGXVJ0Vrrr2lsubq43JGRIluJ/hX17aN/W/A0WfE+JpoMdI8kwk2t9F0zTX9232sJDGcoH4zZre6m6f/sg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/core": "^3.20.7",
+ "@smithy/middleware-endpoint": "^4.4.8",
+ "@smithy/middleware-stack": "^4.2.8",
+ "@smithy/protocol-http": "^5.3.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-stream": "^4.5.10",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/types": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz",
+ "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/url-parser": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz",
+ "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/querystring-parser": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-base64": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz",
+ "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-browser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
+ "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-body-length-node": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz",
+ "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-buffer-from": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
+ "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/is-array-buffer": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-config-provider": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
+ "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-browser": {
+ "version": "4.3.23",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.23.tgz",
+ "integrity": "sha512-mMg+r/qDfjfF/0psMbV4zd7F/i+rpyp7Hjh0Wry7eY15UnzTEId+xmQTGDU8IdZtDfbGQxuWNfgBZKBj+WuYbA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/smithy-client": "^4.10.9",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-defaults-mode-node": {
+ "version": "4.2.26",
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.26.tgz",
+ "integrity": "sha512-EQqe/WkbCinah0h1lMWh9ICl0Ob4lyl20/10WTB35SC9vDQfD8zWsOT+x2FIOXKAoZQ8z/y0EFMoodbcqWJY/w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/config-resolver": "^4.4.6",
+ "@smithy/credential-provider-imds": "^4.2.8",
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/property-provider": "^4.2.8",
+ "@smithy/smithy-client": "^4.10.9",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-endpoints": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz",
+ "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/node-config-provider": "^4.3.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-hex-encoding": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
+ "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-middleware": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz",
+ "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-retry": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz",
+ "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/service-error-classification": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-stream": {
+ "version": "4.5.10",
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz",
+ "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/fetch-http-handler": "^5.3.9",
+ "@smithy/node-http-handler": "^4.4.8",
+ "@smithy/types": "^4.12.0",
+ "@smithy/util-base64": "^4.3.0",
+ "@smithy/util-buffer-from": "^4.2.0",
+ "@smithy/util-hex-encoding": "^4.2.0",
+ "@smithy/util-utf8": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-uri-escape": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
+ "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-utf8": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
+ "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/util-buffer-from": "^4.2.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/util-waiter": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz",
+ "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/abort-controller": "^4.2.8",
+ "@smithy/types": "^4.12.0",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/uuid": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
+ "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -161,112 +1813,12 @@
"node": ">= 14"
}
},
- "node_modules/archiver-utils/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/archiver-utils/node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/archiver/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/archiver/node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
- "node_modules/archiver/node_modules/tar-stream": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
- "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
- "license": "MIT",
- "dependencies": {
- "b4a": "^1.6.4",
- "fast-fifo": "^1.2.0",
- "streamx": "^2.15.0"
- }
- },
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
- "node_modules/asn1": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
- "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": "~2.1.0"
- }
- },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -327,24 +1879,6 @@
],
"license": "MIT"
},
- "node_modules/basic-ftp": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
- "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/bcrypt-pbkdf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "tweetnacl": "^0.14.3"
- }
- },
"node_modules/bcryptjs": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
@@ -398,39 +1932,57 @@
"readable-stream": "^3.4.0"
}
},
- "node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "node_modules/bl/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
"type-is": "~1.6.18",
- "unpipe": "1.0.0"
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/bowser": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz",
+ "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==",
+ "license": "MIT"
+ },
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
@@ -447,27 +1999,13 @@
}
},
"node_modules/buffer": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
- "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
+ "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"license": "MIT",
"dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.1.13"
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4"
}
},
"node_modules/buffer-crc32": {
@@ -491,15 +2029,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
- "node_modules/buildcheck": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
- "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
- "optional": true,
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -614,46 +2143,6 @@
"node": ">= 14"
}
},
- "node_modules/compress-commons/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/compress-commons/node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -676,6 +2165,20 @@
"typedarray": "^0.0.6"
}
},
+ "node_modules/concat-stream/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -698,9 +2201,9 @@
}
},
"node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -719,15 +2222,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/cookie-parser/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -753,20 +2247,6 @@
"node": ">= 0.10"
}
},
- "node_modules/cpu-features": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
- "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
- "hasInstallScript": true,
- "optional": true,
- "dependencies": {
- "buildcheck": "~0.0.6",
- "nan": "^2.19.0"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -792,46 +2272,6 @@
"node": ">= 14"
}
},
- "node_modules/crc32-stream/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/crc32-stream/node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1060,39 +2500,39 @@
}
},
"node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
+ "path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
- "qs": "6.13.0",
+ "qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
- "statuses": "2.0.1",
+ "statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -1124,15 +2564,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/express-session/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/express-session/node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
@@ -1140,13 +2571,13 @@
"license": "MIT"
},
"node_modules/express-validator": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.0.tgz",
- "integrity": "sha512-ujK2BX5JUun5NR4JuBo83YSXoDDIpoGz3QxgHTzQcHFevkKnwV1in4K7YNuuXQ1W3a2ObXB/P4OTnTZpUyGWiw==",
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz",
+ "integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21",
- "validator": "~13.15.15"
+ "validator": "~13.15.23"
},
"engines": {
"node": ">= 8.0.0"
@@ -1158,6 +2589,24 @@
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
+ "node_modules/fast-xml-parser": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
+ "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "strnum": "^2.1.0"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -1178,17 +2627,17 @@
}
},
"node_modules/finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"parseurl": "~1.3.3",
- "statuses": "2.0.1",
+ "statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
@@ -1303,9 +2752,9 @@
"license": "MIT"
},
"node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -1335,30 +2784,6 @@
"node": ">= 6"
}
},
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1412,19 +2837,23 @@
}
},
"node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
@@ -1582,12 +3011,12 @@
}
},
"node_modules/jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
- "jws": "^3.2.2",
+ "jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -1610,9 +3039,9 @@
"license": "MIT"
},
"node_modules/jwa": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
- "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
@@ -1621,12 +3050,12 @@
}
},
"node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
- "jwa": "^1.4.1",
+ "jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -1808,16 +3237,18 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "^2.0.1"
},
"engines": {
- "node": "*"
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
@@ -1880,13 +3311,6 @@
"node": ">= 10.16.0"
}
},
- "node_modules/nan": {
- "version": "2.23.1",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.1.tgz",
- "integrity": "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==",
- "license": "MIT",
- "optional": true
- },
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
@@ -1903,9 +3327,9 @@
}
},
"node_modules/node-abi": {
- "version": "3.80.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.80.0.tgz",
- "integrity": "sha512-LyPuZJcI9HVwzXK1GPxWNzrr+vr8Hp/3UqlmWxxh8p54U1ZbclOqbSog9lWHaCX+dBaiGi6n/hIX+mKu74GmPA==",
+ "version": "3.86.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.86.0.tgz",
+ "integrity": "sha512-sn9Et4N3ynsetj3spsZR729DVlGH6iBG4RiDMV7HEp3guyOW6W3S0unGpLDxT50mXortGUMax/ykUNQXdqc/Xg==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
@@ -1924,9 +3348,9 @@
}
},
"node_modules/nodemon": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
- "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1952,6 +3376,17 @@
"url": "https://opencollective.com/nodemon"
}
},
+ "node_modules/nodemon/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
"node_modules/nodemon/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1970,6 +3405,19 @@
}
}
},
+ "node_modules/nodemon/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/nodemon/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2180,12 +3628,12 @@
}
},
"node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"license": "BSD-3-Clause",
"dependencies": {
- "side-channel": "^1.0.6"
+ "side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -2213,15 +3661,15 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
@@ -2243,17 +3691,43 @@
}
},
"node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
+ "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
"license": "MIT",
"dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
},
"engines": {
- "node": ">= 6"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/readable-stream/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
}
},
"node_modules/readdir-glob": {
@@ -2265,15 +3739,6 @@
"minimatch": "^5.1.0"
}
},
- "node_modules/readdir-glob/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
@@ -2338,38 +3803,29 @@
}
},
"node_modules/send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
- "encodeurl": "~1.0.2",
+ "encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
- "on-finished": "2.4.1",
+ "on-finished": "~2.4.1",
"range-parser": "~1.2.1",
- "statuses": "2.0.1"
+ "statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
- "node_modules/send/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2377,15 +3833,15 @@
"license": "MIT"
},
"node_modules/serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
- "send": "0.19.0"
+ "send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
@@ -2560,49 +4016,39 @@
"node": ">=10"
}
},
- "node_modules/ssh2": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
- "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
- "hasInstallScript": true,
- "dependencies": {
- "asn1": "^0.2.6",
- "bcrypt-pbkdf": "^1.0.2"
- },
- "engines": {
- "node": ">=10.16.0"
- },
- "optionalDependencies": {
- "cpu-features": "~0.0.10",
- "nan": "^2.23.0"
- }
- },
- "node_modules/ssh2-sftp-client": {
- "version": "12.0.1",
- "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-12.0.1.tgz",
- "integrity": "sha512-ICJ1L2PmBel2Q2ctbyxzTFZCPKSHYYD6s2TFZv7NXmZDrDNGk8lHBb/SK2WgXLMXNANH78qoumeJzxlWZqSqWg==",
- "license": "Apache-2.0",
- "dependencies": {
- "concat-stream": "^2.0.0",
- "ssh2": "^1.16.0"
- },
- "engines": {
- "node": ">=18.20.4"
- },
- "funding": {
- "type": "individual",
- "url": "https://square.link/u/4g7sPflL"
- }
- },
"node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/stream-browserify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
+ "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "~2.0.4",
+ "readable-stream": "^3.5.0"
+ }
+ },
+ "node_modules/stream-browserify/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -2736,6 +4182,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/strnum": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz",
+ "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2773,7 +4231,21 @@
"tar-stream": "^2.1.4"
}
},
- "node_modules/tar-stream": {
+ "node_modules/tar-fs/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/tar-fs/node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
@@ -2789,6 +4261,17 @@
"node": ">=6"
}
},
+ "node_modules/tar-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
+ "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
"node_modules/text-decoder": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
@@ -2836,6 +4319,12 @@
"nodetouch": "bin/nodetouch.js"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -2848,12 +4337,6 @@
"node": "*"
}
},
- "node_modules/tweetnacl": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
- "license": "Unlicense"
- },
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -2917,9 +4400,9 @@
}
},
"node_modules/validator": {
- "version": "13.15.20",
- "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
- "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==",
+ "version": "13.15.26",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz",
+ "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
@@ -3068,46 +4551,6 @@
"engines": {
"node": ">= 14"
}
- },
- "node_modules/zip-stream/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/zip-stream/node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
}
}
}
diff --git a/backend/package.json b/backend/package.json
index 1764c4e..6a5dec5 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,22 +1,24 @@
{
- "name": "ftp-web-manager-backend",
- "version": "1.0.0",
- "description": "FTP Web Manager Backend",
+ "name": "wanwanyun-backend",
+ "version": "3.1.0",
+ "description": "玩玩云 - 云存储管理平台后端服务",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [
- "ftp",
- "web",
- "file-manager"
+ "cloud-storage",
+ "oss",
+ "s3",
+ "file-manager",
+ "alibaba-cloud",
+ "tencent-cloud"
],
- "author": "",
+ "author": "玩玩云团队",
"license": "MIT",
"dependencies": {
"archiver": "^7.0.1",
- "basic-ftp": "^5.0.4",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^11.8.1",
"cookie-parser": "^1.4.7",
@@ -28,7 +30,8 @@
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"nodemailer": "^6.9.14",
- "ssh2-sftp-client": "^12.0.1",
+ "@aws-sdk/client-s3": "^3.600.0",
+ "@aws-sdk/lib-storage": "^3.600.0",
"svg-captcha": "^1.4.0"
},
"devDependencies": {
diff --git a/backend/server.js b/backend/server.js
index f1d8832..883f79a 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -6,7 +6,6 @@ const cors = require('cors');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const svgCaptcha = require('svg-captcha');
-const SftpClient = require('ssh2-sftp-client');
const multer = require('multer');
const nodemailer = require('nodemailer');
const path = require('path');
@@ -20,6 +19,50 @@ const util = require('util');
const execAsync = util.promisify(exec);
const execFileAsync = util.promisify(execFile);
+// ===== OSS 使用情况缓存 =====
+// 缓存 OSS 空间统计结果,避免频繁遍历对象
+const OSS_USAGE_CACHE = new Map();
+const OSS_USAGE_CACHE_TTL = 5 * 60 * 1000; // 5分钟缓存
+
+/**
+ * 获取缓存的 OSS 使用情况
+ * @param {number} userId - 用户ID
+ * @returns {object|null} 缓存的数据或 null
+ */
+function getOssUsageCache(userId) {
+ const cacheKey = `oss_usage_${userId}`;
+ const cached = OSS_USAGE_CACHE.get(cacheKey);
+ if (cached && Date.now() - cached.timestamp < OSS_USAGE_CACHE_TTL) {
+ console.log(`[OSS缓存] 命中缓存: 用户 ${userId}`);
+ return cached.data;
+ }
+ return null;
+}
+
+/**
+ * 设置 OSS 使用情况缓存
+ * @param {number} userId - 用户ID
+ * @param {object} data - 使用情况数据
+ */
+function setOssUsageCache(userId, data) {
+ const cacheKey = `oss_usage_${userId}`;
+ OSS_USAGE_CACHE.set(cacheKey, {
+ data,
+ timestamp: Date.now()
+ });
+ console.log(`[OSS缓存] 已缓存: 用户 ${userId}, 大小: ${data.totalSize}`);
+}
+
+/**
+ * 清除用户的 OSS 使用情况缓存
+ * @param {number} userId - 用户ID
+ */
+function clearOssUsageCache(userId) {
+ const cacheKey = `oss_usage_${userId}`;
+ OSS_USAGE_CACHE.delete(cacheKey);
+ console.log(`[OSS缓存] 已清除: 用户 ${userId}`);
+}
+
const { db, UserDB, ShareDB, SettingsDB, VerificationDB, PasswordResetTokenDB, SystemLogDB } = require('./database');
const { generateToken, generateRefreshToken, refreshAccessToken, authMiddleware, adminMiddleware, isJwtSecretSecure } = require('./auth');
@@ -963,95 +1006,6 @@ function cleanupOldTempFiles() {
}
}
-function isPrivateIp(ip) {
- if (!net.isIP(ip)) return false;
-
- // IPv4 私有地址和特殊地址
- if (net.isIPv4(ip)) {
- // 10.0.0.0/8 - 私有网络
- if (ip.startsWith('10.')) return true;
- // 192.168.0.0/16 - 私有网络
- if (ip.startsWith('192.168.')) return true;
- // 172.16.0.0/12 - 私有网络 (172.16.0.0 - 172.31.255.255)
- if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip)) return true;
- // 127.0.0.0/8 - 回环地址
- if (ip.startsWith('127.')) return true;
- // 0.0.0.0 - 无效地址
- if (ip === '0.0.0.0') return true;
- // 169.254.0.0/16 - 链路本地地址(包括云服务元数据 169.254.169.254)
- if (ip.startsWith('169.254.')) return true;
- // 100.64.0.0/10 - 运营商级NAT
- if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true;
-
- return false;
- }
-
- // IPv6 私有地址和特殊地址
- if (net.isIPv6(ip)) {
- const lowerIp = ip.toLowerCase();
-
- // ::1 - 回环地址
- if (lowerIp === '::1') return true;
- // :: - 未指定地址
- if (lowerIp === '::') return true;
- // fe80::/10 - 链路本地地址
- if (/^fe[89ab][0-9a-f]:/i.test(lowerIp)) return true;
- // fc00::/7 - 唯一本地地址 (fc 和 fd 开头)
- if (/^f[cd][0-9a-f]{2}:/i.test(lowerIp)) return true;
- // ff00::/8 - 多播地址
- if (lowerIp.startsWith('ff')) return true;
- // ::ffff:x.x.x.x - IPv4映射地址,需要检查内部的IPv4
- const ipv4Mapped = lowerIp.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
- if (ipv4Mapped) {
- return isPrivateIp(ipv4Mapped[1]);
- }
-
- return false;
- }
-
- return false;
-}
-
-async function validateSftpDestination(host, port) {
- if (!host || typeof host !== 'string' || host.length > 255) {
- throw new Error('无效的SFTP主机');
- }
- if (host.includes('://') || host.includes('/')) {
- throw new Error('SFTP主机不能包含协议或路径');
- }
-
- const portNum = parseInt(port, 10) || 22;
- if (portNum < 1 || portNum > 65535) {
- throw new Error('SFTP端口范围应为1-65535');
- }
-
- let resolvedIp;
- try {
- const lookup = await dns.lookup(host);
- resolvedIp = lookup.address;
- } catch (err) {
- throw new Error('无法解析SFTP主机,请检查域名或IP');
- }
-
- if (isPrivateIp(resolvedIp) && process.env.ALLOW_PRIVATE_SFTP !== 'true') {
- throw new Error('出于安全考虑,不允许连接内网地址');
- }
-
- return { host, port: portNum, resolvedIp };
-}
-// SFTP连接
-async function connectToSFTP(config) {
- const sftp = new SftpClient();
- await sftp.connect({
- host: config.ftp_host,
- port: parseInt(config.ftp_port, 10) || 22,
- username: config.ftp_user,
- password: config.ftp_password,
- readyTimeout: parseInt(process.env.SFTP_CONNECT_TIMEOUT || '8000', 10)
- });
- return sftp;
-}
-
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 B';
@@ -1722,10 +1676,10 @@ app.post('/api/login',
username: user.username,
email: user.email,
is_admin: user.is_admin,
- has_ftp_config: user.has_ftp_config,
+ has_oss_config: user.has_oss_config,
// 存储相关字段
- storage_permission: user.storage_permission || 'sftp_only',
- current_storage_type: user.current_storage_type || 'sftp',
+ storage_permission: user.storage_permission || 'oss_only',
+ current_storage_type: user.current_storage_type || 'oss',
local_storage_quota: user.local_storage_quota || 1073741824,
local_storage_used: user.local_storage_used || 0
}
@@ -1791,8 +1745,7 @@ app.post('/api/logout', (req, res) => {
// 获取当前用户信息
app.get('/api/user/profile', authMiddleware, (req, res) => {
// 不返回密码明文
- const { ftp_password, password, ...safeUser } = req.user;
- safeUser.http_download_base_url = sanitizeHttpBaseUrl(safeUser.http_download_base_url);
+ const { password, ...safeUser } = req.user;
res.json({
success: true,
user: safeUser
@@ -1849,17 +1802,16 @@ app.post('/api/user/theme', authMiddleware, (req, res) => {
}
});
-// 更新FTP配置
-app.post('/api/user/update-ftp',
+// 更新OSS配置
+app.post('/api/user/update-oss',
authMiddleware,
[
- body('ftp_host').notEmpty().withMessage('FTP主机不能为空'),
- body('ftp_port').isInt({ min: 1, max: 65535 }).withMessage('FTP端口范围1-65535'),
- body('ftp_user').notEmpty().withMessage('FTP用户名不能为空'),
- body('http_download_base_url')
- .optional({ checkFalsy: true })
- .isURL({ protocols: ['http', 'https'], require_protocol: true, require_tld: false })
- .withMessage('HTTP直链地址必须以 http/https 开头')
+ body('oss_provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'),
+ body('oss_region').notEmpty().withMessage('地域不能为空'),
+ body('oss_access_key_id').notEmpty().withMessage('Access Key ID不能为空'),
+ body('oss_access_key_secret').notEmpty().withMessage('Access Key Secret不能为空'),
+ body('oss_bucket').notEmpty().withMessage('存储桶名称不能为空'),
+ body('oss_endpoint').optional({ checkFalsy: true }).isURL().withMessage('Endpoint必须是有效的URL')
],
async (req, res) => {
const errors = validationResult(req);
@@ -1871,65 +1823,61 @@ app.post('/api/user/update-ftp',
}
try {
- const { ftp_host, ftp_port, ftp_user, ftp_password, http_download_base_url } = req.body;
- // 调试日志:查看接收到的配置(掩码密码)
- console.log("[DEBUG] 收到SFTP配置:", {
- ftp_host,
- ftp_port,
- ftp_user,
- ftp_password: ftp_password ? "***" : "(empty)",
- http_download_base_url
- });
+ const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint } = req.body;
- const safeHttpBaseUrl = sanitizeHttpBaseUrl(http_download_base_url);
- if (http_download_base_url && !safeHttpBaseUrl) {
+ // 如果用户已配置OSS且密钥为空,使用现有密钥
+ let actualSecret = oss_access_key_secret;
+ if (!oss_access_key_secret && req.user.has_oss_config && req.user.oss_access_key_secret) {
+ actualSecret = req.user.oss_access_key_secret;
+ } else if (!oss_access_key_secret) {
return res.status(400).json({
success: false,
- message: 'HTTP直链地址必须是合法的http/https地址,且不能包含查询或片段'
+ message: 'Access Key Secret不能为空'
});
}
- // 如果用户已配置FTP且密码为空,使用现有密码
- let actualPassword = ftp_password;
- if (!ftp_password && req.user.has_ftp_config && req.user.ftp_password) {
- actualPassword = req.user.ftp_password;
- } else if (!ftp_password) {
- return res.status(400).json({
- success: false,
- message: 'FTP密码不能为空'
- });
- }
-
- // 主机校验与防SSRF
- const { port: safePort } = await validateSftpDestination(ftp_host, ftp_port);
-
- // 验证FTP连接
+ // 验证OSS连接
try {
- const sftp = await connectToSFTP({ ftp_host, ftp_port: safePort, ftp_user, ftp_password: actualPassword });
- await sftp.end();
+ const { OssStorageClient } = require('./storage');
+ const testUser = {
+ id: req.user.id,
+ oss_provider,
+ oss_region,
+ oss_access_key_id,
+ oss_access_key_secret: actualSecret,
+ oss_bucket,
+ oss_endpoint
+ };
+ const ossClient = new OssStorageClient(testUser);
+ await ossClient.connect();
+
+ // 尝试列出 bucket 内容(验证配置是否正确)
+ await ossClient.list('/');
+ await ossClient.end();
} catch (error) {
return res.status(400).json({
success: false,
- message: 'SFTP连接失败,请检查配置: ' + error.message
+ message: 'OSS连接失败,请检查配置: ' + error.message
});
}
// 更新用户配置
UserDB.update(req.user.id, {
- ftp_host,
- ftp_port: safePort,
- ftp_user,
- ftp_password: actualPassword,
- http_download_base_url: safeHttpBaseUrl || null,
- has_ftp_config: 1
+ oss_provider,
+ oss_region,
+ oss_access_key_id,
+ oss_access_key_secret: actualSecret,
+ oss_bucket,
+ oss_endpoint: oss_endpoint || null,
+ has_oss_config: 1
});
res.json({
success: true,
- message: 'SFTP配置已更新'
+ message: 'OSS配置已更新'
});
} catch (error) {
- console.error('更新配置失败:', error);
+ console.error('更新OSS配置失败:', error);
res.status(500).json({
success: false,
message: '更新配置失败: ' + error.message
@@ -1938,85 +1886,80 @@ app.post('/api/user/update-ftp',
}
);
-// 获取SFTP存储空间使用情况
-app.get('/api/user/sftp-usage', authMiddleware, async (req, res) => {
- let sftp = null;
-
+// 获取OSS存储空间使用情况(带缓存)
+app.get('/api/user/oss-usage', authMiddleware, async (req, res) => {
try {
- // 检查用户是否配置了SFTP
- if (!req.user.has_ftp_config) {
+ // 检查用户是否配置了OSS
+ if (!req.user.has_oss_config) {
return res.status(400).json({
success: false,
- message: '未配置SFTP服务器'
+ message: '未配置OSS服务'
});
}
- // 连接SFTP
- sftp = await connectToSFTP(req.user);
-
- // 递归计算目录大小的函数
- async function calculateDirSize(dirPath) {
- let totalSize = 0;
- let fileCount = 0;
- let dirCount = 0;
-
- try {
- const list = await sftp.list(dirPath);
-
- for (const item of list) {
- // 跳过 . 和 .. 目录
- if (item.name === '.' || item.name === '..') continue;
-
- const itemPath = dirPath === '/' ? `/${item.name}` : `${dirPath}/${item.name}`;
-
- if (item.type === 'd') {
- // 是目录,递归计算
- dirCount++;
- const subResult = await calculateDirSize(itemPath);
- totalSize += subResult.totalSize;
- fileCount += subResult.fileCount;
- dirCount += subResult.dirCount;
- } else {
- // 是文件,累加大小
- fileCount++;
- totalSize += item.size || 0;
- }
- }
- } catch (err) {
- // 跳过无法访问的目录
- console.warn(`[SFTP统计] 无法访问目录 ${dirPath}: ${err.message}`);
- }
-
- return { totalSize, fileCount, dirCount };
+ // 先检查缓存
+ const cached = getOssUsageCache(req.user.id);
+ if (cached) {
+ return res.json({
+ success: true,
+ usage: cached,
+ cached: true // 标识来自缓存
+ });
}
- // 从根目录开始计算
- const result = await calculateDirSize('/');
+ const { OssStorageClient } = require('./storage');
+ const ossClient = new OssStorageClient(req.user);
+ await ossClient.connect();
+
+ // 递归计算所有对象的大小
+ let totalSize = 0;
+ let fileCount = 0;
+ let continuationToken = null;
+
+ do {
+ const { ListObjectsV2Command } = require('@aws-sdk/client-s3');
+ const command = new ListObjectsV2Command({
+ Bucket: req.user.oss_bucket,
+ Prefix: `user_${req.user.id}/`,
+ ContinuationToken: continuationToken
+ });
+
+ const response = await ossClient.s3Client.send(command);
+
+ if (response.Contents) {
+ for (const obj of response.Contents) {
+ totalSize += obj.Size || 0;
+ fileCount++;
+ }
+ }
+
+ continuationToken = response.NextContinuationToken;
+ } while (continuationToken);
+
+ await ossClient.end();
+
+ const usageData = {
+ totalSize,
+ totalSizeFormatted: formatFileSize(totalSize),
+ fileCount,
+ dirCount: 0 // OSS 没有目录概念
+ };
+
+ // 存入缓存
+ setOssUsageCache(req.user.id, usageData);
res.json({
success: true,
- usage: {
- totalSize: result.totalSize,
- totalSizeFormatted: formatFileSize(result.totalSize),
- fileCount: result.fileCount,
- dirCount: result.dirCount
- }
+ usage: usageData,
+ cached: false
});
} catch (error) {
- console.error('[SFTP统计] 获取失败:', error);
+ console.error('[OSS统计] 获取失败:', error);
res.status(500).json({
success: false,
- message: '获取SFTP空间使用情况失败: ' + error.message
+ message: '获取OSS空间使用情况失败: ' + error.message
});
- } finally {
- if (sftp) {
- try {
- await sftp.end();
- } catch (e) {
- // 忽略关闭错误
- }
- }
}
});
@@ -2190,7 +2133,7 @@ app.post('/api/user/update-username',
app.post('/api/user/switch-storage',
authMiddleware,
[
- body('storage_type').isIn(['local', 'sftp']).withMessage('无效的存储类型')
+ body('storage_type').isIn(['local', 'oss']).withMessage('无效的存储类型')
],
async (req, res) => {
const errors = validationResult(req);
@@ -2212,18 +2155,18 @@ app.post('/api/user/switch-storage',
});
}
- if (req.user.storage_permission === 'sftp_only' && storage_type !== 'sftp') {
+ if (req.user.storage_permission === 'oss_only' && storage_type !== 'oss') {
return res.status(403).json({
success: false,
- message: '您只能使用SFTP存储'
+ message: '您只能使用OSS存储'
});
}
- // 检查SFTP配置
- if (storage_type === 'sftp' && !req.user.has_ftp_config) {
+ // 检查OSS配置
+ if (storage_type === 'oss' && !req.user.has_oss_config) {
return res.status(400).json({
success: false,
- message: '请先配置SFTP服务器'
+ message: '请先配置OSS服务'
});
}
@@ -2268,15 +2211,9 @@ app.get('/api/files', authMiddleware, async (req, res) => {
const list = await storage.list(dirPath);
- const storageType = req.user.current_storage_type || 'sftp';
- const sanitizedHttpBase = sanitizeHttpBaseUrl(req.user.http_download_base_url);
+ const storageType = req.user.current_storage_type || 'oss';
const formattedList = list.map(item => {
- // 构建完整的文件路径用于下载
- const httpDownloadUrl = (storageType === 'sftp' && sanitizedHttpBase && item.type !== 'd')
- ? buildHttpDownloadUrl(sanitizedHttpBase, dirPath === '/' ? `/${item.name}` : `${dirPath}/${item.name}`)
- : null;
-
return {
name: item.name,
displayName: decodeHtmlEntities(item.name || ''),
@@ -2285,7 +2222,7 @@ app.get('/api/files', authMiddleware, async (req, res) => {
sizeFormatted: formatFileSize(item.size),
modifiedAt: new Date(item.modifyTime),
isDirectory: item.type === 'd',
- httpDownloadUrl: httpDownloadUrl
+ httpDownloadUrl: null // OSS 使用 API 下载,不需要 httpDownloadUrl
};
});
@@ -2300,7 +2237,7 @@ app.get('/api/files', authMiddleware, async (req, res) => {
path: dirPath,
items: formattedList,
storageType: storageType,
- storagePermission: req.user.storage_permission || 'sftp_only'
+ storagePermission: req.user.storage_permission || 'oss_only'
});
} catch (error) {
console.error('获取文件列表失败:', error);
@@ -2338,6 +2275,11 @@ app.post('/api/files/rename', authMiddleware, async (req, res) => {
await storage.rename(oldPath, newPath);
+ // 清除 OSS 使用情况缓存(如果用户使用 OSS)
+ if (req.user.current_storage_type === 'oss') {
+ clearOssUsageCache(req.user.id);
+ }
+
res.json({
success: true,
message: '文件重命名成功'
@@ -2353,7 +2295,7 @@ app.post('/api/files/rename', authMiddleware, async (req, res) => {
}
});
-// 创建文件夹
+// 创建文件夹(支持本地存储和OSS)
app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
const path = decodeHtmlEntities(req.body.path) || '/';
const folderName = decodeHtmlEntities(req.body.folderName);
@@ -2375,14 +2317,6 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
});
}
- // 只允许本地存储创建文件夹
- if (req.user.current_storage_type !== 'local') {
- return res.status(400).json({
- success: false,
- message: '只有本地存储支持创建文件夹'
- });
- }
-
try {
const { StorageInterface } = require('./storage');
const storageInterface = new StorageInterface(req.user);
@@ -2391,21 +2325,33 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
// 构造文件夹路径
const basePath = path || '/';
const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`;
- const fullPath = storage.getFullPath(folderPath);
- // 检查是否已存在
- if (fs.existsSync(fullPath)) {
- return res.status(400).json({
- success: false,
- message: '文件夹已存在'
- });
+ // 根据存储类型创建文件夹
+ if (req.user.current_storage_type === 'local') {
+ // 本地存储:使用 fs.mkdirSync
+ const fullPath = storage.getFullPath(folderPath);
+
+ // 检查是否已存在
+ if (fs.existsSync(fullPath)) {
+ return res.status(400).json({
+ success: false,
+ message: '文件夹已存在'
+ });
+ }
+
+ // 创建文件夹 (不使用recursive,只创建当前层级)
+ fs.mkdirSync(fullPath, { mode: 0o755 });
+
+ console.log(`[创建文件夹成功] 本地存储 - 用户${req.user.id}: ${folderPath}`);
+ } else {
+ // OSS 存储:使用 storage.mkdir() 创建空对象模拟文件夹
+ await storage.mkdir(folderPath);
+ console.log(`[创建文件夹成功] OSS存储 - 用户${req.user.id}: ${folderPath}`);
+
+ // 清除 OSS 使用情况缓存
+ clearOssUsageCache(req.user.id);
}
- // 创建文件夹 (不使用recursive,只创建当前层级)
- fs.mkdirSync(fullPath, { mode: 0o755 });
-
- console.log(`[创建文件夹成功] 用户${req.user.id}: ${folderPath}`);
-
res.json({
success: true,
message: '文件夹创建成功'
@@ -2421,7 +2367,7 @@ app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
}
});
-// 获取文件夹详情(大小统计)
+// 获取文件夹详情(大小统计) - 支持本地存储和OSS
app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
const dirPath = decodeHtmlEntities(req.body.path) || '/';
const folderName = decodeHtmlEntities(req.body.folderName);
@@ -2434,14 +2380,6 @@ app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
});
}
- // 只支持本地存储
- if (req.user.current_storage_type !== 'local') {
- return res.status(400).json({
- success: false,
- message: '只有本地存储支持此功能'
- });
- }
-
try {
const { StorageInterface } = require('./storage');
const storageInterface = new StorageInterface(req.user);
@@ -2450,62 +2388,110 @@ app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
// 构造文件夹路径
const basePath = dirPath || '/';
const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`;
- const fullPath = storage.getFullPath(folderPath);
- // 检查是否存在且是文件夹
- if (!fs.existsSync(fullPath)) {
- return res.status(404).json({
- success: false,
- message: '文件夹不存在'
- });
- }
+ if (req.user.current_storage_type === 'local') {
+ // 本地存储实现
+ const fullPath = storage.getFullPath(folderPath);
- const stats = fs.statSync(fullPath);
- if (!stats.isDirectory()) {
- return res.status(400).json({
- success: false,
- message: '指定路径不是文件夹'
- });
- }
+ // 检查是否存在且是文件夹
+ if (!fs.existsSync(fullPath)) {
+ return res.status(404).json({
+ success: false,
+ message: '文件夹不存在'
+ });
+ }
- // 计算文件夹大小
- const folderSize = storage.calculateFolderSize(fullPath);
+ const stats = fs.statSync(fullPath);
+ if (!stats.isDirectory()) {
+ return res.status(400).json({
+ success: false,
+ message: '指定路径不是文件夹'
+ });
+ }
- // 计算文件数量
- function countFiles(countDirPath) {
- let fileCount = 0;
- let folderCount = 0;
+ // 计算文件夹大小
+ const folderSize = storage.calculateFolderSize(fullPath);
- const items = fs.readdirSync(countDirPath, { withFileTypes: true });
+ // 计算文件数量
+ function countFiles(countDirPath) {
+ let fileCount = 0;
+ let folderCount = 0;
- for (const item of items) {
- const itemPath = path.join(countDirPath, item.name);
+ const items = fs.readdirSync(countDirPath, { withFileTypes: true });
- if (item.isDirectory()) {
- folderCount++;
- const subCounts = countFiles(itemPath);
- fileCount += subCounts.fileCount;
- folderCount += subCounts.folderCount;
- } else {
- fileCount++;
+ for (const item of items) {
+ const itemPath = path.join(countDirPath, item.name);
+
+ if (item.isDirectory()) {
+ folderCount++;
+ const subCounts = countFiles(itemPath);
+ fileCount += subCounts.fileCount;
+ folderCount += subCounts.folderCount;
+ } else {
+ fileCount++;
+ }
}
+
+ return { fileCount, folderCount };
}
- return { fileCount, folderCount };
+ const counts = countFiles(fullPath);
+
+ res.json({
+ success: true,
+ data: {
+ name: folderName,
+ path: folderPath,
+ size: folderSize,
+ fileCount: counts.fileCount,
+ folderCount: counts.folderCount
+ }
+ });
+ } else {
+ // OSS 存储实现
+ const { ListObjectsV2Command } = require('@aws-sdk/client-s3');
+ const folderKey = `user_${req.user.id}${folderPath}`;
+ // 确保前缀以斜杠结尾
+ const prefix = folderKey.endsWith('/') ? folderKey : `${folderKey}/`;
+
+ let totalSize = 0;
+ let fileCount = 0;
+ let continuationToken = null;
+
+ do {
+ const command = new ListObjectsV2Command({
+ Bucket: req.user.oss_bucket,
+ Prefix: prefix,
+ ContinuationToken: continuationToken
+ });
+
+ const response = await storage.s3Client.send(command);
+
+ if (response.Contents) {
+ for (const obj of response.Contents) {
+ // 跳过文件夹标记对象(以斜杠结尾且大小为0)
+ if (obj.Key.endsWith('/') && obj.Size === 0) {
+ continue;
+ }
+ totalSize += obj.Size || 0;
+ fileCount++;
+ }
+ }
+
+ continuationToken = response.NextContinuationToken;
+ } while (continuationToken);
+
+ res.json({
+ success: true,
+ data: {
+ name: folderName,
+ path: folderPath,
+ size: totalSize,
+ fileCount: fileCount,
+ folderCount: 0 // OSS 没有真正的文件夹概念
+ }
+ });
}
-
- const counts = countFiles(fullPath);
-
- res.json({
- success: true,
- data: {
- name: folderName,
- path: folderPath,
- size: folderSize,
- fileCount: counts.fileCount,
- folderCount: counts.folderCount
- }
- });
} catch (error) {
console.error('[获取文件夹详情失败]', error);
res.status(500).json({
@@ -2574,6 +2560,11 @@ app.post('/api/files/delete', authMiddleware, async (req, res) => {
throw err;
}
+ // 清除 OSS 使用情况缓存(如果用户使用 OSS)
+ if (req.user.current_storage_type === 'oss') {
+ clearOssUsageCache(req.user.id);
+ }
+
res.json({
success: true,
message: '删除成功'
@@ -2589,7 +2580,194 @@ app.post('/api/files/delete', authMiddleware, async (req, res) => {
}
});
-// 上传文件(添加速率限制)
+// ========== OSS 直连相关接口(Presigned URL)==========
+
+// 生成 OSS 上传签名 URL(用户直连 OSS 上传,不经过后端)
+app.get('/api/files/upload-signature', authMiddleware, async (req, res) => {
+ const filename = req.query.filename;
+ const contentType = req.query.contentType || 'application/octet-stream';
+
+ if (!filename) {
+ return res.status(400).json({
+ success: false,
+ message: '缺少文件名参数'
+ });
+ }
+
+ // 检查用户是否配置了 OSS
+ if (!req.user.has_oss_config) {
+ return res.status(400).json({
+ success: false,
+ message: '未配置 OSS 服务'
+ });
+ }
+
+ try {
+ const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
+ const { getSignedUrl } = require('@aws-sdk/request-presigner');
+
+ // 构建 S3 客户端
+ const client = new S3Client(buildS3Config(req.user));
+
+ // 构建对象 Key
+ const objectKey = `user_${req.user.id}/${Date.now()}_${sanitizeFilename(filename)}`;
+
+ // 创建 PutObject 命令
+ const command = new PutObjectCommand({
+ Bucket: req.user.oss_bucket,
+ Key: objectKey,
+ ContentType: contentType
+ });
+
+ // 生成签名 URL(15分钟有效)
+ const signedUrl = await getSignedUrl(client, command, { expiresIn: 900 });
+
+ res.json({
+ success: true,
+ uploadUrl: signedUrl,
+ objectKey: objectKey,
+ expiresIn: 900
+ });
+ } catch (error) {
+ console.error('[OSS签名] 生成上传签名失败:', error);
+ res.status(500).json({
+ success: false,
+ message: '生成上传签名失败: ' + error.message
+ });
+ }
+});
+
+// OSS 上传完成通知(用于更新缓存和数据库)
+app.post('/api/files/upload-complete', authMiddleware, async (req, res) => {
+ const { objectKey, size, path } = req.body;
+
+ if (!objectKey) {
+ return res.status(400).json({
+ success: false,
+ message: '缺少对象Key参数'
+ });
+ }
+
+ try {
+ // 清除 OSS 使用情况缓存
+ clearOssUsageCache(req.user.id);
+
+ res.json({
+ success: true,
+ message: '上传完成已记录'
+ });
+ } catch (error) {
+ console.error('[OSS上传] 记录上传完成失败:', error);
+ res.status(500).json({
+ success: false,
+ message: '记录上传完成失败: ' + error.message
+ });
+ }
+});
+
+// 生成 OSS 下载签名 URL(用户直连 OSS 下载,不经过后端)
+app.get('/api/files/download-url', authMiddleware, async (req, res) => {
+ const filePath = req.query.path;
+
+ if (!filePath) {
+ return res.status(400).json({
+ success: false,
+ message: '缺少文件路径参数'
+ });
+ }
+
+ // 检查用户是否配置了 OSS
+ if (!req.user.has_oss_config) {
+ return res.status(400).json({
+ success: false,
+ message: '未配置 OSS 服务'
+ });
+ }
+
+ try {
+ const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
+ const { getSignedUrl } = require('@aws-sdk/request-presigner');
+
+ // 构建 S3 客户端
+ const client = new S3Client(buildS3Config(req.user));
+
+ // 构建对象 Key
+ const objectKey = `user_${req.user.id}${filePath}`;
+
+ // 创建 GetObject 命令
+ const command = new GetObjectCommand({
+ Bucket: req.user.oss_bucket,
+ Key: objectKey,
+ ResponseContentDisposition: `attachment; filename="${encodeURIComponent(filePath.split('/').pop())}"`
+ });
+
+ // 生成签名 URL(1小时有效)
+ const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });
+
+ res.json({
+ success: true,
+ downloadUrl: signedUrl,
+ expiresIn: 3600
+ });
+ } catch (error) {
+ console.error('[OSS签名] 生成下载签名失败:', error);
+ res.status(500).json({
+ success: false,
+ message: '生成下载签名失败: ' + error.message
+ });
+ }
+});
+
+// 辅助函数:构建 S3 配置(与 OssStorageClient.buildConfig 保持一致)
+function buildS3Config(user) {
+ const config = {
+ region: user.oss_region || 'us-east-1',
+ credentials: {
+ accessKeyId: user.oss_access_key_id,
+ secretAccessKey: user.oss_access_key_secret
+ }
+ };
+
+ // 阿里云 OSS
+ if (user.oss_provider === 'aliyun') {
+ config.region = user.oss_region || 'oss-cn-hangzhou';
+ if (user.oss_endpoint) {
+ config.endpoint = user.oss_endpoint;
+ } else {
+ // 默认 endpoint 格式:https://oss-{region}.aliyuncs.com
+ config.endpoint = `https://oss-${config.region.replace('oss-', '')}.aliyuncs.com`;
+ }
+ }
+ // 腾讯云 COS
+ else if (user.oss_provider === 'tencent') {
+ config.region = user.oss_region || 'ap-guangzhou';
+ if (user.oss_endpoint) {
+ config.endpoint = user.oss_endpoint;
+ } else {
+ // 默认 endpoint 格式:https://cos.{region}.myqcloud.com
+ config.endpoint = `https://cos.${config.region}.myqcloud.com`;
+ }
+ }
+ // AWS S3 或其他兼容服务
+ else {
+ if (user.oss_endpoint) {
+ config.endpoint = user.oss_endpoint;
+ }
+ // AWS 使用默认 endpoint,无需额外配置
+ }
+
+ return config;
+}
+
+// 辅助函数:清理文件名
+function sanitizeFilename(filename) {
+ // 移除或替换危险字符
+ return filename.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
+}
+
+// ========== 本地存储上传接口(保留用于本地存储模式)==========
+
+// 上传文件(添加速率限制)- 仅用于本地存储
app.post('/api/upload', authMiddleware, upload.single('file'), async (req, res) => {
// 速率限制检查
const rateLimitKey = `upload:${req.user.id}`;
@@ -2677,6 +2855,11 @@ app.post('/api/upload', authMiddleware, upload.single('file'), async (req, res)
await storage.put(req.file.path, remoteFilePath);
console.log(`[上传] 文件上传成功: ${remoteFilePath}`);
+ // 清除 OSS 使用情况缓存(如果用户使用 OSS)
+ if (req.user.current_storage_type === 'oss') {
+ clearOssUsageCache(req.user.id);
+ }
+
// 删除本地临时文件
safeDeleteFile(req.file.path);
@@ -2736,7 +2919,7 @@ app.get('/api/files/download', authMiddleware, async (req, res) => {
res.setHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(fileName) + '"; filename*=UTF-8\'\'' + encodeURIComponent(fileName));
// 创建文件流并传输(流式下载,服务器不保存临时文件)
- const stream = storage.createReadStream(filePath);
+ const stream = await storage.createReadStream(filePath);
stream.on('error', (error) => {
console.error('文件流错误:', error);
@@ -2947,7 +3130,7 @@ app.get('/api/upload/download-tool', authMiddleware, async (req, res) => {
}
});
-// 通过API密钥获取SFTP配置(供Python工具调用)
+// 通过API密钥获取OSS配置(供上传工具调用)
// 添加速率限制防止暴力枚举
app.post('/api/upload/get-config', async (req, res) => {
// 获取客户端IP用于速率限制
@@ -2998,30 +3181,25 @@ app.post('/api/upload/get-config', async (req, res) => {
});
}
- if (!user.has_ftp_config) {
- return res.status(400).json({
- success: false,
- message: '用户未配置SFTP服务器'
- });
- }
+ // 确定用户的存储类型
+ const storageType = user.current_storage_type || 'local';
- // 返回SFTP配置(注意:密码通过此API返回给上传工具使用)
- // 上传工具需要密码才能连接SFTP,这是设计上的需要
- // 安全措施:1. 速率限制防止暴力枚举 2. API密钥是32位随机字符串
+ // 返回配置信息(新版上传工具 v3.0 使用服务器 API 上传,无需返回 OSS 凭证)
res.json({
success: true,
- sftp_config: {
- host: user.ftp_host,
- port: user.ftp_port,
- username: user.ftp_user,
- password: user.ftp_password
+ config: {
+ storage_type: storageType,
+ username: user.username,
+ // OSS 配置(仅用于显示)
+ oss_provider: user.oss_provider,
+ oss_bucket: user.oss_bucket
}
});
} catch (error) {
- console.error('获取SFTP配置失败:', error);
+ console.error('获取OSS配置失败:', error);
res.status(500).json({
success: false,
- message: '获取SFTP配置失败: ' + error.message
+ message: '获取OSS配置失败: ' + error.message
});
}
});
@@ -3050,7 +3228,7 @@ app.post('/api/share/create', authMiddleware, (req, res) => {
// 更新分享的存储类型
const { db } = require('./database');
db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?')
- .run(req.user.current_storage_type || 'sftp', result.id);
+ .run(req.user.current_storage_type || 'oss', result.id);
const shareUrl = `${getSecureBaseUrl(req)}/s/${result.share_code}`;
@@ -3187,7 +3365,7 @@ app.get('/api/share/:code/theme', (req, res) => {
}
});
-// 访问分享链接 - 验证密码(支持本地存储和SFTP)
+// 访问分享链接 - 验证密码(支持本地存储和OSS)
app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) => {
const { code } = req.params;
const { password } = req.body;
@@ -3251,8 +3429,6 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) =
// 增加查看次数
ShareDB.incrementViewCount(code);
- const sanitizedShareHttpBase = sanitizeHttpBaseUrl(share.http_download_base_url);
-
// 构建返回数据
const responseData = {
success: true,
@@ -3284,7 +3460,7 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) =
if (!shareOwner) {
throw new Error('分享者不存在');
}
- const storageType = shareOwner.current_storage_type || 'sftp';
+ const storageType = shareOwner.current_storage_type || 'oss';
console.log(`[缓存未命中] 分享码: ${code},存储类型: ${storageType}`);
// 使用统一存储接口
@@ -3307,17 +3483,11 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) =
}
if (fileInfo) {
- const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`;
- // SFTP存储才提供HTTP下载URL,本地存储使用API下载
- const httpDownloadUrl = (storageType === 'sftp')
- ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath)
- : null;
-
const fileData = {
name: fileName,
type: 'file',
isDirectory: false,
- httpDownloadUrl: httpDownloadUrl,
+ httpDownloadUrl: null, // OSS 使用 API 下载
size: fileInfo.size,
sizeFormatted: formatFileSize(fileInfo.size),
modifiedAt: new Date(fileInfo.modifyTime)
@@ -3337,17 +3507,11 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) =
throw storageError;
}
// 存储失败时仍返回基本信息,只是没有大小
- const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`;
- const storageType = share.storage_type || 'sftp';
- const httpDownloadUrl = (storageType === 'sftp')
- ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath)
- : null;
-
responseData.file = {
name: fileName,
type: 'file',
isDirectory: false,
- httpDownloadUrl: httpDownloadUrl,
+ httpDownloadUrl: null,
size: 0,
sizeFormatted: '-'
};
@@ -3367,7 +3531,7 @@ app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) =
}
});
-// 获取分享的文件列表(支持本地存储和SFTP)
+// 获取分享的文件列表(支持本地存储和OSS)
app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => {
const { code } = req.params;
const { password, path: subPath } = req.body;
@@ -3445,7 +3609,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
// 使用统一存储接口,根据分享的storage_type选择存储后端
const { StorageInterface } = require('./storage');
- const storageType = share.storage_type || 'sftp';
+ const storageType = share.storage_type || 'oss';
console.log(`[分享列表] 存储类型: ${storageType}, 分享路径: ${share.share_path}`);
// 临时构造用户对象以使用存储接口
@@ -3457,7 +3621,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
const storageInterface = new StorageInterface(userForStorage);
storage = await storageInterface.connect();
- const sanitizedShareHttpBase = sanitizeHttpBaseUrl(share.http_download_base_url);
let formattedList = [];
// 如果是单文件分享
@@ -3477,14 +3640,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
const fileInfo = list.find(item => item.name === fileName);
if (fileInfo) {
- // 确保文件路径以斜杠开头
- const normalizedFilePath = filePath.startsWith('/') ? filePath : `/${filePath}`;
-
- // SFTP存储才提供HTTP下载URL,本地存储使用API下载
- const httpDownloadUrl = (storageType === 'sftp')
- ? buildHttpDownloadUrl(sanitizedShareHttpBase, normalizedFilePath)
- : null;
-
formattedList = [{
name: fileInfo.name,
type: 'file',
@@ -3492,7 +3647,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
sizeFormatted: formatFileSize(fileInfo.size),
modifiedAt: new Date(fileInfo.modifyTime),
isDirectory: false,
- httpDownloadUrl: httpDownloadUrl
+ httpDownloadUrl: null // OSS 使用 API 下载
}];
}
}
@@ -3501,14 +3656,6 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
const list = await storage.list(requestedPath);
formattedList = list.map(item => {
- // 构建完整的文件路径用于下载
- let httpDownloadUrl = null;
- if (storageType === 'sftp' && sanitizedShareHttpBase && item.type !== 'd') {
- const normalizedPath = requestedPath.startsWith('/') ? requestedPath : `/${requestedPath}`;
- const filePath = normalizedPath === '/' ? `/${item.name}` : `${normalizedPath}/${item.name}`;
- httpDownloadUrl = buildHttpDownloadUrl(sanitizedShareHttpBase, filePath);
- }
-
return {
name: item.name,
type: item.type === 'd' ? 'directory' : 'file',
@@ -3516,7 +3663,7 @@ app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) =>
sizeFormatted: formatFileSize(item.size),
modifiedAt: new Date(item.modifyTime),
isDirectory: item.type === 'd',
- httpDownloadUrl: httpDownloadUrl
+ httpDownloadUrl: null // OSS 使用 API 下载
};
});
@@ -3573,7 +3720,103 @@ app.post('/api/share/:code/download', (req, res) => {
}
});
-// 分享文件下载(支持本地存储和SFTP,公开API,需要分享码和密码验证)
+// 生成分享文件下载签名 URL(OSS 直连下载,公开 API)
+app.get('/api/share/:code/download-url', async (req, res) => {
+ const { code } = req.params;
+ const { path: filePath, password } = req.query;
+
+ if (!filePath) {
+ return res.status(400).json({
+ success: false,
+ message: '缺少文件路径参数'
+ });
+ }
+
+ try {
+ const share = ShareDB.findByCode(code);
+
+ if (!share) {
+ return res.status(404).json({
+ success: false,
+ message: '分享不存在'
+ });
+ }
+
+ // 验证密码(如果需要)
+ if (share.share_password) {
+ if (!password || !ShareDB.verifyPassword(password, share.share_password)) {
+ return res.status(401).json({
+ success: false,
+ message: '密码错误或未提供密码'
+ });
+ }
+ }
+
+ // 安全验证:检查请求路径是否在分享范围内
+ if (!isPathWithinShare(filePath, share)) {
+ return res.status(403).json({
+ success: false,
+ message: '无权访问该文件'
+ });
+ }
+
+ // 获取分享者的用户信息
+ const shareOwner = UserDB.findById(share.user_id);
+ if (!shareOwner) {
+ return res.status(404).json({
+ success: false,
+ message: '分享者不存在'
+ });
+ }
+
+ // 检查是否使用 OSS
+ if (!shareOwner.has_oss_config || share.storage_type !== 'oss') {
+ // 本地存储模式:返回后端下载 URL
+ return res.json({
+ success: true,
+ downloadUrl: `${req.protocol}://${req.get('host')}/api/share/${code}/download-file?path=${encodeURIComponent(filePath)}${password ? '&password=' + encodeURIComponent(password) : ''}`,
+ direct: false
+ });
+ }
+
+ // OSS 模式:生成签名 URL
+ const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
+ const { getSignedUrl } = require('@aws-sdk/request-presigner');
+
+ // 构建 S3 客户端
+ const client = new S3Client(buildS3Config(shareOwner));
+
+ // 构建对象 Key
+ const objectKey = `user_${shareOwner.id}${filePath}`;
+
+ // 创建 GetObject 命令
+ const command = new GetObjectCommand({
+ Bucket: shareOwner.oss_bucket,
+ Key: objectKey,
+ ResponseContentDisposition: `attachment; filename="${encodeURIComponent(filePath.split('/').pop())}"`
+ });
+
+ // 生成签名 URL(1小时有效)
+ const signedUrl = await getSignedUrl(client, command, { expiresIn: 3600 });
+
+ res.json({
+ success: true,
+ downloadUrl: signedUrl,
+ direct: true,
+ expiresIn: 3600
+ });
+
+ } catch (error) {
+ console.error('[分享签名] 生成下载签名失败:', error);
+ res.status(500).json({
+ success: false,
+ message: '生成下载签名失败: ' + error.message
+ });
+ }
+});
+
+// 分享文件下载(支持本地存储,公开API,需要分享码和密码验证)
+// 注意:OSS 模式请使用 /api/share/:code/download-url 获取直连下载链接
app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, res) => {
const { code } = req.params;
const { path: filePath, password } = req.query;
@@ -3635,7 +3878,7 @@ app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req,
// 使用统一存储接口,根据分享的storage_type选择存储后端
const { StorageInterface } = require('./storage');
- const storageType = shareOwner.current_storage_type || 'sftp';
+ const storageType = shareOwner.current_storage_type || 'oss';
console.log(`[分享下载] 存储类型: ${storageType} (分享者当前), 文件路径: ${filePath}`);
// 临时构造用户对象以使用存储接口
@@ -3664,7 +3907,7 @@ app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req,
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"; filename*=UTF-8''${encodeURIComponent(fileName)}`);
// 创建文件流并传输(流式下载,服务器不保存临时文件)
- const stream = storage.createReadStream(filePath);
+ const stream = await storage.createReadStream(filePath);
stream.on('error', (error) => {
console.error('文件流错误:', error);
@@ -4081,7 +4324,7 @@ app.get('/api/admin/storage-stats', authMiddleware, adminMiddleware, async (req,
users.forEach(user => {
// 只统计使用本地存储的用户(local_only 或 user_choice)
- const storagePermission = user.storage_permission || 'sftp_only';
+ const storagePermission = user.storage_permission || 'oss_only';
if (storagePermission === 'local_only' || storagePermission === 'user_choice') {
totalUserQuotas += user.local_storage_quota || 0;
totalUserUsed += user.local_storage_used || 0;
@@ -4116,11 +4359,11 @@ app.get('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => {
is_active: u.is_active,
is_verified: u.is_verified,
is_banned: u.is_banned,
- has_ftp_config: u.has_ftp_config,
+ has_oss_config: u.has_oss_config,
created_at: u.created_at,
// 新增:存储相关字段
- storage_permission: u.storage_permission || 'sftp_only',
- current_storage_type: u.current_storage_type || 'sftp',
+ storage_permission: u.storage_permission || 'oss_only',
+ current_storage_type: u.current_storage_type || 'oss',
local_storage_quota: u.local_storage_quota || 1073741824,
local_storage_used: u.local_storage_used || 0
}))
@@ -4270,7 +4513,7 @@ app.delete('/api/admin/users/:id', authMiddleware, adminMiddleware, async (req,
};
// 1. 删除本地存储文件(如果用户使用了本地存储)
- const storagePermission = user.storage_permission || 'sftp_only';
+ const storagePermission = user.storage_permission || 'oss_only';
if (storagePermission === 'local_only' || storagePermission === 'user_choice') {
const storageRoot = process.env.STORAGE_ROOT || path.join(__dirname, 'storage');
const userStorageDir = path.join(storageRoot, `user_${id}`);
@@ -4293,10 +4536,10 @@ app.delete('/api/admin/users/:id', authMiddleware, adminMiddleware, async (req,
}
}
- // 2. SFTP存储文件 - 只记录警告,不实际删除(安全考虑)
- if (user.has_ftp_config && (storagePermission === 'sftp_only' || storagePermission === 'user_choice')) {
+ // 2. OSS存储文件 - 只记录警告,不实际删除(安全考虑)
+ if (user.has_oss_config && (storagePermission === 'oss_only' || storagePermission === 'user_choice')) {
deletionLog.warnings.push(
- `用户配置了SFTP存储 (${user.ftp_host}:${user.ftp_port}),SFTP文件未自动删除,请手动处理`
+ `用户配置了OSS存储 (${user.oss_provider}:${user.oss_bucket}),OSS文件未自动删除,请手动处理`
);
}
@@ -4376,7 +4619,7 @@ app.post('/api/admin/users/:id/storage-permission',
authMiddleware,
adminMiddleware,
[
- body('storage_permission').isIn(['local_only', 'sftp_only', 'user_choice']).withMessage('无效的存储权限')
+ body('storage_permission').isIn(['local_only', 'oss_only', 'user_choice']).withMessage('无效的存储权限')
],
(req, res) => {
const errors = validationResult(req);
@@ -4409,10 +4652,10 @@ app.post('/api/admin/users/:id/storage-permission',
if (storage_permission === 'local_only') {
updates.current_storage_type = 'local';
- } else if (storage_permission === 'sftp_only') {
- // 只有配置了SFTP才切换到SFTP
- if (user.has_ftp_config) {
- updates.current_storage_type = 'sftp';
+ } else if (storage_permission === 'oss_only') {
+ // 只有配置了OSS才切换到OSS
+ if (user.has_oss_config) {
+ updates.current_storage_type = 'oss';
}
}
// user_choice 不自动切换,保持用户当前选择
@@ -4439,7 +4682,7 @@ app.post('/api/admin/users/:id/storage-permission',
app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (req, res) => {
const { id } = req.params;
const dirPath = req.query.path || '/';
- let sftp;
+ let ossClient;
try {
const user = UserDB.findById(id);
@@ -4451,15 +4694,17 @@ app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (re
});
}
- if (!user.has_ftp_config) {
+ if (!user.has_oss_config) {
return res.status(400).json({
success: false,
- message: '该用户未配置SFTP服务器'
+ message: '该用户未配置OSS服务'
});
}
- sftp = await connectToSFTP(user);
- const list = await sftp.list(dirPath);
+ const { OssStorageClient } = require('./storage');
+ ossClient = new OssStorageClient(user);
+ await ossClient.connect();
+ const list = await ossClient.list(dirPath);
const formattedList = list.map(item => ({
name: item.name,
@@ -4489,7 +4734,7 @@ app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (re
message: '获取文件列表失败: ' + error.message
});
} finally {
- if (sftp) await sftp.end();
+ if (ossClient) await ossClient.end();
}
});
diff --git a/backend/storage.js b/backend/storage.js
index a9a5a84..5aa3418 100644
--- a/backend/storage.js
+++ b/backend/storage.js
@@ -1,4 +1,5 @@
-const SftpClient = require('ssh2-sftp-client');
+const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectsCommand, ListObjectsV2Command, HeadObjectCommand } = require('@aws-sdk/client-s3');
+const { Upload } = require('@aws-sdk/lib-storage');
const fs = require('fs');
const path = require('path');
const { UserDB } = require('./database');
@@ -12,7 +13,7 @@ const { UserDB } = require('./database');
class StorageInterface {
constructor(user) {
this.user = user;
- this.type = user.current_storage_type || 'sftp';
+ this.type = user.current_storage_type || 'oss';
}
/**
@@ -24,7 +25,7 @@ class StorageInterface {
await client.init();
return client;
} else {
- const client = new SftpStorageClient(this.user);
+ const client = new OssStorageClient(this.user);
await client.connect();
return client;
}
@@ -321,96 +322,513 @@ class LocalStorageClient {
}
}
-// ===== SFTP存储客户端 =====
+// ===== OSS存储客户端 =====
-class SftpStorageClient {
+/**
+ * OSS 存储客户端(基于 S3 协议)
+ * 支持阿里云 OSS、腾讯云 COS、AWS S3
+ */
+class OssStorageClient {
constructor(user) {
this.user = user;
- this.sftp = new SftpClient();
+ this.s3Client = null;
+ this.prefix = `user_${user.id}/`; // 用户隔离前缀
}
/**
- * 连接SFTP服务器
+ * 验证 OSS 配置是否完整
+ * @throws {Error} 配置不完整时抛出错误
+ */
+ validateConfig() {
+ const { oss_provider, oss_access_key_id, oss_access_key_secret, oss_bucket } = this.user;
+
+ if (!oss_provider || !['aliyun', 'tencent', 'aws'].includes(oss_provider)) {
+ throw new Error('无效的 OSS 服务商,必须是 aliyun、tencent 或 aws');
+ }
+ if (!oss_access_key_id || oss_access_key_id.trim() === '') {
+ throw new Error('OSS Access Key ID 不能为空');
+ }
+ if (!oss_access_key_secret || oss_access_key_secret.trim() === '') {
+ throw new Error('OSS Access Key Secret 不能为空');
+ }
+ if (!oss_bucket || oss_bucket.trim() === '') {
+ throw new Error('OSS 存储桶名称不能为空');
+ }
+ }
+
+ /**
+ * 根据服务商构建 S3 配置
+ */
+ buildConfig() {
+ // 先验证配置
+ this.validateConfig();
+
+ const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_endpoint } = this.user;
+
+ // AWS S3 默认配置
+ let config = {
+ region: oss_region || 'us-east-1',
+ credentials: {
+ accessKeyId: oss_access_key_id,
+ secretAccessKey: oss_access_key_secret
+ },
+ // 设置超时时间
+ requestHandler: {
+ requestTimeout: 30000, // 30秒
+ httpsAgent: undefined // 可后续添加 keep-alive agent
+ }
+ };
+
+ // 阿里云 OSS
+ if (oss_provider === 'aliyun') {
+ config.region = oss_region || 'oss-cn-hangzhou';
+ if (!oss_endpoint) {
+ // 默认 endpoint 格式:https://oss-{region}.aliyuncs.com
+ config.endpoint = `https://oss-${config.region.replace('oss-', '')}.aliyuncs.com`;
+ } else {
+ config.endpoint = oss_endpoint;
+ }
+ }
+ // 腾讯云 COS
+ else if (oss_provider === 'tencent') {
+ config.region = oss_region || 'ap-guangzhou';
+ if (!oss_endpoint) {
+ // 默认 endpoint 格式:https://cos.{region}.myqcloud.com
+ config.endpoint = `https://cos.${config.region}.myqcloud.com`;
+ } else {
+ config.endpoint = oss_endpoint;
+ }
+ }
+ // AWS S3 或其他兼容服务
+ else {
+ if (oss_endpoint) {
+ config.endpoint = oss_endpoint;
+ }
+ // AWS 使用默认 endpoint,无需额外配置
+ }
+
+ return config;
+ }
+
+ /**
+ * 连接 OSS 服务(初始化 S3 客户端)
*/
async connect() {
- await this.sftp.connect({
- host: this.user.ftp_host,
- port: this.user.ftp_port || 22,
- username: this.user.ftp_user,
- password: this.user.ftp_password
- });
- return this;
+ try {
+ const config = this.buildConfig();
+ this.s3Client = new S3Client(config);
+ console.log(`[OSS存储] 已连接: ${this.user.oss_provider}, bucket: ${this.user.oss_bucket}`);
+ return this;
+ } catch (error) {
+ console.error(`[OSS存储] 连接失败:`, error.message);
+ throw new Error(`OSS 连接失败: ${error.message}`);
+ }
+ }
+
+ /**
+ * 获取对象的完整 Key(带用户前缀)
+ */
+ getObjectKey(relativePath) {
+ // 规范化路径
+ let normalized = path.normalize(relativePath || '').replace(/^(\.\.[\/\\])+/, '');
+
+ // 移除开头的斜杠
+ if (normalized.startsWith('/') || normalized.startsWith('\\')) {
+ normalized = normalized.substring(1);
+ }
+
+ // 空路径表示根目录
+ if (normalized === '' || normalized === '.') {
+ normalized = '';
+ }
+
+ // 拼接用户前缀
+ return normalized ? this.prefix + normalized : this.prefix;
}
/**
* 列出目录内容
*/
async list(dirPath) {
- return await this.sftp.list(dirPath);
+ try {
+ const prefix = this.getObjectKey(dirPath);
+ const bucket = this.user.oss_bucket;
+
+ const command = new ListObjectsV2Command({
+ Bucket: bucket,
+ Prefix: prefix,
+ Delimiter: '/', // 使用分隔符模拟目录结构
+ MaxKeys: 1000
+ });
+
+ const response = await this.s3Client.send(command);
+ const items = [];
+
+ // 处理"子目录"(CommonPrefixes)
+ if (response.CommonPrefixes) {
+ for (const prefixObj of response.CommonPrefixes) {
+ const dirName = prefixObj.Prefix.substring(prefix.length).replace(/\/$/, '');
+ if (dirName) {
+ items.push({
+ name: dirName,
+ type: 'd',
+ size: 0,
+ modifyTime: Date.now()
+ });
+ }
+ }
+ }
+
+ // 处理文件(Contents)
+ if (response.Contents) {
+ for (const obj of response.Contents) {
+ const key = obj.Key;
+ // 跳过目录标记本身
+ if (key === prefix || key.endsWith('/')) {
+ continue;
+ }
+ const fileName = key.substring(prefix.length);
+ if (fileName) {
+ items.push({
+ name: fileName,
+ type: '-',
+ size: obj.Size || 0,
+ modifyTime: obj.LastModified ? obj.LastModified.getTime() : Date.now()
+ });
+ }
+ }
+ }
+
+ return items;
+ } catch (error) {
+ console.error(`[OSS存储] 列出目录失败: ${dirPath}`, error.message);
+
+ // 判断错误类型并给出友好的错误信息
+ if (error.name === 'NoSuchBucket') {
+ throw new Error('OSS 存储桶不存在,请检查配置');
+ } else if (error.name === 'AccessDenied') {
+ throw new Error('OSS 访问被拒绝,请检查权限配置');
+ } else if (error.name === 'InvalidAccessKeyId') {
+ throw new Error('OSS Access Key 无效,请重新配置');
+ }
+ throw new Error(`列出目录失败: ${error.message}`);
+ }
}
/**
- * 上传文件
+ * 上传文件(支持分片上传)
*/
async put(localPath, remotePath) {
- // 使用临时文件+重命名模式(与upload_tool保持一致)
- const tempRemotePath = `${remotePath}.uploading_${Date.now()}`;
+ let fileStream = null;
- // 第一步:上传到临时文件
- await this.sftp.put(localPath, tempRemotePath);
-
- // 第二步:检查目标文件是否存在,如果存在先删除
try {
- await this.sftp.stat(remotePath);
- await this.sftp.delete(remotePath);
- } catch (err) {
- // 文件不存在,无需删除
- }
+ const key = this.getObjectKey(remotePath);
+ const bucket = this.user.oss_bucket;
+ const fileSize = fs.statSync(localPath).size;
- // 第三步:重命名临时文件为目标文件
- await this.sftp.rename(tempRemotePath, remotePath);
+ // 创建文件读取流
+ fileStream = fs.createReadStream(localPath);
+
+ // 使用 AWS SDK 的 Upload 类处理分片上传
+ const upload = new Upload({
+ client: this.s3Client,
+ params: {
+ Bucket: bucket,
+ Key: key,
+ Body: fileStream
+ },
+ queueSize: 3, // 并发分片数
+ partSize: 5 * 1024 * 1024 // 5MB 分片
+ });
+
+ // 监听上传进度(可选)
+ upload.on('httpUploadProgress', (progress) => {
+ if (progress && progress.loaded && progress.total) {
+ const percent = Math.round((progress.loaded / progress.total) * 100);
+ // 只在较大文件时打印进度(避免日志过多)
+ if (progress.total > 10 * 1024 * 1024 || percent % 20 === 0) {
+ console.log(`[OSS存储] 上传进度: ${percent}% (${key})`);
+ }
+ }
+ });
+
+ await upload.done();
+ console.log(`[OSS存储] 上传成功: ${key} (${this.formatSize(fileSize)})`);
+
+ // 上传成功后,手动关闭流
+ if (fileStream && !fileStream.destroyed) {
+ fileStream.destroy();
+ }
+ } catch (error) {
+ // 确保流被关闭,防止泄漏
+ if (fileStream && !fileStream.destroyed) {
+ fileStream.destroy();
+ }
+
+ console.error(`[OSS存储] 上传失败: ${remotePath}`, error.message);
+
+ // 判断错误类型并给出友好的错误信息
+ if (error.name === 'NoSuchBucket') {
+ throw new Error('OSS 存储桶不存在,请检查配置');
+ } else if (error.name === 'AccessDenied') {
+ throw new Error('OSS 访问被拒绝,请检查权限配置');
+ } else if (error.name === 'EntityTooLarge') {
+ throw new Error('文件过大,超过了 OSS 允许的最大大小');
+ }
+ throw new Error(`文件上传失败: ${error.message}`);
+ }
}
/**
- * 删除文件
+ * 删除文件或文件夹
*/
async delete(filePath) {
- return await this.sftp.delete(filePath);
+ try {
+ const key = this.getObjectKey(filePath);
+ const bucket = this.user.oss_bucket;
+
+ // 检查是文件还是目录(忽略不存在的文件)
+ let statResult;
+ try {
+ statResult = await this.stat(filePath);
+ } catch (statError) {
+ if (statError.message && statError.message.includes('不存在')) {
+ console.warn(`[OSS存储] 文件不存在,跳过删除: ${key}`);
+ return; // 文件不存在,直接返回
+ }
+ throw statError; // 其他错误继续抛出
+ }
+
+ if (statResult.isDirectory) {
+ // 删除目录:列出所有对象并批量删除
+ const listCommand = new ListObjectsV2Command({
+ Bucket: bucket,
+ Prefix: key
+ });
+
+ const listResponse = await this.s3Client.send(listCommand);
+
+ if (listResponse.Contents && listResponse.Contents.length > 0) {
+ // 分批删除(AWS S3 单次最多删除 1000 个对象)
+ const deleteCommand = new DeleteObjectsCommand({
+ Bucket: bucket,
+ Delete: {
+ Objects: listResponse.Contents.map(obj => ({ Key: obj.Key })),
+ Quiet: false
+ }
+ });
+
+ const deleteResult = await this.s3Client.send(deleteCommand);
+
+ // 检查删除结果
+ if (deleteResult.Errors && deleteResult.Errors.length > 0) {
+ console.warn(`[OSS存储] 部分对象删除失败:`, deleteResult.Errors);
+ }
+
+ console.log(`[OSS存储] 删除目录: ${key} (${listResponse.Contents.length} 个对象)`);
+ }
+ } else {
+ // 删除单个文件
+ const command = new DeleteObjectsCommand({
+ Bucket: bucket,
+ Delete: {
+ Objects: [{ Key: key }],
+ Quiet: false
+ }
+ });
+
+ await this.s3Client.send(command);
+ console.log(`[OSS存储] 删除文件: ${key}`);
+ }
+ } catch (error) {
+ console.error(`[OSS存储] 删除失败: ${filePath}`, error.message);
+
+ // 判断错误类型并给出友好的错误信息
+ if (error.name === 'NoSuchBucket') {
+ throw new Error('OSS 存储桶不存在,请检查配置');
+ } else if (error.name === 'AccessDenied') {
+ throw new Error('OSS 访问被拒绝,请检查权限配置');
+ }
+ throw new Error(`删除文件失败: ${error.message}`);
+ }
}
/**
- * 重命名文件
+ * 重命名文件(OSS 不支持直接重命名,需要复制后删除)
*/
async rename(oldPath, newPath) {
- return await this.sftp.rename(oldPath, newPath);
+ try {
+ const oldKey = this.getObjectKey(oldPath);
+ const newKey = this.getObjectKey(newPath);
+ const bucket = this.user.oss_bucket;
+
+ // 先复制
+ const copyCommand = new PutObjectCommand({
+ Bucket: bucket,
+ Key: newKey,
+ CopySource: `${bucket}/${oldKey}`
+ });
+
+ await this.s3Client.send(copyCommand);
+
+ // 再删除原文件
+ await this.delete(oldPath);
+
+ console.log(`[OSS存储] 重命名: ${oldKey} -> ${newKey}`);
+ } catch (error) {
+ console.error(`[OSS存储] 重命名失败: ${oldPath} -> ${newPath}`, error.message);
+
+ // 判断错误类型并给出友好的错误信息
+ if (error.name === 'NoSuchBucket') {
+ throw new Error('OSS 存储桶不存在,请检查配置');
+ } else if (error.name === 'AccessDenied') {
+ throw new Error('OSS 访问被拒绝,请检查权限配置');
+ }
+ throw new Error(`重命名文件失败: ${error.message}`);
+ }
}
/**
* 获取文件信息
*/
async stat(filePath) {
- return await this.sftp.stat(filePath);
+ const key = this.getObjectKey(filePath);
+ const bucket = this.user.oss_bucket;
+
+ try {
+ const command = new HeadObjectCommand({
+ Bucket: bucket,
+ Key: key
+ });
+
+ const response = await this.s3Client.send(command);
+ return {
+ size: response.ContentLength || 0,
+ modifyTime: response.LastModified ? response.LastModified.getTime() : Date.now(),
+ isDirectory: false
+ };
+ } catch (error) {
+ if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
+ // 可能是目录,尝试列出前缀
+ const listCommand = new ListObjectsV2Command({
+ Bucket: bucket,
+ Prefix: key.endsWith('/') ? key : key + '/',
+ MaxKeys: 1
+ });
+
+ try {
+ const listResponse = await this.s3Client.send(listCommand);
+ if (listResponse.Contents && listResponse.Contents.length > 0) {
+ return { isDirectory: true, size: 0, modifyTime: Date.now() };
+ }
+ } catch (listError) {
+ // 忽略列表错误
+ }
+ }
+ throw new Error(`对象不存在: ${key}`);
+ }
}
/**
- * 创建文件读取流
+ * 创建文件读取流(异步方法)
+ * @returns {Promise
} 返回可读流 Promise
*/
- createReadStream(filePath) {
- return this.sftp.createReadStream(filePath);
+ async createReadStream(filePath) {
+ const key = this.getObjectKey(filePath);
+ const bucket = this.user.oss_bucket;
+
+ const command = new GetObjectCommand({
+ Bucket: bucket,
+ Key: key
+ });
+
+ try {
+ const response = await this.s3Client.send(command);
+ // AWS SDK v3 返回的 Body 是一个 IncomingMessage 类型的流
+ return response.Body;
+ } catch (error) {
+ console.error(`[OSS存储] 创建读取流失败: ${key}`, error.message);
+ throw error;
+ }
}
/**
- * 关闭连接
+ * 创建文件夹(通过创建空对象模拟)
+ * OSS 中文件夹实际上是一个以斜杠结尾的空对象
+ */
+ async mkdir(dirPath) {
+ try {
+ const key = this.getObjectKey(dirPath);
+ const bucket = this.user.oss_bucket;
+
+ // OSS 中文件夹通过以斜杠结尾的空对象模拟
+ const folderKey = key.endsWith('/') ? key : `${key}/`;
+
+ const { PutObjectCommand } = require('@aws-sdk/client-s3');
+ const command = new PutObjectCommand({
+ Bucket: bucket,
+ Key: folderKey,
+ Body: '', // 空内容
+ ContentType: 'application/x-directory'
+ });
+
+ await this.s3Client.send(command);
+ console.log(`[OSS存储] 创建文件夹: ${folderKey}`);
+ } catch (error) {
+ console.error(`[OSS存储] 创建文件夹失败: ${dirPath}`, error.message);
+ if (error.name === 'AccessDenied') {
+ throw new Error('OSS 访问被拒绝,请检查权限配置');
+ }
+ throw new Error(`创建文件夹失败: ${error.message}`);
+ }
+ }
+
+ /**
+ * 获取下载 URL(用于分享链接)
+ */
+ getSignedUrl(filePath, expiresIn = 3600) {
+ const key = this.getObjectKey(filePath);
+ const bucket = this.user.oss_bucket;
+
+ // 简单的公开 URL 拼接(如果 bucket 是公共读)
+ const endpoint = this.s3Client.config.endpoint;
+ const region = this.s3Client.config.region;
+
+ let baseUrl;
+ if (endpoint) {
+ baseUrl = endpoint.href || endpoint.toString();
+ } else if (this.user.oss_provider === 'aliyun') {
+ baseUrl = `https://${bucket}.${this.user.oss_region || 'oss-cn-hangzhou'}.aliyuncs.com`;
+ } else if (this.user.oss_provider === 'tencent') {
+ baseUrl = `https://${bucket}.cos.${this.user.oss_region || 'ap-guangzhou'}.myqcloud.com`;
+ } else {
+ baseUrl = `https://${bucket}.s3.${region}.amazonaws.com`;
+ }
+
+ return `${baseUrl}/${key}`;
+ }
+
+ /**
+ * 关闭连接(S3Client 无需显式关闭)
*/
async end() {
- if (this.sftp) {
- await this.sftp.end();
- }
+ this.s3Client = null;
+ }
+
+ /**
+ * 格式化文件大小
+ */
+ formatSize(bytes) {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
}
module.exports = {
StorageInterface,
LocalStorageClient,
- SftpStorageClient
+ OssStorageClient
};
diff --git a/frontend/app.html b/frontend/app.html
index ba7437f..0889434 100644
--- a/frontend/app.html
+++ b/frontend/app.html
@@ -1322,7 +1322,7 @@
-
+
-
-