@dd-code/oss-uploader 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env CHANGED
@@ -17,3 +17,5 @@ OBS_CDN_BASE_URL=https://hruat-cos.jtexpress.com.cn
17
17
 
18
18
  OSS_PROVIDER=huawei
19
19
 
20
+ FILE_RE_WHITE_LIST=["/index.html"]
21
+
package/dist/cli.cjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("fs/promises"),t=require("path"),o=require("micromatch"),s=require("js-yaml");(async()=>{const[{Command:e},{OssService:t},{ProviderConfigResolverImpl:o},{ConsoleReporter:s}]=await Promise.all([import("commander"),Promise.resolve().then(function(){return l}),Promise.resolve().then(function(){return h}),Promise.resolve().then(function(){return p})]),r=new e;r.name("oss-upload").description("上传本地目录到 OSS(当前为华为 OBS)").argument("<localDir>","本地目录").requiredOption("-b, --bucket <bucket>","Bucket 名称","hr-uat").option("-e, --env <env>","环境标识,如 dev/test/prod").option("--path-prefix <pathPrefix>","业务路径前缀,将拼在 base/env 后面").option("--include <pattern...>","包含的文件模式(可多次)").option("--exclude <pattern...>","排除的文件模式(可多次)").action(async(e,r)=>{const{loadObsCredentialsFromUrlIfNeeded:n}=await Promise.resolve().then(function(){return f});await n();const c=new t(new o),i=new s;try{(await c.uploadDirectory({env:r.env,bucket:r.bucket,localDir:e,pathPrefix:r.pathPrefix,include:r.include,exclude:r.exclude,reporter:i})).failed>0&&(process.exitCode=1)}catch(e){console.error("上传过程中发生错误:",e?.message||e),process.exitCode=1}}),r.parse(process.argv)})();const r=require("esdk-obs-nodejs");class n{constructor(e){this.config=e,this.client=new r({access_key_id:e.accessKey,secret_access_key:e.secretKey,server:e.endpoint})}async headObject(e,t){try{const o=await this.client.getObjectMetadata({Bucket:e,Key:t});if(o.CommonMsg.Status<=300)return{exists:!0};if(404===o.CommonMsg.Status||"NoSuchKey"===o.CommonMsg.Code||"NotFound"===o.CommonMsg.Code)return{exists:!1};throw new Error(`OBS getObjectMetadata 失败: ${o.CommonMsg.Status} ${o.CommonMsg.Code} ${o.CommonMsg.Message}`)}catch(e){const t=e&&"string"==typeof e.message?e.message:String(e),o=e?.Code??e?.code;if("NoSuchKey"===o||"NotFound"===o||t.includes("404")||t.includes("NoSuchKey"))return{exists:!1};throw e}}async putObject(e){const{bucket:t,key:o,body:s,contentType:r}=e,n=await this.client.putObject({Bucket:t,Key:o,Body:s});if(n.CommonMsg.Status>300){const{Status:e,Code:t,Message:o,RequestId:s}=n.CommonMsg,r=[e,t,o,s].filter(Boolean).join(" ");throw new Error(`OBS putObject 失败 (${r||"无详情"}). 请检查: 1) 桶名是否正确且已创建 2) OBS_ENDPOINT 是否与该桶所在区域一致,如 https://obs.xx-xx-1.myhuaweicloud.com`)}}}class c{constructor(e){this.context=e}async execute(){const{reporter:e}=this.context;e?.onStart&&await e.onStart({env:this.context.env,bucket:this.context.bucket,basePrefix:this.context.basePrefix,pathPrefix:this.context.pathPrefix,localDir:this.context.localDir,cdnBaseUrl:this.context.cdnBaseUrl});const t=await this.collectFiles(),o=this.filterFiles(t),s=await this.uploadFiles(o);return e?.onComplete&&await e.onComplete(s),s}filterFiles(e){const{include:t,exclude:s}=this.context;return e.filter(e=>function(e,t,s){let r=!0;return t&&t.length>0&&(r=o.isMatch(e,t)),!(!r||s&&s.length>0&&o.isMatch(e,s))}(e.relativePath,t,s))}buildKey(e){const t=[];return this.context.basePrefix&&t.push(this.context.basePrefix),this.context.env&&t.push(this.context.env),this.context.pathPrefix&&t.push(this.context.pathPrefix),t.push(e.replace(/\\/g,"/")),t.join("/")}async walkDir(o,s=""){const r=t.join(o,s),n=await e.readdir(r,{withFileTypes:!0}),c=[];for(const e of n){const r=t.join(s,e.name),n=t.join(o,r);e.isDirectory()?c.push(...await this.walkDir(o,r)):e.isFile()&&c.push({absolutePath:n,relativePath:r.replace(/\\/g,"/")})}return c}async reportFileResult(e){const{reporter:t}=this.context;t?.onFileResult&&await t.onFileResult(e)}}function i(e,t){const o=e.replace(/\/+$/,""),s=t.replace(/^\/+/,"");return s?`${o}/${s}`:o}class a extends c{constructor(e){super(e)}async collectFiles(){return this.walkDir(this.context.localDir)}async uploadFiles(t){const{bucket:o,storageClient:s,cdnBaseUrl:r}=this.context,n={total:t.length,success:0,failed:0,skipped:0};for(const c of t){const t=this.buildKey(c.relativePath);try{if((await s.headObject(o,t)).exists){n.skipped+=1,await this.reportFileResult({localPath:c.absolutePath,relativePath:c.relativePath,bucket:o,key:t,status:"skipped",...r&&{accessUrl:i(r,t)}});continue}const a=await e.readFile(c.absolutePath),{buffer:l,contentType:u}=this.context.fileProcessor?await this.context.fileProcessor.process({localPath:c.absolutePath,relativePath:c.relativePath,buffer:a}):{buffer:a,contentType:void 0};await s.putObject({bucket:o,key:t,body:l,contentType:u}),n.success+=1,await this.reportFileResult({localPath:c.absolutePath,relativePath:c.relativePath,bucket:o,key:t,status:"success",...r&&{accessUrl:i(r,t)}})}catch(e){n.failed+=1,await this.reportFileResult({localPath:c.absolutePath,relativePath:c.relativePath,bucket:o,key:t,status:"failed",error:e instanceof Error?e:new Error(String(e))})}}return n}}var l=Object.freeze({__proto__:null,OssService:class{constructor(e){this.providerConfigResolver=e}async uploadDirectory(e){const{env:t,bucket:o="hr-uat",localDir:s,pathPrefix:r,include:c,exclude:i,reporter:l,fileProcessor:u}=e,h=this.providerConfigResolver.resolve(),{providerConfig:p,basePrefix:d,cdnBaseUrl:f}=h,S=function(e){if("huawei"===e.type)return new n(e.options);throw new Error(`Unsupported provider type: ${e.type}`)}(p);return new a({env:t,bucket:o,localDir:s,pathPrefix:r,include:c,exclude:i,basePrefix:d,storageClient:S,reporter:l,cdnBaseUrl:f,fileProcessor:u}).execute()}}});class u{resolveHuaweiConfig(e){const t=process.env.OBS_ACCESS_KEY,o=process.env.OBS_SECRET_KEY;if(!t||!o)throw new Error("缺少华为 OBS 配置: OBS_ENDPOINT / OBS_ACCESS_KEY / OBS_SECRET_KEY");return{endpoint:"https://obs.cn-east-3.myhuaweicloud.com",accessKey:t,secretKey:o,region:"cn-east-3",basePrefix:"jhr-static",cdnBaseUrl:"https://hruat-cos.jtexpress.com.cn"}}}var h=Object.freeze({__proto__:null,ProviderConfigResolverImpl:class{constructor(){this.huaweiResolver=new u}resolve(){{const e=this.huaweiResolver.resolveHuaweiConfig();return{providerConfig:{type:"huawei",options:e},basePrefix:e.basePrefix,cdnBaseUrl:e.cdnBaseUrl}}}}});var p=Object.freeze({__proto__:null,ConsoleReporter:class{onStart(e){const{env:t,bucket:o,basePrefix:s,pathPrefix:r,localDir:n,cdnBaseUrl:c}=e;c&&console.log(` CDN 根地址: ${c}`)}onFileResult(e){const{status:t,localPath:o,key:s,error:r,accessUrl:n}=e;"success"===t?(console.log(`[SUCCESS] ${o} -> ${s}`),n&&console.log(` 访问地址: ${n}`)):"skipped"===t?(console.log(`[SKIPPED] ${o} -> ${s} (云端同名已存在)`),n&&console.log(` 访问地址: ${n}`)):(console.error(`[FAILED] ${o} -> ${s}`),r&&console.error(` Error: ${r.message}`))}onComplete(e){console.log("上传完成:"),console.log(` 总数: ${e.total}`),console.log(` 成功: ${e.success}`),console.log(` 失败: ${e.failed}`),console.log(` 跳过: ${e.skipped}`)}}});const d="OBS_CREDENTIALS_URL";var f=Object.freeze({__proto__:null,loadObsCredentialsFromUrlIfNeeded:async function(){const e="https://hr-uat.jtexpress.com.cn/hrpt/ast/static/obs-config.yaml",t=process.env.OBS_ACCESS_KEY,o=process.env.OBS_SECRET_KEY;if(t&&o)return;const r=await fetch(e);if(!r.ok)throw new Error(`拉取 OBS 凭证失败 (${r.status}): ${e}`);const n=await r.text(),c=s.load(n);if(!c||"object"!=typeof c)throw new Error(`OBS 凭证 YAML 解析结果无效,请检查 ${d} 返回内容`);const i=c.OBS_ACCESS_KEY,a=c.OBS_SECRET_KEY;if("string"==typeof i&&(process.env.OBS_ACCESS_KEY=i),"string"==typeof a&&(process.env.OBS_SECRET_KEY=a),!process.env.OBS_ACCESS_KEY||!process.env.OBS_SECRET_KEY)throw new Error(`OBS 凭证 YAML 中未找到 OBS_ACCESS_KEY 或 OBS_SECRET_KEY,请检查 ${d} 返回内容`)}});
2
+ "use strict";var e=require("cli-progress"),t=require("fs/promises"),s=require("path"),r=require("micromatch"),o=require("js-yaml");(async()=>{const[{Command:e},{uploadDirectory:t}]=await Promise.all([import("commander"),Promise.resolve().then(function(){return m})]),s=new e;s.name("oss-upload").description("上传本地目录到 OSS(当前为华为 OBS)").argument("<localDir>","本地目录").requiredOption("-b, --bucket <bucket>","Bucket 名称","hr-uat").option("-e, --env <env>","环境标识,如 dev/test/prod").option("--path-prefix <pathPrefix>","业务路径前缀,将拼在 base/env 后面").option("--include <pattern...>","包含的文件模式(可多次)").option("--exclude <pattern...>","排除的文件模式(可多次)").action(async(e,s)=>{const{loadObsCredentialsFromUrlIfNeeded:r}=await Promise.resolve().then(function(){return b});await r();try{(await t({env:s.env,bucket:s.bucket,localDir:e,pathPrefix:s.pathPrefix,include:s.include,exclude:s.exclude})).failed.length>0&&(process.exitCode=1)}catch(e){console.error("上传过程中发生错误:",e?.message||e),process.exitCode=1}}),s.parse(process.argv)})();class i{constructor(){this.progressBar=null}onStart(e){const{cdnBaseUrl:t}=e;t&&console.log(` CDN 根地址: ${t}`)}onProgress(t,s){null===this.progressBar&&(this.progressBar=new e.SingleBar({format:" 上传进度 |{bar}| {percentage}% | {value}/{total} 文件",barCompleteChar:"█",barIncompleteChar:"░",hideCursor:!0},e.Presets.shades_classic),this.progressBar.start(s,0)),this.progressBar.update(t),t>=s&&(this.progressBar.stop(),this.progressBar=null)}onFileResult(e){}onComplete(e){console.log("上传完成:"),console.log(` 总数: ${e.total}`),console.log(` 成功:\n ${e.success.join("\n")}`),console.log(`\n\n\n 失败:\n ${e.failed.join("\n")}`),console.log(`\n\n\n 跳过:\n ${e.skipped.join("\n")}`)}}const n=require("esdk-obs-nodejs");class a{constructor(e){this.config=e,this.client=new n({access_key_id:e.accessKey,secret_access_key:e.secretKey,server:e.endpoint}),this.whiteList=JSON.parse('["/index.html"]')}async headObject(e,t){try{if(this.whiteList.some(e=>t.includes(e)))return{exists:!1};const s=await this.client.getObjectMetadata({Bucket:e,Key:t});if(s.CommonMsg.Status<=300)return{exists:!0};if(404===s.CommonMsg.Status||"NoSuchKey"===s.CommonMsg.Code||"NotFound"===s.CommonMsg.Code)return{exists:!1};throw new Error(`OBS getObjectMetadata 失败: ${s.CommonMsg.Status} ${s.CommonMsg.Code} ${s.CommonMsg.Message}`)}catch(e){const t=e&&"string"==typeof e.message?e.message:String(e),s=e?.Code??e?.code;if("NoSuchKey"===s||"NotFound"===s||t.includes("404")||t.includes("NoSuchKey"))return{exists:!1};throw e}}async putObject(e){const{bucket:t,key:s,contentType:r}=e,o={Bucket:t,Key:s,ContentType:r};if(e.sourceFile)o.SourceFile=e.sourceFile;else{if(!e.body)throw new Error("putObject 需要 body 或 sourceFile");o.Body=e.body}const i=await this.client.putObject(o);if(i.CommonMsg.Status>300){const{Status:e,Code:t,Message:s,RequestId:r}=i.CommonMsg,o=[e,t,s,r].filter(Boolean).join(" ");throw new Error(`OBS putObject 失败 (${o||"无详情"}). 请检查: 1) 桶名是否正确且已创建 2) OBS_ENDPOINT 是否与该桶所在区域一致,如 https://obs.xx-xx-1.myhuaweicloud.com`)}}}class c{resolveHuaweiConfig(e){const t=process.env.OBS_ACCESS_KEY,s=process.env.OBS_SECRET_KEY;if(!t||!s)throw new Error("缺少华为 OBS 配置: OBS_ENDPOINT / OBS_ACCESS_KEY / OBS_SECRET_KEY");return{endpoint:"https://obs.cn-east-3.myhuaweicloud.com",accessKey:t,secretKey:s,region:"cn-east-3",basePrefix:"jhr-static",cdnBaseUrl:"https://hruat-cos.jtexpress.com.cn"}}}class l{constructor(){this.huaweiResolver=new c}resolve(){{const e=this.huaweiResolver.resolveHuaweiConfig();return{providerConfig:{type:"huawei",options:e},basePrefix:e.basePrefix,cdnBaseUrl:e.cdnBaseUrl}}}}function u(e,t){const s=e.replace(/\/+$/,""),r=t.replace(/^\/+/,"");return r?`${s}/${r}`:s}const p={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",ico:"image/x-icon",bmp:"image/bmp",tiff:"image/tiff",tif:"image/tiff",js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",htm:"text/html",txt:"text/plain",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",eot:"application/vnd.ms-fontobject"};function h(e){const t=s.extname(e).slice(1).toLowerCase();return t?p[t]:void 0}class d{constructor(e,t,s,r,o){this.storageClient=e,this.basePrefix=t,this.cdnBaseUrl=s,this.reporter=r,this.fileProcessor=o}async uploadDirectory(e){const t=await this.collectFiles(e.localDir,e.include,e.exclude),s={total:t.length,success:[],failed:[],skipped:[]};this.reporter?.onStart&&await this.reporter.onStart({env:e.env,bucket:e.bucket,basePrefix:this.basePrefix,pathPrefix:e.pathPrefix,localDir:e.localDir,cdnBaseUrl:this.cdnBaseUrl});for(const r of t){const o=await this.uploadOneFile(r,e);s[o.status].push(o.key);const i=s.success.length+s.failed.length+s.skipped.length;this.reporter?.onProgress&&await this.reporter.onProgress(i,t.length)}return this.reporter?.onComplete&&await this.reporter.onComplete(s),s}async collectFiles(e,t,s){return(await this.walkDir(e)).filter(e=>function(e,t,s){let o=!0;return t&&t.length>0&&(o=r.isMatch(e,t)),!(!o||s&&s.length>0&&r.isMatch(e,s))}(e.relativePath,t,s))}async uploadOneFile(e,s){const r=this.buildKey(this.basePrefix,s.env,s.pathPrefix,e.relativePath);try{if((await this.storageClient.headObject(s.bucket,r)).exists)return await this.reportFile(e,s.bucket,r,"skipped"),{status:"skipped",key:r};if(this.fileProcessor){const o=await t.readFile(e.absolutePath),{buffer:i,contentType:n}=await this.fileProcessor.process({localPath:e.absolutePath,relativePath:e.relativePath,buffer:o});await this.storageClient.putObject({bucket:s.bucket,key:r,body:i,contentType:n})}else await this.storageClient.putObject({bucket:s.bucket,key:r,sourceFile:e.absolutePath,contentType:h(e.relativePath)});return await this.reportFile(e,s.bucket,r,"success"),{status:"success",key:r}}catch(t){const o=t instanceof Error?t:new Error(String(t));return await this.reportFile(e,s.bucket,r,"failed",o),{status:"failed",key:r}}}buildKey(e,t,s,r){const o=[];return e&&o.push(e),t&&o.push(t),s&&o.push(s),o.push((r??"").replace(/\\/g,"/")),o.join("/")}async walkDir(e,r=""){const o=s.join(e,r),i=await t.readdir(o,{withFileTypes:!0}),n=[];for(const t of i){const o=s.join(r,t.name),i=s.join(e,o);t.isDirectory()?n.push(...await this.walkDir(e,o)):t.isFile()&&n.push({absolutePath:i,relativePath:o.replace(/\\/g,"/")})}return n}async reportFile(e,t,s,r,o){if(!this.reporter?.onFileResult)return;const i={localPath:e.absolutePath,relativePath:e.relativePath,bucket:t,key:s,status:r,...this.cdnBaseUrl&&{accessUrl:u(this.cdnBaseUrl,s)},...o&&{error:o}};await this.reporter.onFileResult(i)}}class f extends d{constructor(e,t,s,r,o){super(e,t,s,r,o)}}class g{constructor(){this.resolver=new l}async uploadDirectory(e){const t=this.resolver.resolve(),s=function(e){if("huawei"===e.type)return new a(e.options);throw new Error(`Unsupported provider type: ${e.type}`)}(t.providerConfig),r=e.reporter??new i;return new f(s,t.basePrefix,t.cdnBaseUrl,r,e.fileProcessor).uploadDirectory({bucket:e.bucket,localDir:e.localDir,env:e.env,pathPrefix:e.pathPrefix,include:e.include,exclude:e.exclude})}}var m=Object.freeze({__proto__:null,OssService:g,uploadDirectory:async function(e){return(new g).uploadDirectory(e)}});const w="OBS_CREDENTIALS_URL";var b=Object.freeze({__proto__:null,loadObsCredentialsFromUrlIfNeeded:async function(){const e="https://hr-uat.jtexpress.com.cn/hrpt/ast/static/obs-config.yaml",t=process.env.OBS_ACCESS_KEY,s=process.env.OBS_SECRET_KEY;if(t&&s)return;const r=await fetch(e);if(!r.ok)throw new Error(`拉取 OBS 凭证失败 (${r.status}): ${e}`);const i=await r.text(),n=o.load(i);if(!n||"object"!=typeof n)throw new Error(`OBS 凭证 YAML 解析结果无效,请检查 ${w} 返回内容`);const a=n.OBS_ACCESS_KEY,c=n.OBS_SECRET_KEY;if("string"==typeof a&&(process.env.OBS_ACCESS_KEY=a),"string"==typeof c&&(process.env.OBS_SECRET_KEY=c),!process.env.OBS_ACCESS_KEY||!process.env.OBS_SECRET_KEY)throw new Error(`OBS 凭证 YAML 中未找到 OBS_ACCESS_KEY 或 OBS_SECRET_KEY,请检查 ${w} 返回内容`)}});
@@ -1,17 +1,9 @@
1
- import { UploadFlowTemplate, UploadContext, LocalFile } from './UploadFlowTemplate';
2
- import { UploadSummary } from './reporter';
1
+ import { UploadService } from './UploadService';
3
2
  /**
4
- * 目录上传流程的具体实现:遍历本地目录,按 key 规则上传,云端同名则跳过。
5
- * 规则:先 headObject,exists 则 skipped,否则 putObject。
3
+ * 目录上传的具体实现:继承 UploadService,按目录遍历上传,云端已存在则跳过。
4
+ * 未覆盖任何方法时行为与基类一致;子类可覆盖 collectFiles、uploadOneFile 等。
6
5
  */
7
- export declare class DirectoryUploadFlow extends UploadFlowTemplate {
8
- constructor(context: UploadContext);
9
- /** 递归收集 localDir 下所有文件 */
10
- protected collectFiles(): Promise<LocalFile[]>;
11
- /**
12
- * 逐个文件上传:先 head 判断是否存在,存在则跳过,否则读取本地文件并 putObject。
13
- * 每个文件的结果通过 reportFileResult 交给 Reporter。
14
- */
15
- protected uploadFiles(files: LocalFile[]): Promise<UploadSummary>;
6
+ export declare class DirectoryUploadFlow extends UploadService {
7
+ constructor(storageClient: UploadService['storageClient'], basePrefix: string, cdnBaseUrl?: string, reporter?: UploadService['reporter'], fileProcessor?: UploadService['fileProcessor']);
16
8
  }
17
9
  //# sourceMappingURL=DirectoryUploadFlow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DirectoryUploadFlow.d.ts","sourceRoot":"","sources":["../../src/core/DirectoryUploadFlow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,kBAAkB;gBAC7C,OAAO,EAAE,aAAa;IAIlC,0BAA0B;cACV,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAIpD;;;OAGG;cACa,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;CAuExE"}
1
+ {"version":3,"file":"DirectoryUploadFlow.d.ts","sourceRoot":"","sources":["../../src/core/DirectoryUploadFlow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA+B,MAAM,iBAAiB,CAAC;AAE7E;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,aAAa;gBAElD,aAAa,EAAE,aAAa,CAAC,eAAe,CAAC,EAC7C,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,EACpC,aAAa,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC;CAIjD"}
@@ -2,11 +2,14 @@
2
2
  * 存储客户端统一接口(中间件层与各云厂商 Adapter 的契约)。
3
3
  * 实现类负责将 putObject/headObject 翻译为具体云厂商的 API 调用。
4
4
  */
5
- /** 上传单个对象时的参数 */
5
+ /** 上传单个对象时的参数(body 与 sourceFile 二选一:sourceFile 为本地路径时由 SDK 直接读文件上传,避免先读成 Buffer) */
6
6
  export interface PutObjectOptions {
7
7
  bucket: string;
8
8
  key: string;
9
- body: Buffer | NodeJS.ReadableStream;
9
+ /** 文件内容,与 sourceFile 二选一 */
10
+ body?: Buffer | NodeJS.ReadableStream;
11
+ /** 本地文件路径,与 body 二选一;设置时由实现方用 SDK 的 SourceFile 等方式直接上传 */
12
+ sourceFile?: string;
10
13
  contentType?: string;
11
14
  headers?: Record<string, string>;
12
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StorageClient.d.ts","sourceRoot":"","sources":["../../src/core/StorageClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iBAAiB;AACjB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpE"}
1
+ {"version":3,"file":"StorageClient.d.ts","sourceRoot":"","sources":["../../src/core/StorageClient.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;IACtC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpE"}
@@ -1,8 +1,8 @@
1
1
  import { Reporter, UploadSummary, UploadFileResult } from './reporter';
2
2
  import { StorageClient } from './StorageClient';
3
3
  import type { FileProcessor } from './fileProcessor';
4
- /** 上传流程的上下文:环境、bucket、前缀、本地目录、存储客户端、Reporter、CDN 根地址、上传前处理器等 */
5
- export interface UploadContext {
4
+ /** 单次上传的运行配置(纯数据,无实例) */
5
+ export interface UploadRunConfig {
6
6
  env?: string;
7
7
  bucket: string;
8
8
  basePrefix: string;
@@ -10,13 +10,18 @@ export interface UploadContext {
10
10
  localDir: string;
11
11
  include?: string[];
12
12
  exclude?: string[];
13
- storageClient: StorageClient;
14
- reporter?: Reporter;
15
13
  /** CDN 加速根地址,配置后可为每个文件生成 accessUrl */
16
14
  cdnBaseUrl?: string;
17
- /** 上传前对文件内容的处理器(如图片压缩),未配置则直接上传原内容 */
15
+ }
16
+ /** 上传流程依赖的服务(仅包含实例,由上层注入) */
17
+ export interface UploadServices {
18
+ storageClient: StorageClient;
19
+ reporter?: Reporter;
20
+ /** 上传前对文件内容的处理器,未配置则直接上传原内容 */
18
21
  fileProcessor?: FileProcessor;
19
22
  }
23
+ /** @deprecated 使用 UploadRunConfig + UploadServices 替代 */
24
+ export type UploadContext = UploadRunConfig & UploadServices;
20
25
  /** 本地文件信息:绝对路径与相对路径 */
21
26
  export interface LocalFile {
22
27
  absolutePath: string;
@@ -24,12 +29,12 @@ export interface LocalFile {
24
29
  }
25
30
  /**
26
31
  * 上传流程模板(Template Method)。
27
- * 定义固定步骤:onStart -> collectFiles -> filterFiles -> uploadFiles -> onComplete,
28
- * 子类实现 collectFiles 与 uploadFiles,其余步骤可复用或重写。
32
+ * 只接收「运行配置」与「服务实例」两类参数,便于阅读与维护。
29
33
  */
30
34
  export declare abstract class UploadFlowTemplate {
31
- protected readonly context: UploadContext;
32
- protected constructor(context: UploadContext);
35
+ protected readonly config: UploadRunConfig;
36
+ protected readonly services: UploadServices;
37
+ protected constructor(config: UploadRunConfig, services: UploadServices);
33
38
  /** 执行完整上传流程 */
34
39
  execute(): Promise<UploadSummary>;
35
40
  protected abstract collectFiles(): Promise<LocalFile[]>;
@@ -38,7 +43,6 @@ export declare abstract class UploadFlowTemplate {
38
43
  protected filterFiles(files: LocalFile[]): LocalFile[];
39
44
  /**
40
45
  * 构造远端 key:basePrefix / env / pathPrefix / relativePath。
41
- * 保持目录结构,且 base 前缀来自 config,不对外暴露。
42
46
  */
43
47
  protected buildKey(relativePath: string): string;
44
48
  /** 递归遍历目录,收集所有文件的绝对路径与相对路径 */
@@ -1 +1 @@
1
- {"version":3,"file":"UploadFlowTemplate.d.ts","sourceRoot":"","sources":["../../src/core/UploadFlowTemplate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,uBAAuB;AACvB,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,8BAAsB,kBAAkB;IAChB,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,aAAa;IAA/D,SAAS,aAAgC,OAAO,EAAE,aAAa;IAE/D,eAAe;IACT,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC;IAyBvC,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IACvD,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAE1E,gCAAgC;IAChC,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE;IAKtD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAUhD,8BAA8B;cACd,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAsB/E,4BAA4B;cACZ,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAM1E"}
1
+ {"version":3,"file":"UploadFlowTemplate.d.ts","sourceRoot":"","sources":["../../src/core/UploadFlowTemplate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,yBAAyB;AACzB,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,+BAA+B;IAC/B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,yDAAyD;AACzD,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,cAAc,CAAC;AAE7D,uBAAuB;AACvB,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,8BAAsB,kBAAkB;IAEpC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe;IAC1C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc;IAF7C,SAAS,aACY,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,cAAc;IAG7C,eAAe;IACT,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC;IAyBvC,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IACvD,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAE1E,gCAAgC;IAChC,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE;IAKtD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAUhD,8BAA8B;cACd,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAsB/E,4BAA4B;cACZ,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAM1E"}
@@ -0,0 +1,40 @@
1
+ import { Reporter, UploadSummary, UploadFileResult } from './reporter';
2
+ import { StorageClient } from './StorageClient';
3
+ import type { FileProcessor } from './fileProcessor';
4
+ /** 单次上传的目录参数 */
5
+ export interface UploadDirectoryOptions {
6
+ bucket: string;
7
+ localDir: string;
8
+ env?: string;
9
+ pathPrefix?: string;
10
+ include?: string[];
11
+ exclude?: string[];
12
+ }
13
+ export interface LocalFile {
14
+ absolutePath: string;
15
+ relativePath: string;
16
+ }
17
+ /**
18
+ * 上传流程基类:封装「收集文件 → 过滤 → 逐个上传 → 上报」。
19
+ * 子类可覆盖 collectFiles、uploadOneFile 等扩展行为。
20
+ */
21
+ export declare abstract class UploadService {
22
+ protected readonly storageClient: StorageClient;
23
+ protected readonly basePrefix: string;
24
+ protected readonly cdnBaseUrl?: string | undefined;
25
+ protected readonly reporter?: Reporter | undefined;
26
+ protected readonly fileProcessor?: FileProcessor | undefined;
27
+ protected constructor(storageClient: StorageClient, basePrefix: string, cdnBaseUrl?: string | undefined, reporter?: Reporter | undefined, fileProcessor?: FileProcessor | undefined);
28
+ uploadDirectory(options: UploadDirectoryOptions): Promise<UploadSummary>;
29
+ /** 收集并过滤要上传的文件,子类可覆盖 */
30
+ protected collectFiles(localDir: string, include?: string[], exclude?: string[]): Promise<LocalFile[]>;
31
+ /** 上传单个文件:先 head,存在则跳过,否则读文件并 put;子类可覆盖 */
32
+ protected uploadOneFile(file: LocalFile, options: UploadDirectoryOptions): Promise<{
33
+ status: 'success' | 'skipped' | 'failed';
34
+ key: string;
35
+ }>;
36
+ protected buildKey(basePrefix: string, env?: string, pathPrefix?: string, relativePath?: string): string;
37
+ protected walkDir(rootDir: string, currentDir?: string): Promise<LocalFile[]>;
38
+ protected reportFile(file: LocalFile, bucket: string, key: string, status: UploadFileResult['status'], error?: Error): Promise<void>;
39
+ }
40
+ //# sourceMappingURL=UploadService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UploadService.d.ts","sourceRoot":"","sources":["../../src/core/UploadService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAgCrD,gBAAgB;AAChB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,8BAAsB,aAAa;IAE/B,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,aAAa;IAC/C,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM;IACrC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM;IACtC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ;IACtC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa;IALlD,SAAS,aACY,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,QAAQ,CAAC,EAAE,QAAQ,YAAA,EACnB,aAAa,CAAC,EAAE,aAAa,YAAA;IAG5C,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyC9E,wBAAwB;cACR,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAAE,EAClB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC;IAKvB,2CAA2C;cAC3B,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IA8CrE,SAAS,CAAC,QAAQ,CAChB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM;cASO,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;cAoB/D,UAAU,CACxB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,KAAK,CAAC,EAAE,KAAK,GACZ,OAAO,CAAC,IAAI,CAAC;CAajB"}
@@ -17,11 +17,11 @@ export interface UploadFileResult {
17
17
  /** 整次上传的汇总统计 */
18
18
  export interface UploadSummary {
19
19
  total: number;
20
- success: number;
21
- failed: number;
22
- skipped: number;
20
+ success: string[];
21
+ failed: string[];
22
+ skipped: string[];
23
23
  }
24
- /** Reporter 接口:上传开始、每个文件结果、全部完成时回调,便于接入不同通知方式 */
24
+ /** Reporter 接口:上传开始、进度、每个文件结果、全部完成时回调 */
25
25
  export interface Reporter {
26
26
  onStart?(context: {
27
27
  env?: string;
@@ -31,11 +31,14 @@ export interface Reporter {
31
31
  localDir: string;
32
32
  cdnBaseUrl?: string;
33
33
  }): void | Promise<void>;
34
+ /** 每完成一个文件后调用,便于显示进度(当前数、总数) */
35
+ onProgress?(current: number, total: number): void | Promise<void>;
34
36
  onFileResult?(result: UploadFileResult): void | Promise<void>;
35
37
  onComplete?(summary: UploadSummary): void | Promise<void>;
36
38
  }
37
39
  /** 默认实现:将上传进度与结果输出到控制台 */
38
40
  export declare class ConsoleReporter implements Reporter {
41
+ private progressBar;
39
42
  onStart(context: {
40
43
  env?: string;
41
44
  bucket: string;
@@ -44,7 +47,8 @@ export declare class ConsoleReporter implements Reporter {
44
47
  localDir: string;
45
48
  cdnBaseUrl?: string;
46
49
  }): void;
47
- onFileResult(result: UploadFileResult): void;
50
+ onProgress(current: number, total: number): void;
51
+ onFileResult(_result: UploadFileResult): void;
48
52
  onComplete(summary: UploadSummary): void;
49
53
  }
50
54
  //# sourceMappingURL=reporter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,8BAA8B;AAC9B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iDAAiD;AACjD,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,CAAC,OAAO,EAAE;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,YAAY,CAAC,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D,UAAU,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,0BAA0B;AAC1B,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,OAAO,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI;IAaR,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAc5C,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;CAOzC"}
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,8BAA8B;AAC9B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,CAAC,OAAO,EAAE;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,gCAAgC;IAChC,UAAU,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE,YAAY,CAAC,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9D,UAAU,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,0BAA0B;AAC1B,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,WAAW,CAA0B;IAE7C,OAAO,CAAC,OAAO,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI;IAOR,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAoBhD,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAE7C,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;CAOzC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("fs/promises"),t=require("path"),s=require("micromatch");function o(e,t){const s=e.replace(/\/+$/,""),o=t.replace(/^\/+/,"");return o?`${s}/${o}`:s}class r{constructor(e){this.context=e}async execute(){const{reporter:e}=this.context;e?.onStart&&await e.onStart({env:this.context.env,bucket:this.context.bucket,basePrefix:this.context.basePrefix,pathPrefix:this.context.pathPrefix,localDir:this.context.localDir,cdnBaseUrl:this.context.cdnBaseUrl});const t=await this.collectFiles(),s=this.filterFiles(t),o=await this.uploadFiles(s);return e?.onComplete&&await e.onComplete(o),o}filterFiles(e){const{include:t,exclude:o}=this.context;return e.filter(e=>function(e,t,o){let r=!0;return t&&t.length>0&&(r=s.isMatch(e,t)),!(!r||o&&o.length>0&&s.isMatch(e,o))}(e.relativePath,t,o))}buildKey(e){const t=[];return this.context.basePrefix&&t.push(this.context.basePrefix),this.context.env&&t.push(this.context.env),this.context.pathPrefix&&t.push(this.context.pathPrefix),t.push(e.replace(/\\/g,"/")),t.join("/")}async walkDir(s,o=""){const r=t.join(s,o),c=await e.readdir(r,{withFileTypes:!0}),i=[];for(const e of c){const r=t.join(o,e.name),c=t.join(s,r);e.isDirectory()?i.push(...await this.walkDir(s,r)):e.isFile()&&i.push({absolutePath:c,relativePath:r.replace(/\\/g,"/")})}return i}async reportFileResult(e){const{reporter:t}=this.context;t?.onFileResult&&await t.onFileResult(e)}}class c extends r{constructor(e){super(e)}async collectFiles(){return this.walkDir(this.context.localDir)}async uploadFiles(t){const{bucket:s,storageClient:r,cdnBaseUrl:c}=this.context,i={total:t.length,success:0,failed:0,skipped:0};for(const a of t){const t=this.buildKey(a.relativePath);try{if((await r.headObject(s,t)).exists){i.skipped+=1,await this.reportFileResult({localPath:a.absolutePath,relativePath:a.relativePath,bucket:s,key:t,status:"skipped",...c&&{accessUrl:o(c,t)}});continue}const n=await e.readFile(a.absolutePath),{buffer:l,contentType:u}=this.context.fileProcessor?await this.context.fileProcessor.process({localPath:a.absolutePath,relativePath:a.relativePath,buffer:n}):{buffer:n,contentType:void 0};await r.putObject({bucket:s,key:t,body:l,contentType:u}),i.success+=1,await this.reportFileResult({localPath:a.absolutePath,relativePath:a.relativePath,bucket:s,key:t,status:"success",...c&&{accessUrl:o(c,t)}})}catch(e){i.failed+=1,await this.reportFileResult({localPath:a.absolutePath,relativePath:a.relativePath,bucket:s,key:t,status:"failed",error:e instanceof Error?e:new Error(String(e))})}}return i}}const i=require("esdk-obs-nodejs");class a{constructor(e){this.config=e,this.client=new i({access_key_id:e.accessKey,secret_access_key:e.secretKey,server:e.endpoint})}async headObject(e,t){try{const s=await this.client.getObjectMetadata({Bucket:e,Key:t});if(s.CommonMsg.Status<=300)return{exists:!0};if(404===s.CommonMsg.Status||"NoSuchKey"===s.CommonMsg.Code||"NotFound"===s.CommonMsg.Code)return{exists:!1};throw new Error(`OBS getObjectMetadata 失败: ${s.CommonMsg.Status} ${s.CommonMsg.Code} ${s.CommonMsg.Message}`)}catch(e){const t=e&&"string"==typeof e.message?e.message:String(e),s=e?.Code??e?.code;if("NoSuchKey"===s||"NotFound"===s||t.includes("404")||t.includes("NoSuchKey"))return{exists:!1};throw e}}async putObject(e){const{bucket:t,key:s,body:o,contentType:r}=e,c=await this.client.putObject({Bucket:t,Key:s,Body:o});if(c.CommonMsg.Status>300){const{Status:e,Code:t,Message:s,RequestId:o}=c.CommonMsg,r=[e,t,s,o].filter(Boolean).join(" ");throw new Error(`OBS putObject 失败 (${r||"无详情"}). 请检查: 1) 桶名是否正确且已创建 2) OBS_ENDPOINT 是否与该桶所在区域一致,如 https://obs.xx-xx-1.myhuaweicloud.com`)}}}function n(e){if("huawei"===e.type)return new a(e.options);throw new Error(`Unsupported provider type: ${e.type}`)}class l{resolveHuaweiConfig(e){const t=process.env.OBS_ACCESS_KEY,s=process.env.OBS_SECRET_KEY;if(!t||!s)throw new Error("缺少华为 OBS 配置: OBS_ENDPOINT / OBS_ACCESS_KEY / OBS_SECRET_KEY");return{endpoint:"https://obs.cn-east-3.myhuaweicloud.com",accessKey:t,secretKey:s,region:"cn-east-3",basePrefix:"jhr-static",cdnBaseUrl:"https://hruat-cos.jtexpress.com.cn"}}}exports.ConsoleReporter=class{onStart(e){const{env:t,bucket:s,basePrefix:o,pathPrefix:r,localDir:c,cdnBaseUrl:i}=e;i&&console.log(` CDN 根地址: ${i}`)}onFileResult(e){const{status:t,localPath:s,key:o,error:r,accessUrl:c}=e;"success"===t?(console.log(`[SUCCESS] ${s} -> ${o}`),c&&console.log(` 访问地址: ${c}`)):"skipped"===t?(console.log(`[SKIPPED] ${s} -> ${o} (云端同名已存在)`),c&&console.log(` 访问地址: ${c}`)):(console.error(`[FAILED] ${s} -> ${o}`),r&&console.error(` Error: ${r.message}`))}onComplete(e){console.log("上传完成:"),console.log(` 总数: ${e.total}`),console.log(` 成功: ${e.success}`),console.log(` 失败: ${e.failed}`),console.log(` 跳过: ${e.skipped}`)}},exports.DirectoryUploadFlow=c,exports.EnvConfigResolverImpl=l,exports.NoOpFileProcessor=class{async process(e){return{buffer:e.buffer}}},exports.OssService=class{constructor(e){this.providerConfigResolver=e}async uploadDirectory(e){const{env:t,bucket:s="hr-uat",localDir:o,pathPrefix:r,include:i,exclude:a,reporter:l,fileProcessor:u}=e,h=this.providerConfigResolver.resolve(),{providerConfig:p,basePrefix:d,cdnBaseUrl:f}=h,x=n(p);return new c({env:t,bucket:s,localDir:o,pathPrefix:r,include:i,exclude:a,basePrefix:d,storageClient:x,reporter:l,cdnBaseUrl:f,fileProcessor:u}).execute()}},exports.ProviderConfigResolverImpl=class{constructor(){this.huaweiResolver=new l}resolve(){{const e=this.huaweiResolver.resolveHuaweiConfig();return{providerConfig:{type:"huawei",options:e},basePrefix:e.basePrefix,cdnBaseUrl:e.cdnBaseUrl}}}},exports.UploadFlowTemplate=r,exports.buildCdnAccessUrl=o,exports.createStorageClient=n;
1
+ "use strict";var e=require("cli-progress"),t=require("fs/promises"),s=require("path"),r=require("micromatch");class o{constructor(){this.progressBar=null}onStart(e){const{cdnBaseUrl:t}=e;t&&console.log(` CDN 根地址: ${t}`)}onProgress(t,s){null===this.progressBar&&(this.progressBar=new e.SingleBar({format:" 上传进度 |{bar}| {percentage}% | {value}/{total} 文件",barCompleteChar:"",barIncompleteChar:"",hideCursor:!0},e.Presets.shades_classic),this.progressBar.start(s,0)),this.progressBar.update(t),t>=s&&(this.progressBar.stop(),this.progressBar=null)}onFileResult(e){}onComplete(e){console.log("上传完成:"),console.log(` 总数: ${e.total}`),console.log(` 成功:\n ${e.success.join("\n")}`),console.log(`\n\n\n 失败:\n ${e.failed.join("\n")}`),console.log(`\n\n\n 跳过:\n ${e.skipped.join("\n")}`)}}const i=require("esdk-obs-nodejs");class a{constructor(e){this.config=e,this.client=new i({access_key_id:e.accessKey,secret_access_key:e.secretKey,server:e.endpoint}),this.whiteList=JSON.parse('["/index.html"]')}async headObject(e,t){try{if(this.whiteList.some(e=>t.includes(e)))return{exists:!1};const s=await this.client.getObjectMetadata({Bucket:e,Key:t});if(s.CommonMsg.Status<=300)return{exists:!0};if(404===s.CommonMsg.Status||"NoSuchKey"===s.CommonMsg.Code||"NotFound"===s.CommonMsg.Code)return{exists:!1};throw new Error(`OBS getObjectMetadata 失败: ${s.CommonMsg.Status} ${s.CommonMsg.Code} ${s.CommonMsg.Message}`)}catch(e){const t=e&&"string"==typeof e.message?e.message:String(e),s=e?.Code??e?.code;if("NoSuchKey"===s||"NotFound"===s||t.includes("404")||t.includes("NoSuchKey"))return{exists:!1};throw e}}async putObject(e){const{bucket:t,key:s,contentType:r}=e,o={Bucket:t,Key:s,ContentType:r};if(e.sourceFile)o.SourceFile=e.sourceFile;else{if(!e.body)throw new Error("putObject 需要 body 或 sourceFile");o.Body=e.body}const i=await this.client.putObject(o);if(i.CommonMsg.Status>300){const{Status:e,Code:t,Message:s,RequestId:r}=i.CommonMsg,o=[e,t,s,r].filter(Boolean).join(" ");throw new Error(`OBS putObject 失败 (${o||"无详情"}). 请检查: 1) 桶名是否正确且已创建 2) OBS_ENDPOINT 是否与该桶所在区域一致,如 https://obs.xx-xx-1.myhuaweicloud.com`)}}}class n{resolveHuaweiConfig(e){const t=process.env.OBS_ACCESS_KEY,s=process.env.OBS_SECRET_KEY;if(!t||!s)throw new Error("缺少华为 OBS 配置: OBS_ENDPOINT / OBS_ACCESS_KEY / OBS_SECRET_KEY");return{endpoint:"https://obs.cn-east-3.myhuaweicloud.com",accessKey:t,secretKey:s,region:"cn-east-3",basePrefix:"jhr-static",cdnBaseUrl:"https://hruat-cos.jtexpress.com.cn"}}}class c{constructor(){this.huaweiResolver=new n}resolve(){{const e=this.huaweiResolver.resolveHuaweiConfig();return{providerConfig:{type:"huawei",options:e},basePrefix:e.basePrefix,cdnBaseUrl:e.cdnBaseUrl}}}}function l(e,t){const s=e.replace(/\/+$/,""),r=t.replace(/^\/+/,"");return r?`${s}/${r}`:s}const u={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",ico:"image/x-icon",bmp:"image/bmp",tiff:"image/tiff",tif:"image/tiff",js:"application/javascript",mjs:"application/javascript",json:"application/json",css:"text/css",html:"text/html",htm:"text/html",txt:"text/plain",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",eot:"application/vnd.ms-fontobject"};function h(e){const t=s.extname(e).slice(1).toLowerCase();return t?u[t]:void 0}class p{constructor(e,t,s,r,o){this.storageClient=e,this.basePrefix=t,this.cdnBaseUrl=s,this.reporter=r,this.fileProcessor=o}async uploadDirectory(e){const t=await this.collectFiles(e.localDir,e.include,e.exclude),s={total:t.length,success:[],failed:[],skipped:[]};this.reporter?.onStart&&await this.reporter.onStart({env:e.env,bucket:e.bucket,basePrefix:this.basePrefix,pathPrefix:e.pathPrefix,localDir:e.localDir,cdnBaseUrl:this.cdnBaseUrl});for(const r of t){const o=await this.uploadOneFile(r,e);s[o.status].push(o.key);const i=s.success.length+s.failed.length+s.skipped.length;this.reporter?.onProgress&&await this.reporter.onProgress(i,t.length)}return this.reporter?.onComplete&&await this.reporter.onComplete(s),s}async collectFiles(e,t,s){return(await this.walkDir(e)).filter(e=>function(e,t,s){let o=!0;return t&&t.length>0&&(o=r.isMatch(e,t)),!(!o||s&&s.length>0&&r.isMatch(e,s))}(e.relativePath,t,s))}async uploadOneFile(e,s){const r=this.buildKey(this.basePrefix,s.env,s.pathPrefix,e.relativePath);try{if((await this.storageClient.headObject(s.bucket,r)).exists)return await this.reportFile(e,s.bucket,r,"skipped"),{status:"skipped",key:r};if(this.fileProcessor){const o=await t.readFile(e.absolutePath),{buffer:i,contentType:a}=await this.fileProcessor.process({localPath:e.absolutePath,relativePath:e.relativePath,buffer:o});await this.storageClient.putObject({bucket:s.bucket,key:r,body:i,contentType:a})}else await this.storageClient.putObject({bucket:s.bucket,key:r,sourceFile:e.absolutePath,contentType:h(e.relativePath)});return await this.reportFile(e,s.bucket,r,"success"),{status:"success",key:r}}catch(t){const o=t instanceof Error?t:new Error(String(t));return await this.reportFile(e,s.bucket,r,"failed",o),{status:"failed",key:r}}}buildKey(e,t,s,r){const o=[];return e&&o.push(e),t&&o.push(t),s&&o.push(s),o.push((r??"").replace(/\\/g,"/")),o.join("/")}async walkDir(e,r=""){const o=s.join(e,r),i=await t.readdir(o,{withFileTypes:!0}),a=[];for(const t of i){const o=s.join(r,t.name),i=s.join(e,o);t.isDirectory()?a.push(...await this.walkDir(e,o)):t.isFile()&&a.push({absolutePath:i,relativePath:o.replace(/\\/g,"/")})}return a}async reportFile(e,t,s,r,o){if(!this.reporter?.onFileResult)return;const i={localPath:e.absolutePath,relativePath:e.relativePath,bucket:t,key:s,status:r,...this.cdnBaseUrl&&{accessUrl:l(this.cdnBaseUrl,s)},...o&&{error:o}};await this.reporter.onFileResult(i)}}class f extends p{constructor(e,t,s,r,o){super(e,t,s,r,o)}}class d{constructor(){this.resolver=new c}async uploadDirectory(e){const t=this.resolver.resolve(),s=function(e){if("huawei"===e.type)return new a(e.options);throw new Error(`Unsupported provider type: ${e.type}`)}(t.providerConfig),r=e.reporter??new o;return new f(s,t.basePrefix,t.cdnBaseUrl,r,e.fileProcessor).uploadDirectory({bucket:e.bucket,localDir:e.localDir,env:e.env,pathPrefix:e.pathPrefix,include:e.include,exclude:e.exclude})}}exports.upload=async function(e){return async function(e){return(new d).uploadDirectory(e)}(e)};
package/dist/index.d.ts CHANGED
@@ -1,15 +1,26 @@
1
1
  /**
2
- * 包入口:显式导出所有对外 API 与类型,便于调用方按需引用。
2
+ * 包入口:仅暴露上传方法及参数、返回值类型,内部实现固定。
3
3
  */
4
- export { buildCdnAccessUrl } from './core/urlHelper';
5
- export { type StorageClient, type PutObjectOptions, type HeadObjectResult, } from './core/StorageClient';
6
- export { type Reporter, type UploadFileResult, type UploadSummary, type UploadStatus, ConsoleReporter, } from './core/reporter';
7
- export { type FileProcessor, type ProcessFileInput, type ProcessFileResult, NoOpFileProcessor, } from './core/fileProcessor';
8
- export { UploadFlowTemplate, type UploadContext, type LocalFile, } from './core/UploadFlowTemplate';
9
- export { DirectoryUploadFlow, } from './core/DirectoryUploadFlow';
10
- export { createStorageClient, } from './core/StorageFactory';
11
- export { OssService, type UploadDirectoryInput, } from './middleware/OssService';
12
- export { type HuaweiObsConfig, type ProviderConfig, type ProviderType, type EnvConfigResolver, type ResolvedProviderConfig, type ProviderConfigResolver, } from './config/types';
13
- export { EnvConfigResolverImpl, } from './config/EnvConfigResolverImpl';
14
- export { ProviderConfigResolverImpl, } from './config/ProviderConfigResolverImpl';
4
+ import type { UploadSummary } from './core/reporter';
5
+ /** 上传参数 */
6
+ export type UploadOptions = {
7
+ /** 本地目录路径 */
8
+ localDir: string;
9
+ /** Bucket 名称 */
10
+ bucket: string;
11
+ /** 环境标识,如 dev / test / prod */
12
+ env?: string;
13
+ /** 业务路径前缀 */
14
+ pathPrefix?: string;
15
+ /** 包含的文件模式 */
16
+ include?: string[];
17
+ /** 排除的文件模式 */
18
+ exclude?: string[];
19
+ };
20
+ /** 上传结果:总数、成功/失败/跳过的 key 列表 */
21
+ export type UploadResult = UploadSummary;
22
+ /**
23
+ * 上传本地目录到 OSS(华为 OBS)。配置从环境变量/配置文件读取,进度输出到控制台。
24
+ */
25
+ export declare function upload(options: UploadOptions): Promise<UploadResult>;
15
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,UAAU,EACV,KAAK,oBAAoB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,qBAAqB,GACtB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,0BAA0B,GAC3B,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,WAAW;AACX,MAAM,MAAM,aAAa,GAAG;IAC1B,aAAa;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,+BAA+B;AAC/B,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC;AAEzC;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAE1E"}
@@ -1,27 +1,20 @@
1
1
  import { Reporter, UploadSummary } from '../core/reporter';
2
- import { ProviderConfigResolver } from '../config/types';
3
2
  import type { FileProcessor } from '../core/fileProcessor';
4
- /** 上传目录的入参:环境、bucket、本地目录、路径前缀、过滤规则、Reporter、上传前文件处理器 */
5
- export interface UploadDirectoryInput {
6
- env?: string;
7
- bucket: string;
8
- localDir: string;
9
- pathPrefix?: string;
10
- include?: string[];
11
- exclude?: string[];
3
+ import { type UploadDirectoryOptions as BaseOptions } from '../core/UploadService';
4
+ /** 上传目录入参:含可选 reporter / fileProcessor,不传则用默认控制台上报 */
5
+ export interface UploadDirectoryOptions extends BaseOptions {
12
6
  reporter?: Reporter;
13
- /** 上传前对文件内容的处理器(如图片压缩),未配置则直接上传原内容 */
14
7
  fileProcessor?: FileProcessor;
15
8
  }
16
9
  /**
17
- * 中间件:对主流程暴露统一 API(Facade)。
18
- * 内部只依赖 ProviderConfigResolver 得到当前 Provider 配置,再创建 StorageClient 与流程并执行。
19
- * 切换云厂商时只需改 ProviderConfigResolver 的【一个实现文件】。
10
+ * 一行调用即可上传:内部使用默认配置解析器 + 控制台 Reporter。
11
+ */
12
+ export declare function uploadDirectory(options: UploadDirectoryOptions): Promise<UploadSummary>;
13
+ /**
14
+ * OSS 上传服务:解析配置并委托 DirectoryUploadFlow(继承 UploadService)执行上传。
20
15
  */
21
16
  export declare class OssService {
22
- private readonly providerConfigResolver;
23
- constructor(providerConfigResolver: ProviderConfigResolver);
24
- /** 上传指定本地目录到 OSS,key 规则为 basePrefix/env/pathPrefix/相对路径,同名则跳过 */
25
- uploadDirectory(input: UploadDirectoryInput): Promise<UploadSummary>;
17
+ private readonly resolver;
18
+ uploadDirectory(options: UploadDirectoryOptions): Promise<UploadSummary>;
26
19
  }
27
20
  //# sourceMappingURL=OssService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OssService.d.ts","sourceRoot":"","sources":["../../src/middleware/OssService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,sCAAsC;IACtC,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,sBAAsB;gBAAtB,sBAAsB,EAAE,sBAAsB;IAE3E,iEAAiE;IAC3D,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;CAwB3E"}
1
+ {"version":3,"file":"OssService.d.ts","sourceRoot":"","sources":["../../src/middleware/OssService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAmB,MAAM,kBAAkB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,KAAK,sBAAsB,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKnF,sDAAsD;AACtD,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,aAAa,CAAC,CAExB;AAID;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAEvD,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC;CAsB/E"}
@@ -7,12 +7,13 @@ import { HuaweiObsConfig } from '../../config/types';
7
7
  export declare class HuaweiObsClient implements StorageClient {
8
8
  private readonly config;
9
9
  private readonly client;
10
+ private readonly whiteList;
10
11
  constructor(config: HuaweiObsConfig);
11
12
  /**
12
13
  * 调用 OBS getObjectMetadata 查元数据,判断对象是否存在(同路径即跳过上传)。
13
14
  */
14
15
  headObject(bucket: string, key: string): Promise<HeadObjectResult>;
15
- /** 调用 OBS putObject 上传对象 */
16
+ /** 调用 OBS putObject 上传对象(支持 Body 或 SourceFile 本地路径,SourceFile 由 SDK 直接读文件避免 Buffer 问题) */
16
17
  putObject(options: PutObjectOptions): Promise<void>;
17
18
  }
18
19
  //# sourceMappingURL=HuaweiObsClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HuaweiObsClient.d.ts","sourceRoot":"","sources":["../../../src/providers/huawei/HuaweiObsClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAKrD;;;GAGG;AACH,qBAAa,eAAgB,YAAW,aAAa;IAGvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;gBAE3B,MAAM,EAAE,eAAe;IAQpD;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2BxE,4BAA4B;IACtB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAmB1D"}
1
+ {"version":3,"file":"HuaweiObsClient.d.ts","sourceRoot":"","sources":["../../../src/providers/huawei/HuaweiObsClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAKrD;;;GAGG;AACH,qBAAa,eAAgB,YAAW,aAAa;IAGvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;gBACR,MAAM,EAAE,eAAe;IASpD;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6BxE,0FAA0F;IACpF,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CA0B1D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dd-code/oss-uploader",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Upload local directories to OSS (currently Huawei OBS) with pluggable middleware and providers.",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
@@ -13,6 +13,7 @@
13
13
  "prepare": "npm run build"
14
14
  },
15
15
  "dependencies": {
16
+ "cli-progress": "^3.12.0",
16
17
  "commander": "^12.0.0",
17
18
  "esdk-obs-nodejs": "^3.25.0",
18
19
  "js-yaml": "^4.1.1",
@@ -23,6 +24,7 @@
23
24
  "@rollup/plugin-node-resolve": "^15.0.0",
24
25
  "@rollup/plugin-replace": "^6.0.0",
25
26
  "@rollup/plugin-terser": "^0.4.4",
27
+ "@types/cli-progress": "^3.11.6",
26
28
  "@types/js-yaml": "^4.0.9",
27
29
  "@types/micromatch": "^4.0.9",
28
30
  "@types/node": "^22.0.0",
@@ -32,7 +34,9 @@
32
34
  "typescript": "^5.6.0"
33
35
  },
34
36
  "license": "MIT",
35
- "publishConfig": { "access": "public" },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
36
40
  "keywords": [
37
41
  "oss",
38
42
  "obs",
package/src/cli.ts CHANGED
@@ -4,11 +4,9 @@
4
4
  * .env 在打包时已注入 dist/cli.cjs,运行时不再读取。
5
5
  */
6
6
  (async () => {
7
- const [{ Command }, { OssService }, { ProviderConfigResolverImpl }, { ConsoleReporter }] = await Promise.all([
7
+ const [{ Command }, { uploadDirectory }] = await Promise.all([
8
8
  import('commander'),
9
9
  import('./middleware/OssService'),
10
- import('./config/ProviderConfigResolverImpl'),
11
- import('./core/reporter'),
12
10
  ]);
13
11
 
14
12
  const program = new Command();
@@ -17,7 +15,7 @@
17
15
  .name('oss-upload')
18
16
  .description('上传本地目录到 OSS(当前为华为 OBS)')
19
17
  .argument('<localDir>', '本地目录')
20
- .requiredOption('-b, --bucket <bucket>', 'Bucket 名称','hr-uat')
18
+ .requiredOption('-b, --bucket <bucket>', 'Bucket 名称', 'hr-uat')
21
19
  .option('-e, --env <env>', '环境标识,如 dev/test/prod')
22
20
  .option('--path-prefix <pathPrefix>', '业务路径前缀,将拼在 base/env 后面')
23
21
  .option('--include <pattern...>', '包含的文件模式(可多次)')
@@ -26,21 +24,17 @@
26
24
  const { loadObsCredentialsFromUrlIfNeeded } = await import('./config/loadObsCredentialsFromUrl');
27
25
  await loadObsCredentialsFromUrlIfNeeded();
28
26
 
29
- const service = new OssService(new ProviderConfigResolverImpl());
30
- const reporter = new ConsoleReporter();
31
-
32
27
  try {
33
- const summary = await service.uploadDirectory({
28
+ const summary = await uploadDirectory({
34
29
  env: options.env,
35
30
  bucket: options.bucket,
36
31
  localDir,
37
32
  pathPrefix: options.pathPrefix,
38
33
  include: options.include,
39
34
  exclude: options.exclude,
40
- reporter,
41
35
  });
42
36
 
43
- if (summary.failed > 0) {
37
+ if (summary.failed.length > 0) {
44
38
  process.exitCode = 1;
45
39
  }
46
40
  } catch (err: any) {