licell 0.9.40 → 0.9.41

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.
Files changed (2) hide show
  1. package/dist/licell.js +2 -2
  2. package/package.json +1 -1
package/dist/licell.js CHANGED
@@ -1587,13 +1587,13 @@ ${c} [${o.level}] ${o.id}`),console.log(o.message),o.remediation&&o.remediation.
1587
1587
  function: ${wn.cyan(f.functionName||r)}`),o)console.log(`qualifier: ${wn.cyan(o)}`);console.log(`runtime: ${wn.cyan(f.runtime||"-")}`),console.log(`handler: ${wn.cyan(f.handler||"-")}`),console.log(`state: ${wn.cyan(f.state||"-")}`),console.log(`memory: ${wn.cyan(String(f.memorySize||"-"))}`),console.log(`vcpu: ${wn.cyan(String(f.cpu??"-"))}`),console.log(`concur: ${wn.cyan(String(f.instanceConcurrency??"-"))}`),console.log(`timeout: ${wn.cyan(String(f.timeout||"-"))}`);let u=f.vpcConfig;if(u?.vpcId)console.log(`vpc: ${wn.cyan(`${u.vpcId} / ${(u.vSwitchIds||[]).join(",")||"-"} / ${u.securityGroupId||"-"}`)}`);else console.log(`vpc: ${wn.cyan("-")}`);console.log(`updated: ${wn.cyan(f.lastModifiedTime||"-")}`),console.log(`envCount: ${wn.cyan(String(Object.keys(f.environmentVariables||{}).length))}`),console.log(""),q("Done.")})}),n.command("fn invoke [name]","调用函数(同步)").option("--target <target>","指定 alias/version(如 prod/preview/1)").option("--payload <text>","传入原始 payload 文本").option("--file <path>","从文件读取 payload").action(async(e,t)=>{await G({commandLabel:"licell fn invoke",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let s=k.getProject(),r=E(e)||s.appName;if(!r)throw Error("请传入函数名,或先在当前项目执行 licell deploy 生成 appName");let o=E(t.target),c=E(t.payload),f=E(t.file);if(c&&f)throw Error("--payload 与 --file 不能同时使用");let u;if(f){let d;try{d=cp(fp(f))}catch{throw Error(`文件不存在或无法访问: ${f}`)}let a=up(process.cwd(),d);if(a.startsWith("..")||ip(a))throw Error("--file 路径必须在当前工作目录内");u=op(d,"utf-8")}else u=c;let g=H(),i=await F(g,`正在调用函数 ${r}...`,"❌ 函数调用失败",()=>bo(r,{qualifier:o||void 0,payload:u||void 0}));if(!i)return;if(!m())g.stop(wn.green(`✅ 调用完成 (status=${i.statusCode})`));let l=i.body&&i.body.trim().length>0?i.body:"";if(m()){T({stage:"fn.invoke",functionName:r,qualifier:o||null,statusCode:i.statusCode,body:l});return}if(console.log(""),l)console.log(l);else console.log("<empty response>");console.log(""),q("Done.")})}),n.command("fn rm [name]","删除函数").option("--force","级联删除触发器、alias、已发布版本后再删除函数").option("--yes","跳过二次确认(危险)").action(async(e,t)=>{await G({commandLabel:"licell fn rm",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let s=k.getProject(),r=E(e)||s.appName;if(!r)throw Error("请传入函数名,或先在当前项目执行 licell deploy 生成 appName");await Qn(t.force?`删除函数 ${r}(含触发器/alias/版本)`:`删除函数 ${r}`,{yes:Boolean(t.yes)});let o=H(),c=await F(o,t.force?`正在级联清理并删除函数 ${r}...`:`正在删除函数 ${r}...`,"❌ 删除函数失败",()=>$o(r,{force:Boolean(t.force)}));if(!c)return;if(!m())o.stop(wn.green("✅ 函数已删除"));if(m()){T({stage:"fn.rm",functionName:r,force:Boolean(t.force),forced:c.forced,deletedTriggers:c.deletedTriggers,deletedAliases:c.deletedAliases,deletedVersions:c.deletedVersions});return}if(c.forced)console.log(`
1588
1588
  cleanup: triggers=${c.deletedTriggers.length} aliases=${c.deletedAliases.length} versions=${c.deletedVersions.length}`);q("Done.")})})}import{text as vg}from"@clack/prompts";import bn from"picocolors";Hn();on();fn();function Kg(n){n.command("oss list","查看 OSS Bucket 列表").option("--limit <n>","返回数量,默认 50").action(async(t)=>{await G({commandLabel:"licell oss list",interactiveTTY:I(),requiredCapabilities:["oss"]},async()=>{B();let s=An(t.limit,50,500),r=H(),o=await F(r,"正在拉取 OSS Bucket 列表...","❌ 获取 Bucket 列表失败",()=>Eg(s));if(!o)return;if(!m())r.stop(bn.green(`✅ 共获取 ${o.length} 个 Bucket`));if(m()){T({stage:"oss.list",count:o.length,buckets:o});return}if(o.length===0){q("当前账号没有 Bucket");return}for(let c of o)console.log(`${bn.cyan(c.name)} region=${bn.gray(c.location||"-")} created=${bn.gray(c.creationDate||"-")}`);console.log(""),q("Done.")})}),n.command("oss info <bucket>","查看 OSS Bucket 详情").action(async(t)=>{await G({commandLabel:"licell oss info",interactiveTTY:I(),requiredCapabilities:["oss"]},async()=>{B();let s=K(t,"bucket"),r=H(),o=await F(r,`正在拉取 Bucket ${s} 详情...`,"❌ 获取 Bucket 详情失败",()=>qg(s));if(!o)return;if(!m())r.stop(bn.green("✅ 获取成功"));else{T({stage:"oss.info",bucket:s,info:o});return}console.log(`
1589
1589
  name: ${bn.cyan(o.name)}`),console.log(`location: ${bn.cyan(o.location||"-")}`),console.log(`created: ${bn.cyan(o.creationDate||"-")}`),console.log(`endpoint: ${bn.cyan(o.extranetEndpoint||"-")}`),console.log(`intranet: ${bn.cyan(o.intranetEndpoint||"-")}`),console.log(""),q("Done.")})}),n.command("oss ls <bucket> [prefix]","列出 Bucket 对象").option("--limit <n>","返回数量,默认 100").action(async(t,s,r)=>{await G({commandLabel:"licell oss ls",interactiveTTY:I(),requiredCapabilities:["oss"]},async()=>{B();let o=K(t,"bucket"),c=E(s),f=An(r.limit,100,2000),u=H(),g=await F(u,`正在列出 ${o} 对象...`,"❌ 获取对象列表失败",()=>er(o,c||void 0,f));if(!g)return;if(!m())u.stop(bn.green(`✅ 共获取 ${g.length} 个对象`));if(m()){T({stage:"oss.ls",bucket:o,prefix:c||null,count:g.length,objects:g});return}if(g.length===0){q("当前条件下无对象");return}for(let i of g)console.log(`${bn.cyan(i.name)} size=${bn.gray(String(i.size??"-"))} modified=${bn.gray(i.lastModified||"-")}`);console.log(""),q("Done.")})});let e=(t,s)=>{n.command(t,s).option("--bucket <bucket>","Bucket 名称(可替代位置参数)").option("--source-dir <dir>","本地目录(默认 dist)").option("--target-dir <dir>","Bucket 内目标目录前缀(如 mysite 或 mysite/v2)").action(async(r,o)=>{await G({commandLabel:"licell oss upload",interactiveTTY:I(),requiredCapabilities:["oss"]},async()=>{B();let c=I(),f=E(o.bucket)||E(r)||(c?K(await vg({message:"Bucket 名称:"}),"bucket"):void 0);if(!f)throw Error("请通过 <bucket> 或 --bucket 指定 Bucket 名称");let u=E(o.sourceDir)||(c?K(await vg({message:"本地目录:",initialValue:"dist"}),"source-dir"):"dist"),g=E(o.targetDir),i=H(),l=await F(i,`正在上传 ${u} 到 OSS Bucket ${f}${g?`/${g}`:""}...`,"❌ OSS 目录上传失败",()=>Qo(f,u,{targetDir:g}));if(!l)return;if(!m())i.stop(bn.green(`✅ 上传完成,共 ${l.uploadedCount} 个文件`));else{T({stage:"oss.upload",bucket:l.bucket,sourceDir:u,targetDir:l.targetDir||null,uploadedCount:l.uploadedCount,skippedSymlinkCount:l.skippedSymlinkCount,baseUrl:l.baseUrl});return}let d=l.targetDir?`${l.targetDir}/`:"";if(console.log(`
1590
- bucket: ${bn.cyan(l.bucket)}`),console.log(`prefix: ${bn.cyan(l.targetDir||"(root)")}`),console.log(`base: ${bn.cyan(l.baseUrl)}`),console.log(`hint: ${bn.gray(`${l.baseUrl}/${d}<file>`)}`),l.skippedSymlinkCount>0)console.log(bn.yellow(`warning: 已跳过 ${l.skippedSymlinkCount} 个符号链接(为避免目录逃逸与递归风险)`));console.log(""),q("Done.")})})};e("oss upload [bucket]","上传本地目录到 OSS Bucket 指定目录"),e("oss bucket [bucket]","上传本地目录到 OSS Bucket 指定目录(兼容命令,等同 oss upload)")}import{select as Fp,confirm as xp,isCancel as zg}from"@clack/prompts";import v from"picocolors";Hn();Q();ht();dn();Zn();pt();import*as mn from"@alicloud/rds20140815";Q();xn();import gp from"@alicloud/rds20140815";import*as Yg from"@alicloud/openapi-client";var lp=60000,dp=120000,yp=nn(gp,"@alicloud/rds20140815");function ie(){let n=k.requireAuth(),e=new yp(new Yg.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:`rds.${n.region}.aliyuncs.com`,connectTimeout:lp,readTimeout:dp}));return{auth:n,client:e}}var ap=1200000,Vg=5000,hp=300000,zo=5,mp="pg.n1e.1c.1m",wp=20,pp="18.0",$p={postgres:"pg.n2.serverless.1c",mysql:"mysql.n2.serverless.1c"},bp={postgres:20,mysql:20},nc={postgres:{minCapacity:0.5,maxCapacity:8,autoPause:!0},mysql:{minCapacity:0.5,maxCapacity:2,autoPause:!0}},jg={postgres:"AliyunServiceRoleForRdsPgsqlOnEcs",mysql:"AliyunServiceRoleForRds"};function kp(n,e){if(!e)return!1;if(e===n)return!0;let t=n.split(".")[0],s=e.split(".")[0];return t.length>0&&t===s}async function _p(n,e,t,s){try{let o=((await n.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:e,engine:t,engineVersion:s,category:"serverless_basic"}))).body?.availableZones||[]).map((c)=>c.zoneId).filter((c)=>typeof c==="string"&&c.length>0);if(o.length>0)return[...new Set(o)]}catch{}try{let o=(await n.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:e,engine:t,engineVersion:s}))).body?.availableZones||[],c=[];for(let u of o){let g=u.zoneId;if(typeof g!=="string"||g.length===0)continue;if((u.supportedEngines||[]).some((l)=>{if((l.engine||"").toLowerCase()!==t.toLowerCase())return!1;return(l.supportedEngineVersions||[]).some((d)=>{if(!kp(s,d.version))return!1;return(d.supportedCategorys||[]).some((a)=>a.category==="serverless_basic")})}))c.push(g)}if(c.length>0)return[...new Set(c)];let f=o.map((u)=>u.zoneId).filter((u)=>typeof u==="string"&&u.length>0);return[...new Set(f)]}catch{return[]}}async function Dg(n,e,t){let s=t==="postgres"?["AliyunServiceRoleForRds",jg.postgres]:[jg.mysql];for(let r of s){let o=!1;try{let f=((await n.checkServiceLinkedRole(new mn.CheckServiceLinkedRoleRequest({regionId:e,serviceLinkedRole:r}))).body?.hasServiceLinkedRole||"").toString().toLowerCase();o=f==="true"||f==="1"}catch{}if(o)continue;try{await n.createServiceLinkedRole(new mn.CreateServiceLinkedRoleRequest({regionId:e,serviceLinkedRole:r}))}catch(c){if(Er(c))continue;throw c}}}async function Wg(n,e,t){let s;for(let r=1;r<=zo;r+=1)try{return await n.createDBInstance(e)}catch(o){if(s=o,!Rn(o)||r===zo)throw o;t.message(`\uD83C\uDF10 RDS API 网络抖动,${r}/${zo} 次失败,正在重试...`),await yn(1500*r)}throw s instanceof Error?s:Error("RDS 创建失败")}async function Cp(n,e,t,s){let r=t==="postgres"?"5432":"3306",o=Date.now();while(!0){if(Date.now()-o>hp)throw Error("数据库网络信息未就绪,等待连接地址超时");let f=(await n.describeDBInstanceNetInfo(new mn.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[],u=f.find((d)=>d.IPType==="Private")||f[0],g=u?.connectionString?.trim(),i=u?.port||r;if(g)return{host:g,port:i};let l=f.map((d)=>`${d.IPType||"Unknown"}:${d.connectionString||"-"}`).join(",")||"pending";s.message(`☕ 数据库连接地址初始化中,请稍候... [${l}]`),await yn(Vg)}}function Qg(n){let e=n.toLowerCase().replace(/[^a-z0-9_]/g,"_");if(!/^[a-z]/.test(e))e=`a_${e}`;if(e=e.replace(/_+/g,"_").replace(/_$/,""),e.length<2||!/[a-z0-9]/.test(e.slice(1)))e="licell_user";return e.slice(0,16)}async function ec(n,e,t={}){let{auth:s,client:r}=ie(),o=k.getProject(),c="main",f=Qg(o.appName||"licell_app"),u=Bn(),g=n==="postgres"?"PostgreSQL":"MySQL",i=n==="postgres",l=t.engineVersion?.trim()||(i?pp:n==="postgres"?"18.0":"8.0"),d=i?t.category?.trim()||"Basic":t.category?.trim()||"serverless_basic",a=t.storageType?.trim()||"cloud_essd";if(i)e.message("\uD83D\uDD0E 正在查询 RDS PostgreSQL 可用区...");else e.message("\uD83D\uDD0E 正在查询 RDS Serverless 可用区...");let p;if(i)try{p=((await r.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:s.region,engine:g,engineVersion:l,category:d}))).body?.availableZones||[]).map((Mn)=>Mn.zoneId).filter((Mn)=>typeof Mn==="string"&&Mn.length>0),p=[...new Set(p)]}catch{p=[]}else p=await _p(r,s.region,g,l);e.message("\uD83D\uDD0D 正在探测/拉起专属私有网络平面 (VPC & VSwitch)...");let w=t.zoneId?.trim(),_=t.vpcId?.trim(),$=t.vSwitchId?.trim(),y;if(_||$){if(!_||!$)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!w)throw Error("自定义网络时需提供 --zone");y=await wt({vpcId:_,vswId:$,zoneId:w})}else y=await Re({preferredZoneIds:w?[w]:p});let h=t.instanceClass?.trim()||(i?mp:$p[n]),U=t.storageGb||(i?wp:bp[n]);try{let Mn=(await r.describeAvailableClasses(new mn.DescribeAvailableClassesRequest({regionId:s.region,zoneId:y.zoneId,engine:g,engineVersion:l,instanceChargeType:i?"PostPaid":"Serverless",category:d,DBInstanceStorageType:a}))).body?.DBInstanceClasses?.find((lt)=>{if(typeof lt.DBInstanceClass!=="string"||lt.DBInstanceClass.length===0)return!1;if(!t.instanceClass)return!0;return lt.DBInstanceClass===t.instanceClass});if(!t.instanceClass&&typeof Mn?.DBInstanceClass==="string"&&Mn.DBInstanceClass.length>0)h=Mn.DBInstanceClass;let gt=Mn?.DBInstanceStorageRange?.minValue;if(!t.storageGb&&typeof gt==="number"&&Number.isFinite(gt)&&gt>0)U=Math.floor(gt)}catch{e.message("⚠️ 未能查询到可售规格,回退到内置默认规格继续创建...")}e.message("\uD83D\uDD10 正在确保 RDS 服务关联角色已就绪..."),await Dg(r,s.region,n),e.message(i?"\uD83D\uDCE6 正在拉起 PostgreSQL (按量付费)...":`\uD83D\uDCE6 正在拉起 Serverless ${n.toUpperCase()} (按量计费)...`);let C={engine:g,engineVersion:l,payType:i?"Postpaid":"Serverless",category:d,regionId:s.region,zoneId:y.zoneId,DBInstanceClass:h,DBInstanceStorage:U,DBInstanceStorageType:a,securityIPList:t.securityIpList?.trim()||y.cidrBlock||"10.0.0.0/8",instanceNetworkType:"VPC",DBInstanceNetType:"Intranet",DBInstanceDescription:t.description?.trim()||`${o.appName||"licell-app"}-${n}`,VPCId:y.vpcId,vSwitchId:y.vswId};if(!i)C.serverlessConfig={minCapacity:t.minCapacity??nc[n].minCapacity,maxCapacity:t.maxCapacity??nc[n].maxCapacity,autoPause:t.autoPause??nc[n].autoPause};let L=t.zoneIdSlave1?.trim(),S=t.zoneIdSlave2?.trim();if(L)C.zoneIdSlave1=L;if(S)C.zoneIdSlave2=S;let b=new mn.CreateDBInstanceRequest(C),P;try{P=await Wg(r,b,e)}catch(Se){if(!Es(Se))throw Se;e.message("\uD83D\uDD10 首次使用检测到缺少服务关联角色,正在自动创建并重试..."),await Dg(r,s.region,n);try{P=await Wg(r,b,e)}catch(Mn){if(Es(Mn))throw Error("RDS PostgreSQL 仍提示缺少服务关联角色。请先在阿里云控制台开通 RDS PostgreSQL 服务后重试,或先创建 MySQL Serverless 实例。");throw Mn}}let R=P.body?.DBInstanceId;if(!R)throw Error("RDS 创建失败:未返回 DBInstanceId");let j="Creating",gn=Date.now();while(j!=="Running"){if(Date.now()-gn>ap)throw Error(`数据库创建超时,最后状态: ${j}`);if(await yn(Vg),j=(await r.describeDBInstances(new mn.DescribeDBInstancesRequest({DBInstanceId:R}))).body?.items?.DBInstance?.[0]?.DBInstanceStatus||"Creating",j==="Deleted"||j==="Failed")throw Error(`数据库创建失败,实例状态: ${j}`);e.message(`☕ 数据库底层初始化中,请稍候... [${j}]`)}if(e.message("\uD83E\uDDF1 正在创建数据库与应用账号..."),await Rs(()=>r.createAccount(new mn.CreateAccountRequest({DBInstanceId:R,accountName:f,accountPassword:u,accountType:"Normal",accountDescription:"licell managed account"}))),await Rs(()=>r.createDatabase(new mn.CreateDatabaseRequest({DBInstanceId:R,DBName:"main",characterSetName:n==="postgres"?"UTF8":"utf8mb4",ownerAccount:n==="postgres"?f:void 0}))),n!=="postgres")await Rs(()=>r.grantAccountPrivilege(new mn.GrantAccountPrivilegeRequest({DBInstanceId:R,DBName:"main",accountName:f,accountPrivilege:"ReadWrite"})));let{host:Xn,port:ln}=await Cp(r,R,n,e),We=`${n==="postgres"?"postgresql":"mysql"}://${encodeURIComponent(f)}:${encodeURIComponent(u)}@${Xn}:${ln}/main`;return o.envs={...o.envs,DATABASE_URL:We},o.network=y,o.database={type:n,instanceId:R,user:f,name:"main"},k.setProject(o),e.message("⚠️ DATABASE_URL(含密码)已写入 .licell/project.json,请确认该目录已在 .gitignore 中"),We}Q();import*as ee from"@alicloud/rds20140815";function Xg(n){if(!n)return null;try{let e=new URL(n),t=e.protocol==="postgresql:"?"postgresql":e.protocol==="mysql:"?"mysql":null;if(!t||!e.hostname)return null;let s=e.port||(t==="postgresql"?"5432":"3306"),r=Number(s);if(!Number.isFinite(r)||r<=0)return null;let o=e.pathname.replace(/^\//,"")||"main";return{protocol:t,host:e.hostname,port:r,database:o,username:decodeURIComponent(e.username||""),password:decodeURIComponent(e.password||"")}}catch{return null}}function Zg(n){let e=n.database;if(typeof e!=="object"||e===null)return{};let t=e;return{instanceId:typeof t.instanceId==="string"?t.instanceId:void 0,user:typeof t.user==="string"?t.user:void 0,name:typeof t.name==="string"?t.name:void 0}}function Og(n){return(n||"").toLowerCase().includes("postgres")?"postgresql":"mysql"}function Jg(n){return{instanceId:n.DBInstanceId||"",description:n.DBInstanceDescription,engine:n.engine,engineVersion:n.engineVersion,status:n.DBInstanceStatus,payType:n.payType,category:n.category,instanceClass:n.DBInstanceClass,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}async function Sp(n){let{auth:e,client:t}=ie(),r=(await t.describeDBInstances(new ee.DescribeDBInstancesRequest({regionId:e.region,DBInstanceId:n,pageNumber:1,pageSize:30}))).body?.items?.DBInstance||[],o=r.find((c)=>c.DBInstanceId===n)||r[0];if(!o?.DBInstanceId)throw Error(`未找到数据库实例: ${n}`);return Jg(o)}async function tc(n=200){let{auth:e,client:t}=ie(),s=[],r=Math.max(1,Math.min(Math.floor(n),500)),o=Math.min(100,r);for(let c=1;c<=20&&s.length<r;c+=1){let f=await t.describeDBInstances(new ee.DescribeDBInstancesRequest({regionId:e.region,pageNumber:c,pageSize:o})),u=f.body?.items?.DBInstance||[];for(let l of u){let d=Jg(l);if(!d.instanceId)continue;if(s.push(d),s.length>=r)break}let g=f.body||{},i=Number(g.totalRecordCount||g.totalCount||0);if(u.length===0||Number.isFinite(i)&&i>0&&s.length>=i)break}return s}async function tr(n){let e=n.trim();if(!e)throw Error("instanceId 不能为空");let{client:t}=ie(),s=await Sp(e),o=((await t.describeDBInstanceNetInfo(new ee.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).map((i)=>({type:i.connectionStringType,ipType:i.IPType,host:i.connectionString,port:i.port,vpcId:i.VPCId,vSwitchId:i.vSwitchId})),f=((await t.describeDatabases(new ee.DescribeDatabasesRequest({DBInstanceId:e,pageNumber:1,pageSize:100}))).body?.databases?.database||[]).map((i)=>i.DBName).filter((i)=>typeof i==="string"&&i.length>0),g=((await t.describeAccounts(new ee.DescribeAccountsRequest({DBInstanceId:e}))).body?.accounts?.DBInstanceAccount||[]).map((i)=>i.accountName).filter((i)=>typeof i==="string"&&i.length>0);return{summary:s,endpoints:o,databases:f,accounts:g}}async function sr(n){let e=k.getProject(),t=Zg(e),s=n?.trim()||t.instanceId||"";if(!s)throw Error("未指定 DB 实例 ID,且当前项目未绑定数据库实例");let r=await tr(s),o=Og(r.summary.engine),c=r.endpoints.find((L)=>L.ipType==="Private"&&L.host)||r.endpoints.find((L)=>L.host),f=c?.host?.trim();if(!f)throw Error(`未获取到实例 ${s} 的连接地址`);let u=Number(c?.port||(o==="postgresql"?"5432":"3306"));if(!Number.isFinite(u)||u<=0)throw Error(`实例 ${s} 返回了无效端口`);let g=r.endpoints.find((L)=>L.ipType==="Public"&&L.host),l=t.instanceId===s?Xg(e.envs?.DATABASE_URL):null,d=l?.username||t.user||"<username>",a=l?.database||t.name||r.databases[0]||"main",p=l?.password||"",w=p.length>0,_=w?encodeURIComponent(p):"<password>",$=d==="<username>"?d:encodeURIComponent(d),y=`${o}://${$}:${_}@${f}:${u}/${a}`,h,U,C;if(g?.host)h=g.host.trim(),U=Number(g.port||u),C=`${o}://${$}:${_}@${h}:${U}/${a}`;return{instanceId:s,engine:o,host:f,port:u,database:a,username:d,passwordKnown:w,connectionString:y,publicHost:h,publicPort:U,publicConnectionString:C}}async function sc(n){let{client:e}=ie();await e.deleteDBInstance(new ee.DeleteDBInstanceRequest({DBInstanceId:n}))}import*as ot from"@alicloud/rds20140815";dn();var Tp="licell_public";async function rc(n,e){let{client:t}=ie(),o=((await t.describeDBInstanceNetInfo(new ot.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(o?.connectionString)return{host:o.connectionString,port:o.port||"3306"};let c=`${n}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await t.allocateInstancePublicConnection(new ot.AllocateInstancePublicConnectionRequest({DBInstanceId:n,connectionStringPrefix:c,port:"3306"}))}catch(i){let l=A(i);if(l.includes("NetTypeExists")||l.includes("already exists"))e.message("公网地址已存在,正在获取...");else throw i}let g=((await t.describeDBInstanceNetInfo(new ot.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(!g?.connectionString)return null;return{host:g.connectionString,port:g.port||"3306"}}async function oc(n,e,t){let{client:s}=ie(),r=`${e}/32`;try{await s.modifySecurityIps(new ot.ModifySecurityIpsRequest({DBInstanceId:n,DBInstanceIPArrayName:Tp,securityIps:r,modifyMode:"Cover"}))}catch(o){t.message(`⚠️ 公网白名单设置失败: ${A(o)}`)}}on();fn();function nl(n){n.command("db add","分配数据库实例").option("--type <type>","数据库类型:postgresql 或 mysql(默认 serverless-postgresql,即将上线)").option("--engine-version <version>","数据库引擎版本(postgres 默认 18.0,mysql 默认 8.0)").option("--category <category>","RDS Category(默认 serverless_basic)").option("--class <instanceClass>","实例规格(如 pg.n2.serverless.1c)").option("--storage <gb>","存储空间 GB(默认 20)").option("--storage-type <storageType>","存储类型(默认 cloud_essd)").option("--min-rcu <n>","Serverless 最小 RCU(如 0.5)").option("--max-rcu <n>","Serverless 最大 RCU(如 8)").option("--auto-pause <mode>","自动启停:on/off").option("--zone <zoneId>","主可用区(如 cn-hangzhou-b)").option("--zone-slave1 <zoneId>","备可用区 1(多可用区部署)").option("--zone-slave2 <zoneId>","备可用区 2(多可用区部署)").option("--vpc <vpcId>","指定 VPC ID").option("--vsw <vSwitchId>","指定 VSwitch ID").option("--security-ip-list <cidrs>","白名单 CIDR(逗号分隔)").option("--description <text>","实例描述").action(async(e)=>{await G({commandLabel:"licell db add",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{W(v.bgMagenta(v.white(" \uD83D\uDDC4️ Database Provisioning (IaC) "))),B();let t=I(),s,r=E(e.type);if(r)s=qi(r);else if(t){let d=await Fp({message:"选择数据库引擎:",options:[{value:"postgres",label:"\uD83D\uDC18 RDS PostgreSQL(按量付费)"},{value:"mysql",label:"\uD83D\uDC2C RDS Serverless MySQL"},{value:"serverless-postgresql",label:"\uD83D\uDC18 RDS Serverless PostgreSQL(即将上线)"}]});if(zg(d))process.exit(0);s=d}else throw Error("非交互模式下请传入 --type postgresql|mysql");if(s==="serverless-postgresql"){console.log(v.yellow("⏳ Serverless PostgreSQL 即将上线,敬请期待。")),console.log(v.gray(`当前支持的类型:${v.bold("postgresql")}(按量付费)和 ${v.bold("mysql")}(Serverless)`)),q("");return}let o=s,c=Fn(e.storage,"storage"),f=Lo(e.minRcu,"min-rcu"),u=Lo(e.maxRcu,"max-rcu");if(typeof f==="number"&&f<=0)throw Error("min-rcu 必须大于 0");if(typeof u==="number"&&u<=0)throw Error("max-rcu 必须大于 0");if(typeof f==="number"&&typeof u==="number"&&f>u)throw Error("min-rcu 不能大于 max-rcu");let g=E(e.autoPause)?Ri(e.autoPause):void 0,i=H(),l=await F(i,"正在初始化基础设施编排引擎...","❌ 拉起失败",()=>ec(o,i,{engineVersion:E(e.engineVersion),category:E(e.category),instanceClass:E(e.class),storageGb:c,storageType:E(e.storageType),minCapacity:f,maxCapacity:u,autoPause:g,zoneId:E(e.zone),zoneIdSlave1:E(e.zoneSlave1),zoneIdSlave2:E(e.zoneSlave2),vpcId:E(e.vpc),vSwitchId:E(e.vsw),securityIpList:E(e.securityIpList),description:E(e.description)}));if(!l)return;if(!m())i.stop(v.green("✅ 数据库实例已就绪并绑定到本工程内网!"));if(m()){T({stage:"db.add",type:s,connectionStringMasked:Ae(l)});return}console.log(`
1590
+ bucket: ${bn.cyan(l.bucket)}`),console.log(`prefix: ${bn.cyan(l.targetDir||"(root)")}`),console.log(`base: ${bn.cyan(l.baseUrl)}`),console.log(`hint: ${bn.gray(`${l.baseUrl}/${d}<file>`)}`),l.skippedSymlinkCount>0)console.log(bn.yellow(`warning: 已跳过 ${l.skippedSymlinkCount} 个符号链接(为避免目录逃逸与递归风险)`));console.log(""),q("Done.")})})};e("oss upload [bucket]","上传本地目录到 OSS Bucket 指定目录"),e("oss bucket [bucket]","上传本地目录到 OSS Bucket 指定目录(兼容命令,等同 oss upload)")}import{select as Fp,confirm as xp,isCancel as zg}from"@clack/prompts";import v from"picocolors";Hn();Q();ht();dn();Zn();pt();import*as mn from"@alicloud/rds20140815";Q();xn();import gp from"@alicloud/rds20140815";import*as Yg from"@alicloud/openapi-client";var lp=60000,dp=120000,yp=nn(gp,"@alicloud/rds20140815");function ie(){let n=k.requireAuth(),e=new yp(new Yg.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:`rds.${n.region}.aliyuncs.com`,connectTimeout:lp,readTimeout:dp}));return{auth:n,client:e}}var ap=1200000,Vg=5000,hp=300000,zo=5,mp="pg.n1e.1c.1m",wp=20,pp="18.0",$p={postgres:"pg.n2.serverless.1c",mysql:"mysql.n2.serverless.1c"},bp={postgres:20,mysql:20},nc={postgres:{minCapacity:0.5,maxCapacity:8,autoPause:!0},mysql:{minCapacity:0.5,maxCapacity:2,autoPause:!0}},jg={postgres:"AliyunServiceRoleForRdsPgsqlOnEcs",mysql:"AliyunServiceRoleForRds"};function kp(n,e){if(!e)return!1;if(e===n)return!0;let t=n.split(".")[0],s=e.split(".")[0];return t.length>0&&t===s}async function _p(n,e,t,s){try{let o=((await n.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:e,engine:t,engineVersion:s,category:"serverless_basic"}))).body?.availableZones||[]).map((c)=>c.zoneId).filter((c)=>typeof c==="string"&&c.length>0);if(o.length>0)return[...new Set(o)]}catch{}try{let o=(await n.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:e,engine:t,engineVersion:s}))).body?.availableZones||[],c=[];for(let u of o){let g=u.zoneId;if(typeof g!=="string"||g.length===0)continue;if((u.supportedEngines||[]).some((l)=>{if((l.engine||"").toLowerCase()!==t.toLowerCase())return!1;return(l.supportedEngineVersions||[]).some((d)=>{if(!kp(s,d.version))return!1;return(d.supportedCategorys||[]).some((a)=>a.category==="serverless_basic")})}))c.push(g)}if(c.length>0)return[...new Set(c)];let f=o.map((u)=>u.zoneId).filter((u)=>typeof u==="string"&&u.length>0);return[...new Set(f)]}catch{return[]}}async function Dg(n,e,t){let s=t==="postgres"?["AliyunServiceRoleForRds",jg.postgres]:[jg.mysql];for(let r of s){let o=!1;try{let f=((await n.checkServiceLinkedRole(new mn.CheckServiceLinkedRoleRequest({regionId:e,serviceLinkedRole:r}))).body?.hasServiceLinkedRole||"").toString().toLowerCase();o=f==="true"||f==="1"}catch{}if(o)continue;try{await n.createServiceLinkedRole(new mn.CreateServiceLinkedRoleRequest({regionId:e,serviceLinkedRole:r}))}catch(c){if(Er(c))continue;throw c}}}async function Wg(n,e,t){let s;for(let r=1;r<=zo;r+=1)try{return await n.createDBInstance(e)}catch(o){if(s=o,!Rn(o)||r===zo)throw o;t.message(`\uD83C\uDF10 RDS API 网络抖动,${r}/${zo} 次失败,正在重试...`),await yn(1500*r)}throw s instanceof Error?s:Error("RDS 创建失败")}async function Cp(n,e,t,s){let r=t==="postgres"?"5432":"3306",o=Date.now();while(!0){if(Date.now()-o>hp)throw Error("数据库网络信息未就绪,等待连接地址超时");let f=(await n.describeDBInstanceNetInfo(new mn.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[],u=f.find((d)=>d.IPType==="Private")||f[0],g=u?.connectionString?.trim(),i=u?.port||r;if(g)return{host:g,port:i};let l=f.map((d)=>`${d.IPType||"Unknown"}:${d.connectionString||"-"}`).join(",")||"pending";s.message(`☕ 数据库连接地址初始化中,请稍候... [${l}]`),await yn(Vg)}}function Qg(n){let e=n.toLowerCase().replace(/[^a-z0-9_]/g,"_");if(!/^[a-z]/.test(e))e=`a_${e}`;if(e=e.replace(/_+/g,"_").replace(/_$/,""),e.length<2||!/[a-z0-9]/.test(e.slice(1)))e="licell_user";return e.slice(0,16)}async function ec(n,e,t={}){let{auth:s,client:r}=ie(),o=k.getProject(),c="main",f=Qg(o.appName||"licell_app"),u=Bn(),g=n==="postgres"?"PostgreSQL":"MySQL",i=n==="postgres",l=t.engineVersion?.trim()||(i?pp:"8.0"),d=i?t.category?.trim()||"Basic":t.category?.trim()||"serverless_basic",a=t.storageType?.trim()||"cloud_essd";if(i)e.message("\uD83D\uDD0E 正在查询 RDS PostgreSQL 可用区...");else e.message("\uD83D\uDD0E 正在查询 RDS Serverless 可用区...");let p;if(i)try{p=((await r.describeAvailableZones(new mn.DescribeAvailableZonesRequest({regionId:s.region,engine:g,engineVersion:l,category:d}))).body?.availableZones||[]).map((Mn)=>Mn.zoneId).filter((Mn)=>typeof Mn==="string"&&Mn.length>0),p=[...new Set(p)]}catch{p=[]}else p=await _p(r,s.region,g,l);e.message("\uD83D\uDD0D 正在探测/拉起专属私有网络平面 (VPC & VSwitch)...");let w=t.zoneId?.trim(),_=t.vpcId?.trim(),$=t.vSwitchId?.trim(),y;if(_||$){if(!_||!$)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!w)throw Error("自定义网络时需提供 --zone");y=await wt({vpcId:_,vswId:$,zoneId:w})}else y=await Re({preferredZoneIds:w?[w]:p});let h=t.instanceClass?.trim()||(i?mp:$p[n]),U=t.storageGb||(i?wp:bp[n]);try{let Mn=(await r.describeAvailableClasses(new mn.DescribeAvailableClassesRequest({regionId:s.region,zoneId:y.zoneId,engine:g,engineVersion:l,instanceChargeType:i?"PostPaid":"Serverless",category:d,DBInstanceStorageType:a}))).body?.DBInstanceClasses?.find((lt)=>{if(typeof lt.DBInstanceClass!=="string"||lt.DBInstanceClass.length===0)return!1;if(!t.instanceClass)return!0;return lt.DBInstanceClass===t.instanceClass});if(!t.instanceClass&&typeof Mn?.DBInstanceClass==="string"&&Mn.DBInstanceClass.length>0)h=Mn.DBInstanceClass;let gt=Mn?.DBInstanceStorageRange?.minValue;if(!t.storageGb&&typeof gt==="number"&&Number.isFinite(gt)&&gt>0)U=Math.floor(gt)}catch{e.message("⚠️ 未能查询到可售规格,回退到内置默认规格继续创建...")}e.message("\uD83D\uDD10 正在确保 RDS 服务关联角色已就绪..."),await Dg(r,s.region,n),e.message(i?"\uD83D\uDCE6 正在拉起 PostgreSQL (按量付费)...":`\uD83D\uDCE6 正在拉起 Serverless ${n.toUpperCase()} (按量计费)...`);let C={engine:g,engineVersion:l,payType:i?"Postpaid":"Serverless",category:d,regionId:s.region,zoneId:y.zoneId,DBInstanceClass:h,DBInstanceStorage:U,DBInstanceStorageType:a,securityIPList:t.securityIpList?.trim()||y.cidrBlock||"10.0.0.0/8",instanceNetworkType:"VPC",DBInstanceNetType:"Intranet",DBInstanceDescription:t.description?.trim()||`${o.appName||"licell-app"}-${n}`,VPCId:y.vpcId,vSwitchId:y.vswId};if(!i)C.serverlessConfig={minCapacity:t.minCapacity??nc[n].minCapacity,maxCapacity:t.maxCapacity??nc[n].maxCapacity,autoPause:t.autoPause??nc[n].autoPause};let L=t.zoneIdSlave1?.trim(),S=t.zoneIdSlave2?.trim();if(L)C.zoneIdSlave1=L;if(S)C.zoneIdSlave2=S;let b=new mn.CreateDBInstanceRequest(C),P;try{P=await Wg(r,b,e)}catch(Se){if(!Es(Se))throw Se;e.message("\uD83D\uDD10 首次使用检测到缺少服务关联角色,正在自动创建并重试..."),await Dg(r,s.region,n);try{P=await Wg(r,b,e)}catch(Mn){if(Es(Mn))throw Error("RDS PostgreSQL 仍提示缺少服务关联角色。请先在阿里云控制台开通 RDS PostgreSQL 服务后重试,或先创建 MySQL Serverless 实例。");throw Mn}}let R=P.body?.DBInstanceId;if(!R)throw Error("RDS 创建失败:未返回 DBInstanceId");let j="Creating",gn=Date.now();while(j!=="Running"){if(Date.now()-gn>ap)throw Error(`数据库创建超时,最后状态: ${j}`);if(await yn(Vg),j=(await r.describeDBInstances(new mn.DescribeDBInstancesRequest({DBInstanceId:R}))).body?.items?.DBInstance?.[0]?.DBInstanceStatus||"Creating",j==="Deleted"||j==="Failed")throw Error(`数据库创建失败,实例状态: ${j}`);e.message(`☕ 数据库底层初始化中,请稍候... [${j}]`)}if(e.message("\uD83E\uDDF1 正在创建数据库与应用账号..."),await Rs(()=>r.createAccount(new mn.CreateAccountRequest({DBInstanceId:R,accountName:f,accountPassword:u,accountType:"Normal",accountDescription:"licell managed account"}))),await Rs(()=>r.createDatabase(new mn.CreateDatabaseRequest({DBInstanceId:R,DBName:"main",characterSetName:n==="postgres"?"UTF8":"utf8mb4",ownerAccount:n==="postgres"?f:void 0}))),n!=="postgres")await Rs(()=>r.grantAccountPrivilege(new mn.GrantAccountPrivilegeRequest({DBInstanceId:R,DBName:"main",accountName:f,accountPrivilege:"ReadWrite"})));let{host:Xn,port:ln}=await Cp(r,R,n,e),We=`${n==="postgres"?"postgresql":"mysql"}://${encodeURIComponent(f)}:${encodeURIComponent(u)}@${Xn}:${ln}/main`;return o.envs={...o.envs,DATABASE_URL:We},o.network=y,o.database={type:n,instanceId:R,user:f,name:"main"},k.setProject(o),e.message("⚠️ DATABASE_URL(含密码)已写入 .licell/project.json,请确认该目录已在 .gitignore 中"),We}Q();import*as ee from"@alicloud/rds20140815";function Xg(n){if(!n)return null;try{let e=new URL(n),t=e.protocol==="postgresql:"?"postgresql":e.protocol==="mysql:"?"mysql":null;if(!t||!e.hostname)return null;let s=e.port||(t==="postgresql"?"5432":"3306"),r=Number(s);if(!Number.isFinite(r)||r<=0)return null;let o=e.pathname.replace(/^\//,"")||"main";return{protocol:t,host:e.hostname,port:r,database:o,username:decodeURIComponent(e.username||""),password:decodeURIComponent(e.password||"")}}catch{return null}}function Zg(n){let e=n.database;if(typeof e!=="object"||e===null)return{};let t=e;return{instanceId:typeof t.instanceId==="string"?t.instanceId:void 0,user:typeof t.user==="string"?t.user:void 0,name:typeof t.name==="string"?t.name:void 0}}function Og(n){return(n||"").toLowerCase().includes("postgres")?"postgresql":"mysql"}function Jg(n){return{instanceId:n.DBInstanceId||"",description:n.DBInstanceDescription,engine:n.engine,engineVersion:n.engineVersion,status:n.DBInstanceStatus,payType:n.payType,category:n.category,instanceClass:n.DBInstanceClass,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}async function Sp(n){let{auth:e,client:t}=ie(),r=(await t.describeDBInstances(new ee.DescribeDBInstancesRequest({regionId:e.region,DBInstanceId:n,pageNumber:1,pageSize:30}))).body?.items?.DBInstance||[],o=r.find((c)=>c.DBInstanceId===n)||r[0];if(!o?.DBInstanceId)throw Error(`未找到数据库实例: ${n}`);return Jg(o)}async function tc(n=200){let{auth:e,client:t}=ie(),s=[],r=Math.max(1,Math.min(Math.floor(n),500)),o=Math.min(100,r);for(let c=1;c<=20&&s.length<r;c+=1){let f=await t.describeDBInstances(new ee.DescribeDBInstancesRequest({regionId:e.region,pageNumber:c,pageSize:o})),u=f.body?.items?.DBInstance||[];for(let l of u){let d=Jg(l);if(!d.instanceId)continue;if(s.push(d),s.length>=r)break}let g=f.body||{},i=Number(g.totalRecordCount||g.totalCount||0);if(u.length===0||Number.isFinite(i)&&i>0&&s.length>=i)break}return s}async function tr(n){let e=n.trim();if(!e)throw Error("instanceId 不能为空");let{client:t}=ie(),s=await Sp(e),o=((await t.describeDBInstanceNetInfo(new ee.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).map((i)=>({type:i.connectionStringType,ipType:i.IPType,host:i.connectionString,port:i.port,vpcId:i.VPCId,vSwitchId:i.vSwitchId})),f=((await t.describeDatabases(new ee.DescribeDatabasesRequest({DBInstanceId:e,pageNumber:1,pageSize:100}))).body?.databases?.database||[]).map((i)=>i.DBName).filter((i)=>typeof i==="string"&&i.length>0),g=((await t.describeAccounts(new ee.DescribeAccountsRequest({DBInstanceId:e}))).body?.accounts?.DBInstanceAccount||[]).map((i)=>i.accountName).filter((i)=>typeof i==="string"&&i.length>0);return{summary:s,endpoints:o,databases:f,accounts:g}}async function sr(n){let e=k.getProject(),t=Zg(e),s=n?.trim()||t.instanceId||"";if(!s)throw Error("未指定 DB 实例 ID,且当前项目未绑定数据库实例");let r=await tr(s),o=Og(r.summary.engine),c=r.endpoints.find((L)=>L.ipType==="Private"&&L.host)||r.endpoints.find((L)=>L.host),f=c?.host?.trim();if(!f)throw Error(`未获取到实例 ${s} 的连接地址`);let u=Number(c?.port||(o==="postgresql"?"5432":"3306"));if(!Number.isFinite(u)||u<=0)throw Error(`实例 ${s} 返回了无效端口`);let g=r.endpoints.find((L)=>L.ipType==="Public"&&L.host),l=t.instanceId===s?Xg(e.envs?.DATABASE_URL):null,d=l?.username||t.user||"<username>",a=l?.database||t.name||r.databases[0]||"main",p=l?.password||"",w=p.length>0,_=w?encodeURIComponent(p):"<password>",$=d==="<username>"?d:encodeURIComponent(d),y=`${o}://${$}:${_}@${f}:${u}/${a}`,h,U,C;if(g?.host)h=g.host.trim(),U=Number(g.port||u),C=`${o}://${$}:${_}@${h}:${U}/${a}`;return{instanceId:s,engine:o,host:f,port:u,database:a,username:d,passwordKnown:w,connectionString:y,publicHost:h,publicPort:U,publicConnectionString:C}}async function sc(n){let{client:e}=ie();await e.deleteDBInstance(new ee.DeleteDBInstanceRequest({DBInstanceId:n}))}import*as ot from"@alicloud/rds20140815";dn();var Tp="licell_public";async function rc(n,e){let{client:t}=ie(),o=((await t.describeDBInstanceNetInfo(new ot.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(o?.connectionString)return{host:o.connectionString,port:o.port||"3306"};let c=`${n}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await t.allocateInstancePublicConnection(new ot.AllocateInstancePublicConnectionRequest({DBInstanceId:n,connectionStringPrefix:c,port:"3306"}))}catch(i){let l=A(i);if(l.includes("NetTypeExists")||l.includes("already exists"))e.message("公网地址已存在,正在获取...");else throw i}let g=((await t.describeDBInstanceNetInfo(new ot.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(!g?.connectionString)return null;return{host:g.connectionString,port:g.port||"3306"}}async function oc(n,e,t){let{client:s}=ie(),r=`${e}/32`;try{await s.modifySecurityIps(new ot.ModifySecurityIpsRequest({DBInstanceId:n,DBInstanceIPArrayName:Tp,securityIps:r,modifyMode:"Cover"}))}catch(o){t.message(`⚠️ 公网白名单设置失败: ${A(o)}`)}}on();fn();function nl(n){n.command("db add","分配数据库实例").option("--type <type>","数据库类型:postgresql 或 mysql(默认 serverless-postgresql,即将上线)").option("--engine-version <version>","数据库引擎版本(postgres 默认 18.0,mysql 默认 8.0)").option("--category <category>","RDS Category(默认 serverless_basic)").option("--class <instanceClass>","实例规格(如 pg.n2.serverless.1c)").option("--storage <gb>","存储空间 GB(默认 20)").option("--storage-type <storageType>","存储类型(默认 cloud_essd)").option("--min-rcu <n>","Serverless 最小 RCU(如 0.5)").option("--max-rcu <n>","Serverless 最大 RCU(如 8)").option("--auto-pause <mode>","自动启停:on/off").option("--zone <zoneId>","主可用区(如 cn-hangzhou-b)").option("--zone-slave1 <zoneId>","备可用区 1(多可用区部署)").option("--zone-slave2 <zoneId>","备可用区 2(多可用区部署)").option("--vpc <vpcId>","指定 VPC ID").option("--vsw <vSwitchId>","指定 VSwitch ID").option("--security-ip-list <cidrs>","白名单 CIDR(逗号分隔)").option("--description <text>","实例描述").action(async(e)=>{await G({commandLabel:"licell db add",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{W(v.bgMagenta(v.white(" \uD83D\uDDC4️ Database Provisioning (IaC) "))),B();let t=I(),s,r=E(e.type);if(r)s=qi(r);else if(t){let d=await Fp({message:"选择数据库引擎:",options:[{value:"postgres",label:"\uD83D\uDC18 RDS PostgreSQL(按量付费)"},{value:"mysql",label:"\uD83D\uDC2C RDS Serverless MySQL"},{value:"serverless-postgresql",label:"\uD83D\uDC18 RDS Serverless PostgreSQL(即将上线)"}]});if(zg(d))process.exit(0);s=d}else throw Error("非交互模式下请传入 --type postgresql|mysql");if(s==="serverless-postgresql"){console.log(v.yellow("⏳ Serverless PostgreSQL 即将上线,敬请期待。")),console.log(v.gray(`当前支持的类型:${v.bold("postgresql")}(按量付费)和 ${v.bold("mysql")}(Serverless)`)),q("");return}let o=s,c=Fn(e.storage,"storage"),f=Lo(e.minRcu,"min-rcu"),u=Lo(e.maxRcu,"max-rcu");if(typeof f==="number"&&f<=0)throw Error("min-rcu 必须大于 0");if(typeof u==="number"&&u<=0)throw Error("max-rcu 必须大于 0");if(typeof f==="number"&&typeof u==="number"&&f>u)throw Error("min-rcu 不能大于 max-rcu");let g=E(e.autoPause)?Ri(e.autoPause):void 0,i=H(),l=await F(i,"正在初始化基础设施编排引擎...","❌ 拉起失败",()=>ec(o,i,{engineVersion:E(e.engineVersion),category:E(e.category),instanceClass:E(e.class),storageGb:c,storageType:E(e.storageType),minCapacity:f,maxCapacity:u,autoPause:g,zoneId:E(e.zone),zoneIdSlave1:E(e.zoneSlave1),zoneIdSlave2:E(e.zoneSlave2),vpcId:E(e.vpc),vSwitchId:E(e.vsw),securityIpList:E(e.securityIpList),description:E(e.description)}));if(!l)return;if(!m())i.stop(v.green("✅ 数据库实例已就绪并绑定到本工程内网!"));if(m()){T({stage:"db.add",type:s,connectionStringMasked:Ae(l)});return}console.log(`
1591
1591
  \uD83D\uDD11 内网直连凭证已生成: ${v.cyan(Ae(l))}
1592
1592
  `),q("下次执行 licell deploy 时,将自动作为 process.env.DATABASE_URL 注入!")})}),n.command("db list","查看数据库实例列表").option("--limit <n>","返回数量,默认 20").action(async(e)=>{await G({commandLabel:"licell db list",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{B();let t=An(e.limit,20,200),s=H(),r=await F(s,"正在拉取数据库实例列表...","❌ 获取数据库实例列表失败",()=>tc(t));if(!r)return;if(!m())s.stop(v.green(`✅ 共获取 ${r.length} 个实例`));if(m()){T({stage:"db.list",count:r.length,instances:r});return}if(r.length===0){q("当前地域没有数据库实例");return}for(let o of r)console.log(`${v.cyan(o.instanceId)} engine=${v.gray(`${o.engine||"-"} ${o.engineVersion||""}`.trim())} status=${v.gray(o.status||"-")} class=${v.gray(o.instanceClass||"-")}`);console.log(""),q("Done.")})}),n.command("db info <instanceId>","查看数据库实例详情").action(async(e)=>{await G({commandLabel:"licell db info",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{B();let t=K(e,"instanceId"),s=H(),r=await F(s,`正在拉取实例 ${t} 详情...`,"❌ 获取数据库实例详情失败",()=>tr(t));if(!r)return;let o=r.summary;if(!m())s.stop(v.green("✅ 获取成功"));else{T({stage:"db.info",instanceId:t,detail:r});return}if(console.log(`
1593
1593
  instanceId: ${v.cyan(o.instanceId)}`),console.log(`engine: ${v.cyan(`${o.engine||"-"} ${o.engineVersion||""}`.trim())}`),console.log(`status: ${v.cyan(o.status||"-")}`),console.log(`class: ${v.cyan(o.instanceClass||"-")}`),console.log(`payType: ${v.cyan(o.payType||"-")}`),console.log(`vpc/vsw: ${v.cyan(`${o.vpcId||"-"} / ${o.vSwitchId||"-"}`)}`),console.log(`zone: ${v.cyan(o.zoneId||"-")}`),r.endpoints.length>0)console.log(`endpoints: ${v.cyan(r.endpoints.map((c)=>`${c.ipType||c.type||"-"}:${c.host||"-"}:${c.port||"-"}`).join(", "))}`);if(r.databases.length>0)console.log(`databases: ${v.cyan(r.databases.join(", "))}`);if(r.accounts.length>0)console.log(`accounts: ${v.cyan(r.accounts.join(", "))}`);console.log(""),q("Done.")})}),n.command("db connect [instanceId]","输出数据库连接信息").action(async(e)=>{await G({commandLabel:"licell db connect",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{B();let t=E(e),s=H(),r=await F(s,"正在解析数据库连接信息...","❌ 连接信息解析失败",()=>sr(t));if(!r)return;if(!m())s.stop(v.green("✅ 连接信息已生成"));else{T({stage:"db.connect",instanceId:r.instanceId,connection:r});return}if(console.log(`
1594
1594
  instanceId: ${v.cyan(r.instanceId)}`),console.log(`engine: ${v.cyan(r.engine)}`),console.log(`host: ${v.cyan(r.host)}`),console.log(`port: ${v.cyan(String(r.port))}`),console.log(`database: ${v.cyan(r.database)}`),console.log(`username: ${v.cyan(r.username)}`),console.log(`password: ${v.cyan(r.passwordKnown?"<known in project>":"<unknown, please provide manually>")}`),console.log(`url: ${v.cyan(r.connectionString)}`),r.publicHost)console.log(""),console.log(v.yellow("── 公网访问 ──")),console.log(`public host: ${v.cyan(r.publicHost)}`),console.log(`public port: ${v.cyan(String(r.publicPort))}`),console.log(`public url: ${v.cyan(r.publicConnectionString)}`);console.log(""),q("Done.")})}),n.command("db public-access [instanceId]","开通数据库公网访问并添加当前 IP 到白名单").option("--ip <ip>","手动指定公网 IP(不传则自动获取)").action(async(e,t)=>{await G({commandLabel:"licell db public-access",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{let{resolvePublicIp:s}=await Promise.resolve().then(() => (fc(),cc));W(v.bgMagenta(v.white(" \uD83C\uDF10 DB Public Access "))),B();let r=E(e),o=H();o.start("正在获取公网 IP...");let c=t.ip?.trim()||await s();o.stop(`公网 IP: ${v.cyan(c)}`);let f=await F(o,"正在解析数据库连接信息...","❌ 连接信息解析失败",()=>sr(r));if(!f)return;await F(o,`正在将 ${c}/32 添加到白名单 (licell_public)...`,"❌ 白名单设置失败",()=>oc(f.instanceId,c,o));let u=await F(o,"正在开通公网访问...","❌ 公网访问开通失败",()=>rc(f.instanceId,o));if(!m())o.stop(v.green("✅ 公网访问已开通"));else{T({stage:"db.public-access",instanceId:f.instanceId,publicIp:c,publicHost:u?.host||null,publicPort:u?.port||null});return}if(console.log(""),console.log(v.yellow("── 内网访问 ──")),console.log(`host: ${v.cyan(f.host)}`),console.log(`port: ${v.cyan(String(f.port))}`),console.log(`url: ${v.cyan(f.connectionString)}`),u){console.log(""),console.log(v.yellow("── 公网访问 ──")),console.log(`host: ${v.cyan(u.host)}`),console.log(`port: ${v.cyan(u.port)}`);let g=f.engine,i=f.username==="<username>"?f.username:encodeURIComponent(f.username),l=f.passwordKnown?"<password>":"<password>";console.log(`url: ${v.cyan(`${g}://${i}:${l}@${u.host}:${u.port}/${f.database}`)}`)}else console.log(v.yellow(`
1595
1595
  ⚠️ 公网地址尚未就绪,请稍后通过 db connect 查看`));console.log(`
1596
- 白名单 IP: ${v.cyan(`${c}/32`)} (分组: licell_public)`),q("Done.")})}),n.command("db rm <instanceId>","删除数据库实例").option("--yes","跳过确认").action(async(e,t)=>{await G({commandLabel:"licell db rm",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{W(v.bgRed(v.white(" \uD83D\uDDD1️ Delete Database "))),B();let s=e.trim();if(!s)throw Error("请提供 instanceId");if(!t.yes&&I()){let o=await xp({message:`确认删除数据库实例 ${v.red(s)}?此操作不可恢复。`});if(zg(o)||!o){q("已取消");return}}let r=H();if(await F(r,`正在删除实例 ${s}...`,"❌ 删除失败",()=>sc(s)),m()){T({stage:"db.rm",instanceId:s});return}q(`实例 ${s} 已删除`)})})}import{select as Qp,confirm as Xp,isCancel as $l}from"@clack/prompts";import Y from"picocolors";Hn();Q();ht();Zn();pt();import*as ge from"@alicloud/r-kvstore20150101";import{randomUUID as ml}from"crypto";xn();import Lp from"@alicloud/r-kvstore20150101";import*as be from"@alicloud/openapi-client";import*as el from"@alicloud/tea-util";var Ap=nn(Lp,"@alicloud/r-kvstore20150101"),Hp=nn(be.default,"@alicloud/openapi-client");function jn(n){return new Ap(new be.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:"r-kvstore.aliyuncs.com",connectTimeout:30000,readTimeout:60000}))}function Up(n){return new Hp(new be.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:"r-kvstore.aliyuncs.com"}))}function Mp(n){if(n===null||n===void 0)return;if(typeof n==="boolean")return n?"true":"false";return String(n)}function Gp(n){let e={};for(let[t,s]of Object.entries(n)){let r=Mp(s);if(r===void 0)continue;e[t]=r}return e}async function tl(n,e,t){let s=Up(n),r=new be.Params({action:e,version:"2015-01-01",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),o=new be.OpenApiRequest({query:Gp(t)});return s.callApi(r,o,new el.RuntimeOptions({readTimeout:20000,connectTimeout:8000}))}ht();dn();Zn();import*as En from"@alicloud/r-kvstore20150101";import{randomUUID as Bp}from"crypto";dn();function uc(n){return["Error","Released","Inactive","Unavailable","Flushing"].includes(n)}function te(n,e,t,s){if(!n)return`redis://:${encodeURIComponent(e)}@${t}:${s}`;return`redis://${encodeURIComponent(n)}:${encodeURIComponent(e)}@${t}:${s}`}function es(n,e,t,s,r){let o=r?encodeURIComponent(e):"<password>";if(!n)return`redis://:${o}@${t}:${s}`;return`redis://${n==="<username>"?n:encodeURIComponent(n)}:${o}@${t}:${s}`}function rr(n){if(typeof n!=="object"||n===null)return"";let e="code"in n?String(n.code||""):"";if(e)return e;if("data"in n&&typeof n.data==="object"&&n.data!==null){let t=n.data?.Code;if(t)return String(t)}return""}function sl(n){return/InvalidInstanceId\.NotFound/i.test(rr(n)||A(n))}function rl(n){let e=`${rr(n)} ${A(n)}`;return/NotSupport|Unsupported|InvalidInstanceId|InvalidParameter|OperationDenied|AccessDenied/i.test(e)}function or(n){let e=new Set;for(let t of n){if(!t)continue;let s=t.trim();if(s)e.add(s)}return[...e]}function ct(n){let e=(n||"").trim();if(!e)return null;let t=e.split(",")[0]?.trim();if(!t)return null;let s=/^[a-z][a-z0-9+.-]*:\/\//i.test(t)?t:`redis://${t}`;try{let r=new URL(s),o=r.hostname;if(!o)return null;let c=r.port?Number(r.port):6379;if(!Number.isFinite(c)||c<=0)return null;let f=r.username?decodeURIComponent(r.username):void 0,u=r.password?decodeURIComponent(r.password):void 0,g=r.toString();return{host:o,port:c,accountName:f,password:u,url:g}}catch{return null}}function ft(n){return n.startsWith("r-")}function ts(n){return n.startsWith("tk-")||n.startsWith("tt-")}function Ft(n,e){return{vpcId:e.vpcId,vswId:e.vswId,sgId:e.sgId??n?.sgId,cidrBlock:e.cidrBlock??n?.cidrBlock}}function ic(n){return{instanceId:n.instanceId||"",mode:"classic-redis",instanceName:n.instanceName,status:n.instanceStatus,instanceClass:n.instanceClass,engineVersion:n.engineVersion,host:n.connectionDomain,port:n.port,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}function ol(n){return{instanceId:n.instanceId||"",mode:"tair-serverless-kv",instanceName:n.instanceName,status:n.instanceStatus,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}var cl=1200000,fl=5000,ul=60000,il=180000,cr="kvcache.cu.g4b.2",gl=1;async function ss(n,e,t){let r=(await n.describeAccounts(new En.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[],o=r.filter((f)=>f.accountStatus!=="Unavailable");return(o.find((f)=>f.accountType==="Normal")||o[0]||r[0])?.accountName||t||""}async function ll(n,e,t){try{if((await n.describeServiceLinkedRoleExists(new En.DescribeServiceLinkedRoleExistsRequest({}))).body?.existsServiceLinkedRole)return;t.message("\uD83D\uDD10 正在初始化 Kvstore 服务关联角色..."),await n.initializeKvstorePermission(new En.InitializeKvstorePermissionRequest({regionId:e}))}catch(s){t.message(`⚠️ 服务关联角色检查失败 (${A(s)}),将继续尝试创建缓存`)}}async function dl(n,e,t,s,r){let o=s.instanceClass?.trim()||cr,c=`${r||"licell-app"}-redis`,f=Bn(),u=s.securityIpList?.trim()||t.cidrBlock||"10.0.0.0/8",i=(await tl(n,"CreateTairKVCacheInferInstance",{RegionId:n.region,InstanceName:c,InstanceClass:o,ZoneId:t.zoneId,VpcId:t.vpcId,VSwitchId:t.vswId,ChargeType:"PostPaid",AutoPay:!0,Password:f,SecurityIPList:u,ClientToken:Bp()})).body||{},l=typeof i.InstanceId==="string"?i.InstanceId:typeof i.instanceId==="string"?i.instanceId:"";if(!l)return null;let d=typeof i.ConnectionString==="string"?i.ConnectionString:typeof i.connectionString==="string"?i.connectionString:"",a=ct(d),p=a?.host||"",w=a?.port||6379,_=a?.accountName||"",$=jn(n);if(!p){let h=await xt($,e,[l]);p=h.host,w=h.port,_=h.accountName||_}if(!p)throw Error(`CreateTairKVCacheInferInstance 返回成功但未获取连接地址 (${l})`);let y=te(_||void 0,f,p,w);return{instanceId:l,host:p,port:w,accountName:_||void 0,password:f,redisUrl:y}}async function fr(n,e){let t=[],s=30;for(let r=1;r<=50;r+=1){let o=await n.describeTairKVCacheInferInstances(new En.DescribeTairKVCacheInferInstancesRequest({regionId:e,pageNumber:r,pageSize:30})),c=o.body?.instances?.tairInferInstanceDTO||[];t.push(...c);let f=o.body?.totalCount||0;if(c.length===0||t.length>=f)break}return t}function yl(n,e,t){let s=t?.trim();if(s)return s;let r=n.filter((f)=>{if(!(f.instanceId||"").startsWith("tk-"))return!1;let g=f.instanceStatus||"";if(g&&uc(g))return!1;return!0}),o=r.filter((f)=>{if(f.vpcId&&f.vpcId!==e.vpcId)return!1;if(f.vSwitchId&&f.vSwitchId!==e.vswId)return!1;if(e.zoneId&&f.zoneId&&f.zoneId!==e.zoneId)return!1;return!0}),c=o.length>0?o:r;if(c.length===1)return c[0].instanceId||"";if(c.length>1){let f=c.map((u)=>u.instanceId).filter(Boolean).join(", ");throw Error(`发现多个可用 vkName (${f}),请使用 --vk-name 显式指定`)}return""}async function al(n,e){try{return(await n.describeTairKVCacheInferInstanceAttribute(new En.DescribeTairKVCacheInferInstanceAttributeRequest({instanceId:e}))).body?.instances?.DBInstanceAttribute?.[0]}catch(t){if(sl(t))return null;throw t}}async function xt(n,e,t,s={}){let r=or(t);if(r.length===0)throw Error("未找到可查询的 Tair KVCache 实例 ID");let o=s.waitTimeoutMs||cl,c=Date.now(),f="Creating",u=0;while(!0){if(Date.now()-c>o)throw Error(`Tair Serverless KV 初始化超时,最后状态: ${f}`);let g=!1;for(let i of r){let l=await al(n,i);if(!l)continue;g=!0;let d=l.instanceStatus||"Creating";if(f=`${i}:${d}`,uc(d))throw Error(`Tair KVCache 创建失败,实例状态: ${d} (${i})`);let a=ct(l.connectionString);if(d==="Normal"&&a?.host)return{...a,sourceInstanceId:i}}if(!g){if(!u)u=Date.now();if(Date.now()-u>=ul)throw Error(`未查询到可用实例信息,请检查实例 ID 是否正确: ${r.join(", ")}`)}else u=0;e.message(`☕ Tair Serverless KV 初始化中,请稍候... [${f}]`),await yn(fl)}}async function rs(n,e,t,s){let r=or(e);for(let o of r)try{let c=await ss(n,o,s);if(!c)continue;return await n.resetAccountPassword(new En.ResetAccountPasswordRequest({instanceId:o,accountName:c,accountPassword:t})),{instanceId:o,accountName:c}}catch{continue}return null}async function os(n,e,t){let s=or(e);for(let r of s)try{return await n.resetTairKVCacheCustomInstancePassword(new En.ResetTairKVCacheCustomInstancePasswordRequest({instanceId:r,password:t})),{instanceId:r}}catch{continue}return null}async function Lt(n,e,t,s){try{await n.modifySecurityIps(new En.ModifySecurityIpsRequest({instanceId:e,securityIpGroupName:"default",modifyMode:"Append",securityIps:t}))}catch(r){if(tn(r)||rl(r)){s.message(`⚠️ 当前实例未应用白名单配置 (${A(r)})`);return}throw r}}async function cs(n,e,t){return((await n.describeInstances(new En.DescribeInstancesRequest({regionId:e.region,instanceIds:t,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance||[]).find((o)=>o.instanceId===t)||null}async function gc(n,e){return al(n,e)}function hl(n){let e=rr(n),t=(()=>{if(typeof n!=="object"||n===null)return"";if("data"in n&&typeof n.data==="object"&&n.data!==null){let r=n.data?.RequestId;if(r)return String(r)}return""})(),s=t?`, requestId=${t}`:"";return`⚠️ 直连 API 创建失败 [${e||"Unknown"}] ${A(n)}${s}`}async function vp(n,e,t,s,r,o){let c=r.instanceId?.trim()||"";n.message(`\uD83D\uDD17 正在绑定已有 Redis 实例 (${c})...`);let u=(await e.describeInstances(new ge.DescribeInstancesRequest({regionId:t.region,instanceIds:c,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance?.find((w)=>w.instanceId===c),g=u?.connectionDomain||s.cache?.host,i=u?.port||s.cache?.port||6379;if(!g)throw Error(`未查询到 Redis 连接地址,请确认实例 ${c} 可用`);let l=r.accountName?.trim()||s.cache?.accountName||"";if(!l)l=await ss(e,c,s.cache?.accountName);let d=r.existingPassword?.trim()||"";if(!d){if(!l)throw Error("未查询到 Redis 账号,请使用 --username 指定实例账号,或使用 --password 直接绑定");let w=Bn();await e.resetAccountPassword(new ge.ResetAccountPasswordRequest({instanceId:c,accountName:l,accountPassword:w})),d=w}let a=r.securityIpList?.trim()||o.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,c,a,n);let p=te(l||void 0,d,g,i);return s.envs={...s.envs,REDIS_URL:p,REDIS_HOST:g,REDIS_PORT:String(i),REDIS_PASSWORD:d,REDIS_USERNAME:l},s.network=Ft(s.network,o),s.cache={type:"redis",instanceId:c,host:g,port:i,accountName:l,mode:"classic-redis"},k.setProject(s),p}async function Kp(n,e,t,s,r){let o=s.instanceId?.trim()||"";n.message(`\uD83D\uDD17 正在绑定已有 Tair Serverless KV 实例 (${o})...`);let c=await xt(e,n,[o,s.vkName,t.cache?.vkName],{waitTimeoutMs:il});if(!c.host&&t.cache?.host)c={host:t.cache.host,port:t.cache.port||6379,url:`redis://${t.cache.host}:${t.cache.port||6379}`,sourceInstanceId:o};let f=s.accountName?.trim()||c.accountName||t.cache?.accountName||"",u=s.existingPassword?.trim()||c.password||"";if(!u){n.message("\uD83D\uDD10 未传入 --password,正在自动轮换实例密码...");let l=Bn(),d=await rs(e,[c.sourceInstanceId,s.vkName,o],l,f);if(d)f=d.accountName,u=l;else{if(!await os(e,[c.sourceInstanceId,o,s.vkName],l))throw Error("未能自动重置已存在实例密码。请使用 --password 传入控制台已设置密码,或先执行 `licell cache rotate-password --instance <id>` 再重试");u=l}}let g=s.securityIpList?.trim()||r.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,c.sourceInstanceId,g,n);let i=te(f||void 0,u,c.host,c.port);return t.envs={...t.envs,REDIS_URL:i,REDIS_HOST:c.host,REDIS_PORT:String(c.port),REDIS_PASSWORD:u,REDIS_USERNAME:f},t.network=Ft(t.network,r),t.cache={type:"redis",instanceId:c.sourceInstanceId,host:c.host,port:c.port,accountName:f,vkName:s.vkName?.trim()||t.cache?.vkName||(c.sourceInstanceId.startsWith("tk-")?c.sourceInstanceId:void 0),mode:"tair-serverless-kv"},k.setProject(t),i}async function lc(n,e={}){let t=k.requireAuth(),s=k.getProject();if(e.engineVersion||e.nodeType||e.capacityMb)throw Error("Tair Serverless KV 不支持 --engine-version/--node-type/--capacity 参数");let r=e.zoneId?.trim(),o=e.vpcId?.trim(),c=e.vSwitchId?.trim(),f=await(o||c?(()=>{if(!o||!c)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!r)throw Error("自定义网络时需提供 --zone");return wt({vpcId:o,vswId:c,zoneId:r})})():Re({preferredZoneIds:r?[r]:void 0})),u=jn(t);await ll(u,t.region,n);let g=e.instanceId?.trim();if(g){if(ts(g))return Kp(n,u,s,e,f);if(ft(g))return vp(n,u,t,s,e,f);throw Error("--instance 仅支持 tt-/tk-(Tair)或 r-(经典 Redis)开头的实例 ID")}let i;try{n.message("⚡ 正在通过直连 API 创建 Tair Serverless KV...");let R=await dl(t,n,f,e,s.appName);if(R){let j=e.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(u,R.instanceId,j,n),s.envs={...s.envs,REDIS_URL:R.redisUrl,REDIS_HOST:R.host,REDIS_PORT:String(R.port),REDIS_PASSWORD:R.password,REDIS_USERNAME:R.accountName||""},s.network=Ft(s.network,f),s.cache={type:"redis",instanceId:R.instanceId,host:R.host,port:R.port,accountName:R.accountName,mode:"tair-serverless-kv"},k.setProject(s),R.redisUrl}}catch(R){i=R,n.message(hl(R))}n.message("\uD83D\uDD0E 正在查询可用的 Tair Serverless KV 虚拟集群...");let l=await fr(u,t.region),d=yl(l,f,e.vkName);if(!d)return n.message("⚠️ Tair Serverless KV 不可用,正在兜底创建云原生 Redis 社区版..."),Wp(n,u,t,s,f,e);let a=e.instanceClass?.trim()||cr,p=e.computeUnitNum||gl;if(!Number.isInteger(p)||p<=0)throw Error("--compute-unit 必须是正整数");if(p!==1)throw Error("当前阿里云 CreateTairKVCacheVNode 仅支持 --compute-unit 1");let w=`${s.appName||"licell-app"}-redis`;n.message(`⚡ 正在创建 Tair Serverless KV: class=${a}, cu=${p}, vk=${d}`);let _=await u.createTairKVCacheVNode(new ge.CreateTairKVCacheVNodeRequest({regionId:t.region,instanceName:w,instanceClass:a,computeUnitNum:p,zoneId:f.zoneId||r,vSwitchId:f.vswId,vkName:d,clientToken:ml()})),$=_.body?.instanceId,y=_.body?.vkName||d;if(!$)throw Error("Tair Serverless KV 创建失败:未返回 instanceId");let h=await xt(u,n,[y,$]),U=h.host,C=h.port,L=h.accountName||s.cache?.accountName||"",S=h.password||"",b=h.url;if(!S){n.message("\uD83D\uDD10 正在设置 Redis 密码...");let R=Bn(),j=await rs(u,[h.sourceInstanceId,y,$],R,L);if(j)L=j.accountName,S=R,b=te(L,S,U,C);else{if(!await os(u,[h.sourceInstanceId,$,y],R))throw Error("未能自动设置 Tair Serverless KV 密码,请在控制台手动设置后重试");S=R,b=te(L||void 0,S,U,C)}}let P=e.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(u,h.sourceInstanceId,P,n),s.envs={...s.envs,REDIS_URL:b,REDIS_HOST:U,REDIS_PORT:String(C),REDIS_PASSWORD:S,REDIS_USERNAME:L},s.network=Ft(s.network,f),s.cache={type:"redis",instanceId:h.sourceInstanceId,host:U,port:C,accountName:L,vkName:y,mode:"tair-serverless-kv"},k.setProject(s),b}var Yp=600000,jp=5000,Dp="redis.master.small.default";async function Wp(n,e,t,s,r,o){let c=`${s.appName||"licell-app"}-redis`,f=Bn(),u=o.instanceClass?.trim()||Dp;n.message(`\uD83D\uDCE6 正在创建云原生 Redis 社区版 (${u}, 按量付费)...`);let g=await e.createInstance(new ge.CreateInstanceRequest({regionId:t.region,instanceType:"Redis",engineVersion:"5.0",instanceClass:u,instanceName:c,chargeType:"PostPaid",nodeType:"double",networkType:"VPC",vpcId:r.vpcId,vSwitchId:r.vswId,zoneId:r.zoneId,password:f,token:ml()})),i=g.body?.instanceId;if(!i)throw Error("Redis 创建失败:未返回 instanceId");let l=g.body?.connectionDomain||"",d=g.body?.port||6379,a=Date.now();while(!0){if(Date.now()-a>Yp)throw Error("Redis 实例创建超时");await yn(jp);let w=(await e.describeInstanceAttribute(new ge.DescribeInstanceAttributeRequest({instanceId:i}))).body?.instances?.DBInstanceAttribute?.[0],_=w?.instanceStatus||"Creating";if(_==="Normal"){let $=w?.connectionDomain||l,y=w?.port||d;if(!$)throw Error("Redis 实例已就绪但未获取到连接地址");let h=o.securityIpList?.trim()||r.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,i,h,n);let U=te(void 0,f,$,y);return s.envs={...s.envs,REDIS_URL:U,REDIS_HOST:$,REDIS_PORT:String(y),REDIS_PASSWORD:f,REDIS_USERNAME:""},s.network=Ft(s.network,r),s.cache={type:"redis",instanceId:i,host:$,port:y,accountName:void 0,mode:"classic-redis"},k.setProject(s),U}n.message(`☕ Redis 实例初始化中,请稍候... [${_}]`)}}Q();ht();import*as wl from"@alicloud/r-kvstore20150101";async function dc(n,e){let t=k.requireAuth(),s=k.getProject(),r=jn(t),o=e||s.cache?.instanceId;if(!o)throw Error("未找到 Redis 实例 ID,请先执行 licell cache add");if(ft(o)){n.message("\uD83D\uDD0E 正在获取 Redis 账号...");let l=await ss(r,o,s.cache?.accountName);if(!l)throw Error("未找到可用 Redis 账号,无法轮换密码");let d=Bn();n.message("\uD83D\uDD10 正在轮换 Redis 密码..."),await r.resetAccountPassword(new wl.ResetAccountPasswordRequest({instanceId:o,accountName:l,accountPassword:d}));let a=await cs(r,t,o),p=a?.connectionDomain||s.cache?.host,w=a?.port||s.cache?.port||6379;if(!p)throw Error("未查询到 Redis 连接地址");let _=te(l,d,p,w);return s.envs={...s.envs,REDIS_URL:_,REDIS_HOST:p,REDIS_PORT:String(w),REDIS_PASSWORD:d,REDIS_USERNAME:l},s.cache={...s.cache||{type:"redis",instanceId:o},type:"redis",instanceId:o,host:p,port:w,accountName:l},k.setProject(s),_}n.message("\uD83D\uDD0E 正在解析 Tair Serverless KV 连接地址...");let c=await xt(r,n,[o,s.cache?.vkName]);if(!c.host&&s.cache?.host)c={host:s.cache.host,port:s.cache.port||6379,url:`redis://${s.cache.host}:${s.cache.port||6379}`,sourceInstanceId:o};let f=Bn();n.message("\uD83D\uDD10 正在轮换 Tair Serverless KV 密码...");let u=await rs(r,[c.sourceInstanceId,s.cache?.vkName,o],f,s.cache?.accountName),g=u?.accountName||s.cache?.accountName||"";if(!u){if(!await os(r,[c.sourceInstanceId,o,s.cache?.vkName],f))throw Error("轮换密码失败:当前实例不支持自动密码重置")}let i=te(g||void 0,f,c.host,c.port);return s.envs={...s.envs,REDIS_URL:i,REDIS_HOST:c.host,REDIS_PORT:String(c.port),REDIS_PASSWORD:f,REDIS_USERNAME:g},s.cache={...s.cache||{type:"redis",instanceId:o},type:"redis",instanceId:c.sourceInstanceId,host:c.host,port:c.port,accountName:g,vkName:s.cache?.vkName,mode:"tair-serverless-kv"},k.setProject(s),i}Q();import*as ke from"@alicloud/r-kvstore20150101";async function pl(n,e){try{let r=((await n.describeDBInstanceNetInfo(new ke.DescribeDBInstanceNetInfoRequest({instanceId:e}))).body?.netInfoItems?.instanceNetInfo||[]).find((o)=>o.IPType==="Public");if(r?.connectionString)return{host:r.connectionString,port:Number(r.port)||6379}}catch{}return null}async function yc(n=200){let e=k.requireAuth(),t=jn(e),s=[],r=Math.max(1,Math.min(Math.floor(n),500));for(let o=1;o<=20&&s.length<r;o+=1){let c=await t.describeInstances(new ke.DescribeInstancesRequest({regionId:e.region,pageNumber:o,pageSize:50})),f=c.body?.instances?.KVStoreInstance||[];for(let g of f){let i=ic(g);if(!i.instanceId)continue;if(s.push(i),s.length>=r)break}let u=c.body?.totalCount||0;if(f.length===0||u>0&&s.length>=u)break}if(s.length<r){let o=await fr(t,e.region);for(let c of o){let f=ol(c);if(!f.instanceId)continue;if(s.some((u)=>u.instanceId===f.instanceId))continue;if(s.push(f),s.length>=r)break}}return s.slice(0,r)}async function ac(n){let e=n.trim();if(!e)throw Error("instanceId 不能为空");let t=k.requireAuth(),s=jn(t);if(ft(e)){let u=await cs(s,t,e);if(!u?.instanceId)throw Error(`未找到 Redis 实例: ${e}`);let i=((await s.describeAccounts(new ke.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[]).map((l)=>l.accountName).filter((l)=>typeof l==="string"&&l.length>0);return{summary:ic(u),accountNames:i}}if(!ts(e))throw Error("cache info 仅支持 tt-/tk-/r- 开头的实例 ID");let r=await gc(s,e);if(!r?.instanceId)throw Error(`未找到 Tair 实例: ${e}`);let o=ct(r.connectionString),f=((await s.describeAccounts(new ke.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[]).map((u)=>u.accountName).filter((u)=>typeof u==="string"&&u.length>0);return{summary:{instanceId:r.instanceId,mode:"tair-serverless-kv",instanceName:r.instanceName,status:r.instanceStatus,instanceClass:r.instanceClass,host:o?.host,port:o?.port,zoneId:r.zoneId,vpcId:r.vpcId,vSwitchId:r.vSwitchId},accountNames:f}}async function ur(n){let e=k.requireAuth(),t=jn(e),s=k.getProject(),r=n?.trim()||s.cache?.instanceId||"";if(!r)throw Error("未指定缓存实例 ID,且当前项目未绑定缓存实例");let c=s.cache?.instanceId===r?ct(s.envs?.REDIS_URL):null;if(ft(r)){let p=await cs(t,e,r);if(!p?.instanceId)throw Error(`未找到 Redis 实例: ${r}`);let w=p.connectionDomain||s.cache?.host||"",_=p.port||s.cache?.port||6379;if(!w)throw Error(`未获取到实例 ${r} 的连接地址`);let $=c?.accountName||s.cache?.accountName||"<username>",y=c?.password||"",h=y.length>0,U=es($==="<username>"?void 0:$,y,w,_,h),C=await pl(t,r);return{instanceId:r,host:w,port:_,username:$==="<username>"?void 0:$,passwordKnown:h,connectionString:U,mode:"classic-redis",...C?{publicHost:C.host,publicPort:C.port,publicConnectionString:es($==="<username>"?void 0:$,y,C.host,C.port,h)}:{}}}if(!ts(r))throw Error("cache connect 仅支持 tt-/tk-/r- 开头的实例 ID");let f=await gc(t,r),u=ct(f?.connectionString)||(s.cache?.host?{host:s.cache.host,port:s.cache.port||6379,url:`redis://${s.cache.host}:${s.cache.port||6379}`,accountName:s.cache.accountName,password:void 0}:null);if(!u?.host)throw Error(`未获取到实例 ${r} 的连接地址`);let g=c?.accountName||u.accountName||s.cache?.accountName,i=c?.password||"",l=i.length>0,d=es(g,i,u.host,u.port,l),a=await pl(t,r);return{instanceId:r,host:u.host,port:u.port,username:g,passwordKnown:l,connectionString:d,mode:"tair-serverless-kv",...a?{publicHost:a.host,publicPort:a.port,publicConnectionString:es(g,i,a.host,a.port,l)}:{}}}async function hc(n){await jn().deleteInstance(new ke.DeleteInstanceRequest({instanceId:n}))}Q();import*as ut from"@alicloud/r-kvstore20150101";dn();var Vp="licell_public";async function mc(n,e){let t=k.requireAuth(),s=jn(t),c=((await s.describeDBInstanceNetInfo(new ut.DescribeDBInstanceNetInfoRequest({instanceId:n}))).body?.netInfoItems?.instanceNetInfo||[]).find((l)=>l.IPType==="Public");if(c?.connectionString)return{host:c.connectionString,port:Number(c.port)||6379};let f=`${n}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await s.allocateInstancePublicConnection(new ut.AllocateInstancePublicConnectionRequest({instanceId:n,connectionStringPrefix:f,port:"6379"}))}catch(l){let d=A(l);if(d.includes("NetTypeExists")||d.includes("already exists"))e.message("公网地址已存在,正在获取...");else throw l}let i=((await s.describeDBInstanceNetInfo(new ut.DescribeDBInstanceNetInfoRequest({instanceId:n}))).body?.netInfoItems?.instanceNetInfo||[]).find((l)=>l.IPType==="Public");if(!i?.connectionString)return null;return{host:i.connectionString,port:Number(i.port)||6379}}async function wc(n,e,t){let s=k.requireAuth(),r=jn(s),o=`${e}/32`;try{await r.modifySecurityIps(new ut.ModifySecurityIpsRequest({instanceId:n,securityIpGroupName:Vp,securityIps:o,modifyMode:"Cover"}))}catch(c){t.message(`⚠️ 公网白名单设置失败: ${A(c)}`)}}on();fn();function bl(n){n.command("cache add","分配 Redis 缓存").option("--type <type>","缓存类型:redis(CI 场景建议显式传入)").option("--instance <instanceId>","绑定已有实例 ID(tt-/tk-/r-),传入后跳过创建").option("--password <password>","绑定已有实例时的访问密码(不传则尝试自动轮换)").option("--username <accountName>","绑定已有实例时指定账号名(可选)").option("--engine-version <version>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--class <instanceClass>","Tair Serverless KV 规格(如 kvcache.cu.g4b.2)").option("--node-type <type>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--capacity <mb>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--vk-name <vkName>","Tair KV 回退模式使用的 vkName(tk- 开头,不传则自动探测)").option("--compute-unit <n>","Tair Serverless KV 计算单元(当前仅支持 1)").option("--zone <zoneId>","可用区(如 cn-hangzhou-b)").option("--vpc <vpcId>","指定 VPC ID").option("--vsw <vSwitchId>","指定 VSwitch ID").option("--security-ip-list <cidrs>","白名单 CIDR(逗号分隔)").action(async(e)=>{await G({commandLabel:"licell cache add",interactiveTTY:I(),requiredCapabilities:["redis","vpc"]},async()=>{W(Y.bgGreen(Y.black(" \uD83E\uDDE0 Cache Provisioning (Redis) "))),B();let t=I(),s=E(e.type)?.toLowerCase();if(!s){if(!t)throw Error("非交互模式下请传入 --type redis");let u=await Qp({message:"选择缓存引擎:",options:[{value:"redis",label:"\uD83D\uDFE5 Tair/Redis (VPC 内网)"}]});if($l(u))process.exit(0);s=K(u,"缓存类型").toLowerCase()}if(s!=="redis")throw Error("--type 目前仅支持 redis");let r=Fn(e.capacity,"capacity"),o=Fn(e.computeUnit,"compute-unit"),c=H(),f=await F(c,"正在初始化缓存资源编排...","❌ 缓存拉起失败",()=>lc(c,{instanceId:E(e.instance),existingPassword:E(e.password),accountName:E(e.username),engineVersion:E(e.engineVersion),instanceClass:E(e.class),nodeType:E(e.nodeType),capacityMb:r,vkName:E(e.vkName),computeUnitNum:o,zoneId:E(e.zone),vpcId:E(e.vpc),vSwitchId:E(e.vsw),securityIpList:E(e.securityIpList)}));if(!f)return;if(!m())c.stop(Y.green("✅ Redis 缓存已就绪并绑定到本工程内网!"));if(m()){T({stage:"cache.add",type:s,connectionStringMasked:Ae(f)});return}console.log(`
1596
+ 白名单 IP: ${v.cyan(`${c}/32`)} (分组: licell_public)`),q("Done.")})}),n.command("db rm <instanceId>","删除数据库实例").option("--yes","跳过确认").action(async(e,t)=>{await G({commandLabel:"licell db rm",interactiveTTY:I(),requiredCapabilities:["rds"]},async()=>{W(v.bgRed(v.white(" \uD83D\uDDD1️ Delete Database "))),B();let s=e.trim();if(!s)throw Error("请提供 instanceId");if(!t.yes&&I()){let o=await xp({message:`确认删除数据库实例 ${v.red(s)}?此操作不可恢复。`});if(zg(o)||!o){q("已取消");return}}let r=H();if(await F(r,`正在删除实例 ${s}...`,"❌ 删除失败",()=>sc(s)),m()){T({stage:"db.rm",instanceId:s});return}q(`实例 ${s} 已删除`)})})}import{select as Qp,confirm as Xp,isCancel as $l}from"@clack/prompts";import Y from"picocolors";Hn();Q();ht();Zn();pt();import*as ge from"@alicloud/r-kvstore20150101";import{randomUUID as ml}from"crypto";xn();import Lp from"@alicloud/r-kvstore20150101";import*as be from"@alicloud/openapi-client";import*as el from"@alicloud/tea-util";var Ap=nn(Lp,"@alicloud/r-kvstore20150101"),Hp=nn(be.default,"@alicloud/openapi-client");function jn(n){return new Ap(new be.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:"r-kvstore.aliyuncs.com",connectTimeout:30000,readTimeout:60000}))}function Up(n){return new Hp(new be.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:"r-kvstore.aliyuncs.com"}))}function Mp(n){if(n===null||n===void 0)return;if(typeof n==="boolean")return n?"true":"false";return String(n)}function Gp(n){let e={};for(let[t,s]of Object.entries(n)){let r=Mp(s);if(r===void 0)continue;e[t]=r}return e}async function tl(n,e,t){let s=Up(n),r=new be.Params({action:e,version:"2015-01-01",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),o=new be.OpenApiRequest({query:Gp(t)});return s.callApi(r,o,new el.RuntimeOptions({readTimeout:20000,connectTimeout:8000}))}ht();dn();Zn();import*as En from"@alicloud/r-kvstore20150101";import{randomUUID as Bp}from"crypto";dn();function uc(n){return["Error","Released","Inactive","Unavailable","Flushing"].includes(n)}function te(n,e,t,s){if(!n)return`redis://:${encodeURIComponent(e)}@${t}:${s}`;return`redis://${encodeURIComponent(n)}:${encodeURIComponent(e)}@${t}:${s}`}function es(n,e,t,s,r){let o=r?encodeURIComponent(e):"<password>";if(!n)return`redis://:${o}@${t}:${s}`;return`redis://${n==="<username>"?n:encodeURIComponent(n)}:${o}@${t}:${s}`}function rr(n){if(typeof n!=="object"||n===null)return"";let e="code"in n?String(n.code||""):"";if(e)return e;if("data"in n&&typeof n.data==="object"&&n.data!==null){let t=n.data?.Code;if(t)return String(t)}return""}function sl(n){return/InvalidInstanceId\.NotFound/i.test(rr(n)||A(n))}function rl(n){let e=`${rr(n)} ${A(n)}`;return/NotSupport|Unsupported|InvalidInstanceId|InvalidParameter|OperationDenied|AccessDenied/i.test(e)}function or(n){let e=new Set;for(let t of n){if(!t)continue;let s=t.trim();if(s)e.add(s)}return[...e]}function ct(n){let e=(n||"").trim();if(!e)return null;let t=e.split(",")[0]?.trim();if(!t)return null;let s=/^[a-z][a-z0-9+.-]*:\/\//i.test(t)?t:`redis://${t}`;try{let r=new URL(s),o=r.hostname;if(!o)return null;let c=r.port?Number(r.port):6379;if(!Number.isFinite(c)||c<=0)return null;let f=r.username?decodeURIComponent(r.username):void 0,u=r.password?decodeURIComponent(r.password):void 0,g=r.toString();return{host:o,port:c,accountName:f,password:u,url:g}}catch{return null}}function ft(n){return n.startsWith("r-")}function ts(n){return n.startsWith("tk-")||n.startsWith("tt-")}function Ft(n,e){return{vpcId:e.vpcId,vswId:e.vswId,sgId:e.sgId??n?.sgId,cidrBlock:e.cidrBlock??n?.cidrBlock}}function ic(n){return{instanceId:n.instanceId||"",mode:"classic-redis",instanceName:n.instanceName,status:n.instanceStatus,instanceClass:n.instanceClass,engineVersion:n.engineVersion,host:n.connectionDomain,port:n.port,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}function ol(n){return{instanceId:n.instanceId||"",mode:"tair-serverless-kv",instanceName:n.instanceName,status:n.instanceStatus,zoneId:n.zoneId,vpcId:n.vpcId,vSwitchId:n.vSwitchId}}var cl=1200000,fl=5000,ul=60000,il=180000,cr="kvcache.cu.g4b.2",gl=1;async function ss(n,e,t){let r=(await n.describeAccounts(new En.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[],o=r.filter((f)=>f.accountStatus!=="Unavailable");return(o.find((f)=>f.accountType==="Normal")||o[0]||r[0])?.accountName||t||""}async function ll(n,e,t){try{if((await n.describeServiceLinkedRoleExists(new En.DescribeServiceLinkedRoleExistsRequest({}))).body?.existsServiceLinkedRole)return;t.message("\uD83D\uDD10 正在初始化 Kvstore 服务关联角色..."),await n.initializeKvstorePermission(new En.InitializeKvstorePermissionRequest({regionId:e}))}catch(s){t.message(`⚠️ 服务关联角色检查失败 (${A(s)}),将继续尝试创建缓存`)}}async function dl(n,e,t,s,r){let o=s.instanceClass?.trim()||cr,c=`${r||"licell-app"}-redis`,f=Bn(),u=s.securityIpList?.trim()||t.cidrBlock||"10.0.0.0/8",i=(await tl(n,"CreateTairKVCacheInferInstance",{RegionId:n.region,InstanceName:c,InstanceClass:o,ZoneId:t.zoneId,VpcId:t.vpcId,VSwitchId:t.vswId,ChargeType:"PostPaid",AutoPay:!0,Password:f,SecurityIPList:u,ClientToken:Bp()})).body||{},l=typeof i.InstanceId==="string"?i.InstanceId:typeof i.instanceId==="string"?i.instanceId:"";if(!l)return null;let d=typeof i.ConnectionString==="string"?i.ConnectionString:typeof i.connectionString==="string"?i.connectionString:"",a=ct(d),p=a?.host||"",w=a?.port||6379,_=a?.accountName||"",$=jn(n);if(!p){let h=await xt($,e,[l]);p=h.host,w=h.port,_=h.accountName||_}if(!p)throw Error(`CreateTairKVCacheInferInstance 返回成功但未获取连接地址 (${l})`);let y=te(_||void 0,f,p,w);return{instanceId:l,host:p,port:w,accountName:_||void 0,password:f,redisUrl:y}}async function fr(n,e){let t=[],s=30;for(let r=1;r<=50;r+=1){let o=await n.describeTairKVCacheInferInstances(new En.DescribeTairKVCacheInferInstancesRequest({regionId:e,pageNumber:r,pageSize:30})),c=o.body?.instances?.tairInferInstanceDTO||[];t.push(...c);let f=o.body?.totalCount||0;if(c.length===0||t.length>=f)break}return t}function yl(n,e,t){let s=t?.trim();if(s)return s;let r=n.filter((f)=>{if(!(f.instanceId||"").startsWith("tk-"))return!1;let g=f.instanceStatus||"";if(g&&uc(g))return!1;return!0}),o=r.filter((f)=>{if(f.vpcId&&f.vpcId!==e.vpcId)return!1;if(f.vSwitchId&&f.vSwitchId!==e.vswId)return!1;if(e.zoneId&&f.zoneId&&f.zoneId!==e.zoneId)return!1;return!0}),c=o.length>0?o:r;if(c.length===1)return c[0].instanceId||"";if(c.length>1){let f=c.map((u)=>u.instanceId).filter(Boolean).join(", ");throw Error(`发现多个可用 vkName (${f}),请使用 --vk-name 显式指定`)}return""}async function al(n,e){try{return(await n.describeTairKVCacheInferInstanceAttribute(new En.DescribeTairKVCacheInferInstanceAttributeRequest({instanceId:e}))).body?.instances?.DBInstanceAttribute?.[0]}catch(t){if(sl(t))return null;throw t}}async function xt(n,e,t,s={}){let r=or(t);if(r.length===0)throw Error("未找到可查询的 Tair KVCache 实例 ID");let o=s.waitTimeoutMs||cl,c=Date.now(),f="Creating",u=0;while(!0){if(Date.now()-c>o)throw Error(`Tair Serverless KV 初始化超时,最后状态: ${f}`);let g=!1;for(let i of r){let l=await al(n,i);if(!l)continue;g=!0;let d=l.instanceStatus||"Creating";if(f=`${i}:${d}`,uc(d))throw Error(`Tair KVCache 创建失败,实例状态: ${d} (${i})`);let a=ct(l.connectionString);if(d==="Normal"&&a?.host)return{...a,sourceInstanceId:i}}if(!g){if(!u)u=Date.now();if(Date.now()-u>=ul)throw Error(`未查询到可用实例信息,请检查实例 ID 是否正确: ${r.join(", ")}`)}else u=0;e.message(`☕ Tair Serverless KV 初始化中,请稍候... [${f}]`),await yn(fl)}}async function rs(n,e,t,s){let r=or(e);for(let o of r)try{let c=await ss(n,o,s);if(!c)continue;return await n.resetAccountPassword(new En.ResetAccountPasswordRequest({instanceId:o,accountName:c,accountPassword:t})),{instanceId:o,accountName:c}}catch{continue}return null}async function os(n,e,t){let s=or(e);for(let r of s)try{return await n.resetTairKVCacheCustomInstancePassword(new En.ResetTairKVCacheCustomInstancePasswordRequest({instanceId:r,password:t})),{instanceId:r}}catch{continue}return null}async function Lt(n,e,t,s){try{await n.modifySecurityIps(new En.ModifySecurityIpsRequest({instanceId:e,securityIpGroupName:"default",modifyMode:"Append",securityIps:t}))}catch(r){if(tn(r)||rl(r)){s.message(`⚠️ 当前实例未应用白名单配置 (${A(r)})`);return}throw r}}async function cs(n,e,t){return((await n.describeInstances(new En.DescribeInstancesRequest({regionId:e.region,instanceIds:t,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance||[]).find((o)=>o.instanceId===t)||null}async function gc(n,e){return al(n,e)}function hl(n){let e=rr(n),t=(()=>{if(typeof n!=="object"||n===null)return"";if("data"in n&&typeof n.data==="object"&&n.data!==null){let r=n.data?.RequestId;if(r)return String(r)}return""})(),s=t?`, requestId=${t}`:"";return`⚠️ 直连 API 创建失败 [${e||"Unknown"}] ${A(n)}${s}`}async function vp(n,e,t,s,r,o){let c=r.instanceId?.trim()||"";n.message(`\uD83D\uDD17 正在绑定已有 Redis 实例 (${c})...`);let u=(await e.describeInstances(new ge.DescribeInstancesRequest({regionId:t.region,instanceIds:c,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance?.find((w)=>w.instanceId===c),g=u?.connectionDomain||s.cache?.host,i=u?.port||s.cache?.port||6379;if(!g)throw Error(`未查询到 Redis 连接地址,请确认实例 ${c} 可用`);let l=r.accountName?.trim()||s.cache?.accountName||"";if(!l)l=await ss(e,c,s.cache?.accountName);let d=r.existingPassword?.trim()||"";if(!d){if(!l)throw Error("未查询到 Redis 账号,请使用 --username 指定实例账号,或使用 --password 直接绑定");let w=Bn();await e.resetAccountPassword(new ge.ResetAccountPasswordRequest({instanceId:c,accountName:l,accountPassword:w})),d=w}let a=r.securityIpList?.trim()||o.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,c,a,n);let p=te(l||void 0,d,g,i);return s.envs={...s.envs,REDIS_URL:p,REDIS_HOST:g,REDIS_PORT:String(i),REDIS_PASSWORD:d,REDIS_USERNAME:l},s.network=Ft(s.network,o),s.cache={type:"redis",instanceId:c,host:g,port:i,accountName:l,mode:"classic-redis"},k.setProject(s),p}async function Kp(n,e,t,s,r){let o=s.instanceId?.trim()||"";n.message(`\uD83D\uDD17 正在绑定已有 Tair Serverless KV 实例 (${o})...`);let c=await xt(e,n,[o,s.vkName,t.cache?.vkName],{waitTimeoutMs:il});if(!c.host&&t.cache?.host)c={host:t.cache.host,port:t.cache.port||6379,url:`redis://${t.cache.host}:${t.cache.port||6379}`,sourceInstanceId:o};let f=s.accountName?.trim()||c.accountName||t.cache?.accountName||"",u=s.existingPassword?.trim()||c.password||"";if(!u){n.message("\uD83D\uDD10 未传入 --password,正在自动轮换实例密码...");let l=Bn(),d=await rs(e,[c.sourceInstanceId,s.vkName,o],l,f);if(d)f=d.accountName,u=l;else{if(!await os(e,[c.sourceInstanceId,o,s.vkName],l))throw Error("未能自动重置已存在实例密码。请使用 --password 传入控制台已设置密码,或先执行 `licell cache rotate-password --instance <id>` 再重试");u=l}}let g=s.securityIpList?.trim()||r.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,c.sourceInstanceId,g,n);let i=te(f||void 0,u,c.host,c.port);return t.envs={...t.envs,REDIS_URL:i,REDIS_HOST:c.host,REDIS_PORT:String(c.port),REDIS_PASSWORD:u,REDIS_USERNAME:f},t.network=Ft(t.network,r),t.cache={type:"redis",instanceId:c.sourceInstanceId,host:c.host,port:c.port,accountName:f,vkName:s.vkName?.trim()||t.cache?.vkName||(c.sourceInstanceId.startsWith("tk-")?c.sourceInstanceId:void 0),mode:"tair-serverless-kv"},k.setProject(t),i}async function lc(n,e={}){let t=k.requireAuth(),s=k.getProject();if(e.engineVersion||e.nodeType||e.capacityMb)throw Error("Tair Serverless KV 不支持 --engine-version/--node-type/--capacity 参数");let r=e.zoneId?.trim(),o=e.vpcId?.trim(),c=e.vSwitchId?.trim(),f=await(o||c?(()=>{if(!o||!c)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!r)throw Error("自定义网络时需提供 --zone");return wt({vpcId:o,vswId:c,zoneId:r})})():Re({preferredZoneIds:r?[r]:void 0})),u=jn(t);await ll(u,t.region,n);let g=e.instanceId?.trim();if(g){if(ts(g))return Kp(n,u,s,e,f);if(ft(g))return vp(n,u,t,s,e,f);throw Error("--instance 仅支持 tt-/tk-(Tair)或 r-(经典 Redis)开头的实例 ID")}let i;try{n.message("⚡ 正在通过直连 API 创建 Tair Serverless KV...");let R=await dl(t,n,f,e,s.appName);if(R){let j=e.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(u,R.instanceId,j,n),s.envs={...s.envs,REDIS_URL:R.redisUrl,REDIS_HOST:R.host,REDIS_PORT:String(R.port),REDIS_PASSWORD:R.password,REDIS_USERNAME:R.accountName||""},s.network=Ft(s.network,f),s.cache={type:"redis",instanceId:R.instanceId,host:R.host,port:R.port,accountName:R.accountName,mode:"tair-serverless-kv"},k.setProject(s),R.redisUrl}}catch(R){i=R,n.message(hl(R))}n.message("\uD83D\uDD0E 正在查询可用的 Tair Serverless KV 虚拟集群...");let l=await fr(u,t.region),d=yl(l,f,e.vkName);if(!d)return n.message("⚠️ Tair Serverless KV 不可用,正在兜底创建云原生 Redis 社区版..."),Wp(n,u,t,s,f,e);let a=e.instanceClass?.trim()||cr,p=e.computeUnitNum||gl;if(!Number.isInteger(p)||p<=0)throw Error("--compute-unit 必须是正整数");if(p!==1)throw Error("当前阿里云 CreateTairKVCacheVNode 仅支持 --compute-unit 1");let w=`${s.appName||"licell-app"}-redis`;n.message(`⚡ 正在创建 Tair Serverless KV: class=${a}, cu=${p}, vk=${d}`);let _=await u.createTairKVCacheVNode(new ge.CreateTairKVCacheVNodeRequest({regionId:t.region,instanceName:w,instanceClass:a,computeUnitNum:p,zoneId:f.zoneId||r,vSwitchId:f.vswId,vkName:d,clientToken:ml()})),$=_.body?.instanceId,y=_.body?.vkName||d;if(!$)throw Error("Tair Serverless KV 创建失败:未返回 instanceId");let h=await xt(u,n,[y,$]),U=h.host,C=h.port,L=h.accountName||s.cache?.accountName||"",S=h.password||"",b=h.url;if(!S){n.message("\uD83D\uDD10 正在设置 Redis 密码...");let R=Bn(),j=await rs(u,[h.sourceInstanceId,y,$],R,L);if(j)L=j.accountName,S=R,b=te(L,S,U,C);else{if(!await os(u,[h.sourceInstanceId,$,y],R))throw Error("未能自动设置 Tair Serverless KV 密码,请在控制台手动设置后重试");S=R,b=te(L||void 0,S,U,C)}}let P=e.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(u,h.sourceInstanceId,P,n),s.envs={...s.envs,REDIS_URL:b,REDIS_HOST:U,REDIS_PORT:String(C),REDIS_PASSWORD:S,REDIS_USERNAME:L},s.network=Ft(s.network,f),s.cache={type:"redis",instanceId:h.sourceInstanceId,host:U,port:C,accountName:L,vkName:y,mode:"tair-serverless-kv"},k.setProject(s),b}var Yp=600000,jp=5000,Dp="redis.master.small.default";async function Wp(n,e,t,s,r,o){let c=`${s.appName||"licell-app"}-redis`,f=Bn(),u=o.instanceClass?.trim()||Dp;n.message(`\uD83D\uDCE6 正在创建云原生 Redis 社区版 (${u}, 按量付费)...`);let g=await e.createInstance(new ge.CreateInstanceRequest({regionId:t.region,instanceType:"Redis",engineVersion:"5.0",instanceClass:u,instanceName:c,chargeType:"PostPaid",nodeType:"double",networkType:"VPC",vpcId:r.vpcId,vSwitchId:r.vswId,zoneId:r.zoneId,password:f,token:ml()})),i=g.body?.instanceId;if(!i)throw Error("Redis 创建失败:未返回 instanceId");let l=g.body?.connectionDomain||"",d=g.body?.port||6379,a=Date.now();while(!0){if(Date.now()-a>Yp)throw Error("Redis 实例创建超时");await yn(jp);let w=(await e.describeInstanceAttribute(new ge.DescribeInstanceAttributeRequest({instanceId:i}))).body?.instances?.DBInstanceAttribute?.[0],_=w?.instanceStatus||"Creating";if(_==="Normal"){let $=w?.connectionDomain||l,y=w?.port||d;if(!$)throw Error("Redis 实例已就绪但未获取到连接地址");let h=o.securityIpList?.trim()||r.cidrBlock||"10.0.0.0/8";n.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await Lt(e,i,h,n);let U=te(void 0,f,$,y);return s.envs={...s.envs,REDIS_URL:U,REDIS_HOST:$,REDIS_PORT:String(y),REDIS_PASSWORD:f,REDIS_USERNAME:""},s.network=Ft(s.network,r),s.cache={type:"redis",instanceId:i,host:$,port:y,accountName:void 0,mode:"classic-redis"},k.setProject(s),U}n.message(`☕ Redis 实例初始化中,请稍候... [${_}]`)}}Q();ht();import*as wl from"@alicloud/r-kvstore20150101";async function dc(n,e){let t=k.requireAuth(),s=k.getProject(),r=jn(t),o=e||s.cache?.instanceId;if(!o)throw Error("未找到 Redis 实例 ID,请先执行 licell cache add");if(ft(o)){n.message("\uD83D\uDD0E 正在获取 Redis 账号...");let l=await ss(r,o,s.cache?.accountName);if(!l)throw Error("未找到可用 Redis 账号,无法轮换密码");let d=Bn();n.message("\uD83D\uDD10 正在轮换 Redis 密码..."),await r.resetAccountPassword(new wl.ResetAccountPasswordRequest({instanceId:o,accountName:l,accountPassword:d}));let a=await cs(r,t,o),p=a?.connectionDomain||s.cache?.host,w=a?.port||s.cache?.port||6379;if(!p)throw Error("未查询到 Redis 连接地址");let _=te(l,d,p,w);return s.envs={...s.envs,REDIS_URL:_,REDIS_HOST:p,REDIS_PORT:String(w),REDIS_PASSWORD:d,REDIS_USERNAME:l},s.cache={...s.cache||{type:"redis",instanceId:o},type:"redis",instanceId:o,host:p,port:w,accountName:l},k.setProject(s),_}n.message("\uD83D\uDD0E 正在解析 Tair Serverless KV 连接地址...");let c=await xt(r,n,[o,s.cache?.vkName]);if(!c.host&&s.cache?.host)c={host:s.cache.host,port:s.cache.port||6379,url:`redis://${s.cache.host}:${s.cache.port||6379}`,sourceInstanceId:o};let f=Bn();n.message("\uD83D\uDD10 正在轮换 Tair Serverless KV 密码...");let u=await rs(r,[c.sourceInstanceId,s.cache?.vkName,o],f,s.cache?.accountName),g=u?.accountName||s.cache?.accountName||"";if(!u){if(!await os(r,[c.sourceInstanceId,o,s.cache?.vkName],f))throw Error("轮换密码失败:当前实例不支持自动密码重置")}let i=te(g||void 0,f,c.host,c.port);return s.envs={...s.envs,REDIS_URL:i,REDIS_HOST:c.host,REDIS_PORT:String(c.port),REDIS_PASSWORD:f,REDIS_USERNAME:g},s.cache={...s.cache||{type:"redis",instanceId:o},type:"redis",instanceId:c.sourceInstanceId,host:c.host,port:c.port,accountName:g,vkName:s.cache?.vkName,mode:"tair-serverless-kv"},k.setProject(s),i}Q();import*as ke from"@alicloud/r-kvstore20150101";async function pl(n,e){try{let r=((await n.describeDBInstanceNetInfo(new ke.DescribeDBInstanceNetInfoRequest({instanceId:e}))).body?.netInfoItems?.instanceNetInfo||[]).find((o)=>o.IPType==="Public");if(r?.connectionString)return{host:r.connectionString,port:Number(r.port)||6379}}catch{}return null}async function yc(n=200){let e=k.requireAuth(),t=jn(e),s=[],r=Math.max(1,Math.min(Math.floor(n),500));for(let o=1;o<=20&&s.length<r;o+=1){let c=await t.describeInstances(new ke.DescribeInstancesRequest({regionId:e.region,pageNumber:o,pageSize:50})),f=c.body?.instances?.KVStoreInstance||[];for(let g of f){let i=ic(g);if(!i.instanceId)continue;if(s.push(i),s.length>=r)break}let u=c.body?.totalCount||0;if(f.length===0||u>0&&s.length>=u)break}if(s.length<r){let o=await fr(t,e.region);for(let c of o){let f=ol(c);if(!f.instanceId)continue;if(s.some((u)=>u.instanceId===f.instanceId))continue;if(s.push(f),s.length>=r)break}}return s.slice(0,r)}async function ac(n){let e=n.trim();if(!e)throw Error("instanceId 不能为空");let t=k.requireAuth(),s=jn(t);if(ft(e)){let u=await cs(s,t,e);if(!u?.instanceId)throw Error(`未找到 Redis 实例: ${e}`);let i=((await s.describeAccounts(new ke.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[]).map((l)=>l.accountName).filter((l)=>typeof l==="string"&&l.length>0);return{summary:ic(u),accountNames:i}}if(!ts(e))throw Error("cache info 仅支持 tt-/tk-/r- 开头的实例 ID");let r=await gc(s,e);if(!r?.instanceId)throw Error(`未找到 Tair 实例: ${e}`);let o=ct(r.connectionString),f=((await s.describeAccounts(new ke.DescribeAccountsRequest({instanceId:e}))).body?.accounts?.account||[]).map((u)=>u.accountName).filter((u)=>typeof u==="string"&&u.length>0);return{summary:{instanceId:r.instanceId,mode:"tair-serverless-kv",instanceName:r.instanceName,status:r.instanceStatus,instanceClass:r.instanceClass,host:o?.host,port:o?.port,zoneId:r.zoneId,vpcId:r.vpcId,vSwitchId:r.vSwitchId},accountNames:f}}async function ur(n){let e=k.requireAuth(),t=jn(e),s=k.getProject(),r=n?.trim()||s.cache?.instanceId||"";if(!r)throw Error("未指定缓存实例 ID,且当前项目未绑定缓存实例");let c=s.cache?.instanceId===r?ct(s.envs?.REDIS_URL):null;if(ft(r)){let p=await cs(t,e,r);if(!p?.instanceId)throw Error(`未找到 Redis 实例: ${r}`);let w=p.connectionDomain||s.cache?.host||"",_=p.port||s.cache?.port||6379;if(!w)throw Error(`未获取到实例 ${r} 的连接地址`);let $=c?.accountName||s.cache?.accountName||"<username>",y=c?.password||"",h=y.length>0,U=es($==="<username>"?void 0:$,y,w,_,h),C=await pl(t,r);return{instanceId:r,host:w,port:_,username:$==="<username>"?void 0:$,passwordKnown:h,connectionString:U,mode:"classic-redis",...C?{publicHost:C.host,publicPort:C.port,publicConnectionString:es($==="<username>"?void 0:$,y,C.host,C.port,h)}:{}}}if(!ts(r))throw Error("cache connect 仅支持 tt-/tk-/r- 开头的实例 ID");let f=await gc(t,r),u=ct(f?.connectionString)||(s.cache?.host?{host:s.cache.host,port:s.cache.port||6379,url:`redis://${s.cache.host}:${s.cache.port||6379}`,accountName:s.cache.accountName,password:void 0}:null);if(!u?.host)throw Error(`未获取到实例 ${r} 的连接地址`);let g=c?.accountName||u.accountName||s.cache?.accountName,i=c?.password||"",l=i.length>0,d=es(g,i,u.host,u.port,l),a=await pl(t,r);return{instanceId:r,host:u.host,port:u.port,username:g,passwordKnown:l,connectionString:d,mode:"tair-serverless-kv",...a?{publicHost:a.host,publicPort:a.port,publicConnectionString:es(g,i,a.host,a.port,l)}:{}}}async function hc(n){let e=k.requireAuth();await jn(e).deleteInstance(new ke.DeleteInstanceRequest({instanceId:n}))}Q();import*as ut from"@alicloud/r-kvstore20150101";dn();var Vp="licell_public";async function mc(n,e){let t=k.requireAuth(),s=jn(t),c=((await s.describeDBInstanceNetInfo(new ut.DescribeDBInstanceNetInfoRequest({instanceId:n}))).body?.netInfoItems?.instanceNetInfo||[]).find((l)=>l.IPType==="Public");if(c?.connectionString)return{host:c.connectionString,port:Number(c.port)||6379};let f=`${n}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await s.allocateInstancePublicConnection(new ut.AllocateInstancePublicConnectionRequest({instanceId:n,connectionStringPrefix:f,port:"6379"}))}catch(l){let d=A(l);if(d.includes("NetTypeExists")||d.includes("already exists"))e.message("公网地址已存在,正在获取...");else throw l}let i=((await s.describeDBInstanceNetInfo(new ut.DescribeDBInstanceNetInfoRequest({instanceId:n}))).body?.netInfoItems?.instanceNetInfo||[]).find((l)=>l.IPType==="Public");if(!i?.connectionString)return null;return{host:i.connectionString,port:Number(i.port)||6379}}async function wc(n,e,t){let s=k.requireAuth(),r=jn(s),o=`${e}/32`;try{await r.modifySecurityIps(new ut.ModifySecurityIpsRequest({instanceId:n,securityIpGroupName:Vp,securityIps:o,modifyMode:"Cover"}))}catch(c){t.message(`⚠️ 公网白名单设置失败: ${A(c)}`)}}on();fn();function bl(n){n.command("cache add","分配 Redis 缓存").option("--type <type>","缓存类型:redis(CI 场景建议显式传入)").option("--instance <instanceId>","绑定已有实例 ID(tt-/tk-/r-),传入后跳过创建").option("--password <password>","绑定已有实例时的访问密码(不传则尝试自动轮换)").option("--username <accountName>","绑定已有实例时指定账号名(可选)").option("--engine-version <version>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--class <instanceClass>","Tair Serverless KV 规格(如 kvcache.cu.g4b.2)").option("--node-type <type>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--capacity <mb>","旧版 Redis 参数(Tair Serverless KV 模式下不支持)").option("--vk-name <vkName>","Tair KV 回退模式使用的 vkName(tk- 开头,不传则自动探测)").option("--compute-unit <n>","Tair Serverless KV 计算单元(当前仅支持 1)").option("--zone <zoneId>","可用区(如 cn-hangzhou-b)").option("--vpc <vpcId>","指定 VPC ID").option("--vsw <vSwitchId>","指定 VSwitch ID").option("--security-ip-list <cidrs>","白名单 CIDR(逗号分隔)").action(async(e)=>{await G({commandLabel:"licell cache add",interactiveTTY:I(),requiredCapabilities:["redis","vpc"]},async()=>{W(Y.bgGreen(Y.black(" \uD83E\uDDE0 Cache Provisioning (Redis) "))),B();let t=I(),s=E(e.type)?.toLowerCase();if(!s){if(!t)throw Error("非交互模式下请传入 --type redis");let u=await Qp({message:"选择缓存引擎:",options:[{value:"redis",label:"\uD83D\uDFE5 Tair/Redis (VPC 内网)"}]});if($l(u))process.exit(0);s=K(u,"缓存类型").toLowerCase()}if(s!=="redis")throw Error("--type 目前仅支持 redis");let r=Fn(e.capacity,"capacity"),o=Fn(e.computeUnit,"compute-unit"),c=H(),f=await F(c,"正在初始化缓存资源编排...","❌ 缓存拉起失败",()=>lc(c,{instanceId:E(e.instance),existingPassword:E(e.password),accountName:E(e.username),engineVersion:E(e.engineVersion),instanceClass:E(e.class),nodeType:E(e.nodeType),capacityMb:r,vkName:E(e.vkName),computeUnitNum:o,zoneId:E(e.zone),vpcId:E(e.vpc),vSwitchId:E(e.vsw),securityIpList:E(e.securityIpList)}));if(!f)return;if(!m())c.stop(Y.green("✅ Redis 缓存已就绪并绑定到本工程内网!"));if(m()){T({stage:"cache.add",type:s,connectionStringMasked:Ae(f)});return}console.log(`
1597
1597
  \uD83D\uDD11 缓存连接串已生成: ${Y.cyan(Ae(f))}
1598
1598
  `),q("下次执行 licell deploy 时,将自动作为 process.env.REDIS_URL 注入!")})}),n.command("cache list","查看缓存实例列表").option("--limit <n>","返回数量,默认 20").action(async(e)=>{await G({commandLabel:"licell cache list",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{B();let t=An(e.limit,20,200),s=H(),r=await F(s,"正在拉取缓存实例列表...","❌ 获取缓存实例列表失败",()=>yc(t));if(!r)return;if(!m())s.stop(Y.green(`✅ 共获取 ${r.length} 个实例`));if(m()){T({stage:"cache.list",count:r.length,instances:r});return}if(r.length===0){q("当前地域没有缓存实例");return}for(let o of r)console.log(`${Y.cyan(o.instanceId)} mode=${Y.gray(o.mode)} status=${Y.gray(o.status||"-")} class=${Y.gray(o.instanceClass||"-")}`);console.log(""),q("Done.")})}),n.command("cache info <instanceId>","查看缓存实例详情").action(async(e)=>{await G({commandLabel:"licell cache info",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{B();let t=K(e,"instanceId"),s=H(),r=await F(s,`正在拉取实例 ${t} 详情...`,"❌ 获取缓存实例详情失败",()=>ac(t));if(!r)return;let o=r.summary;if(!m())s.stop(Y.green("✅ 获取成功"));else{T({stage:"cache.info",instanceId:t,detail:r});return}if(console.log(`
1599
1599
  instanceId: ${Y.cyan(o.instanceId)}`),console.log(`mode: ${Y.cyan(o.mode)}`),console.log(`status: ${Y.cyan(o.status||"-")}`),console.log(`class: ${Y.cyan(o.instanceClass||"-")}`),o.engineVersion)console.log(`engine: ${Y.cyan(o.engineVersion)}`);if(o.host)console.log(`endpoint: ${Y.cyan(`${o.host}:${o.port||6379}`)}`);if(console.log(`network: ${Y.cyan(`${o.vpcId||"-"} / ${o.vSwitchId||"-"} / ${o.zoneId||"-"}`)}`),r.accountNames.length>0)console.log(`accounts: ${Y.cyan(r.accountNames.join(", "))}`);console.log(""),q("Done.")})}),n.command("cache connect [instanceId]","输出缓存连接信息").action(async(e)=>{await G({commandLabel:"licell cache connect",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{B();let t=E(e),s=H(),r=await F(s,"正在解析缓存连接信息...","❌ 连接信息解析失败",()=>ur(t));if(!r)return;if(!m())s.stop(Y.green("✅ 连接信息已生成"));else{T({stage:"cache.connect",instanceId:r.instanceId,connection:r});return}if(console.log(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "licell",
3
- "version": "0.9.40",
3
+ "version": "0.9.41",
4
4
  "description": "Deploy and manage Alibaba Cloud Serverless applications — FC, OSS, ACR, DNS, SSL, CDN in one CLI",
5
5
  "type": "module",
6
6
  "bin": {