licell 0.9.39 → 0.9.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/licell.js +152 -141
- package/package.json +2 -1
package/dist/licell.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createRequire as
|
|
2
|
+
import{createRequire as Jd}from"node:module";var Vd=Object.create;var{getPrototypeOf:Qd,defineProperty:Gt,getOwnPropertyNames:ff,getOwnPropertyDescriptor:Xd}=Object,uf=Object.prototype.hasOwnProperty;var Zd=(n,e,t)=>{t=n!=null?Vd(Qd(n)):{};let s=e||!n||!n.__esModule?Gt(t,"default",{value:n,enumerable:!0}):t;for(let r of ff(n))if(!uf.call(s,r))Gt(s,r,{get:()=>n[r],enumerable:!0});return s},cf=new WeakMap,Od=(n)=>{var e=cf.get(n),t;if(e)return e;if(e=Gt({},"__esModule",{value:!0}),n&&typeof n==="object"||typeof n==="function")ff(n).map((s)=>!uf.call(e,s)&&Gt(e,s,{get:()=>n[s],enumerable:!(t=Xd(n,s))||t.enumerable}));return cf.set(n,e),e};var bs=(n,e)=>{for(var t in e)Gt(n,t,{get:e[t],enumerable:!0,configurable:!0,set:(s)=>e[t]=()=>s})};var X=(n,e)=>()=>(n&&(e=n(n=0)),e);var Nd=Jd(import.meta.url);import{homedir as mf}from"os";import{join as re}from"path";import{chmodSync as wf,existsSync as dt,mkdirSync as ny,readFileSync as pf,renameSync as ey,rmSync as Cr,writeFileSync as Sr}from"fs";function $f(){return re(process.cwd(),".licell")}function sy(){return re(process.cwd(),".ali")}function bf(){return re($f(),"project.json")}function ry(){return re(sy(),"project.json")}function Bt(n){if(!dt(n))ny(n,{recursive:!0,mode:yf});try{wf(n,yf)}catch{console.error(`⚠️ 无法设置目录权限 ${n},请手动确认权限为 0700`)}}function br(n,e){try{return JSON.parse(pf(n,"utf-8"))}catch{return e}}function kr(n,e,t=!1){let s=`${n}.${process.pid}.tmp`;try{if(Sr(s,JSON.stringify(e,null,2),t?{mode:af}:void 0),t)try{wf(s,af)}catch{throw Error(`无法设置安全文件权限 ${n},凭证未写入`)}ey(s,n)}catch(r){try{Cr(s,{force:!0})}catch{}throw r}}function Ve(n){return typeof n==="object"&&n!==null&&!Array.isArray(n)}function kf(n){if(typeof n==="number"){if(!Number.isFinite(n)||n<=0)return;return n}if(typeof n==="string"){let e=n.trim();if(!e)return;let t=Number(e);if(!Number.isFinite(t)||t<=0)return;return t}return}function _r(n){let e=kf(n);if(e===void 0||!Number.isInteger(e))return;return e}function hf(n){let e=Ve(n)?n:{},t=Ve(e.envs)?e.envs:{},s={};for(let[y,h]of Object.entries(t))if(typeof h==="string")s[y]=h;let r={envs:s};if(typeof e.appName==="string"&&e.appName.trim().length>0)r.appName=e.appName.trim();if(typeof e.runtime==="string"&&e.runtime.trim().length>0)r.runtime=e.runtime.trim();if(typeof e.acrNamespace==="string"&&e.acrNamespace.trim().length>0)r.acrNamespace=e.acrNamespace.trim();let o=Ve(e.resources)?e.resources:null;if(o){let y=_r(o.memorySize),h=_r(o.timeout),U=kf(o.cpu),C=_r(o.instanceConcurrency),L={...y!==void 0?{memorySize:y}:{},...h!==void 0?{timeout:h}:{},...U!==void 0?{cpu:U}:{},...C!==void 0?{instanceConcurrency:C}:{}};if(Object.keys(L).length>0)r.resources=L}let c=Ve(e.hooks)?e.hooks:null;if(c){let y=typeof c.preDeploy==="string"?c.preDeploy.trim():"",h=typeof c.postDeploy==="string"?c.postDeploy.trim():"",U={...y?{preDeploy:y}:{},...h?{postDeploy:h}:{}};if(Object.keys(U).length>0)r.hooks=U}let f=Ve(e.network)?e.network:null;if(f&&typeof f.vpcId==="string"&&typeof f.vswId==="string")r.network={vpcId:f.vpcId,vswId:f.vswId,sgId:typeof f.sgId==="string"?f.sgId:void 0,cidrBlock:typeof f.cidrBlock==="string"?f.cidrBlock:void 0};let u=Ve(e.cache)?e.cache:null;if(u&&typeof u.type==="string"&&typeof u.instanceId==="string"){let y={type:u.type,instanceId:u.instanceId,host:typeof u.host==="string"?u.host:void 0,port:typeof u.port==="number"?u.port:void 0,accountName:typeof u.accountName==="string"?u.accountName:void 0};if(typeof u.vkName==="string")y.vkName=u.vkName;if(typeof u.mode==="string")y.mode=u.mode;r.cache=y}let{appName:g,runtime:i,acrNamespace:l,envs:d,resources:a,hooks:p,network:w,cache:_,...$}=e;return{...$,...r,envs:s}}function oy(n){if(!Ve(n))return null;if(typeof n.accountId!=="string"||typeof n.ak!=="string"||typeof n.sk!=="string")return null;let t=(typeof n.region==="string"?n.region:Wn).trim().toLowerCase()||Wn,s=n.authSource==="bootstrap"||n.authSource==="manual"?n.authSource:void 0,r=typeof n.ramUser==="string"&&n.ramUser.trim().length>0?n.ramUser.trim():void 0,o=typeof n.ramPolicy==="string"&&n.ramPolicy.trim().length>0?n.ramPolicy.trim():void 0;return{accountId:n.accountId.trim(),ak:n.ak.trim(),sk:n.sk.trim(),region:t,...s?{authSource:s}:{},...r?{ramUser:r}:{},...o?{ramPolicy:o}:{}}}function cy(){let n=re(process.cwd(),".gitignore"),e=[".licell/",".ali/"];if(!dt(n)){Sr(n,`${e.join(`
|
|
3
3
|
`)}
|
|
4
|
-
`);return}let t=
|
|
4
|
+
`);return}let t=pf(n,"utf-8");for(let s of e){let r=s.replace(/\/$/,"");if(t.split(/\r?\n/).some((f)=>{let u=f.trim();return u===s||u===r}))continue;let c=t.endsWith(`
|
|
5
5
|
`)||t.length===0?"":`
|
|
6
6
|
`;t=`${t}${c}${s}
|
|
7
|
-
`}po(e,t)}function qy(){if(ct(hs))return hs;if(ct(as))return as;return null}function Ry(){let e=Qc();if(ct(e))return e;let n=Cy();if(ct(n))return n;return null}var ds,_y,hs,Gc,as,Mc=448,Yc=384,Ye="cn-hangzhou",k;var j=O(()=>{ds=ze(Kc(),".licell-cli"),_y=ze(Kc(),".ali-cli"),hs=ze(ds,"auth.json"),Gc=ze(ds,"config.json"),as=ze(_y,"auth.json");k={getAuth(){let e=qy();if(!e)return null;let n=Ey(yo(e,null));if(n&&e===as)this.setAuth(n);return n},requireAuth(){let e=this.getAuth();if(!e)throw Error("未登录,请先执行 `licell login`");return e},setAuth(e){Ht(ds),ho(hs,e,!0)},clearAuth(){mo(hs,{force:!0}),mo(as,{force:!0})},getProject(){let e=Ry();if(!e)return{envs:{}};return Bc(yo(e,{envs:{}}))},setProject(e,n){Ht(Wc()),Sy();let t=this.getProject(),s=n?.replaceEnvs?{...e.envs||{}}:{...t.envs,...e.envs||{}},o=Bc({...t,...e,envs:s});ho(Qc(),o,!0)},getGlobalConfig(){return yo(Gc,{})},setGlobalConfig(e){Ht(ds);let t={...this.getGlobalConfig(),...e};ho(Gc,t)}}});function Vc(e,n){return Object.prototype.hasOwnProperty.call(e,n)}function V(e,n,t){if(Vc(e,n))return e[n];if(t&&Vc(e,t))return e[t];return}function re(e,n){return V(e,`LICELL_${n}`,`ALI_${n}`)}var Jc={};ys(Jc,{resolveSdkCtor:()=>se,createSharedFcClient:()=>Kn});import Py from"@alicloud/fc20230330";import*as Zc from"@alicloud/openapi-client";function se(e,n){let t=e,s=typeof e==="function"?e:typeof t?.default==="function"?t.default:typeof t?.default?.default==="function"?(t?.default).default:null;if(!s)throw Error(`无法加载 ${n} SDK 构造器,请检查依赖安装和运行时模块格式`);return s}function Xc(e,n){if(!e)return n;let t=Number(e);if(!Number.isFinite(t)||t<=0)return n;return Math.floor(t)}function Kn(e){let n=e??k.requireAuth(),t=Xc(re(process.env,"FC_CONNECT_TIMEOUT_MS"),Iy),s=Xc(re(process.env,"FC_READ_TIMEOUT_MS"),Fy),o=new xy(new Zc.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:`${n.accountId}.${n.region}.fc.aliyuncs.com`,connectTimeout:t,readTimeout:s}));return{auth:n,client:o}}var Iy=60000,Fy=600000,xy;var Fe=O(()=>{j();xy=se(Py,"@alicloud/fc20230330")});var Nc={};ys(Nc,{isTransientError:()=>Ee,isRoleMissingError:()=>ws,isNotFoundError:()=>ie,isInvalidDomainNameError:()=>ps,isInstanceClassError:()=>ms,isConflictError:()=>N,isCidrConflictError:()=>ko,isAuthCredentialInvalidError:()=>ln,isAlreadyExistsRoleError:()=>$o,isAccessDeniedError:()=>xe});function en(e){if(typeof e!=="object"||e===null)return"";let n=String(e.code||""),t=String(e.message||"");return`${n} ${t}`}function un(e,n){let t=e.toLowerCase();return n.some((s)=>t.includes(s))}function N(e){return un(en(e),["alreadyexist","alreadyexists","entityalreadyexists","already exists","conflict","duplicate","domainrecordduplicate"])}function ie(e){return un(en(e),["notfound","no such","404","entitynotexist","not exist"])}function Ee(e){return un(en(e),["throttling","too many requests","connecttimeout","readtimeout","requesttimeouterror","socket disconnected","econnreset","econnrefused","service unavailable","internal error"])}function ms(e){return un(en(e),["instanceclass","soldout","outofstock","invalidparameter","not support","unsupported"])}function xe(e){return un(en(e),["accessdenied","forbidden","no permission"])}function ln(e){return un(en(e),["invalidaccesskeyid","invalidaccesskeyid.notfound","signaturedoesnotmatch","incompletesignature","authenticationfailed","invalidsecuritytoken","security token is invalid","access key id does not exist","accesskey secret not found"])}function ps(e){return un(en(e),["invaliddomainname.format","invaliddomainname.noexist","invalid domain name","domain name does not exist"])}function ws(e){return un(en(e),["servicelinkedrole.notexist"])}function $o(e){return un(en(e),["entityalreadyexists.role","already exists"])}function ko(e){let n=en(e).toLowerCase();return n.includes("cidr")&&(n.includes("conflict")||n.includes("overlap")||n.includes("invalid"))}import Hy,*as z from"@alicloud/ram20150501";import*as zc from"@alicloud/openapi-client";function $s(e){let n=(e||Ay).trim();if(!vy.test(n))throw Error("bootstrap RAM 用户名不合法:仅支持字母、数字、点、短横线、下划线,长度 1-64");return n}function ef(e){let n=(e||Uy).trim();if(!Gy.test(n))throw Error("bootstrap RAM 策略名不合法:仅支持字母、数字、短横线,长度 1-128");return n}function My(){return nf([...ks])}function nf(e){let n=[...new Set(e)].sort();return JSON.stringify({Version:"1",Statement:[{Effect:"Allow",Action:n,Resource:"*"}]})}function Co(e){return new Ly(new zc.Config({accessKeyId:e.ak,accessKeySecret:e.sk,endpoint:"ram.aliyuncs.com"}))}async function tf(e,n){try{return await e.getUser(new z.GetUserRequest({userName:n})),{created:!1}}catch(t){if(!ie(t))throw t}return await e.createUser(new z.CreateUserRequest({userName:n,displayName:n,comments:"Managed by licell bootstrap login"})),{created:!0}}async function Yy(e,n){try{return await e.getPolicy(new z.GetPolicyRequest({policyType:"Custom",policyName:n})),{created:!1}}catch(t){if(!ie(t))throw t}return await e.createPolicy(new z.CreatePolicyRequest({policyName:n,description:"Least-privilege policy generated by licell bootstrap login",policyDocument:My()})),{created:!0}}function By(e){if(typeof e==="string")return[e].map((n)=>n.trim()).filter(Boolean);if(!Array.isArray(e))return[];return e.map((n)=>typeof n==="string"?n.trim():"").filter(Boolean)}function Ky(e){if(!e)return null;try{return JSON.parse(e)}catch{try{return JSON.parse(decodeURIComponent(e))}catch{return null}}}function Oy(e,n){let t=Ky(e);if(!t||typeof t!=="object")return{changed:!0,policyDocument:nf(n)};let s=t.Statement,o=Array.isArray(s)?[...s]:s&&typeof s==="object"?[s]:[],r=-1;for(let l=0;l<o.length;l+=1){let i=o[l];if(!i||typeof i!=="object")continue;if(String(i.Effect||"").toLowerCase()==="allow"){r=l;break}}let c=[...new Set(n)].sort(),f=!1;if(r===-1)o.push({Effect:"Allow",Action:c,Resource:"*"}),f=!0;else{let l=o[r];if(!l||typeof l!=="object")throw Error("策略文档格式错误");let i=By(l.Action),g=[...new Set([...i,...c])].sort(),y=[...new Set(i)].sort();if(g.join("|")!==y.join("|"))f=!0;if(l.Action=g,l.Resource===void 0)l.Resource="*",f=!0}let u={...t,Version:typeof t.Version==="string"&&t.Version?t.Version:"1",Statement:o};return{changed:f,policyDocument:JSON.stringify(u)}}async function _o(e,n,t){if((await Yy(e,n)).created)return{created:!0,updated:!1};let r=(await e.getPolicy(new z.GetPolicyRequest({policyType:"Custom",policyName:n}))).body?.defaultPolicyVersion?.versionId;if(!r)return{created:!1,updated:!1};let f=(await e.getPolicyVersion(new z.GetPolicyVersionRequest({policyType:"Custom",policyName:n,versionId:r}))).body?.policyVersion?.policyDocument,u=Oy(f,t);if(!u.changed)return{created:!1,updated:!1};return await e.createPolicyVersion(new z.CreatePolicyVersionRequest({policyName:n,policyDocument:u.policyDocument,setAsDefault:!0,rotateStrategy:"DeleteOldestNonDefaultVersionWhenLimitExceeded"})),{created:!1,updated:!0}}async function To(e,n,t){try{await e.attachPolicyToUser(new z.AttachPolicyToUserRequest({policyType:"Custom",policyName:n,userName:t}))}catch(s){let o=`${String(s?.code||"")} ${String(s?.message||"")}`.toLowerCase();if(o.includes("entityalreadyexists")||o.includes("already attached")||o.includes("attached already"))return;throw s}}async function sf(e,n){if(((await e.listAccessKeys(new z.ListAccessKeysRequest({userName:n}))).body?.accessKeys?.accessKey||[]).filter((u)=>u.status==="Active").length>=2)throw Error(`RAM 用户 ${n} 的 AccessKey 已达到上限(2 个)。请先在控制台删除旧 key 后重试。`);let r=await e.createAccessKey(new z.CreateAccessKeyRequest({userName:n})),c=r.body?.accessKey?.accessKeyId,f=r.body?.accessKey?.accessKeySecret;if(!c||!f)throw Error("创建 RAM AccessKey 成功但返回字段不完整,请重试");return{accessKeyId:c,accessKeySecret:f}}async function jy(e,n){let t=n.trim();if(!t)return;let s=20,o;for(let r=0;r<s;r+=1){let c=await e.listUsers(new z.ListUsersRequest({marker:o,maxItems:100})),f=c.body?.users?.user||[];for(let u of f){let l=u.userName;if(!l)continue;if(((await e.listAccessKeys(new z.ListAccessKeysRequest({userName:l}))).body?.accessKeys?.accessKey||[]).some((y)=>y.accessKeyId===t))return l}if(!c.body?.isTruncated||!c.body.marker)break;o=c.body.marker}return}async function of(e){let n=$s(e.userName),t=ef(e.policyName),s=Co(e.adminAuth),o=await tf(s,n),r=await _o(s,t,[...ks]);await To(s,t,n);let c=await sf(s,n);try{await rf(e.adminAuth)}catch{}return{userName:n,policyName:t,accessKeyId:c.accessKeyId,accessKeySecret:c.accessKeySecret,createdUser:o.created,createdPolicy:r.created,updatedPolicy:r.updated}}async function Eo(e){let n=e.currentAuth||null,t=ef(e.policyName||n?.ramPolicy),s=Co(e.adminAuth);try{await rf(e.adminAuth)}catch{}let o=e.userName?$s(e.userName):n?.ramUser?$s(n.ramUser):void 0;if(!o&&n?.ak)o=await jy(s,n.ak);if(!e.forceRotateKey&&o&&n?.ak&&n.sk){let l=await _o(s,t,[...ks]);return await To(s,t,o),{mode:"updated-existing-key",userName:o,policyName:t,accessKeyId:n.ak,accessKeySecret:n.sk,createdUser:!1,createdPolicy:l.created,updatedPolicy:l.updated}}let r=o||$s(e.userName||n?.ramUser),c=await tf(s,r),f=await _o(s,t,[...ks]);await To(s,t,r);let u=await sf(s,r);return{mode:"rotated-new-key",userName:r,policyName:t,accessKeyId:u.accessKeyId,accessKeySecret:u.accessKeySecret,createdUser:c.created,createdPolicy:f.created,updatedPolicy:f.updated}}async function rf(e){let n=Co(e);try{return await n.getRole(new z.GetRoleRequest({roleName:bo})),{created:!1}}catch(t){if(!ie(t))throw t}await n.createRole(new z.CreateRoleRequest({roleName:bo,assumeRolePolicyDocument:Wy,description:"FC default service role for accessing OSS and other Alibaba Cloud services"}));for(let t of Qy)try{await n.attachPolicyToRole(new z.AttachPolicyToRoleRequest({policyType:"System",policyName:t,roleName:bo}))}catch{}return{created:!0}}var Ly,Ay="licell-operator",Uy="LicellOperatorPolicy",vy,Gy,ks,bo="AliyunFCDefaultRole",Wy,Qy;var So=O(()=>{Fe();Ly=se(Hy,"@alicloud/ram20150501"),vy=/^[A-Za-z0-9._-]{1,64}$/,Gy=/^[A-Za-z0-9-]{1,128}$/,ks=["fc:CreateFunction","fc:UpdateFunction","fc:GetFunction","fc:ListFunctions","fc:InvokeFunction","fc:CreateTrigger","fc:UpdateTrigger","fc:ListTriggers","fc:CreateCustomDomain","fc:UpdateCustomDomain","fc:DeleteCustomDomain","fc:GetCustomDomain","fc:PublishFunctionVersion","fc:ListFunctionVersions","fc:DeleteFunctionVersion","fc:CreateAlias","fc:UpdateAlias","fc:ListAliases","fc:DeleteAlias","fc:DeleteTrigger","fc:DeleteFunction","rds:DescribeAvailableZones","rds:DescribeAvailableClasses","rds:DescribeDBInstances","rds:CreateDBInstance","rds:DescribeDBInstanceNetInfo","rds:CreateAccount","rds:CreateDatabase","rds:GrantAccountPrivilege","rds:CheckServiceLinkedRole","rds:CreateServiceLinkedRole","kvstore:DescribeInstances","kvstore:DescribeAccounts","kvstore:DescribeServiceLinkedRoleExists","kvstore:InitializeKvstorePermission","kvstore:DescribeTairKVCacheInferInstances","kvstore:DescribeTairKVCacheInferInstanceAttribute","kvstore:CreateTairKVCacheVNode","kvstore:ResetAccountPassword","kvstore:ResetTairKVCacheCustomInstancePassword","kvstore:ModifySecurityIps","oss:PutBucket","oss:GetBucketInfo","oss:PutBucketACL","oss:PutObject","oss:ListBuckets","oss:ListObjects","alidns:DescribeSubDomainRecords","alidns:DescribeDomainRecords","alidns:AddDomainRecord","alidns:UpdateDomainRecord","alidns:DeleteDomainRecord","cdn:DescribeUserDomains","cdn:AddCdnDomain","cdn:BatchSetCdnDomainConfig","cdn:DeleteCdnDomain","cdn:DeleteUserCdnDomain","cdn:SetCdnDomainSSLCertificate","vpc:DescribeVpcs","vpc:DescribeZones","vpc:DescribeVSwitchAttributes","vpc:DescribeVSwitches","vpc:CreateVpc","vpc:CreateVSwitch","ecs:DescribeSecurityGroups","ecs:CreateSecurityGroup","cr:ListInstance","cr:CreateNamespace","cr:CreateRepository","cr:GetAuthorizationToken","log:GetLogs","ram:PassRole","ram:GetRole"];Wy=JSON.stringify({Statement:[{Action:"sts:AssumeRole",Effect:"Allow",Principal:{Service:["fc.aliyuncs.com"]}}],Version:"1"}),Qy=["AliyunOSSFullAccess"]});async function bs(e){try{await e()}catch(n){if(!N(n))throw n}}function P(e){if(typeof e==="object"&&e!==null&&"message"in e){let n=String(e.message),t=e.stack;if(t&&typeof t==="string"&&n.includes("is not a function"))return`${n}
|
|
8
|
-
${t}`;return
|
|
9
|
-
${t}`)}}function
|
|
7
|
+
`}Sr(n,t)}function fy(){if(dt(_s))return _s;if(dt(Cs))return Cs;return null}function uy(){let n=bf();if(dt(n))return n;let e=ry();if(dt(e))return e;return null}var ks,ty,_s,df,Cs,yf=448,af=384,Wn="cn-hangzhou",k;var Q=X(()=>{ks=re(mf(),".licell-cli"),ty=re(mf(),".ali-cli"),_s=re(ks,"auth.json"),df=re(ks,"config.json"),Cs=re(ty,"auth.json");k={getAuth(){let n=fy();if(!n)return null;let e=oy(br(n,null));if(e&&n===Cs)this.setAuth(e);return e},requireAuth(){let n=this.getAuth();if(!n)throw Error("未登录,请先执行 `licell login`");return n},setAuth(n){Bt(ks),kr(_s,n,!0)},clearAuth(){Cr(_s,{force:!0}),Cr(Cs,{force:!0})},getProject(){let n=uy();if(!n)return{envs:{}};return hf(br(n,{envs:{}}))},setProject(n,e){Bt($f()),cy();let t=this.getProject(),s=e?.replaceEnvs?{...n.envs||{}}:{...t.envs,...n.envs||{}},r=hf({...t,...n,envs:s});kr(bf(),r,!0)},getGlobalConfig(){return br(df,{})},setGlobalConfig(n){Bt(ks);let t={...this.getGlobalConfig(),...n};kr(df,t)}}});function _f(n,e){return Object.prototype.hasOwnProperty.call(n,e)}function J(n,e,t){if(_f(n,e))return n[e];if(t&&_f(n,t))return n[t];return}function un(n,e){return J(n,`LICELL_${e}`,`ALI_${e}`)}var Tf={};bs(Tf,{resolveSdkCtor:()=>nn,createSharedFcClient:()=>Qe});import iy from"@alicloud/fc20230330";import*as Sf from"@alicloud/openapi-client";function nn(n,e){let t=n,s=typeof n==="function"?n:typeof t?.default==="function"?t.default:typeof t?.default?.default==="function"?(t?.default).default:null;if(!s)throw Error(`无法加载 ${e} SDK 构造器,请检查依赖安装和运行时模块格式`);return s}function Cf(n,e){if(!n)return e;let t=Number(n);if(!Number.isFinite(t)||t<=0)return e;return Math.floor(t)}function Qe(n){let e=n??k.requireAuth(),t=Cf(un(process.env,"FC_CONNECT_TIMEOUT_MS"),gy),s=Cf(un(process.env,"FC_READ_TIMEOUT_MS"),ly),r=new dy(new Sf.Config({accessKeyId:e.ak,accessKeySecret:e.sk,endpoint:`${e.accountId}.${e.region}.fc.aliyuncs.com`,connectTimeout:t,readTimeout:s}));return{auth:e,client:r}}var gy=60000,ly=600000,dy;var xn=X(()=>{Q();dy=nn(iy,"@alicloud/fc20230330")});var Ef={};bs(Ef,{isTransientError:()=>Rn,isRoleMissingError:()=>Es,isNotFoundError:()=>an,isInvalidDomainNameError:()=>Ts,isInstanceClassError:()=>Ss,isConflictError:()=>tn,isCidrConflictError:()=>qr,isAuthCredentialInvalidError:()=>ae,isAlreadyExistsRoleError:()=>Er,isAccessDeniedError:()=>Gn});function oe(n){if(typeof n!=="object"||n===null)return"";let e=String(n.code||""),t=String(n.message||"");return`${e} ${t}`}function ye(n,e){let t=n.toLowerCase();return e.some((s)=>t.includes(s))}function tn(n){return ye(oe(n),["alreadyexist","alreadyexists","entityalreadyexists","already exists","conflict","duplicate","domainrecordduplicate"])}function an(n){return ye(oe(n),["notfound","no such","404","entitynotexist","not exist"])}function Rn(n){return ye(oe(n),["throttling","too many requests","connecttimeout","readtimeout","requesttimeouterror","socket disconnected","econnreset","econnrefused","service unavailable","internal error"])}function Ss(n){return ye(oe(n),["instanceclass","soldout","outofstock","invalidparameter","not support","unsupported"])}function Gn(n){return ye(oe(n),["accessdenied","forbidden","no permission"])}function ae(n){return ye(oe(n),["invalidaccesskeyid","invalidaccesskeyid.notfound","signaturedoesnotmatch","incompletesignature","authenticationfailed","invalidsecuritytoken","security token is invalid","access key id does not exist","accesskey secret not found"])}function Ts(n){return ye(oe(n),["invaliddomainname.format","invaliddomainname.noexist","invalid domain name","domain name does not exist"])}function Es(n){return ye(oe(n),["servicelinkedrole.notexist"])}function Er(n){return ye(oe(n),["entityalreadyexists.role","already exists"])}function qr(n){let e=oe(n).toLowerCase();return e.includes("cidr")&&(e.includes("conflict")||e.includes("overlap")||e.includes("invalid"))}import yy,*as sn from"@alicloud/ram20150501";import ay,*as Xe from"@alicloud/openapi-client";import*as qf from"@alicloud/tea-util";function qs(n){let e=(n||wy).trim();if(!$y.test(e))throw Error("bootstrap RAM 用户名不合法:仅支持字母、数字、点、短横线、下划线,长度 1-64");return e}function If(n){let e=(n||py).trim();if(!by.test(e))throw Error("bootstrap RAM 策略名不合法:仅支持字母、数字、短横线,长度 1-128");return e}function ky(){return Rf([...Is])}function Rf(n){let e=[...new Set(n)].sort();return JSON.stringify({Version:"1",Statement:[{Effect:"Allow",Action:e,Resource:"*"}]})}function Fr(n){return new hy(new Xe.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:"ram.aliyuncs.com"}))}async function Pf(n,e){try{return await n.getUser(new sn.GetUserRequest({userName:e})),{created:!1}}catch(t){if(!an(t))throw t}return await n.createUser(new sn.CreateUserRequest({userName:e,displayName:e,comments:"Managed by licell bootstrap login"})),{created:!0}}async function _y(n,e){try{return await n.getPolicy(new sn.GetPolicyRequest({policyType:"Custom",policyName:e})),{created:!1}}catch(t){if(!an(t))throw t}return await n.createPolicy(new sn.CreatePolicyRequest({policyName:e,description:"Least-privilege policy generated by licell bootstrap login",policyDocument:ky()})),{created:!0}}function Cy(n){if(typeof n==="string")return[n].map((e)=>e.trim()).filter(Boolean);if(!Array.isArray(n))return[];return n.map((e)=>typeof e==="string"?e.trim():"").filter(Boolean)}function Sy(n){if(!n)return null;try{return JSON.parse(n)}catch{try{return JSON.parse(decodeURIComponent(n))}catch{return null}}}function Ty(n,e){let t=Sy(n);if(!t||typeof t!=="object")return{changed:!0,policyDocument:Rf(e)};let s=t.Statement,r=Array.isArray(s)?[...s]:s&&typeof s==="object"?[s]:[],o=-1;for(let g=0;g<r.length;g+=1){let i=r[g];if(!i||typeof i!=="object")continue;if(String(i.Effect||"").toLowerCase()==="allow"){o=g;break}}let c=[...new Set(e)].sort(),f=!1;if(o===-1)r.push({Effect:"Allow",Action:c,Resource:"*"}),f=!0;else{let g=r[o];if(!g||typeof g!=="object")throw Error("策略文档格式错误");let i=Cy(g.Action),l=[...new Set([...i,...c])].sort(),d=[...new Set(i)].sort();if(l.join("|")!==d.join("|"))f=!0;if(g.Action=l,g.Resource===void 0)g.Resource="*",f=!0}let u={...t,Version:typeof t.Version==="string"&&t.Version?t.Version:"1",Statement:r};return{changed:f,policyDocument:JSON.stringify(u)}}async function Rr(n,e,t){if((await _y(n,e)).created)return{created:!0,updated:!1};let o=(await n.getPolicy(new sn.GetPolicyRequest({policyType:"Custom",policyName:e}))).body?.defaultPolicyVersion?.versionId;if(!o)return{created:!1,updated:!1};let f=(await n.getPolicyVersion(new sn.GetPolicyVersionRequest({policyType:"Custom",policyName:e,versionId:o}))).body?.policyVersion?.policyDocument,u=Ty(f,t);if(!u.changed)return{created:!1,updated:!1};return await n.createPolicyVersion(new sn.CreatePolicyVersionRequest({policyName:e,policyDocument:u.policyDocument,setAsDefault:!0,rotateStrategy:"DeleteOldestNonDefaultVersionWhenLimitExceeded"})),{created:!1,updated:!0}}async function Pr(n,e,t){try{await n.attachPolicyToUser(new sn.AttachPolicyToUserRequest({policyType:"Custom",policyName:e,userName:t}))}catch(s){let r=`${String(s?.code||"")} ${String(s?.message||"")}`.toLowerCase();if(r.includes("entityalreadyexists")||r.includes("already attached")||r.includes("attached already"))return;throw s}}async function Ff(n,e){if(((await n.listAccessKeys(new sn.ListAccessKeysRequest({userName:e}))).body?.accessKeys?.accessKey||[]).filter((u)=>u.status==="Active").length>=2)throw Error(`RAM 用户 ${e} 的 AccessKey 已达到上限(2 个)。请先在控制台删除旧 key 后重试。`);let o=await n.createAccessKey(new sn.CreateAccessKeyRequest({userName:e})),c=o.body?.accessKey?.accessKeyId,f=o.body?.accessKey?.accessKeySecret;if(!c||!f)throw Error("创建 RAM AccessKey 成功但返回字段不完整,请重试");return{accessKeyId:c,accessKeySecret:f}}async function Ey(n,e){let t=e.trim();if(!t)return;let s=20,r;for(let o=0;o<s;o+=1){let c=await n.listUsers(new sn.ListUsersRequest({marker:r,maxItems:100})),f=c.body?.users?.user||[];for(let u of f){let g=u.userName;if(!g)continue;if(((await n.listAccessKeys(new sn.ListAccessKeysRequest({userName:g}))).body?.accessKeys?.accessKey||[]).some((d)=>d.accessKeyId===t))return g}if(!c.body?.isTruncated||!c.body.marker)break;r=c.body.marker}return}async function xf(n){let e=qs(n.userName),t=If(n.policyName),s=Fr(n.adminAuth),r=await Pf(s,e),o=await Rr(s,t,[...Is]);await Pr(s,t,e);let c=await Ff(s,e);try{await Lf(n.adminAuth)}catch{}try{await Af(n.adminAuth)}catch{}return{userName:e,policyName:t,accessKeyId:c.accessKeyId,accessKeySecret:c.accessKeySecret,createdUser:r.created,createdPolicy:o.created,updatedPolicy:o.updated}}async function xr(n){let e=n.currentAuth||null,t=If(n.policyName||e?.ramPolicy),s=Fr(n.adminAuth);try{await Lf(n.adminAuth)}catch{}try{await Af(n.adminAuth)}catch{}let r=n.userName?qs(n.userName):e?.ramUser?qs(e.ramUser):void 0;if(!r&&e?.ak)r=await Ey(s,e.ak);if(!n.forceRotateKey&&r&&e?.ak&&e.sk){let g=await Rr(s,t,[...Is]);return await Pr(s,t,r),{mode:"updated-existing-key",userName:r,policyName:t,accessKeyId:e.ak,accessKeySecret:e.sk,createdUser:!1,createdPolicy:g.created,updatedPolicy:g.updated}}let o=r||qs(n.userName||e?.ramUser),c=await Pf(s,o),f=await Rr(s,t,[...Is]);await Pr(s,t,o);let u=await Ff(s,o);return{mode:"rotated-new-key",userName:o,policyName:t,accessKeyId:u.accessKeyId,accessKeySecret:u.accessKeySecret,createdUser:c.created,createdPolicy:f.created,updatedPolicy:f.updated}}async function Lf(n){let e=Fr(n);try{return await e.getRole(new sn.GetRoleRequest({roleName:Ir})),{created:!1}}catch(t){if(!an(t))throw t}await e.createRole(new sn.CreateRoleRequest({roleName:Ir,assumeRolePolicyDocument:qy,description:"FC default service role for accessing OSS and other Alibaba Cloud services"}));for(let t of Iy)try{await e.attachPolicyToRole(new sn.AttachPolicyToRoleRequest({policyType:"System",policyName:t,roleName:Ir}))}catch{}return{created:!0}}async function Af(n){let e=new my(new Xe.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:"resourcemanager.aliyuncs.com"})),t=new Xe.Params({action:"CreateServiceLinkedRole",version:"2020-03-31",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),s=new Xe.OpenApiRequest({query:{ServiceName:Ry}});try{return await e.callApi(t,s,new qf.RuntimeOptions({readTimeout:15000,connectTimeout:8000})),{created:!0}}catch(r){let o=String(r?.code||"");if(o==="AlreadyExists"||o.includes("AlreadyExists"))return{created:!1};throw r}}var hy,my,wy="licell-operator",py="LicellOperatorPolicy",$y,by,Is,Ir="AliyunFCDefaultRole",qy,Iy,Ry="supabase.rdsai.aliyuncs.com";var Lr=X(()=>{xn();hy=nn(yy,"@alicloud/ram20150501"),my=nn(ay,"@alicloud/openapi-client"),$y=/^[A-Za-z0-9._-]{1,64}$/,by=/^[A-Za-z0-9-]{1,128}$/,Is=["fc:CreateFunction","fc:UpdateFunction","fc:GetFunction","fc:ListFunctions","fc:InvokeFunction","fc:CreateTrigger","fc:UpdateTrigger","fc:ListTriggers","fc:CreateCustomDomain","fc:UpdateCustomDomain","fc:DeleteCustomDomain","fc:GetCustomDomain","fc:PublishFunctionVersion","fc:ListFunctionVersions","fc:DeleteFunctionVersion","fc:CreateAlias","fc:UpdateAlias","fc:ListAliases","fc:DeleteAlias","fc:DeleteTrigger","fc:DeleteFunction","rds:DescribeAvailableZones","rds:DescribeAvailableClasses","rds:DescribeDBInstances","rds:CreateDBInstance","rds:DescribeDBInstanceNetInfo","rds:CreateAccount","rds:CreateDatabase","rds:GrantAccountPrivilege","rds:CheckServiceLinkedRole","rds:CreateServiceLinkedRole","rdsai:CreateAppInstance","rdsai:DeleteAppInstance","rdsai:DescribeAppInstances","rdsai:DescribeAppInstanceAttribute","rdsai:DescribeInstanceEndpoints","rdsai:DescribeInstanceAuthInfo","rdsai:ModifyInstanceAuthConfig","rdsai:ModifyInstanceStorageConfig","rdsai:ModifyInstanceRAGConfig","rdsai:ModifyInstanceIpWhitelist","rdsai:ModifyInstanceConfig","rdsai:ModifyInstanceSSL","rdsai:DescribeInstanceIpWhitelist","rdsai:DescribeInstanceSSL","rdsai:DescribeInstanceStorageConfig","rdsai:DescribeInstanceRAGConfig","rdsai:ResetInstancePassword","rdsai:RestartInstance","rdsai:StopInstance","rdsai:StartInstance","kvstore:DescribeInstances","kvstore:DescribeAccounts","kvstore:DescribeServiceLinkedRoleExists","kvstore:InitializeKvstorePermission","kvstore:DescribeTairKVCacheInferInstances","kvstore:DescribeTairKVCacheInferInstanceAttribute","kvstore:CreateTairKVCacheVNode","kvstore:ResetAccountPassword","kvstore:ResetTairKVCacheCustomInstancePassword","kvstore:ModifySecurityIps","oss:PutBucket","oss:GetBucketInfo","oss:PutBucketACL","oss:PutObject","oss:ListBuckets","oss:ListObjects","alidns:DescribeSubDomainRecords","alidns:DescribeDomainRecords","alidns:AddDomainRecord","alidns:UpdateDomainRecord","alidns:DeleteDomainRecord","cdn:DescribeUserDomains","cdn:AddCdnDomain","cdn:BatchSetCdnDomainConfig","cdn:DeleteCdnDomain","cdn:DeleteUserCdnDomain","cdn:SetCdnDomainSSLCertificate","vpc:DescribeVpcs","vpc:DescribeZones","vpc:DescribeVSwitchAttributes","vpc:DescribeVSwitches","vpc:CreateVpc","vpc:CreateVSwitch","ecs:DescribeSecurityGroups","ecs:CreateSecurityGroup","cr:ListInstance","cr:CreateNamespace","cr:CreateRepository","cr:GetAuthorizationToken","log:GetLogs","ram:PassRole","ram:GetRole","ram:CreateServiceLinkedRole"];qy=JSON.stringify({Statement:[{Action:"sts:AssumeRole",Effect:"Allow",Principal:{Service:["fc.aliyuncs.com"]}}],Version:"1"}),Iy=["AliyunOSSFullAccess"]});async function Rs(n){try{await n()}catch(e){if(!tn(e))throw e}}function A(n){if(typeof n==="object"&&n!==null&&"message"in n){let e=String(n.message),t=n.stack;if(t&&typeof t==="string"&&e.includes("is not a function"))return`${e}
|
|
8
|
+
${t}`;return e}return String(n)}var dn=()=>{};import{parse as Py}from"tldts";function zn(n){let e=n.trim().toLowerCase(),t=Py(e);if(!t.domain)throw Error(`无效域名: ${n}`);let s=t.domain,r=t.subdomain||"@";return{rootDomain:s,subDomain:r}}var vt=()=>{};function yt(n){Ar.set(n.name,n)}function Te(n){let e=Ar.get(n);if(!e)throw Error(`不支持的运行时: ${n}`);return e}function Ze(){return[...Ar.keys()]}var Ar;var Oe=X(()=>{Ar=new Map});import{spawn as Fy}from"child_process";async function yn(n){if(typeof Bun<"u"&&typeof Bun.sleep==="function"){await Bun.sleep(n);return}await new Promise((e)=>setTimeout(e,n))}async function Ps(n,e){if(typeof Bun<"u"&&typeof Bun.build==="function"){let r=await Bun.build({entrypoints:[n],outdir:e,target:"node",format:"cjs",minify:!0});return{success:r.success,logs:r.logs.map((o)=>({message:o.message}))}}let t=["build",n,"--target","node","--format","cjs","--minify","--outdir",e],s=await new Promise((r)=>{let o=Fy("bun",t,{stdio:["ignore","pipe","pipe"]}),c="",f="";o.stdout.on("data",(u)=>{c+=String(u)}),o.stderr.on("data",(u)=>{f+=String(u)}),o.on("error",(u)=>{r({code:null,stdout:c,stderr:f,error:u})}),o.on("close",(u)=>{r({code:u,stdout:c,stderr:f})})});if(s.error)return{success:!1,logs:[{message:`调用 bun build 失败: ${s.error.message}`}]};if(s.code!==0)return{success:!1,logs:[{message:s.stderr.trim()||s.stdout.trim()||`bun build 失败,退出码 ${s.code}`}]};return{success:!0,logs:[]}}var Zn=()=>{};import{existsSync as Gf,mkdirSync as xy,mkdtempSync as Ly,rmSync as Ay,statSync as Bf}from"fs";import{tmpdir as Hy}from"os";import{isAbsolute as Uy,join as Hf,resolve as vf}from"path";import{spawnSync as My}from"child_process";function Hr(n){if(typeof n!=="string")return;let e=n.trim();return e.length>0?e:void 0}function Uf(n){let e=Hr(n)?.toLowerCase();if(!e)return!1;return e==="1"||e==="true"||e==="yes"||e==="on"}function By(n,e,t){let s=My(n,e,{cwd:t.cwd,encoding:"utf8",stdio:["ignore","pipe","pipe"]});return{status:s.status,stdout:s.stdout||"",stderr:s.stderr||"",error:s.error||void 0}}function Kf(n){let e=n.stderr.trim();if(e)return e;let t=n.stdout.trim();if(t)return t;return`exit=${n.status??"unknown"}`}function vy(n,e){let t=Hr(un(e,"PYTHON_REQUIREMENTS"));if(!t)return null;let s=Uy(t)?t:vf(n,t);if(!Gf(s)||!Bf(s).isFile())throw Error(`LICELL_PYTHON_REQUIREMENTS 指定文件不存在: ${t}`);return s}function Ky(n,e=process.env){let t=vy(n,e);if(t)return t;for(let s of Gy){let r=vf(n,s);if(!Gf(r))continue;if(!Bf(r).isFile())continue;return r}return null}function Yy(n){if(n==="python3.12")return{pythonVersion:"3.12"};return{pythonVersion:"3.13"}}function Mf(n,e,t,s,r){let o=n(e,t,{cwd:s});if(o.error)throw Error(`${r}: ${o.error.message}`);if(o.status!==0)throw Error(`${r}: ${Kf(o)}`)}async function Yf(n){let e=n.env||process.env;if(Uf(un(e,"PYTHON_SKIP_VENDOR")))return;let t=Ky(n.sourceRoot,e);if(!t)return;let s=Hr(un(e,"PYTHON_PIP"))||"python3",r=n.runCommand||By,o=Uf(un(e,"PYTHON_ALLOW_SOURCE")),{pythonVersion:c}=Yy(n.runtime),f=Ly(Hf(Hy(),"licell-pydeps-")),u=Hf(f,"wheelhouse");xy(u,{recursive:!0});try{let i=r(s,["-m","pip","download","--disable-pip-version-check","--dest",u,"--requirement",t,"--only-binary=:all:","--platform","manylinux2014_x86_64","--implementation","cp","--python-version",c],{cwd:n.sourceRoot});if(i.error)throw Error(`准备 Python 依赖失败: ${i.error.message}`);if(i.status!==0){if(!o)throw Error("下载 Linux 兼容 Python 依赖失败。请优先使用可用 manylinux wheel 的依赖,"+"或在 Linux CI 执行部署。若你接受本机编译依赖可设置 LICELL_PYTHON_ALLOW_SOURCE=1。"+` 原因: ${Kf(i)}`);Mf(r,s,["-m","pip","install","--disable-pip-version-check","--no-cache-dir","--no-input","--no-compile","--target",n.outdir,"--requirement",t],n.sourceRoot,"安装 Python 依赖失败");return}Mf(r,s,["-m","pip","install","--disable-pip-version-check","--no-cache-dir","--no-input","--no-compile","--no-index","--find-links",u,"--only-binary=:all:","--platform","manylinux2014_x86_64","--implementation","cp","--python-version",c,"--target",n.outdir,"--requirement",t],n.sourceRoot,"安装 Python 依赖失败")}finally{Ay(f,{recursive:!0,force:!0})}}var Gy;var jf=X(()=>{Gy=["requirements.txt","requirements-prod.txt","requirements.prod.txt","requirements/production.txt"]});import{copyFileSync as jy,existsSync as Dy,mkdirSync as Wy,readFileSync as Vy,readdirSync as Vf,statSync as Qf}from"fs";import{dirname as Qy,join as Kt}from"path";function Yt(n){let e=Vf(n);for(let t of e){let s=Kt(n,t);if(Qf(s).isDirectory()){let r=Yt(s);if(r)return r;continue}if(s.endsWith(".js"))return s}return null}function Oy(n){return/^\s*(async\s+)?def\s+handler\s*\(/m.test(n)}function Df(n){return[/\bexport\s+(?:async\s+)?function\s+handler\b/,/\bexport\s+(?:const|let|var)\s+handler\b/,/\bexport\s*\{\s*handler(?:\s+as\s+\w+)?\s*\}/,/\bexport\s*\{\s*default\s+as\s+handler\s*\}/,/\bmodule\.exports\.handler\s*=/,/\bexports\.handler\s*=/,/\bmodule\.exports\s*=\s*\{[\s\S]*\bhandler\b[\s\S]*\}/].some((t)=>t.test(n))}function Jy(n){return[/\bexport\s+default\b/,/\bexports\.default\s*=/].some((t)=>t.test(n))}function Wf(n){try{return Vy(n,"utf8")}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`读取入口文件失败: ${n}
|
|
9
|
+
${t}`)}}function Fs(n,e){if(e==="docker")return;if(e.startsWith("python")){if(!n.toLowerCase().endsWith(".py"))throw Error(`Python runtime=${e} 要求入口文件为 .py,当前为: ${n}`);let s=Wf(n);if(Oy(s))return;throw Error(`Python 入口文件缺少 handler 函数: ${n}
|
|
10
10
|
`+`请导出可调用函数,例如:
|
|
11
11
|
`+`def handler(event, context):
|
|
12
|
-
return {"statusCode": 200, "body": "ok"}`)}if(!
|
|
13
|
-
`+"runtime=nodejs22 需导出 handler 或 default 函数。")}if(
|
|
14
|
-
`+"runtime=nodejs20 需导出 handler(event, context) 函数。")}function
|
|
12
|
+
return {"statusCode": 200, "body": "ok"}`)}if(!e.startsWith("nodejs"))return;let t=Wf(n);if(e==="nodejs22"){if(Df(t)||Jy(t))return;throw Error(`Node 入口文件缺少导出函数: ${n}
|
|
13
|
+
`+"runtime=nodejs22 需导出 handler 或 default 函数。")}if(Df(t))return;throw Error(`Node 入口文件缺少 handler 导出: ${n}
|
|
14
|
+
`+"runtime=nodejs20 需导出 handler(event, context) 函数。")}function Ny(n){let e=n.replace(/\\/g,"/");if(e.endsWith(".py"))return!0;let t=e.split("/").pop()?.toLowerCase()||"";return Zy.has(t)}function Xf(n,e,t=""){let s=t?Kt(n,t):n;for(let r of Vf(s)){let o=r.toLowerCase();if(Xy.has(o))continue;let c=t?`${t}/${r}`:r,f=Kt(n,c),u=Qf(f);if(u.isDirectory()){Xf(n,e,c);continue}if(!u.isFile())continue;if(!Ny(c))continue;let g=Kt(e,c);Wy(Qy(g),{recursive:!0}),jy(f,g)}}async function xs(n,e,t){let s=n.replace(/\\/g,"/");if(!s.endsWith(".py"))throw Error("Python runtime 要求入口文件为 .py(并导出 handler 函数)");Xf(process.cwd(),e),await Yf({runtime:t,sourceRoot:process.cwd(),outdir:e});let r=Kt(e,s);if(!Dy(r))throw Error(`Python 入口文件未打包进部署产物: ${s}`);return s}var Xy,Zy;var Je=X(()=>{jf();Xy=new Set([".git",".github",".licell",".ali",".tmp-build",".pytest_cache","__pycache__","node_modules",".venv","venv","dist","coverage"]),Zy=new Set(["requirements.txt","requirements-dev.txt","pyproject.toml","poetry.lock","pipfile","pipfile.lock"])});import{relative as zy}from"path";var Zf;var Of=X(()=>{Zn();Je();Zf={name:"nodejs20",defaultEntry:"src/index.ts",unsupportedMessage:"当前地域暂不支持 runtime=nodejs20。请确认 nodejs20 在目标地域可用后重试。",async prepareBootFile(n,e){let t=await Ps(n,e);if(!t.success){let r=t.logs.map((o)=>o.message).join(`
|
|
15
15
|
`);throw Error(`构建失败:
|
|
16
|
-
${
|
|
17
|
-
`)){let s=
|
|
16
|
+
${r}`)}let s=Yt(e);if(!s)throw Error("构建完成但未发现可执行 JS 产物");return zy(e,s).replace(/\\/g,"/")},async resolveConfig(n,e){return{runtime:"nodejs20",handler:`${e.replace(/\.[^.]+$/,"").replace(/\//g,".")}.handler`}}}});import{chmodSync as na,cpSync as ea,createReadStream as ta,createWriteStream as sa,existsSync as Ur,mkdirSync as Nf,rmSync as As}from"fs";import{homedir as ra}from"os";import{join as Ee}from"path";import oa from"https";import{pipeline as ca}from"stream/promises";import{spawnSync as fa}from"child_process";import{createHash as ua}from"crypto";function ga(n){let e=Ee(n,"bin","node");if(!Ur(e))return;na(e,493)}function la(){let n=un(process.env,"NODE22_SHASUMS_URL"),e=["https://nodejs.org/dist/latest-v22.x/SHASUMS256.txt","https://cdn.npmmirror.com/binaries/node/latest-v22.x/SHASUMS256.txt"];return n?[n,...e]:e}function Mr(n,e=5){return new Promise((t,s)=>{let r=oa.get(n,{timeout:da},(o)=>{let c=o.statusCode||0,f=o.headers.location;if([301,302,307,308].includes(c)&&f&&e>0){o.resume();let u=new URL(f,n).toString();Mr(u,e-1).then(t).catch(s);return}if(c<200||c>=300){let u=`HTTP ${c} for ${n}`;o.resume(),s(Error(u));return}t(o)});r.on("timeout",()=>{r.destroy(Error(`请求超时: ${n}`))}),r.on("error",s)})}async function ya(n){let e=await Mr(n),t="";e.setEncoding("utf8");for await(let s of e)t+=s;return t}async function aa(n,e){let t=await Mr(n);await ca(t,sa(e))}function ha(n){for(let e of n.split(`
|
|
17
|
+
`)){let s=e.trim().match(/^([a-fA-F0-9]{64})\s+\*?(node-v22\.\d+\.\d+-linux-x64\.tar\.gz)$/);if(!s)continue;return{sha256:s[1].toLowerCase(),tarballName:s[2]}}return null}async function ma(){let n=la(),e=null;for(let t of n)try{let s=await ya(t),r=ha(s);if(!r){e=Error(`未在 ${t} 找到 Node 22 Linux x64 安装包`);continue}let o=t.endsWith(Jf)?t.slice(0,-Jf.length):`${t.replace(/\/+$/,"")}/`;return{tarballName:r.tarballName,downloadUrl:new URL(r.tarballName,o).toString(),sha256:r.sha256}}catch(s){e=s instanceof Error?s:Error(String(s))}throw Error(`无法获取 Node 22 运行时下载信息: ${e?.message||"未知错误"}`)}async function wa(n){let e=ua("sha256"),t=ta(n);for await(let s of t)e.update(s);return e.digest("hex")}function pa(n,e){let t=fa("tar",["-xzf",n,"-C",e],{encoding:"utf8"});if(t.status===0)return;let s=t.stderr?.trim()||t.stdout?.trim()||"tar extract failed";throw Error(`解压 Node 22 运行时失败: ${s}`)}async function $a(){Nf(Ls,{recursive:!0});let{tarballName:n,downloadUrl:e,sha256:t}=await ma(),s=n.replace(/\.tar\.gz$/,""),r=Ee(Ls,s),o=Ee(r,"bin","node");if(Ur(o))return{folderName:s,extractedDir:r};let c=Ee(Ls,n);As(r,{recursive:!0,force:!0}),await aa(e,c);let f=await wa(c);if(f!==t)throw As(c,{force:!0}),Error(`Node 22 运行时包校验失败: expected=${t}, actual=${f}`);if(pa(c,Ls),As(c,{force:!0}),!Ur(o))throw Error("Node 22 运行时下载完成但未找到可执行文件 bin/node");return{folderName:s,extractedDir:r}}async function zf(n){let e=Ee(n,".licell-runtime");Nf(e,{recursive:!0});let{folderName:t,extractedDir:s}=await $a(),r=Ee(e,t);return As(r,{recursive:!0,force:!0}),ea(s,r,{recursive:!0}),ga(r),{nodeBinaryInCode:`/code/.licell-runtime/${t}/bin/node`}}var ia,Ls,Jf="SHASUMS256.txt",da=60000;var nu=X(()=>{ia=un(process.env,"RUNTIME_CACHE_DIR")?.trim()||Ee(ra(),".licell-cli","runtimes"),Ls=Ee(ia,"node22")});import*as tu from"@alicloud/fc20230330";import{mkdirSync as ba,writeFileSync as ka}from"fs";import{dirname as _a,join as eu,relative as su}from"path";function Sa(n,e){let t=eu(n,Gr);ba(eu(n,".licell"),{recursive:!0});let s=_a(Gr),r=`./${su(s,e).replace(/\\/g,"/")}`,o=`'use strict';
|
|
18
18
|
const http = require('http');
|
|
19
|
-
const mod = require(${JSON.stringify(
|
|
19
|
+
const mod = require(${JSON.stringify(r)});
|
|
20
20
|
const handler = typeof mod.handler === 'function'
|
|
21
21
|
? mod.handler
|
|
22
22
|
: (typeof mod.default === 'function' ? mod.default : null);
|
|
@@ -94,7 +94,7 @@ function writeResult(res, result) {
|
|
|
94
94
|
res.end(JSON.stringify(result));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || ${
|
|
97
|
+
const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || ${ru});
|
|
98
98
|
const server = http.createServer(async (req, res) => {
|
|
99
99
|
try {
|
|
100
100
|
const event = await toEvent(req);
|
|
@@ -110,9 +110,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
server.listen(port, '0.0.0.0');
|
|
113
|
-
`;return
|
|
113
|
+
`;return ka(t,o),Gr}var Ca="custom.debian12",Gr=".licell/node22-bootstrap.cjs",ru=9000,ou;var cu=X(()=>{Zn();nu();Je();ou={name:"nodejs22",defaultEntry:"src/index.ts",unsupportedMessage:"当前地域暂不支持 runtime=nodejs22。请改用 nodejs20,或确认 custom.debian12 在目标地域可用后重试。",async prepareBootFile(n,e){let t=await Ps(n,e);if(!t.success){let r=t.logs.map((o)=>o.message).join(`
|
|
114
114
|
`);throw Error(`构建失败:
|
|
115
|
-
${
|
|
115
|
+
${r}`)}let s=Yt(e);if(!s)throw Error("构建完成但未发现可执行 JS 产物");return su(e,s).replace(/\\/g,"/")},async resolveConfig(n,e){let t=`${e.replace(/\.[^.]+$/,"").replace(/\//g,".")}.handler`,s=await zf(n),r=Sa(n,e).replace(/\\/g,"/");return{runtime:Ca,handler:t,customRuntimeConfig:new tu.CustomRuntimeConfig({command:[s.nodeBinaryInCode],args:[`/code/${r}`],port:ru})}}}});var fu;var uu=X(()=>{Je();fu={name:"python3.12",defaultEntry:"src/main.py",unsupportedMessage:"当前地域暂不支持 runtime=python3.12。请改用 nodejs20,或确认 python3.12 在目标地域可用后重试。",async prepareBootFile(n,e){return xs(n,e,"python3.12")},async resolveConfig(n,e){return{runtime:"python3.12",handler:`${e.replace(/\.[^.]+$/,"").replace(/\//g,".")}.handler`}}}});import{chmodSync as Ta,cpSync as Ea,createReadStream as qa,createWriteStream as Ia,existsSync as vr,mkdirSync as Kr,readdirSync as Ra,rmSync as Hs}from"fs";import{homedir as Pa}from"os";import{join as he}from"path";import Fa from"https";import{pipeline as xa}from"stream/promises";import{spawnSync as La}from"child_process";import{createHash as Aa}from"crypto";function Ga(n){let e=he(n,"python","bin");if(!vr(e))return;for(let t of Ra(e,{withFileTypes:!0})){if(!t.isFile())continue;Ta(he(e,t.name),493)}}function Yr(n,e=5,t={}){return new Promise((s,r)=>{let o=Fa.get(n,{headers:t,timeout:Ba},(c)=>{let f=c.statusCode||0,u=c.headers.location;if([301,302,307,308].includes(f)&&u&&e>0){c.resume();let g=new URL(u,n).toString();Yr(g,e-1,t).then(s).catch(r);return}if(f<200||f>=300){let g=`HTTP ${f} for ${n}`;c.resume(),r(Error(g));return}s(c)});o.on("timeout",()=>{o.destroy(Error(`请求超时: ${n}`))}),o.on("error",r)})}async function va(n,e){let t=await Yr(n);await xa(t,Ia(e))}async function Ka(n){let e=await Yr(n,5,{Accept:"application/vnd.github+json","User-Agent":"licell-cli"}),t="";e.setEncoding("utf8");for await(let s of e)t+=s;try{return JSON.parse(t)}catch{throw Error("GitHub API 返回了非 JSON 响应,请检查网络或稍后重试")}}function Ya(){let n=un(process.env,"PYTHON313_TARBALL_URL")?.trim();if(!n)return null;let e=un(process.env,"PYTHON313_SHA256")?.trim().toLowerCase();if(!e||!/^[a-f0-9]{64}$/.test(e))throw Error("设置 LICELL_PYTHON313_TARBALL_URL 时,必须同时提供 LICELL_PYTHON313_SHA256(64 位十六进制)");let t=new URL(n).pathname,s=decodeURIComponent(t.split("/").pop()||"");if(!s.endsWith(".tar.gz"))throw Error(`LICELL_PYTHON313_TARBALL_URL 必须指向 .tar.gz 文件,当前为: ${s||"(empty)"}`);return{tarballName:s,downloadUrl:n,sha256:e}}function ja(n){if(typeof n!=="object"||n===null)return null;let e=n.assets;if(!Array.isArray(e))return null;for(let t of e){if(typeof t!=="object"||t===null)continue;let s=t,r=typeof s.name==="string"?s.name:"";if(!Ma.test(r))continue;let o=typeof s.browser_download_url==="string"?s.browser_download_url:"";if(!o)continue;let f=(typeof s.digest==="string"?s.digest:"").match(/^sha256:([a-fA-F0-9]{64})$/);if(!f)continue;return{tarballName:r,downloadUrl:o,sha256:f[1].toLowerCase()}}return null}async function Da(){let n=Ya();if(n)return n;let e=un(process.env,"PYTHON313_RELEASE_API_URL")?.trim()||Ua,t=await Ka(e),s=ja(t);if(!s)throw Error(`无法在 ${e} 找到 python3.13 Linux x64 运行时包`);return s}async function Wa(n){let e=Aa("sha256"),t=qa(n);for await(let s of t)e.update(s);return e.digest("hex")}function Va(n,e){let t=La("tar",["-xzf",n,"-C",e],{encoding:"utf8"});if(t.status===0)return;let s=t.stderr?.trim()||t.stdout?.trim()||"tar extract failed";throw Error(`解压 Python 3.13 运行时失败: ${s}`)}async function Qa(){Kr(Br,{recursive:!0});let{tarballName:n,downloadUrl:e,sha256:t}=await Da(),s=n.replace(/\.tar\.gz$/,""),r=he(Br,s),o=he(r,"python","bin","python3.13");if(vr(o))return{folderName:s,extractedDir:r};let c=he(Br,n);Hs(r,{recursive:!0,force:!0}),await va(e,c);let f=await Wa(c);if(f!==t)throw Hs(c,{force:!0}),Error(`Python 3.13 运行时包校验失败: expected=${t}, actual=${f}`);if(Kr(r,{recursive:!0}),Va(c,r),Hs(c,{force:!0}),!vr(o))throw Error("Python 3.13 运行时下载完成但未找到可执行文件 python/bin/python3.13");return{folderName:s,extractedDir:r}}async function iu(n){let e=he(n,".licell-runtime");Kr(e,{recursive:!0});let{folderName:t,extractedDir:s}=await Qa(),r=he(e,t);return Hs(r,{recursive:!0,force:!0}),Ea(s,r,{recursive:!0}),Ga(r),{pythonBinaryInCode:`/code/.licell-runtime/${t}/python/bin/python3.13`}}var Ha,Br,Ua="https://api.github.com/repos/indygreg/python-build-standalone/releases/latest",Ma,Ba=60000;var gu=X(()=>{Ha=un(process.env,"RUNTIME_CACHE_DIR")?.trim()||he(Pa(),".licell-cli","runtimes"),Br=he(Ha,"python313"),Ma=/^cpython-3\.13\.\d+\+\d+-x86_64-unknown-linux-gnu-install_only_stripped\.tar\.gz$/});import*as yu from"@alicloud/fc20230330";import{mkdirSync as Xa,writeFileSync as Za}from"fs";import{join as lu}from"path";function Ja(n,e){let t=lu(n,du);Xa(lu(n,".licell"),{recursive:!0});let s=`/code/${e.replace(/\\/g,"/")}`,r=`#!/usr/bin/env python3
|
|
116
116
|
import asyncio
|
|
117
117
|
import importlib.util
|
|
118
118
|
import json
|
|
@@ -225,25 +225,25 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
225
225
|
return
|
|
226
226
|
|
|
227
227
|
if __name__ == "__main__":
|
|
228
|
-
port = int(os.getenv("FC_SERVER_PORT", os.getenv("PORT", "${
|
|
228
|
+
port = int(os.getenv("FC_SERVER_PORT", os.getenv("PORT", "${au}")))
|
|
229
229
|
server = ThreadingHTTPServer(("0.0.0.0", port), RequestHandler)
|
|
230
230
|
server.serve_forever()
|
|
231
|
-
`;return
|
|
231
|
+
`;return Za(t,r),du}var Oa="custom.debian12",du=".licell/python313-bootstrap.py",au=9000,hu;var mu=X(()=>{Je();gu();hu={name:"python3.13",defaultEntry:"src/main.py",unsupportedMessage:"当前地域暂不支持 runtime=python3.13。请改用 nodejs20,或确认 custom.debian12(含 python3.13)在目标地域可用后重试。",async prepareBootFile(n,e){return xs(n,e,"python3.13")},async resolveConfig(n,e){let t=`${e.replace(/\.[^.]+$/,"").replace(/\//g,".")}.handler`,s=await iu(n),r=Ja(n,e).replace(/\\/g,"/");return{runtime:Oa,handler:t,customRuntimeConfig:new yu.CustomRuntimeConfig({command:[s.pythonBinaryInCode],args:[`/code/${r}`],port:au})}}}});import{spawnSync as Us}from"child_process";function Ms(){if(Us("docker",["info"],{stdio:"pipe",timeout:1e4}).status!==0)throw Error(`未检测到 Docker 环境。请安装 Docker Desktop 或 Docker Engine 后重试。
|
|
232
232
|
`+` macOS: https://docs.docker.com/desktop/install/mac-install/
|
|
233
|
-
Linux: https://docs.docker.com/engine/install/`)}function
|
|
234
|
-
`+"请在 package.json 中添加 build 脚本,或通过 --entry 指定可运行的 JS 入口,或手动维护 Dockerfile。");if(t||s){let _=["FROM oven/bun:1-slim","WORKDIR /app",`COPY package.json ${t?"bun.lockb":"bun.lock"} ./`,"RUN bun install --frozen-lockfile","COPY . ."];if(i)_.push("RUN bun run build");return _.push("ENV PORT=9000","EXPOSE 9000",`CMD ["bun", "run", "${
|
|
235
|
-
`)}let
|
|
236
|
-
`)}});
|
|
237
|
-
`)}})});import{randomInt as
|
|
238
|
-
`+`请前往阿里云控制台删除不需要的命名空间,或手动创建 "${
|
|
239
|
-
`+"也可以使用已有命名空间:licell deploy --runtime docker --acr-namespace <name>");this.existing=
|
|
240
|
-
`+"请提供 --entry(例如 dist/index.js),或补充 build 脚本,或手动维护 Dockerfile。");return t?"dist/index.js":"src/index.js"}function
|
|
233
|
+
Linux: https://docs.docker.com/engine/install/`)}function wu(n,e,t){let s=["build","--platform","linux/amd64"];if(t)s.push("-f",t);s.push("-t",n,e);let r=Us("docker",s,{stdio:"inherit",timeout:600000});if(r.status!==0)throw Error(`Docker 构建失败 (exit=${r.status}),请检查 Dockerfile 和构建日志`)}function pu(n,e,t){let s=Us("docker",["login","--username",e,"--password-stdin",n],{input:t,stdio:["pipe","pipe","pipe"],timeout:30000});if(s.status!==0){let r=s.stderr?.toString().trim()||"";throw Error(`Docker 登录 ACR 失败: ${r||"未知错误"}`)}}function $u(n){let e=Us("docker",["push",n],{stdio:"inherit",timeout:600000});if(e.status!==0)throw Error(`Docker 推送失败 (exit=${e.status}),请检查网络连接和镜像仓库权限`)}var jr=()=>{};import{existsSync as On,readFileSync as Na}from"fs";import{join as Vn}from"path";function ku(n){bu.push(n)}function jt(n){for(let e of bu)if(e.detect(n))return e;return null}function _u(n,e){let t=jt(n);if(!t)throw Error("无法自动探测项目类型,请手动创建 Dockerfile");return t.generate(n,e)}function za(n){try{return JSON.parse(Na(n,"utf-8"))}catch{return null}}var bu;var Dr=X(()=>{bu=[];ku({name:"nodejs",detect(n){return On(Vn(n,"package.json"))},generate(n,e){let t=On(Vn(n,"bun.lockb")),s=On(Vn(n,"bun.lock")),r=On(Vn(n,"pnpm-lock.yaml")),o=On(Vn(n,"yarn.lock")),c=On(Vn(n,"package-lock.json"))||On(Vn(n,"npm-shrinkwrap.json")),f=za(Vn(n,"package.json")),u=On(Vn(n,"tsconfig.json")),i="build"in(f?.scripts??{}),l=e||(u?"dist/index.js":"src/index.js");if(!e&&u&&!i)throw Error(`检测到 tsconfig.json 但缺少 build 脚本,无法自动推断容器启动入口。
|
|
234
|
+
`+"请在 package.json 中添加 build 脚本,或通过 --entry 指定可运行的 JS 入口,或手动维护 Dockerfile。");if(t||s){let _=["FROM oven/bun:1-slim","WORKDIR /app",`COPY package.json ${t?"bun.lockb":"bun.lock"} ./`,"RUN bun install --frozen-lockfile","COPY . ."];if(i)_.push("RUN bun run build");return _.push("ENV PORT=9000","EXPOSE 9000",`CMD ["bun", "run", "${l}"]`,""),_.join(`
|
|
235
|
+
`)}let d=c?"npm ci":"npm install",a="COPY package*.json ./";if(r)d="corepack enable && pnpm install --frozen-lockfile",a="COPY package.json pnpm-lock.yaml ./";else if(o)d="corepack enable && yarn install --frozen-lockfile",a="COPY package.json yarn.lock ./";let p=["FROM node:22-slim","WORKDIR /app",a,`RUN ${d}`,"COPY . ."];if(i)p.push("RUN npm run build");return p.push("ENV PORT=9000","EXPOSE 9000",`CMD ["node", "${l}"]`,""),p.join(`
|
|
236
|
+
`)}});ku({name:"python",detect(n){return On(Vn(n,"requirements.txt"))||On(Vn(n,"pyproject.toml"))},generate(n,e){let t=e||"src/main.py",s=On(Vn(n,"requirements.txt")),r=On(Vn(n,"pyproject.toml")),o=["FROM python:3.13-slim","WORKDIR /app"];if(s)o.push("COPY requirements.txt ./","RUN pip install --no-cache-dir -r requirements.txt","COPY . .");else if(r)o.push("COPY . .","RUN pip install --no-cache-dir .");else o.push("COPY . .");return o.push("ENV PORT=9000","EXPOSE 9000",`CMD ["python", "${t}"]`,""),o.join(`
|
|
237
|
+
`)}})});import{randomInt as at}from"crypto";function Bn(n=20){if(n<4)throw Error("Password length must be at least 4 to include all character types");let e=[Wr[at(Wr.length)],Vr[at(Vr.length)],Qr[at(Qr.length)],Xr[at(Xr.length)]];while(e.length<n)e.push(Cu[at(Cu.length)]);for(let t=e.length-1;t>0;t-=1){let s=at(t+1);[e[t],e[s]]=[e[s],e[t]]}return e.join("")}var Wr="abcdefghjkmnpqrstuvwxyz",Vr="ABCDEFGHJKLMNPQRSTUVWXYZ",Qr="23456789",Xr="!@#$%^&*()-_=+",Cu;var ht=X(()=>{Cu=`${Wr}${Vr}${Qr}${Xr}`});import nh,*as Ie from"@alicloud/cr20181201";import*as qe from"@alicloud/openapi-client";import*as Iu from"@alicloud/tea-util";function Or(n){let e=n.trim().toLowerCase();if(!e)throw Error("ACR namespace 不能为空");if(e.length<2||e.length>Su)throw Error(`ACR namespace 长度需在 2-${Su} 之间`);if(!sh.test(e))throw Error("ACR namespace 仅支持小写字母、数字、点、短横线、下划线,且分隔符不能在首尾");return e}function Zr(n){let e=n??k.requireAuth();return new eh(new qe.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:e.region,endpoint:`cr.${e.region}.aliyuncs.com`}))}async function oh(n){let t=(await n.listInstance(new Ie.ListInstanceRequest({instanceStatus:"RUNNING",pageNo:1,pageSize:30}))).body?.instances||[];if(t.length===0)return null;let s=t[0];if(!s.instanceId||!s.instanceName)return null;return{instanceId:s.instanceId,instanceName:s.instanceName}}async function ch(n,e,t){try{await n.createNamespace(new Ie.CreateNamespaceRequest({instanceId:e,namespaceName:t,autoCreateRepo:!0,defaultRepoType:"PRIVATE"}))}catch(s){if(!tn(s)&&!Pu(s))throw s}}async function fh(n,e,t,s){try{await n.createRepository(new Ie.CreateRepositoryRequest({instanceId:e,repoNamespaceName:t,repoName:s,repoType:"PRIVATE",summary:`licell deploy: ${s}`}))}catch(r){if(!tn(r)&&!Fu(r))throw r}}function Pu(n){return me(n,"NAMESPACE_EXIST")||me(n,"NAMESPACE_ALREADY_EXIST")}function Fu(n){return me(n,"REPO_EXIST")||me(n,"REPO_ALREADY_EXIST")}function uh(n){return me(n,"USER_NOT_REGISTERED")||me(n,"USER_NOT_EXIST")}function ih(n){return me(n,"USER_ALREADY_EXIST")||me(n,"USER_EXIST")}function me(n,e){if(typeof n!=="object"||n===null)return!1;let t="code"in n?String(n.code||""):"",s="message"in n?String(n.message||""):"";return t===e||s.includes(e)}function Eu(n=Bn(20)){return{user:{password:n}}}async function mt(n,e,t,s,r){return n.doROARequest(e,rh,"HTTPS",t,"AK",s,"json",r||new qe.OpenApiRequest({}),new Iu.RuntimeOptions({}))}async function gh(n){let e=new qe.OpenApiRequest({body:Eu()});try{await mt(n,"CreateUserInfo","PUT",Tu,e);return}catch(s){if(!ih(s))throw s}let t=new qe.OpenApiRequest({body:Eu()});await mt(n,"UpdateUserInfo","POST",Tu,t)}async function qu(n){return((await mt(n,"GetNamespaceList","GET",Ru)).body?.data?.namespaces||[]).map((t)=>t.namespace||"").filter(Boolean)}async function lh(n){try{return await qu(n)}catch(e){if(!uh(e))throw e}return await gh(n),await qu(n)}async function dh(n,e){let t=await lh(n);if(t.includes(e))return;try{await mt(n,"CreateNamespace","PUT",Ru,new qe.OpenApiRequest({body:{Namespace:{Namespace:e}}}))}catch(s){if(tn(s)||Pu(s))return;if(me(s,"NAMESPACE_LIMIT_EXCEED"))throw new xu(t,e);throw s}}async function yh(n,e,t){try{await mt(n,"CreateRepo","PUT","/repos",new qe.OpenApiRequest({body:{Repo:{RepoNamespace:e,RepoName:t,RepoType:"PRIVATE",Summary:`licell deploy: ${t}`}}}))}catch(s){if(!tn(s)&&!Fu(s))throw s}}async function Lu(n,e,t,s){let r=Zr(t),o=Or(s||th),c=n,f=await oh(r);if(f){await ch(r,f.instanceId,o),await fh(r,f.instanceId,o,c);let i=`${f.instanceName}-registry.${e}.cr.aliyuncs.com`,l=`${f.instanceName}-registry-vpc.${e}.cr.aliyuncs.com`;return{instanceId:f.instanceId,registryEndpoint:i,vpcRegistryEndpoint:l,namespace:o,repoName:c}}await dh(r,o),await yh(r,o,c);let u=`registry.${e}.aliyuncs.com`,g=`registry-vpc.${e}.aliyuncs.com`;return{instanceId:null,registryEndpoint:u,vpcRegistryEndpoint:g,namespace:o,repoName:c}}async function Au(n,e){let t=e??k.requireAuth();if(n.instanceId){let f=await Zr(t).getAuthorizationToken(new Ie.GetAuthorizationTokenRequest({instanceId:n.instanceId}));if(!f.body?.tempUsername||!f.body?.authorizationToken)throw Error("获取 ACR 企业版临时凭证失败,请检查实例状态");return{endpoint:n.registryEndpoint,userName:f.body.tempUsername,password:f.body.authorizationToken}}let s=Zr(t),o=(await mt(s,"GetAuthorizationToken","GET","/tokens")).body?.data;if(!o?.tempUserName||!o?.authorizationToken)throw Error("获取 ACR 个人版临时凭证失败");return{endpoint:n.registryEndpoint,userName:o.tempUserName,password:o.authorizationToken}}function Jr(n,e,t=!1){return`${t?n.vpcRegistryEndpoint:n.registryEndpoint}/${n.namespace}/${n.repoName}:${e}`}function Hu(){let n=new Date,e=(t)=>String(t).padStart(2,"0");return`${n.getFullYear()}${e(n.getMonth()+1)}${e(n.getDate())}-${e(n.getHours())}${e(n.getMinutes())}${e(n.getSeconds())}`}var eh,th="licell",Su=120,sh,rh="2016-06-07",Tu="/users",Ru="/namespace",xu;var Nr=X(()=>{Q();xn();dn();ht();eh=nn(nh,"@alicloud/cr20181201"),sh=/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;xu=class xu extends Error{existing;constructor(n,e){super(`ACR 个人版命名空间数量已达上限(当前: ${n.join(", ")})。
|
|
238
|
+
`+`请前往阿里云控制台删除不需要的命名空间,或手动创建 "${e}"。
|
|
239
|
+
`+"也可以使用已有命名空间:licell deploy --runtime docker --acr-namespace <name>");this.existing=n}}});import*as Uu from"@alicloud/fc20230330";import{existsSync as Dt,mkdirSync as zr,readFileSync as ah,writeFileSync as no}from"fs";import{join as Jn,relative as Mu}from"path";function Wt(n){return n.replace(/\\/g,"/")}function wh(n){let e=Jn(n,"package.json");if(!Dt(e))return{};try{return JSON.parse(ah(e,"utf-8")).scripts||{}}catch{return{}}}function ph(n,e){if(e)return Wt(e);let t=Dt(Jn(n,"tsconfig.json")),s=wh(n),r=typeof s.build==="string"&&s.build.trim().length>0;if(t&&!r)throw Error(`检测到 tsconfig.json,但 package.json 未定义 build 脚本,无法推断容器入口文件。
|
|
240
|
+
`+"请提供 --entry(例如 dist/index.js),或补充 build 脚本,或手动维护 Dockerfile。");return t?"dist/index.js":"src/index.js"}function $h(n,e){if(e)return Wt(e);if(Dt(Jn(n,"src/main.py")))return"src/main.py";if(Dt(Jn(n,"main.py")))return"main.py";return"src/main.py"}function bh(n,e){let t=`${Ne}/node-bootstrap.cjs`,r=Wt(Mu(Ne,e)),o=`'use strict';
|
|
241
241
|
const http = require('http');
|
|
242
242
|
const path = require('path');
|
|
243
243
|
const { pathToFileURL } = require('url');
|
|
244
244
|
|
|
245
|
-
const ENTRY_RELATIVE = ${JSON.stringify(
|
|
246
|
-
const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || ${
|
|
245
|
+
const ENTRY_RELATIVE = ${JSON.stringify(r)};
|
|
246
|
+
const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || ${eo});
|
|
247
247
|
let handlerPromise;
|
|
248
248
|
|
|
249
249
|
async function loadHandler() {
|
|
@@ -361,7 +361,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
361
361
|
});
|
|
362
362
|
|
|
363
363
|
server.listen(port, '0.0.0.0');
|
|
364
|
-
`,c=
|
|
364
|
+
`,c=Jn(n,t);return zr(Jn(n,Ne),{recursive:!0}),no(c,o),t}function kh(n,e){let t=`${Ne}/python-bootstrap.py`,r=Wt(Mu(Ne,e)),o=`#!/usr/bin/env python3
|
|
365
365
|
import asyncio
|
|
366
366
|
import importlib.util
|
|
367
367
|
import json
|
|
@@ -370,7 +370,7 @@ import sys
|
|
|
370
370
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
371
371
|
from urllib.parse import urlparse, parse_qs
|
|
372
372
|
|
|
373
|
-
ENTRY_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), ${JSON.stringify(
|
|
373
|
+
ENTRY_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), ${JSON.stringify(r)}))
|
|
374
374
|
CODE_ROOT = "/app"
|
|
375
375
|
|
|
376
376
|
if CODE_ROOT not in sys.path:
|
|
@@ -473,31 +473,31 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
473
473
|
return
|
|
474
474
|
|
|
475
475
|
if __name__ == "__main__":
|
|
476
|
-
port = int(os.getenv("FC_SERVER_PORT", os.getenv("PORT", "${
|
|
476
|
+
port = int(os.getenv("FC_SERVER_PORT", os.getenv("PORT", "${eo}")))
|
|
477
477
|
server = ThreadingHTTPServer(("0.0.0.0", port), RequestHandler)
|
|
478
478
|
server.serve_forever()
|
|
479
|
-
`,c=
|
|
480
|
-
`+"请在项目根目录创建 Dockerfile,或确保存在 package.json / requirements.txt");let s=
|
|
481
|
-
`+`当前:memory=${
|
|
482
|
-
`+`请调整为:memory ∈ [${t}, ${s}] MB(或调整 vCPU)。`)}function aa(e,n){let t={...e||{},...n||{}},s=t.memorySize??la,o=t.timeout??ga,r=t.cpu,c=(()=>{if(r!==void 0)return Math.max(1,Math.min(100,Math.round(r*10)));if(s>=2048)return 40;if(s>=1024)return 20;return ya})();return{memorySize:s,timeout:o,...r!==void 0?{cpu:r}:{},instanceConcurrency:t.instanceConcurrency??c}}async function rr(e,n,t=Dn,s={}){let{client:o}=le(),r=k.getProject(),c="./.licell/dist";Eu("./.licell/dist",{recursive:!0,force:!0}),sa("./.licell/dist",{recursive:!0});let f=t==="docker";if(!f){let T=Tu(n),F=_u(process.cwd(),T);if(F.startsWith("..")||fa(F))throw Error(`入口文件必须在项目目录内: ${n}`);if(!ta(T))throw Error(`入口文件不存在: ${n}`);if(!ra(T).isFile())throw Error(`入口文件不是有效文件: ${n}`);Ts(T,t)}let u=f?n:_u(process.cwd(),Tu(n)).replace(/\\/g,"/"),l=await mu(u,"./.licell/dist",t),i=await pu(t,"./.licell/dist",l),g={NODE_ENV:"production"};for(let[T,F]of Object.entries(r.envs)){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(T))throw Error(`环境变量键名不合法: "${T}"(仅允许字母、数字、下划线,且不能以数字开头)`);g[T]=F}let y=s.network===void 0?r.network:s.network||void 0,d=await No(y),p;if(!i.skipCodePackaging)p={zipFile:or("./.licell/dist")};let h=aa(r.resources,s.resources),_=h.memorySize,$=h.timeout,a=h.cpu??da;ha(_,a);let w={functionName:e,runtime:i.runtime,handler:i.handler,memorySize:_,diskSize:Cu,timeout:$,environmentVariables:g,vpcConfig:d};if(w.cpu=a,h.instanceConcurrency!==void 0)w.instanceConcurrency=h.instanceConcurrency;if(p)w.code=p;if(i.customRuntimeConfig)w.customRuntimeConfig=i.customRuntimeConfig;if(i.customContainerConfig)w.customContainerConfig=i.customContainerConfig;let H=new En.CreateFunctionRequest({body:new En.CreateFunctionInput(w)});try{await B(()=>o.createFunction(H))}catch(T){if(N(T)){let F={runtime:i.runtime,handler:i.handler,memorySize:_,diskSize:Cu,timeout:$,environmentVariables:g,vpcConfig:d};if(F.cpu=a,h.instanceConcurrency!==void 0)F.instanceConcurrency=h.instanceConcurrency;if(p)F.code=p;if(i.customRuntimeConfig)F.customRuntimeConfig=i.customRuntimeConfig;if(i.customContainerConfig)F.customContainerConfig=i.customContainerConfig;try{await B(()=>o.updateFunction(e,new En.UpdateFunctionRequest({body:new En.UpdateFunctionInput(F)})))}catch(E){if(er(E))throw Error(zo(t));if(wu(E))throw Error(`当前函数运行时无法原地切换到 ${t}。请更换 appName 重新部署,或先手动删除原函数后再重试。`);throw E}}else{if(er(T))throw Error(zo(t));throw T}}return tr(e,o)}var la=512,ga=30,Cu=512,ya=10,da=0.5;var cr=O(()=>{j();fe();Cn();Xn();sr();Wn();nr();Zo()});import{existsSync as dt,readFileSync as ma,statSync as pa}from"fs";import{isAbsolute as wa,relative as $a,resolve as fr,join as Is}from"path";function ba(e){let n=(()=>{try{return kn(e).defaultEntry||""}catch{return""}})();if(n)return n;return ka[e]||""}function Su(e){return e.replace(/\\/g,"/")}function Lu(e,n){let t=$a(e,n);return t===""||!t.startsWith("..")&&!wa(t)}function _a(e){let n=Is(e,"package.json");if(!dt(n))return{};try{return JSON.parse(ma(n,"utf-8")).scripts||{}}catch{return{}}}function Ta(e,n,t,s){let o=fr(e,t);if(!Lu(e,o)){s.push({id:"entry.outside_project",level:"error",message:`入口文件必须在项目目录内: ${t}`,remediation:["将 --entry 指向当前项目目录内的文件,例如 src/index.ts 或 src/main.py"]});return}if(!dt(o)){s.push({id:"entry.not_found",level:"error",message:`入口文件不存在: ${t}`,remediation:["确认路径正确,或创建对应入口文件后重试"]});return}if(!pa(o).isFile()){s.push({id:"entry.not_file",level:"error",message:`入口路径不是有效文件: ${t}`,remediation:["请将 --entry 指向具体文件"]});return}try{Ts(o,n)}catch(r){s.push({id:"entry.runtime_contract",level:"error",message:r instanceof Error?r.message:String(r),remediation:["参考 `licell deploy spec <runtime>` 查看该 runtime 的入口与 handler 规则","修复入口文件后再执行 `licell deploy`"]})}if(n.startsWith("python")){if(!["requirements.txt","pyproject.toml","poetry.lock","pipfile","pipfile.lock"].some((c)=>dt(Is(e,c))))s.push({id:"python.dependencies_manifest_missing",level:"warning",message:"未检测到 Python 依赖清单(requirements.txt/pyproject.toml),部署时将无法自动安装第三方依赖。",remediation:["建议添加 requirements.txt 或 pyproject.toml 并声明依赖"]})}}function Ca(e,n,t,s){let o=Is(e,"Dockerfile"),r=dt(o),c=r?null:vt(e);if(!r&&!c)s.push({id:"docker.project_type_undetected",level:"error",message:"未找到 Dockerfile,且无法自动探测项目类型。",remediation:["在项目根目录创建 Dockerfile,或补充 package.json / requirements.txt","然后重新执行部署"]});if(!r&&c?.name==="nodejs"){let f=dt(Is(e,"tsconfig.json")),u=_a(e),l=typeof u.build==="string"&&u.build.trim().length>0;if(f&&!n&&!l)s.push({id:"docker.node_ts_no_build_script",level:"error",message:"检测到 TypeScript 项目,但 package.json 缺少 build 脚本,无法自动推断容器入口。",remediation:["补充 package.json scripts.build(例如 tsc)","或使用 --entry 显式指定容器运行入口(例如 dist/index.js)","或手动维护 Dockerfile"]})}if(n){let f=fr(e,n);if(!Lu(e,f))s.push({id:"docker.entry.outside_project",level:"error",message:`Docker 入口必须在项目目录内: ${n}`,remediation:["将 --entry 指向当前项目目录内的路径"]});else if(!dt(f))s.push({id:"docker.entry.not_found",level:"error",message:`Docker 入口不存在: ${n}`,remediation:["确认 --entry 对应文件存在,或移除 --entry 使用自动推断"]})}if(t)try{Ps()}catch(f){s.push({id:"docker.daemon_unavailable",level:"error",message:f instanceof Error?f.message:String(f),remediation:["启动本机 Docker 环境(Docker Desktop / Docker Engine)后重试"]})}}function Fs(){let e=new Set(On());return Ea.filter((n)=>e.has(n.runtime)).map((n)=>({...n,handlerContract:{...n.handlerContract,entryFileExtensions:[...n.handlerContract.entryFileExtensions],...n.handlerContract.requiredExports?{requiredExports:[...n.handlerContract.requiredExports]}:{},...n.handlerContract.oneOfExports?{oneOfExports:n.handlerContract.oneOfExports.map((t)=>[...t])}:{},...n.handlerContract.containerPortEnv?{containerPortEnv:[...n.handlerContract.containerPortEnv]}:{}},eventSchema:{requiredFields:[...n.eventSchema.requiredFields],optionalFields:[...n.eventSchema.optionalFields],notes:[...n.eventSchema.notes]},responseSchema:{acceptedForms:[...n.responseSchema.acceptedForms],notes:[...n.responseSchema.notes]},examples:{minimalPassExample:n.examples.minimalPassExample,commonFailExample:n.examples.commonFailExample,fixHint:[...n.examples.fixHint]},validationRules:n.validationRules.map((t)=>({...t})),notes:[...n.notes]}))}function jt(e){let n=e.trim().toLowerCase(),t=Fs().find((s)=>s.runtime===n);if(!t){let s=Fs().map((o)=>o.runtime).join(", ");throw Error(`不支持的 runtime: ${e}(支持: ${s})`)}return t}function Wt(){return{runtimes:Fs(),resources:{defaults:{...qu.defaults},constraints:{memoryToVcpuRatio:{...qu.constraints.memoryToVcpuRatio}}}}}function Qt(e){let n=e.runtime.trim().toLowerCase(),t=fr(e.projectRoot||process.cwd()),s=[],o=new Set(On());if(!o.has(n))return s.push({id:"runtime.unsupported",level:"error",message:`不支持的 runtime: ${n}`,remediation:[`请改用支持的 runtime: ${[...o].join(", ")}`]}),{ok:!1,runtime:n,entry:e.entry?Su(e.entry.trim()):"",projectRoot:t,issues:s};let r=e.entry?.trim()||ba(n),c=Su(r);if(!c&&n!=="docker")s.push({id:"entry.required",level:"error",message:`runtime=${n} 需要入口文件,请通过 --entry 指定。`,remediation:["例如: --entry src/index.ts 或 --entry src/main.py"]});if(n==="docker")Ca(t,c,Boolean(e.checkDockerDaemon),s);else if(c)Ta(t,n,c,s);return{ok:!s.some((f)=>f.level==="error"),runtime:n,entry:c,projectRoot:t,issues:s}}function ur(e){let n={runtime:e.runtime,entry:e.entry,projectRoot:e.projectRoot,issues:e.issues.map((s)=>({id:s.id,level:s.level,message:s.message,...s.remediation?{remediation:[...s.remediation]}:{}}))},t=Error("部署前预检失败(入口/运行时不满足 FC 要求)");return t.code="DEPLOY_PRECHECK_FAILED",t.details=n,t}var Ru=512,Pu=0.5,Iu=30,Fu=10,xu=1,Hu=4,ka,Ea,qu;var Au=O(()=>{jn();Wn();vo();Uo();ka={nodejs20:"src/index.ts",nodejs22:"src/index.ts","python3.12":"src/main.py","python3.13":"src/main.py"};Ea=[{runtime:"nodejs20",mode:"vendor",defaultEntry:"src/index.ts",entryRule:"入口文件需位于项目目录内,通常为 .ts/.js 文件。",handlerRule:"必须导出 handler(event, context) 函数。",handlerContract:{kind:"function",entryFileExtensions:[".ts",".js",".mjs",".cjs"],requiredExports:["handler"],signature:"handler(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["event 为 HTTP 请求归一化对象,body 默认是 UTF-8 字符串。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","Buffer/Uint8Array","plain object (自动 JSON 序列化)","undefined/null (204)"],notes:["建议显式返回 statusCode 与 headers,便于跨 runtime 一致。"]},examples:{minimalPassExample:"export async function handler(event, context) { return { statusCode: 200, body: 'ok' }; }",commonFailExample:"export default async function app(event) { return { statusCode: 200, body: 'ok' }; }",fixHint:["nodejs20 仅接受 handler 导出;把 default 导出改为 named export handler。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"必须导出 handler(event, context)。"}],notes:["部署前会校验 handler 导出。","代码会由 bun build 打包后上传。"]},{runtime:"nodejs22",mode:"custom-runtime",defaultEntry:"src/index.ts",entryRule:"入口文件需位于项目目录内,通常为 .ts/.js 文件。",handlerRule:"需导出 handler 或 default 函数。",handlerContract:{kind:"function",entryFileExtensions:[".ts",".js",".mjs",".cjs"],oneOfExports:[["handler"],["default"]],signature:"handler(event, context) 或 default(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["通过内置 node22 bootstrap 适配为 HTTP handler。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","Buffer/Uint8Array","plain object (自动 JSON 序列化)","undefined/null (204)"],notes:["如果返回 plain object,默认 content-type=application/json。"]},examples:{minimalPassExample:"export default async function app(event, context) { return { statusCode: 200, body: 'ok' }; }",commonFailExample:"export const main = async () => ({ statusCode: 200, body: 'ok' });",fixHint:["nodejs22 需要导出 handler 或 default 函数。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"必须导出 handler 或 default 函数。"}],notes:["部署前会校验导出函数。","运行在 custom.debian12 + 内置 node22。"]},{runtime:"python3.12",mode:"vendor",defaultEntry:"src/main.py",entryRule:"入口文件必须是 .py,且位于项目目录内。",handlerRule:"必须定义可调用函数 handler(event, context)。",handlerContract:{kind:"function",entryFileExtensions:[".py"],requiredExports:["handler"],signature:"handler(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["body 是字符串;需要自行 JSON 反序列化。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","bytes/bytearray","dict/list (自动 JSON 序列化)","None (204)"],notes:["推荐统一返回 statusCode/body 结构。"]},examples:{minimalPassExample:`def handler(event, context):
|
|
479
|
+
`,c=Jn(n,t);return zr(Jn(n,Ne),{recursive:!0}),no(c,o),t}function _h(n,e){let t=jt(n);if(!t)throw Error(`未找到 Dockerfile,且无法自动探测项目类型。
|
|
480
|
+
`+"请在项目根目录创建 Dockerfile,或确保存在 package.json / requirements.txt");let s=Wt(e||""),r;if(t.name==="nodejs"){let f=ph(n,s||void 0);r=bh(n,f)}else if(t.name==="python"){let f=$h(n,s||void 0);r=kh(n,f)}else throw Error(`不支持为 ${t.name} 自动生成 Dockerfile,请手动维护 Dockerfile`);let o=_u(n,r),c=Jn(n,mh);return zr(Jn(n,Ne),{recursive:!0}),no(c,o),c}var hh="custom-container",eo=9000,Ne=".licell/docker",mh=".licell/docker/Dockerfile.generated",Gu;var Bu=X(()=>{Q();jr();Dr();Nr();Gu={name:"docker",defaultEntry:"",unsupportedMessage:"当前地域暂不支持 custom-container 运行时。请确认目标地域支持自定义容器镜像后重试。",async prepareBootFile(n,e){let t=process.cwd();Ms();let s=Dt(Jn(t,"Dockerfile"))?Jn(t,"Dockerfile"):_h(t,n),r=k.requireAuth(),o=k.getProject(),c=o.appName;if(!c)throw Error("appName 未设置,请检查项目配置");let f=await Lu(c,r.region,r,o.acrNamespace),u=Hu(),g=Jr(f,u,!1);wu(g,t,s);let i=await Au(f,r);return pu(i.endpoint,i.userName,i.password),$u(g),Jr(f,u,!0)},async resolveConfig(n,e){return{runtime:hh,handler:"not-used",customContainerConfig:new Uu.CustomContainerConfig({image:e,port:eo}),skipCodePackaging:!0}}}});var to=X(()=>{Oe();Of();cu();uu();mu();Bu();yt(Zf);yt(ou);yt(fu);yt(hu);yt(Gu)});var ze="nodejs20";var so=X(()=>{to();Oe()});import Ch,*as vn from"@alicloud/vpc20160428";import Sh,*as Qt from"@alicloud/ecs20140526";import*as Xt from"@alicloud/openapi-client";function Eh(n){let e=[];for(let t of n||[]){if(typeof t!=="string")continue;let s=t.trim();if(!s||e.includes(s))continue;e.push(s)}return e}function vu(n){return n===ro||(n||"").startsWith(`${ro}-`)}function qh(n){let e=(n.status||"").toLowerCase();if(!e)return!0;return e==="available"}async function Ih(n,e,t){let s=[],r=1;while(!0){let o=await n.describeVSwitches(new vn.DescribeVSwitchesRequest({regionId:e,vpcId:t,pageNumber:r,pageSize:Th})),c=o.body.vSwitches?.vSwitch||[];if(c.length===0)break;s.push(...c);let f=o.body.totalCount||s.length;if(s.length>=f)break;if(r+=1,r>20)break}return s}function Rh(n){let e=new Set(n.map((s)=>s.cidrBlock).filter((s)=>typeof s==="string"&&s.length>0)),t=[];for(let s=1;s<255;s+=1)t.push(`10.0.${s}.0/24`);for(let s=0;s<255;s+=1)t.push(`10.1.${s}.0/24`);return t.filter((s)=>!e.has(s))}function Ph(n,e){let t=n.filter(qh),s=t.length>0?t:n;if(e.length>0){for(let o of e){let c=s.find((f)=>f.zoneId===o&&vu(f.vSwitchName));if(c?.vSwitchId)return c}for(let o of e){let c=s.find((f)=>f.zoneId===o);if(c?.vSwitchId)return c}return null}let r=s.find((o)=>vu(o.vSwitchName));if(r?.vSwitchId)return r;return s.find((o)=>typeof o.vSwitchId==="string"&&o.vSwitchId.length>0)||null}async function Fh(n,e,t){if(t.length>0)return t[0];return(await n.describeZones(new vn.DescribeZonesRequest({regionId:e}))).body.zones?.zone?.[0]?.zoneId||""}async function xh(n,e,t,s,r){let o=Rh(r);for(let c of o)try{let f=await n.createVSwitch(new vn.CreateVSwitchRequest({regionId:e,vpcId:t,zoneId:s,cidrBlock:c,vSwitchName:`${ro}-${s}`}));if(!f.body.vSwitchId)throw Error("VSwitch 创建失败:未返回 vSwitchId");return f.body.vSwitchId}catch(f){if(qr(f))continue;throw f}throw Error(`VSwitch 创建失败:在 ${s} 未找到可用网段`)}async function ju(n,e,t){let r=(await n.describeSecurityGroups(new Qt.DescribeSecurityGroupsRequest({regionId:e,vpcId:t,securityGroupName:"licell-sg"}))).body?.securityGroups?.securityGroup?.[0];if(r?.securityGroupId)return r.securityGroupId;let c=(await n.describeSecurityGroups(new Qt.DescribeSecurityGroupsRequest({regionId:e,vpcId:t}))).body?.securityGroups?.securityGroup?.[0];if(c?.securityGroupId)return c.securityGroupId;let f=await n.createSecurityGroup(new Qt.CreateSecurityGroupRequest({regionId:e,vpcId:t,securityGroupName:"licell-sg",securityGroupType:"normal"}));if(!f.body?.securityGroupId)throw Error("安全组创建失败:未返回 SecurityGroupId");return await yn(2000),f.body.securityGroupId}async function Re(n){let e=k.requireAuth(),t=e.region,s=Eh(n?.preferredZoneIds),r=new Xt.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:t,endpoint:`vpc.${t}.aliyuncs.com`}),o=new Xt.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:t,endpoint:`ecs.${t}.aliyuncs.com`}),c=new Ku(r),f=new Yu(o),u="licell-vpc",g="",i="",l="",d="",a=Vt,w=(await c.describeVpcs(new vn.DescribeVpcsRequest({regionId:t,vpcName:"licell-vpc"}))).body.vpcs?.vpc?.[0];if(w?.vpcId)g=w.vpcId,a=w.cidrBlock||Vt;else{let y=await c.createVpc(new vn.CreateVpcRequest({regionId:t,vpcName:"licell-vpc",cidrBlock:Vt}));if(!y.body.vpcId)throw Error("VPC 创建失败:未返回 vpcId");g=y.body.vpcId,a=Vt,await yn(5000)}let _=await Ih(c,t,g),$=Ph(_,s);if($?.vSwitchId&&$.zoneId)i=$.vSwitchId,d=$.zoneId;else{if(d=await Fh(c,t,s),!d)throw Error(`Region ${t} 未查询到可用区,无法创建 vSwitch`);i=await xh(c,t,g,d,_),await yn(3000)}if(l=await ju(f,t,g),!d)d=(await c.describeVSwitchAttributes(new vn.DescribeVSwitchAttributesRequest({regionId:t,vSwitchId:i}))).body?.zoneId||"";if(!d)throw Error(`无法确定 vSwitch 所在可用区 (vswId=${i})`);return{vpcId:g,vswId:i,sgId:l,cidrBlock:a,zoneId:d}}async function wt(n){let e=k.requireAuth(),t=e.region,s=n.vpcId.trim(),r=n.vswId.trim(),o=n.zoneId?.trim();if(!s||!r)throw Error("解析网络信息失败:vpcId 和 vswId 不能为空");let c=new Ku(new Xt.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:t,endpoint:`vpc.${t}.aliyuncs.com`})),f=new Yu(new Xt.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:t,endpoint:`ecs.${t}.aliyuncs.com`})),g=((await c.describeVpcs(new vn.DescribeVpcsRequest({regionId:t,vpcId:s}))).body?.vpcs?.vpc||[]).find((w)=>w.vpcId===s);if(!g?.vpcId)throw Error(`指定 VPC 不存在或不可访问: ${s}`);let i=await c.describeVSwitchAttributes(new vn.DescribeVSwitchAttributesRequest({regionId:t,vSwitchId:r})),l=i.body?.vpcId;if(typeof l==="string"&&l.length>0&&l!==s)throw Error(`指定 --vsw (${r}) 不属于 --vpc (${s})`);let d=i.body?.zoneId||"";if(o&&d&&o!==d)throw Error(`指定 --zone (${o}) 与 --vsw 实际可用区 (${d}) 不一致`);let a=d||o||"";if(!a)throw Error(`无法确定 vSwitch 所在可用区 (vswId=${r})`);let p=await ju(f,t,s);return{vpcId:s,vswId:r,sgId:p,cidrBlock:g.cidrBlock||Vt,zoneId:a}}var Ku,Yu,Vt="10.0.0.0/8",ro="licell-vsw",Th=50;var pt=X(()=>{Q();Zn();xn();Ku=nn(Ch,"@alicloud/vpc20160428"),Yu=nn(Sh,"@alicloud/ecs20140526")});async function oo(n,e=wt){if(!n)return;let t=n.vpcId?.trim(),s=n.vswId?.trim();if(!t||!s)return;let r=n.sgId?.trim();if(!r)r=(await e({vpcId:t,vswId:s})).sgId?.trim();let o={vpcId:t,vSwitchIds:[s]};if(r)o.securityGroupId=r;return o}function nt(n){let e=n.trim().toLowerCase(),t=Ze();if(t.includes(e))return e;throw Error(`函数运行时仅支持: ${t.join(", ")}`)}async function Du(n,e,t){return Te(t).prepareBootFile(n,e)}async function Wu(n,e,t){return Te(n).resolveConfig(e,t)}function co(n){try{return Te(n).unsupportedMessage}catch{return`当前地域暂不支持 runtime=${n}。请改用 nodejs20 后重试。`}}function fo(n){if(typeof n!=="object"||n===null)return!1;let e="code"in n?String(n.code||""):"",t="message"in n?String(n.message||""):"";return e==="InvalidArgument"&&t.includes("Runtime is set to an invalid value")}function Vu(n){if(typeof n!=="object"||n===null)return!1;let e="code"in n?String(n.code||""):"",t="message"in n?String(n.message||""):"";return e==="InvalidArgument"&&t.toLowerCase().includes("change of runtime")}var uo=X(()=>{pt();Oe();to()});async function D(n,e={}){let t=e.maxAttempts??Lh,s=e.baseDelayMs??Ah,r=e.shouldRetry??Rn,o;for(let c=1;c<=t;c+=1)try{return await n()}catch(f){if(o=f,c===t||!r(f))throw f;await yn(s*c*(0.5+Math.random()*0.5))}throw o}var Lh=3,Ah=1000;var Pe=X(()=>{Zn()});function hn(n){return Qe(n)}var et=X(()=>{xn()});import*as ce from"@alicloud/fc20230330";function Hh(n="anonymous"){return JSON.stringify({authType:n,disableURLInternet:!1,methods:["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]})}function Uh(n){return(n.triggerType||"").toLowerCase()==="http"}async function Xu(n,e){let t=[],s,r=50;for(let o=0;o<50;o+=1){let c=await e.listTriggers(n,new ce.ListTriggersRequest({limit:100,nextToken:s})),f=c.body?.triggers||[];if(t.push(...f),s=c.body?.nextToken,!s||f.length===0)break}return t}function Zu(n){for(let e of n){if(!Uh(e))continue;let t=e.httpTrigger?.urlInternet;if(typeof t==="string"&&t.trim().length>0)return t}return null}async function Mh(n,e,t="anonymous"){let s=Hh(t);try{await e.createTrigger(n,new ce.CreateTriggerRequest({body:new ce.CreateTriggerInput({triggerName:Qu,triggerType:"http",description:"managed by licell",triggerConfig:s})}));return}catch(r){if(!tn(r))throw r}await e.updateTrigger(n,Qu,new ce.UpdateTriggerRequest({body:new ce.UpdateTriggerInput({triggerConfig:s})}))}async function io(n,e,t={}){let s=e??hn().client,r=await Xu(n,s),o=Zu(r);if(o)return o;await Mh(n,s,t.authType);let c=await Xu(n,s),f=Zu(c);if(f)return f;throw Error("函数部署成功,但未获取到公网 HTTP Trigger URL,请检查函数触发器配置")}var Qu="licell-http";var go=X(()=>{et()});import*as Fe from"@alicloud/fc20230330";import Gh from"adm-zip";import{existsSync as Bh,mkdirSync as vh,readFileSync as Kh,rmSync as zu,statSync as Yh}from"fs";import{tmpdir as jh}from"os";import{isAbsolute as Dh,join as Wh,relative as Ou,resolve as Ju}from"path";import{spawnSync as Vh}from"child_process";function lo(n){let e=Wh(jh(),`licell-code-${Date.now()}-${process.pid}.zip`);try{let t=Vh("zip",["-q","-r","-y",e,"."],{cwd:n,encoding:"utf8"});if(t.status===0)return Kh(e).toString("base64");let s=t.stderr?.trim(),r=t.stdout?.trim();if(t.error&&t.error.code==="ENOENT"){let o=new Gh;return o.addLocalFolder(n),o.toBuffer().toString("base64")}throw Error(s||r||t.error?.message||"unknown zip error")}finally{zu(e,{force:!0})}}function Jh(n,e){if(!Number.isFinite(n)||n<=0)throw Error(`无效的 memorySize: ${String(n)}`);if(!Number.isFinite(e)||e<=0)throw Error(`无效的 vCPU: ${String(e)}`);let t=Math.ceil(e*1024),s=Math.floor(e*4096);if(n<t||n>s)throw Error(`FC 资源规格要求:memory 与 vCPU 需满足 1:1~4:1(memoryGB/vCPU ∈ [1,4])。
|
|
481
|
+
`+`当前:memory=${n}MB, vCPU=${e}
|
|
482
|
+
`+`请调整为:memory ∈ [${t}, ${s}] MB(或调整 vCPU)。`)}function Nh(n,e){let t={...n||{},...e||{}},s=t.memorySize??Qh,r=t.timeout??Xh,o=t.cpu,c=(()=>{if(o!==void 0)return Math.max(1,Math.min(100,Math.round(o*10)));if(s>=2048)return 40;if(s>=1024)return 20;return Zh})();return{memorySize:s,timeout:r,...o!==void 0?{cpu:o}:{},instanceConcurrency:t.instanceConcurrency??c}}async function yo(n,e,t=ze,s={}){let{client:r}=hn(),o=k.getProject(),c="./.licell/dist";zu("./.licell/dist",{recursive:!0,force:!0}),vh("./.licell/dist",{recursive:!0});let f=t==="docker";if(!f){let C=Ju(e),L=Ou(process.cwd(),C);if(L.startsWith("..")||Dh(L))throw Error(`入口文件必须在项目目录内: ${e}`);if(!Bh(C))throw Error(`入口文件不存在: ${e}`);if(!Yh(C).isFile())throw Error(`入口文件不是有效文件: ${e}`);Fs(C,t)}let u=f?e:Ou(process.cwd(),Ju(e)).replace(/\\/g,"/"),g=await Du(u,"./.licell/dist",t),i=await Wu(t,"./.licell/dist",g),l={NODE_ENV:"production"};for(let[C,L]of Object.entries(o.envs)){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(C))throw Error(`环境变量键名不合法: "${C}"(仅允许字母、数字、下划线,且不能以数字开头)`);l[C]=L}let d=s.network===void 0?o.network:s.network||void 0,a=await oo(d),p;if(!i.skipCodePackaging)p={zipFile:lo("./.licell/dist")};let w=Nh(o.resources,s.resources),_=w.memorySize,$=w.timeout,y=w.cpu??Oh;Jh(_,y);let h={functionName:n,runtime:i.runtime,handler:i.handler,memorySize:_,diskSize:Nu,timeout:$,environmentVariables:l,vpcConfig:a};if(h.cpu=y,w.instanceConcurrency!==void 0)h.instanceConcurrency=w.instanceConcurrency;if(p)h.code=p;if(i.customRuntimeConfig)h.customRuntimeConfig=i.customRuntimeConfig;if(i.customContainerConfig)h.customContainerConfig=i.customContainerConfig;let U=new Fe.CreateFunctionRequest({body:new Fe.CreateFunctionInput(h)});try{await D(()=>r.createFunction(U))}catch(C){if(tn(C)){let L={runtime:i.runtime,handler:i.handler,memorySize:_,diskSize:Nu,timeout:$,environmentVariables:l,vpcConfig:a};if(L.cpu=y,w.instanceConcurrency!==void 0)L.instanceConcurrency=w.instanceConcurrency;if(p)L.code=p;if(i.customRuntimeConfig)L.customRuntimeConfig=i.customRuntimeConfig;if(i.customContainerConfig)L.customContainerConfig=i.customContainerConfig;try{await D(()=>r.updateFunction(n,new Fe.UpdateFunctionRequest({body:new Fe.UpdateFunctionInput(L)})))}catch(S){if(fo(S))throw Error(co(t));if(Vu(S))throw Error(`当前函数运行时无法原地切换到 ${t}。请更换 appName 重新部署,或先手动删除原函数后再重试。`);throw S}}else{if(fo(C))throw Error(co(t));throw C}}return io(n,r)}var Qh=512,Xh=30,Nu=512,Zh=10,Oh=0.5;var ao=X(()=>{Q();dn();Pe();et();go();Je();uo();so()});import{existsSync as $t,readFileSync as zh,statSync as n0}from"fs";import{isAbsolute as e0,relative as t0,resolve as ho,join as Gs}from"path";function r0(n){let e=(()=>{try{return Te(n).defaultEntry||""}catch{return""}})();if(e)return e;return s0[n]||""}function ni(n){return n.replace(/\\/g,"/")}function ui(n,e){let t=t0(n,e);return t===""||!t.startsWith("..")&&!e0(t)}function o0(n){let e=Gs(n,"package.json");if(!$t(e))return{};try{return JSON.parse(zh(e,"utf-8")).scripts||{}}catch{return{}}}function c0(n,e,t,s){let r=ho(n,t);if(!ui(n,r)){s.push({id:"entry.outside_project",level:"error",message:`入口文件必须在项目目录内: ${t}`,remediation:["将 --entry 指向当前项目目录内的文件,例如 src/index.ts 或 src/main.py"]});return}if(!$t(r)){s.push({id:"entry.not_found",level:"error",message:`入口文件不存在: ${t}`,remediation:["确认路径正确,或创建对应入口文件后重试"]});return}if(!n0(r).isFile()){s.push({id:"entry.not_file",level:"error",message:`入口路径不是有效文件: ${t}`,remediation:["请将 --entry 指向具体文件"]});return}try{Fs(r,e)}catch(o){s.push({id:"entry.runtime_contract",level:"error",message:o instanceof Error?o.message:String(o),remediation:["参考 `licell deploy spec <runtime>` 查看该 runtime 的入口与 handler 规则","修复入口文件后再执行 `licell deploy`"]})}if(e.startsWith("python")){if(!["requirements.txt","pyproject.toml","poetry.lock","pipfile","pipfile.lock"].some((c)=>$t(Gs(n,c))))s.push({id:"python.dependencies_manifest_missing",level:"warning",message:"未检测到 Python 依赖清单(requirements.txt/pyproject.toml),部署时将无法自动安装第三方依赖。",remediation:["建议添加 requirements.txt 或 pyproject.toml 并声明依赖"]})}}function f0(n,e,t,s){let r=Gs(n,"Dockerfile"),o=$t(r),c=o?null:jt(n);if(!o&&!c)s.push({id:"docker.project_type_undetected",level:"error",message:"未找到 Dockerfile,且无法自动探测项目类型。",remediation:["在项目根目录创建 Dockerfile,或补充 package.json / requirements.txt","然后重新执行部署"]});if(!o&&c?.name==="nodejs"){let f=$t(Gs(n,"tsconfig.json")),u=o0(n),g=typeof u.build==="string"&&u.build.trim().length>0;if(f&&!e&&!g)s.push({id:"docker.node_ts_no_build_script",level:"error",message:"检测到 TypeScript 项目,但 package.json 缺少 build 脚本,无法自动推断容器入口。",remediation:["补充 package.json scripts.build(例如 tsc)","或使用 --entry 显式指定容器运行入口(例如 dist/index.js)","或手动维护 Dockerfile"]})}if(e){let f=ho(n,e);if(!ui(n,f))s.push({id:"docker.entry.outside_project",level:"error",message:`Docker 入口必须在项目目录内: ${e}`,remediation:["将 --entry 指向当前项目目录内的路径"]});else if(!$t(f))s.push({id:"docker.entry.not_found",level:"error",message:`Docker 入口不存在: ${e}`,remediation:["确认 --entry 对应文件存在,或移除 --entry 使用自动推断"]})}if(t)try{Ms()}catch(f){s.push({id:"docker.daemon_unavailable",level:"error",message:f instanceof Error?f.message:String(f),remediation:["启动本机 Docker 环境(Docker Desktop / Docker Engine)后重试"]})}}function Bs(){let n=new Set(Ze());return u0.filter((e)=>n.has(e.runtime)).map((e)=>({...e,handlerContract:{...e.handlerContract,entryFileExtensions:[...e.handlerContract.entryFileExtensions],...e.handlerContract.requiredExports?{requiredExports:[...e.handlerContract.requiredExports]}:{},...e.handlerContract.oneOfExports?{oneOfExports:e.handlerContract.oneOfExports.map((t)=>[...t])}:{},...e.handlerContract.containerPortEnv?{containerPortEnv:[...e.handlerContract.containerPortEnv]}:{}},eventSchema:{requiredFields:[...e.eventSchema.requiredFields],optionalFields:[...e.eventSchema.optionalFields],notes:[...e.eventSchema.notes]},responseSchema:{acceptedForms:[...e.responseSchema.acceptedForms],notes:[...e.responseSchema.notes]},examples:{minimalPassExample:e.examples.minimalPassExample,commonFailExample:e.examples.commonFailExample,fixHint:[...e.examples.fixHint]},validationRules:e.validationRules.map((t)=>({...t})),notes:[...e.notes]}))}function Zt(n){let e=n.trim().toLowerCase(),t=Bs().find((s)=>s.runtime===e);if(!t){let s=Bs().map((r)=>r.runtime).join(", ");throw Error(`不支持的 runtime: ${n}(支持: ${s})`)}return t}function Ot(){return{runtimes:Bs(),resources:{defaults:{...ei.defaults},constraints:{memoryToVcpuRatio:{...ei.constraints.memoryToVcpuRatio}}}}}function Jt(n){let e=n.runtime.trim().toLowerCase(),t=ho(n.projectRoot||process.cwd()),s=[],r=new Set(Ze());if(!r.has(e))return s.push({id:"runtime.unsupported",level:"error",message:`不支持的 runtime: ${e}`,remediation:[`请改用支持的 runtime: ${[...r].join(", ")}`]}),{ok:!1,runtime:e,entry:n.entry?ni(n.entry.trim()):"",projectRoot:t,issues:s};let o=n.entry?.trim()||r0(e),c=ni(o);if(!c&&e!=="docker")s.push({id:"entry.required",level:"error",message:`runtime=${e} 需要入口文件,请通过 --entry 指定。`,remediation:["例如: --entry src/index.ts 或 --entry src/main.py"]});if(e==="docker")f0(t,c,Boolean(n.checkDockerDaemon),s);else if(c)c0(t,e,c,s);return{ok:!s.some((f)=>f.level==="error"),runtime:e,entry:c,projectRoot:t,issues:s}}function mo(n){let e={runtime:n.runtime,entry:n.entry,projectRoot:n.projectRoot,issues:n.issues.map((s)=>({id:s.id,level:s.level,message:s.message,...s.remediation?{remediation:[...s.remediation]}:{}}))},t=Error("部署前预检失败(入口/运行时不满足 FC 要求)");return t.code="DEPLOY_PRECHECK_FAILED",t.details=e,t}var ti=512,si=0.5,ri=30,oi=10,ci=1,fi=4,s0,u0,ei;var ii=X(()=>{Oe();Je();Dr();jr();s0={nodejs20:"src/index.ts",nodejs22:"src/index.ts","python3.12":"src/main.py","python3.13":"src/main.py"};u0=[{runtime:"nodejs20",mode:"vendor",defaultEntry:"src/index.ts",entryRule:"入口文件需位于项目目录内,通常为 .ts/.js 文件。",handlerRule:"必须导出 handler(event, context) 函数。",handlerContract:{kind:"function",entryFileExtensions:[".ts",".js",".mjs",".cjs"],requiredExports:["handler"],signature:"handler(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["event 为 HTTP 请求归一化对象,body 默认是 UTF-8 字符串。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","Buffer/Uint8Array","plain object (自动 JSON 序列化)","undefined/null (204)"],notes:["建议显式返回 statusCode 与 headers,便于跨 runtime 一致。"]},examples:{minimalPassExample:"export async function handler(event, context) { return { statusCode: 200, body: 'ok' }; }",commonFailExample:"export default async function app(event) { return { statusCode: 200, body: 'ok' }; }",fixHint:["nodejs20 仅接受 handler 导出;把 default 导出改为 named export handler。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"必须导出 handler(event, context)。"}],notes:["部署前会校验 handler 导出。","代码会由 bun build 打包后上传。"]},{runtime:"nodejs22",mode:"custom-runtime",defaultEntry:"src/index.ts",entryRule:"入口文件需位于项目目录内,通常为 .ts/.js 文件。",handlerRule:"需导出 handler 或 default 函数。",handlerContract:{kind:"function",entryFileExtensions:[".ts",".js",".mjs",".cjs"],oneOfExports:[["handler"],["default"]],signature:"handler(event, context) 或 default(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["通过内置 node22 bootstrap 适配为 HTTP handler。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","Buffer/Uint8Array","plain object (自动 JSON 序列化)","undefined/null (204)"],notes:["如果返回 plain object,默认 content-type=application/json。"]},examples:{minimalPassExample:"export default async function app(event, context) { return { statusCode: 200, body: 'ok' }; }",commonFailExample:"export const main = async () => ({ statusCode: 200, body: 'ok' });",fixHint:["nodejs22 需要导出 handler 或 default 函数。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"必须导出 handler 或 default 函数。"}],notes:["部署前会校验导出函数。","运行在 custom.debian12 + 内置 node22。"]},{runtime:"python3.12",mode:"vendor",defaultEntry:"src/main.py",entryRule:"入口文件必须是 .py,且位于项目目录内。",handlerRule:"必须定义可调用函数 handler(event, context)。",handlerContract:{kind:"function",entryFileExtensions:[".py"],requiredExports:["handler"],signature:"handler(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["body 是字符串;需要自行 JSON 反序列化。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","bytes/bytearray","dict/list (自动 JSON 序列化)","None (204)"],notes:["推荐统一返回 statusCode/body 结构。"]},examples:{minimalPassExample:`def handler(event, context):
|
|
483
483
|
return {'statusCode': 200, 'body': 'ok'}`,commonFailExample:`def main(event, context):
|
|
484
484
|
return {'statusCode': 200, 'body': 'ok'}`,fixHint:["python runtime 必须存在可调用 handler(event, context) 函数。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"入口必须包含 handler(event, context)。"},{id:"python.dependencies_manifest_missing",level:"warning",summary:"缺少 requirements/pyproject,第三方依赖可能不会被安装。"}],notes:["部署前会校验 handler 函数签名。","会自动打包 Python 源码与依赖。"]},{runtime:"python3.13",mode:"custom-runtime",defaultEntry:"src/main.py",entryRule:"入口文件必须是 .py,且位于项目目录内。",handlerRule:"必须定义可调用函数 handler(event, context)。",handlerContract:{kind:"function",entryFileExtensions:[".py"],requiredExports:["handler"],signature:"handler(event, context)",asyncAllowed:!0},eventSchema:{requiredFields:["path","rawPath","rawQueryString","httpMethod","headers","queryParameters","body","isBase64Encoded","requestContext.http.method","requestContext.http.path","requestContext.http.sourceIp"],optionalFields:[],notes:["通过内置 python3.13 bootstrap 适配 HTTP 请求事件。"]},responseSchema:{acceptedForms:["{ statusCode, headers?, body? }","string","bytes/bytearray","dict/list (自动 JSON 序列化)","None (204)"],notes:["返回 dict/list 时会自动 JSON 编码并设置 content-type。"]},examples:{minimalPassExample:`def handler(event, context):
|
|
485
485
|
return {'statusCode': 200, 'body': 'ok'}`,commonFailExample:`def app(event, context):
|
|
486
|
-
return {'statusCode': 200, 'body': 'ok'}`,fixHint:["python3.13 也要求 handler(event, context);函数名不匹配会在预检阶段失败。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"入口必须包含 handler(event, context)。"},{id:"python.dependencies_manifest_missing",level:"warning",summary:"缺少 requirements/pyproject,第三方依赖可能不会被安装。"}],notes:["部署前会校验 handler 函数签名。","运行在 custom.debian12 + 内置 python3.13。"]},{runtime:"docker",mode:"custom-container",defaultEntry:null,entryRule:"优先使用 Dockerfile;若缺失则尝试自动生成(Node/Python 项目)。",handlerRule:"容器内需监听 FC_SERVER_PORT/PORT(默认 9000),并响应 HTTP 请求。",handlerContract:{kind:"container-http",entryFileExtensions:[],containerPortEnv:["FC_SERVER_PORT","PORT"],signature:"HTTP server listening on 0.0.0.0:${FC_SERVER_PORT|PORT|9000}"},eventSchema:{requiredFields:["HTTP request at container port 9000"],optionalFields:[],notes:["如果使用 licell 自动生成 Dockerfile,会附带 node/python bootstrap 事件模型。"]},responseSchema:{acceptedForms:["standard HTTP response from your container app"],notes:["容器模式下由应用自行处理 HTTP 协议,不要求固定 handler 函数导出。"]},examples:{minimalPassExample:"const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || 9000); app.listen(port, '0.0.0.0');",commonFailExample:"app.listen(3000, '127.0.0.1')",fixHint:["必须监听 FC_SERVER_PORT 或 PORT(默认 9000)。","绑定地址需为 0.0.0.0,不能只监听 127.0.0.1。"]},validationRules:[{id:"docker.project_type_undetected",level:"error",summary:"无 Dockerfile 且无法识别项目类型。"},{id:"docker.node_ts_no_build_script",level:"error",summary:"TS 项目缺少 build 脚本且未指定 --entry。"},{id:"docker.entry.outside_project",level:"error",summary:"Docker 入口必须在项目目录内。"},{id:"docker.entry.not_found",level:"error",summary:"Docker 入口不存在。"},{id:"docker.daemon_unavailable",level:"error",summary:"本机 Docker daemon 不可用。"}],notes:["部署依赖本机 Docker daemon。","镜像将推送到 ACR,再由 FC custom-container 拉取。"]}],
|
|
487
|
-
`)}function
|
|
488
|
-
`)),s}}
|
|
486
|
+
return {'statusCode': 200, 'body': 'ok'}`,fixHint:["python3.13 也要求 handler(event, context);函数名不匹配会在预检阶段失败。"]},validationRules:[{id:"entry.outside_project",level:"error",summary:"入口文件必须在项目目录内。"},{id:"entry.not_found",level:"error",summary:"入口文件不存在。"},{id:"entry.not_file",level:"error",summary:"入口路径必须是文件。"},{id:"entry.runtime_contract",level:"error",summary:"入口必须包含 handler(event, context)。"},{id:"python.dependencies_manifest_missing",level:"warning",summary:"缺少 requirements/pyproject,第三方依赖可能不会被安装。"}],notes:["部署前会校验 handler 函数签名。","运行在 custom.debian12 + 内置 python3.13。"]},{runtime:"docker",mode:"custom-container",defaultEntry:null,entryRule:"优先使用 Dockerfile;若缺失则尝试自动生成(Node/Python 项目)。",handlerRule:"容器内需监听 FC_SERVER_PORT/PORT(默认 9000),并响应 HTTP 请求。",handlerContract:{kind:"container-http",entryFileExtensions:[],containerPortEnv:["FC_SERVER_PORT","PORT"],signature:"HTTP server listening on 0.0.0.0:${FC_SERVER_PORT|PORT|9000}"},eventSchema:{requiredFields:["HTTP request at container port 9000"],optionalFields:[],notes:["如果使用 licell 自动生成 Dockerfile,会附带 node/python bootstrap 事件模型。"]},responseSchema:{acceptedForms:["standard HTTP response from your container app"],notes:["容器模式下由应用自行处理 HTTP 协议,不要求固定 handler 函数导出。"]},examples:{minimalPassExample:"const port = Number(process.env.FC_SERVER_PORT || process.env.PORT || 9000); app.listen(port, '0.0.0.0');",commonFailExample:"app.listen(3000, '127.0.0.1')",fixHint:["必须监听 FC_SERVER_PORT 或 PORT(默认 9000)。","绑定地址需为 0.0.0.0,不能只监听 127.0.0.1。"]},validationRules:[{id:"docker.project_type_undetected",level:"error",summary:"无 Dockerfile 且无法识别项目类型。"},{id:"docker.node_ts_no_build_script",level:"error",summary:"TS 项目缺少 build 脚本且未指定 --entry。"},{id:"docker.entry.outside_project",level:"error",summary:"Docker 入口必须在项目目录内。"},{id:"docker.entry.not_found",level:"error",summary:"Docker 入口不存在。"},{id:"docker.daemon_unavailable",level:"error",summary:"本机 Docker daemon 不可用。"}],notes:["部署依赖本机 Docker daemon。","镜像将推送到 ACR,再由 FC custom-container 拉取。"]}],ei={defaults:{memoryMb:ti,vcpu:si,timeoutSeconds:ri,instanceConcurrency:oi},constraints:{memoryToVcpuRatio:{min:ci,max:fi,expression:"memoryGb / vcpu in [1, 4]"}}}});import*as In from"@alicloud/fc20230330";import{Readable as i0}from"stream";async function bt(n,e){let{client:t}=hn();return(await t.getFunction(n,new In.GetFunctionRequest({qualifier:e}))).body?.environmentVariables||{}}function g0(n){let e=n.functionName;if(!e)return null;return{functionName:e,runtime:n.runtime,state:n.state,lastModifiedTime:n.lastModifiedTime,description:n.description}}async function wo(n=100,e){let{client:t}=hn(),s=[],r=Math.max(1,Math.min(Math.floor(n),500)),o;while(s.length<r){let c=await t.listFunctions(new In.ListFunctionsRequest({limit:Math.min(100,r-s.length),nextToken:o,prefix:e,fcVersion:"v3"})),f=c.body?.functions||[];for(let u of f){let g=g0(u);if(!g)continue;if(s.push(g),s.length>=r)break}if(o=c.body?.nextToken,!o||f.length===0)break}return s}async function po(n,e){let t=n.trim();if(!t)throw Error("functionName 不能为空");let{client:s}=hn(),o=(await s.getFunction(t,new In.GetFunctionRequest({qualifier:e}))).body;if(!o?.functionName)throw Error(`未找到函数: ${t}`);return o}async function l0(n,e){let t=[],s,r=50;for(let o=0;o<50;o+=1){let c=await e.listTriggers(n,new In.ListTriggersRequest({limit:100,nextToken:s})),f=c.body?.triggers||[];if(t.push(...f),s=c.body?.nextToken,!s||f.length===0)break}return t}async function d0(n,e){let t=[],s,r=50;for(let o=0;o<50;o+=1){let c=await e.listAliases(n,new In.ListAliasesRequest({limit:100,nextToken:s})),f=c.body?.aliases||[];if(t.push(...f),s=c.body?.nextToken,!s||f.length===0)break}return t}async function y0(n,e){let t=[],s,r=50;for(let o=0;o<50;o+=1){let c=await e.listFunctionVersions(n,new In.ListFunctionVersionsRequest({direction:"BACKWARD",limit:100,nextToken:s})),f=c.body?.versions||[];if(t.push(...f),s=c.body?.nextToken,!s||f.length===0)break}return t}async function $o(n,e={}){let t=n.trim();if(!t)throw Error("functionName 不能为空");let{client:s}=hn();if(!e.force)return await s.deleteFunction(t),{forced:!1,deletedTriggers:[],deletedAliases:[],deletedVersions:[]};let r=[],o=[],c=[],f=await l0(t,s);for(let i of f){let l=i.triggerName;if(!l)continue;try{await s.deleteTrigger(t,l),r.push(l)}catch(d){if(!an(d))throw d}}let u=await d0(t,s);for(let i of u){let l=i.aliasName;if(!l)continue;try{await s.deleteAlias(t,l),o.push(l)}catch(d){if(!an(d))throw d}}let g=await y0(t,s);for(let i of g){let l=i.versionId||"";if(!/^\d+$/.test(l))continue;try{await s.deleteFunctionVersion(t,l),c.push(l)}catch(d){if(!an(d))throw d}}return await s.deleteFunction(t),{forced:!0,deletedTriggers:r,deletedAliases:o,deletedVersions:c}}async function a0(n){if(!n)return"";let e=[];for await(let t of n)if(Buffer.isBuffer(t))e.push(t);else e.push(Buffer.from(String(t)));return Buffer.concat(e).toString("utf8")}async function bo(n,e={}){let t=n.trim();if(!t)throw Error("functionName 不能为空");let{client:s}=hn(),r=typeof e.payload==="string"?i0.from([Buffer.from(e.payload)]):void 0,o=await s.invokeFunction(t,new In.InvokeFunctionRequest({qualifier:e.qualifier,body:r})),c=await a0(o.body);return{statusCode:o.statusCode||0,headers:o.headers||{},body:c}}async function gi(n,e){let{client:t}=hn();await t.updateFunction(n,new In.UpdateFunctionRequest({body:new In.UpdateFunctionInput({environmentVariables:e})}))}async function ko(n,e,t){let r={...await bt(n),[e]:t};return await gi(n,r),r}async function _o(n,e){let t=await bt(n);if(!Object.prototype.hasOwnProperty.call(t,e))return t;let{[e]:s,...r}=t;return await gi(n,r),r}var li=X(()=>{et()});import*as Pn from"@alicloud/fc20230330";function vs(n){let e=Number(n);return Number.isFinite(e)?e:-1}async function kt(n,e=20,t){let s=t??hn().client,r=[],o,c=Math.max(e,1);while(c>0){let f=Math.min(c,100),u=await s.listFunctionVersions(n,new Pn.ListFunctionVersionsRequest({direction:"BACKWARD",limit:f,nextToken:o})),g=u.body?.versions||[];if(r.push(...g),c-=g.length,o=u.body?.nextToken,!o||g.length===0)break}return r.sort((f,u)=>vs(u.versionId||"")-vs(f.versionId||"")),r}async function xe(n,e){let{client:t}=hn(),r=(await t.publishFunctionVersion(n,new Pn.PublishFunctionVersionRequest({body:new Pn.PublishVersionInput({description:e})}))).body?.versionId;if(!r)throw Error("发布函数版本失败:未返回 versionId");return r}async function Le(n,e,t,s){let{client:r}=hn(),o=new Pn.CreateAliasInput({aliasName:e,versionId:t,description:s});try{await r.createAlias(n,new Pn.CreateAliasRequest({body:o}))}catch(c){if(!tn(c))throw c;await r.updateAlias(n,e,new Pn.UpdateAliasRequest({body:new Pn.UpdateAliasInput({versionId:t,description:s})}))}}async function h0(n,e){let t=[],s,r=50;for(let o=0;o<50;o+=1){let c=await e.listAliases(n,new Pn.ListAliasesRequest({limit:100,nextToken:s})),f=c.body?.aliases||[];if(t.push(...f),s=c.body?.nextToken,!s||f.length===0)break}return t}function m0(n){return[...new Set(n)].filter((e)=>/^\d+$/.test(e)).sort((e,t)=>vs(e)-vs(t))}async function Co(n,e,t=!1){let{client:s}=hn(),r=Number.isFinite(e)&&e>0?Math.floor(e):10,o=await kt(n,1000,s),c=await h0(n,s),f=new Set;for(let d of c){if(d.versionId)f.add(d.versionId);let a=d.additionalVersionWeight||{};for(let p of Object.keys(a))f.add(p)}let u=o.map((d)=>d.versionId||"").filter((d)=>/^\d+$/.test(d)),g=u.slice(0,r),i=u.filter((d)=>!g.includes(d)&&!f.has(d)),l={apply:t,keep:r,totalVersions:u.length,aliasProtectedVersions:m0(f),candidates:i,deleted:[],failed:[]};if(!t||i.length===0)return l;for(let d of i)try{await s.deleteFunctionVersion(n,d),l.deleted.push(d)}catch(a){let p=A(a);l.failed.push({versionId:d,reason:p})}return l}var di=X(()=>{dn();et()});var ne=X(()=>{so();Oe();uo();ao();ii();li();go();di()});function p0(n){if(typeof n==="string")return n;if(n instanceof Error)return`${n.name}: ${n.message}`;try{return JSON.stringify(n)}catch{return String(n)}}function $0(n){return n.map((t)=>p0(t)).join(" ").replace(w0,"").trim()}function b0(n){let e=n.trim();if(!e)return!0;return/^(┌|└|│|◇|◆|◼|◻|▲|▼|◉|◎)/.test(e)}function _t(n){return typeof n==="object"&&n!==null&&!Array.isArray(n)}function kn(n){if(typeof n!=="string")return;let e=n.trim();return e.length>0?e:void 0}function To(n){if(typeof n==="number"&&Number.isFinite(n))return n;if(typeof n==="string"&&n.trim().length>0){let e=Number(n);if(Number.isFinite(e))return e}return}function ai(n){let e=n.trim().toLowerCase();if(e==="text")return"text";if(e==="json")return"json";throw Error("--output 仅支持 text 或 json")}function k0(n){let e=n.slice(2),t=[];for(let s of e){if(s==="--")break;if(s.startsWith("-"))break;if(t.push(s),t.length>=3)break}if(t.length===0)return"help";return t.join(" ")}function mi(n){let e=[],t="text";for(let s=0;s<n.length;s+=1){let r=n[s];if(s<2){e.push(r);continue}if(r==="--"){e.push(...n.slice(s));break}if(r==="--output"){let o=n[s+1];if(!o||o.startsWith("-"))throw Error("--output 需要指定 text 或 json");t=ai(o),s+=1;continue}if(r.startsWith("--output=")){t=ai(r.slice(9));continue}e.push(r)}return{mode:t,argv:e}}function wi(n,e){Ln.mode=n,Ln.command=k0(e),Ln.resultEmitted=!1,Ln.errorEmitted=!1}function pi(){return Ln.mode}function m(){return Ln.mode==="json"}function Io(n){process.stdout.write(`${Eo}${JSON.stringify(n)}
|
|
487
|
+
`)}function hi(n){let e=n.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"");return e.length>0?e.toUpperCase():"UNKNOWN"}function _0(n){let e=n.match(/missing required args for command `(.+?)`/);return e?e[1]:void 0}function Ro(n){if(!_t(n))return;return kn(n.code)}function $i(n){if(!_t(n))return;if(!_t(n.details))return;return{...n.details}}function C0(n,e){let t=n.toLowerCase();if(Ro(e)==="DEPLOY_PRECHECK_FAILED")return"input";if(t.includes("unknown command")||t.includes("未知命令")||t.includes("missing required args for command")||t.includes("precheck")||t.includes("预检")||t.includes("缺少")||t.includes("不能为空")||t.includes("无效")||t.includes("不支持")||t.includes("invalid")||t.includes("unsupported"))return"input";if(t.includes("未登录")||t.includes("please login")||ae(e))return"auth";if(Gn(e))return"permission";if(tn(e))return"conflict";if(an(e))return"not_found";if(Rn(e))return"network";if(Ss(e))return"quota";return"internal"}function S0(n,e,t){let s=n.toLowerCase(),r=Ro(e);if(r==="DEPLOY_PRECHECK_FAILED")return"CLI_DEPLOY_PRECHECK_FAILED";if(s.includes("unknown command")||s.includes("未知命令"))return"CLI_UNKNOWN_COMMAND";if(s.includes("missing required args for command"))return"CLI_MISSING_REQUIRED_ARGS";if(t==="auth"){if(ae(e))return"AUTH_INVALID_CREDENTIAL";if(s.includes("未登录")||s.includes("please login"))return"AUTH_MISSING_CREDENTIAL";return"AUTH_ERROR"}if(t==="permission")return"AUTH_PERMISSION_DENIED";if(t==="conflict")return"RESOURCE_CONFLICT";if(t==="not_found")return"RESOURCE_NOT_FOUND";if(t==="network")return"PROVIDER_TRANSIENT";if(t==="quota")return"PROVIDER_QUOTA_OR_CLASS";if(t==="input")return"CLI_INVALID_INPUT";if(r){if(r.startsWith("ALI_"))return r;return`ALI_${hi(r)}`}if(_t(e)){let o=kn(e.code);if(o)return`ALI_${hi(o)}`}return"CLI_RUNTIME_ERROR"}function T0(n){if(!_t(n))return;let e=_t(n.data)?n.data:{},t=kn(n.requestId)||kn(e.RequestId)||kn(e.requestId),s=To(n.statusCode)||To(n.status)||To(e.statusCode),r=kn(n.code)||kn(e.Code),o=kn(n.endpoint)||kn(e.Endpoint)||kn(e.endpoint),c=kn(e.Action)||kn(e.action)||kn(e.Api)||kn(e.api),f=kn(n.service)||kn(e.Service)||kn(e.service);if(!Boolean(t||s!==void 0||o||c||f))return;let g={};if(f)g.service=f;if(c)g.action=c;if(r)g.code=r;if(t)g.requestId=t;if(s!==void 0)g.httpStatus=s;if(o)g.endpoint=o;return Object.keys(g).length>0?g:void 0}function E0(n,e,t){let s=[],r=_0(n),o=Ro(t);if(r)s.push({type:"fix_input",reason:"missing required args",commandTemplate:`licell ${r}`});if(o==="DEPLOY_PRECHECK_FAILED"){let c=$i(t),f=kn(c?.runtime)||"<runtime>",u=kn(c?.entry);s.push({type:"read_spec",reason:"runtime contract must be satisfied before deploy",commandTemplate:`licell deploy spec ${f}`}),s.push({type:"run_precheck",reason:"validate entry and handler contract locally",commandTemplate:u?`licell deploy check --runtime ${f} --entry ${u}`:`licell deploy check --runtime ${f}`})}if(e==="input"&&!r)s.push({type:"fix_input",reason:"invalid or missing CLI arguments",commandTemplate:`licell ${Ln.command} --help`.trim()});if(e==="auth"||e==="permission"){if(s.push({type:"repair_auth",reason:"credentials missing/invalid or insufficient permissions",commandTemplate:"licell auth repair --account-id <id> --ak <super-ak> --sk <super-sk>"}),e==="auth")s.push({type:"login",reason:"no active credential",commandTemplate:"licell login"})}if(e==="network")s.push({type:"retry",reason:"transient network/provider issue",commandTemplate:`licell ${Ln.command}`.trim()});return s}function N(n){if(!m())return;Io({schemaVersion:qo,type:"event",ts:new Date().toISOString(),command:Ln.command,...n})}function bi(){if(!m())return;if(yi)return;yi=!0;let n={log:console.log.bind(console),info:console.info.bind(console),warn:console.warn.bind(console),error:console.error.bind(console)},e=(t,s)=>{if(So)return;let r=$0(s);if(!r||b0(r))return;So=!0;try{N({stage:t==="warn"||t==="error"?"stderr":"stdout",action:t,status:"info",message:r,data:{source:"console",level:t}})}finally{So=!1}};console.log=(...t)=>{e("log",t)},console.info=(...t)=>{e("info",t)},console.warn=(...t)=>{e("warn",t)},console.error=(...t)=>{e("error",t)}}function T(n){if(!m())return;Ln.resultEmitted=!0,Io({schemaVersion:qo,type:"result",ts:new Date().toISOString(),command:Ln.command,ok:!0,...n})}function q0(n,e){let t=A(n),s=C0(t,n),r=S0(t,n,s),o=T0(n),c=s==="network",f={schemaVersion:qo,type:"error",ts:new Date().toISOString(),command:e?.command||Ln.command,stage:e?.stage||"runtime",error:{code:r,category:s,message:t,retryable:c},remediation:E0(t,s,n)};if(o)f.provider=o;let u={},g=$i(n);if(g)Object.assign(u,g);if(e?.details&&Object.keys(e.details).length>0)Object.assign(u,e.details);if(Object.keys(u).length>0)f.details=u;return f}function pn(n,e){if(!m())return;Ln.errorEmitted=!0,Io(q0(n,e))}function ki(){return Ln.resultEmitted}function _i(){return Ln.errorEmitted}function Ci(n){let e=[];for(let t of n.split(/\r?\n/)){if(!t.startsWith(Eo))continue;let s=t.slice(Eo.length);if(!s)continue;try{e.push(JSON.parse(s))}catch{}}return e}var Eo="@@LICELL_JSON@@",qo="1.0",Ln,yi=!1,So=!1,w0;var fn=X(()=>{dn();Ln={mode:"text",command:"",resultEmitted:!1,errorEmitted:!1},w0=/\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g});import{confirm as Si,intro as I0,outro as Ks,spinner as R0,isCancel as Po}from"@clack/prompts";import Nt from"picocolors";import{readFileSync as P0,writeFileSync as F0,existsSync as x0}from"fs";function K(n,e){if(Po(n))process.exit(0);if(typeof n!=="string")throw Error(`${e} 输入无效`);let t=n.trim();if(!t)throw Error(`${e} 不能为空`);return t}function W(n){if(m())return;I0(n)}function q(n){if(m())return;Ks(n)}function H(){if(!m()){let n=R0(),e=!1;return{start:(t)=>{e=!0,n.start(t)},stop:(t)=>{if(e)e=!1,n.stop(t)},message:(t)=>{if(e)n.message(t)}}}return{start:()=>{},stop:()=>{},message:()=>{}}}async function B(){let n=k.getAuth();if(!n){if(m())throw Error("未登录,请先执行 `licell login`");if(I()){Ks(Nt.red("检测到您尚未登录阿里云凭证"));let e=await Si({message:"是否现在进行登录以继续执行命令?",initialValue:!0});if(e&&!Po(e)){let{runInteractiveLogin:t}=(Vs(),Od(Fi));await t();let s=k.getAuth();if(s)return console.log(Nt.green(`继续执行原来的命令...
|
|
488
|
+
`)),s}}Ks(Nt.red("未登录,请先执行 `licell login`")),process.exit(1)}return n}function Kn(n,e="请先执行 licell deploy 部署项目"){if(!n.appName){if(m())throw Error(e);Ks(Nt.red(e)),process.exit(1)}}async function F(n,e,t,s){if(!m())n.start(e);try{return await s()}catch(r){if(m())throw r;let o=A(r).toLowerCase();if(Gn(r)||ae(r)||o.includes("未登录")||o.includes("先执行 `licell login`"))throw r;n.stop(Nt.red(t)),console.error(A(r)),process.exitCode=1;return}}function tt(n){let e=n.trim().toLowerCase().replace(/^\.+|\.+$/g,"");if(!e)throw Error("域名后缀不能为空");if(!/^[a-z0-9.-]+$/.test(e))throw Error("域名后缀仅允许小写字母、数字、点和短横线");return zn(`a.${e}`),e}function Ys(n){let e=n.trim().toLowerCase().replace(/^\.+|\.+$/g,"");if(!e)throw Error("域名不能为空");return zn(e),e}function js(n){if(typeof n!=="string")return;let e=n.trim();if(!e)return;try{return tt(e)}catch{return}}function Ct(n){if(typeof n!=="string")return;let e=n.trim();if(!e)return;try{return nt(e)}catch{return}}function Ti(){if(!x0(".gitignore"))return;let t=P0(".gitignore","utf-8");if(/^\.env$/m.test(t))return;let s=t.endsWith(`
|
|
489
489
|
`)||t.length===0?"":`
|
|
490
|
-
`;
|
|
491
|
-
`)}function
|
|
492
|
-
`+"请先执行 `licell auth repair --account-id <id> --ak <super-ak> --sk <super-sk> [--region cn-hangzhou]`,然后重试。")}function
|
|
493
|
-
检测到需要授权修复。`)),console.log(
|
|
494
|
-
`));let i=await
|
|
495
|
-
⚠️ ${
|
|
496
|
-
不会配置 RAM 权限?建议使用 bootstrap 模式自动完成最小权限配置。`)),console.log(
|
|
497
|
-
`));let
|
|
498
|
-
bootstrap 模式:正在创建 licell 专用 RAM 子用户与 AccessKey(不会保存你输入的高权限 key)...`));let
|
|
499
|
-
accountId: ${
|
|
500
|
-
`),q("Done.")}),
|
|
490
|
+
`;F0(".gitignore",`${t}${s}.env
|
|
491
|
+
`)}function I(){return Boolean(process.stdin.isTTY&&process.stdout.isTTY)}async function Qn(n,e={}){if(e.yes)return;if(!(e.interactiveTTY??I()))throw Error(`${n} 属于删除操作;非交互模式请添加 --yes 明确确认`);let s=e.confirmPrompt||(async(c)=>{let f=await Si({message:c});if(Po(f))process.exit(0);return Boolean(f)});if(!await s(`${n} 将删除云端资源,是否继续?`))throw Error("操作已取消");if(!await s(`请再次确认:继续执行${n}?`))throw Error("操作已取消")}function Ei(n){let e=n.trim().toLowerCase();if(e!=="api"&&e!=="static")throw Error("--type 仅支持 api 或 static");return e}function qi(n){let e=n.trim().toLowerCase().replace(/_/g,"-");if(e==="postgresql"||e==="postgres")return"postgres";if(e==="mysql")return"mysql";if(e==="serverless-postgresql"||e==="serverless-postgres")return"serverless-postgresql";throw Error("--type 仅支持 postgresql 或 mysql(serverless-postgresql 即将上线)")}function E(n){if(n===null||n===void 0)return;let e=String(n).trim();return e.length>0?e:void 0}function Fo(n){let e=n.trim().toLowerCase();if(!e)throw Error("region 不能为空");if(!/^[a-z0-9-]+$/.test(e))throw Error("region 格式无效");return e}function Ii(n){if(n.length<=8)return`${n.slice(0,2)}***${n.slice(-2)}`;return`${n.slice(0,4)}***${n.slice(-4)}`}function xo(n){let e=n.trim();if(!e)throw Error("环境变量名不能为空");if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw Error("环境变量名仅支持字母、数字、下划线,且不能以数字开头");return e}function Ri(n){let e=E(n)?.toLowerCase();if(!e)throw Error("--auto-pause 不能为空");if(e==="on"||e==="true"||e==="1")return!0;if(e==="off"||e==="false"||e==="0")return!1;throw Error("--auto-pause 仅支持 on/off")}function Lo(n,e){let t=E(n);if(!t)return;let s=Number(t);if(!Number.isFinite(s))throw Error(`${e} 必须是数字`);return s}function Pi(n,e){let t=E(n);if(!t)return;let s=Number(t);if(!Number.isFinite(s)||s<=0)throw Error(`${e} 必须是正数`);return s}function Fn(n,e){let t=E(n);if(!t)return;let s=Number(t);if(!Number.isFinite(s)||s<=0||!Number.isInteger(s))throw Error(`${e} 必须是正整数`);return s}function An(n,e,t=500){let s=E(n);if(!s)return e;let r=Number(s);if(!Number.isFinite(r)||r<=0)return e;return Math.min(Math.floor(r),t)}function Ds(n){if(typeof n!=="object"||n===null)return!1;let e="message"in n?String(n.message||""):"";return(("code"in n?String(n.code||""):"")==="VersionPublishError"||e.includes("VersionPublishError"))&&e.includes("No changes were made since last publish")}async function Ws(n){let t=(await kt(n,1))[0]?.versionId;if(!t)throw Error("未找到可用的已发布版本,请先执行 deploy 或 release promote 发布版本");return t}var on=X(()=>{Q();dn();vt();ne();ne();fn()});import{confirm as Ai,isCancel as Hi,password as L0,text as Ao}from"@clack/prompts";import fe from"picocolors";function U0(n){let e=`${String(n?.code||"")} ${String(n?.message||n||"")}`.toLowerCase();return e.includes("未登录")||e.includes("先执行 `licell login`")||e.includes("please login")}function Ho(n){if(U0(n))return"missing_auth";if(ae(n))return"invalid_credentials";if(Gn(n))return"access_denied";return"unknown"}function Ui(n){if(n==="missing_auth")return"未登录";if(n==="invalid_credentials")return"AK/SK 无效或已失效";if(n==="access_denied")return"AK/SK 权限不足";if(n==="manual")return"手动触发授权修复";return"未知认证错误"}function Uo(n){if(!n||n.length===0)return[];return[...new Set(n)]}function M0(n){let e=Uo(n);if(e.length===0)return[];return[...new Set(e.flatMap((t)=>H0[t]||[]))].sort()}function G0(n){let e=Uo(n);if(e.length===0)return"";return e.map((t)=>A0[t]).join(" / ")}function B0(n,e){return Ai({message:`${n} 检测到 ${Ui(e)}。是否现在进入 bootstrap 授权修复流程?`,initialValue:!0})}function v0(n,e){let t=E(n.accountId)||J(process.env,"LICELL_BOOTSTRAP_ACCOUNT_ID","LICELL_ACCOUNT_ID")||J(process.env,"ALI_ACCOUNT_ID")||e?.accountId,s=E(n.adminAk)||J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_ID","LICELL_ACCESS_KEY_ID")||J(process.env,"ALI_ACCESS_KEY_ID"),r=E(n.adminSk)||J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_SECRET","LICELL_ACCESS_KEY_SECRET")||J(process.env,"ALI_ACCESS_KEY_SECRET"),o=E(n.region)||J(process.env,"LICELL_BOOTSTRAP_REGION","LICELL_REGION")||J(process.env,"ALI_REGION")||e?.region||Wn;return{accountId:t,adminAk:s,adminSk:r,region:o}}function K0(n,e){throw Error(`${n} 检测到 ${Ui(e)},且当前为非交互模式。
|
|
492
|
+
`+"请先执行 `licell auth repair --account-id <id> --ak <super-ak> --sk <super-sk> [--region cn-hangzhou]`,然后重试。")}function Y0(n){if(Gn(n)||ae(n))return!0;let e=`${String(n?.code||"")} ${String(n?.message||n||"")}`.toLowerCase();return e.includes("forbidden")||e.includes("accessdenied")||e.includes("not authorized")||e.includes("permission")}async function j0(n,e){if(!e||n.forceRotateKey)return null;try{let t=await xr({adminAuth:{accountId:e.accountId,ak:e.ak,sk:e.sk,region:e.region},currentAuth:e,userName:n.bootstrapUser,policyName:n.bootstrapPolicy,forceRotateKey:!1});return k.setAuth({accountId:e.accountId,ak:t.accessKeyId,sk:t.accessKeySecret,region:e.region,authSource:"bootstrap",ramUser:t.userName,ramPolicy:t.policyName}),{mode:t.mode,userName:t.userName,policyName:t.policyName,accountId:e.accountId,region:e.region,rotatedKey:t.mode==="rotated-new-key"}}catch(t){if(Y0(t))return null;throw t}}async function St(n){let e=n.currentAuth??k.getAuth(),t=n.interactiveTTY??I(),s=v0(n,e),r=await j0(n,e);if(r)return r;let{accountId:o,adminAk:c,adminSk:f,region:u}=s;if(t){console.log(fe.gray(`
|
|
493
|
+
检测到需要授权修复。`)),console.log(fe.gray("不会配置 RAM 权限可使用 bootstrap:只在内存使用超级 AK/SK,不会写入本地文件。")),console.log(fe.gray(`超级 AK/SK 获取地址: https://ram.console.aliyun.com/profile/access-keys
|
|
494
|
+
`));let i=await B0(n.commandLabel,n.reason);if(Hi(i))process.exit(0);if(!i)throw Error("操作已取消。你可以稍后执行 `licell auth repair` 或 `licell login --bootstrap-ram`。")}if(!t&&(!o||!c||!f))K0(n.commandLabel,n.reason);if(!o)o=K(await Ao({message:"输入阿里云 Account ID (主账号ID):",initialValue:e?.accountId||""}),"Account ID");if(!c)c=K(await Ao({message:"输入超级 AccessKey ID:"}),"超级 AccessKey ID");if(!f)f=K(await L0({message:"输入超级 AccessKey Secret:"}),"超级 AccessKey Secret");if(!u)u=K(await Ao({message:`默认 Region (回车使用 ${Wn}):`,initialValue:Wn}),"Region");let g=await xr({adminAuth:{accountId:o,ak:c,sk:f,region:u},currentAuth:e,userName:n.bootstrapUser,policyName:n.bootstrapPolicy,forceRotateKey:Boolean(n.forceRotateKey)});return k.setAuth({accountId:o,ak:g.accessKeyId,sk:g.accessKeySecret,region:u,authSource:"bootstrap",ramUser:g.userName,ramPolicy:g.policyName}),{mode:g.mode,userName:g.userName,policyName:g.policyName,accountId:o,region:u,rotatedKey:g.mode==="rotated-new-key"}}async function Tt(n){if(k.getAuth())return;await St({commandLabel:n.commandLabel,reason:"missing_auth",interactiveTTY:n.interactiveTTY,currentAuth:null})}async function Et(n){let e=Uo(n.requiredCapabilities);if(e.length===0)return;let t=k.getAuth();if(!t)return;if(t.authSource==="bootstrap"||t.ramUser||t.ramPolicy)return;let s=G0(e),r=M0(e).slice(0,6),o=`${n.commandLabel}|${e.join(",")}`,c=n.interactiveTTY??I();if(c){if(xi.has(o))return;if(xi.add(o),console.log(fe.yellow(`
|
|
495
|
+
⚠️ ${n.commandLabel} 需要 ${s} 相关权限。`)),console.log(fe.yellow("当前凭证来自手动登录,若权限不完整会在执行阶段失败。")),r.length>0)console.log(fe.gray(`权限示例: ${r.join(", ")}`));let l=await Ai({message:"是否现在执行 bootstrap 授权修复(推荐)?",initialValue:!0});if(Hi(l))process.exit(0);if(!l)return;await St({commandLabel:n.commandLabel,reason:"manual",interactiveTTY:c,currentAuth:t,bootstrapUser:n.bootstrapUser,bootstrapPolicy:n.bootstrapPolicy});return}let f=J(process.env,"LICELL_BOOTSTRAP_ACCOUNT_ID","LICELL_ACCOUNT_ID")||J(process.env,"ALI_ACCOUNT_ID")||t.accountId,u=J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_ID","LICELL_ACCESS_KEY_ID")||J(process.env,"ALI_ACCESS_KEY_ID"),g=J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_SECRET","LICELL_ACCESS_KEY_SECRET")||J(process.env,"ALI_ACCESS_KEY_SECRET"),i=J(process.env,"LICELL_BOOTSTRAP_REGION","LICELL_REGION")||J(process.env,"ALI_REGION")||t.region||Wn;if(f&&u&&g)try{await St({commandLabel:n.commandLabel,reason:"manual",interactiveTTY:!1,currentAuth:t,accountId:f,region:i,adminAk:u,adminSk:g,bootstrapUser:n.bootstrapUser,bootstrapPolicy:n.bootstrapPolicy});return}catch(l){console.warn(fe.yellow(`⚠️ 自动授权修复未成功,继续执行原命令: ${String(l?.message||l)}`))}if(Li.has(o))return;if(Li.add(o),console.warn(fe.yellow(`⚠️ ${n.commandLabel} 可能需要 ${s} 权限。`)),r.length>0)console.warn(fe.gray(`权限示例: ${r.join(", ")}`));console.warn(fe.yellow("建议先执行 `licell auth repair --account-id <id> --ak <super-ak> --sk <super-sk>` 后再重试。"))}async function Mo(n,e){let t=Ho(n);if(t==="unknown")return!1;return await St({commandLabel:e.commandLabel,reason:t,interactiveTTY:e.interactiveTTY,bootstrapUser:e.bootstrapUser,bootstrapPolicy:e.bootstrapPolicy,forceRotateKey:t==="invalid_credentials"}),!0}async function G(n,e){let t=n.interactiveTTY??I();if(n.preflight!==!1)await Tt({commandLabel:n.commandLabel,interactiveTTY:t}),await Et({commandLabel:n.commandLabel,interactiveTTY:t,bootstrapUser:n.bootstrapUser,bootstrapPolicy:n.bootstrapPolicy,requiredCapabilities:n.requiredCapabilities});let s=!1;while(!0)try{return await e()}catch(r){if(!s){if(await Mo(r,{commandLabel:n.commandLabel,interactiveTTY:t,bootstrapUser:n.bootstrapUser,bootstrapPolicy:n.bootstrapPolicy})){s=!0;continue}}throw r}}var A0,H0,xi,Li;var Hn=X(()=>{Lr();on();Q();A0={fc:"函数计算",dns:"云解析 DNS",oss:"OSS",rds:"RDS",rdsai:"RDS AI (Supabase)",redis:"Redis/Tair",cdn:"CDN",vpc:"VPC",cr:"容器镜像仓库 CR",logs:"日志服务 SLS"},H0={fc:["fc:ListFunctions","fc:GetFunction","fc:UpdateFunction"],dns:["alidns:DescribeDomainRecords","alidns:AddDomainRecord","alidns:DeleteDomainRecord"],oss:["oss:ListBuckets","oss:GetBucketInfo","oss:PutObject"],rds:["rds:DescribeDBInstances","rds:CreateDBInstance","rds:CreateDatabase"],rdsai:["rdsai:CreateAppInstance","rdsai:DescribeAppInstances","rdsai:DeleteAppInstance"],redis:["kvstore:DescribeInstances","kvstore:CreateTairKVCacheVNode","kvstore:ResetAccountPassword"],cdn:["cdn:DescribeUserDomains","cdn:AddCdnDomain","cdn:BatchSetCdnDomainConfig"],vpc:["vpc:DescribeVpcs","vpc:CreateVpc","vpc:CreateVSwitch"],cr:["cr:ListInstance","cr:CreateNamespace","cr:CreateRepository"],logs:["log:GetLogs"]},xi=new Set,Li=new Set});var Fi={};bs(Fi,{runInteractiveLogin:()=>Xs,registerAuthCommands:()=>Go});import{text as Qs,password as D0,confirm as W0,isCancel as V0}from"@clack/prompts";import $n from"picocolors";async function Xs(n={}){let e=I(),t=E(n.accountId)||J(process.env,"LICELL_ACCOUNT_ID","ALI_ACCOUNT_ID"),s=E(n.ak)||J(process.env,"LICELL_ACCESS_KEY_ID","ALI_ACCESS_KEY_ID"),r=E(n.sk)||J(process.env,"LICELL_ACCESS_KEY_SECRET","ALI_ACCESS_KEY_SECRET"),o=E(n.region)||J(process.env,"LICELL_REGION","ALI_REGION"),c=Boolean(n.bootstrapRam);if(e&&!c&&!t&&!s&&!r){console.log($n.gray(`
|
|
496
|
+
不会配置 RAM 权限?建议使用 bootstrap 模式自动完成最小权限配置。`)),console.log($n.gray("超级 AK/SK 获取地址: https://ram.console.aliyun.com/profile/access-keys")),console.log($n.gray(`安全说明: licell 不会保存你输入的超级 key,仅保存自动创建的 licell 专用 key。
|
|
497
|
+
`));let w=await W0({message:"是否启用 bootstrap 模式自动配置 RAM 用户与专用 AccessKey?",initialValue:!0});if(V0(w))process.exit(0);c=Boolean(w)}if(!e&&(!t||!s||!r))throw Error("非交互模式下 login 需要传入 --account-id、--ak、--sk");let f=t?K(t,"Account ID"):K(await Qs({message:"输入阿里云 Account ID (主账号ID):"}),"Account ID"),u=s?K(s,"AccessKey ID"):K(await Qs({message:"输入 AccessKey ID:"}),"AccessKey ID"),g=r?K(r,"AccessKey Secret"):K(await D0({message:"输入 AccessKey Secret:"}),"AccessKey Secret"),i=!e&&!o?Wn:o?K(o,"Region").toLowerCase():K(await Qs({message:`默认 Region (回车使用 ${Wn}):`,initialValue:Wn}),"Region").toLowerCase();if(!c){if(k.setAuth({accountId:f,ak:u,sk:g,region:i,authSource:"manual"}),m())T({stage:"auth",action:"login",mode:"manual",accountId:f,region:i});else q($n.green("✅ 凭证已安全保存至 ~/.licell-cli/auth.json"));return}let l=E(n.bootstrapUser),d=E(n.bootstrapPolicy);console.log($n.gray(`
|
|
498
|
+
bootstrap 模式:正在创建 licell 专用 RAM 子用户与 AccessKey(不会保存你输入的高权限 key)...`));let a=await xf({adminAuth:{accountId:f,ak:u,sk:g,region:i},userName:l||void 0,policyName:d||void 0});k.setAuth({accountId:f,ak:a.accessKeyId,sk:a.accessKeySecret,region:i,authSource:"bootstrap",ramUser:a.userName,ramPolicy:a.policyName});let p=`${a.createdUser?"created-user":"reuse-user"}, ${a.createdPolicy?"created-policy":"reuse-policy"}`;if(m())T({stage:"auth",action:"login",mode:"bootstrap",accountId:f,region:i,ramUser:a.userName,ramPolicy:a.policyName,summary:p});else q($n.green(`✅ bootstrap 完成,已保存 licell 专用凭证到 ~/.licell-cli/auth.json (${p})`))}function Go(n){n.command("login","配置阿里云凭证").option("--account-id <id>","阿里云 Account ID(CI 场景)").option("--ak <accessKeyId>","阿里云 AccessKey ID(CI 场景)").option("--sk <accessKeySecret>","阿里云 AccessKey Secret(CI 场景)").option("--region <region>",`默认地域,默认 ${Wn}`).option("--bootstrap-ram","使用高权限 AK/SK 自动创建 licell 专用 RAM 用户与最小权限 AK/SK(仅保存新 key)").option("--bootstrap-user <name>","bootstrap 模式下 RAM 用户名,默认 licell-operator").option("--bootstrap-policy <name>","bootstrap 模式下自定义策略名,默认 LicellOperatorPolicy").action(async(e)=>{if(!m())W($n.bgBlue($n.white(" ▲ Licell CLI (AliCloud) ")));else N({stage:"auth",action:"login",status:"start"});await Xs(e)}),n.command("auth repair","修复凭证权限(推荐:用超级 AK/SK 自动补齐 licell 最小权限并继续使用)").option("--account-id <id>","阿里云 Account ID(CI 场景)").option("--ak <accessKeyId>","超级 AccessKey ID(仅用于本次修复,不会保存)").option("--sk <accessKeySecret>","超级 AccessKey Secret(仅用于本次修复,不会保存)").option("--region <region>",`默认地域,默认 ${Wn}`).option("--bootstrap-user <name>","修复目标 RAM 用户名(默认自动识别当前 key 所属用户)").option("--bootstrap-policy <name>","修复使用的自定义策略名(默认 LicellOperatorPolicy)").action(async(e)=>{if(!m())W($n.bgBlue($n.white(" ▲ Licell Auth Repair ")));else N({stage:"auth",action:"auth repair",status:"start"});let t=I(),s=E(e.accountId)||J(process.env,"LICELL_BOOTSTRAP_ACCOUNT_ID","LICELL_ACCOUNT_ID")||J(process.env,"ALI_ACCOUNT_ID"),r=E(e.ak)||J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_ID","LICELL_ACCESS_KEY_ID")||J(process.env,"ALI_ACCESS_KEY_ID"),o=E(e.sk)||J(process.env,"LICELL_BOOTSTRAP_ACCESS_KEY_SECRET","LICELL_ACCESS_KEY_SECRET")||J(process.env,"ALI_ACCESS_KEY_SECRET"),c=E(e.region)||J(process.env,"LICELL_BOOTSTRAP_REGION","LICELL_REGION")||J(process.env,"ALI_REGION"),f=E(e.bootstrapUser),u=E(e.bootstrapPolicy),g=k.getAuth(),i=await St({commandLabel:"licell auth repair",reason:"manual",interactiveTTY:t,currentAuth:g,accountId:s||g?.accountId,region:c||g?.region,adminAk:r,adminSk:o,bootstrapUser:f||void 0,bootstrapPolicy:u||void 0,forceRotateKey:!1}),l=i.rotatedKey?"rotated-key":"reuse-current-key";if(m())T({stage:"auth",action:"auth repair",mode:l,accountId:i.accountId,region:i.region,userName:i.userName,policyName:i.policyName});else q($n.green(`✅ 授权修复完成,已更新 ~/.licell-cli/auth.json (${l}, user=${i.userName}, policy=${i.policyName})`))}),n.command("logout","清除本地凭证").action(()=>{if(!k.getAuth()){if(m())T({stage:"auth",action:"logout",cleared:!1});else q($n.yellow("当前没有可清理的登录凭证"));return}if(k.clearAuth(),m())T({stage:"auth",action:"logout",cleared:!0});else q($n.green("✅ 已清除 ~/.licell-cli/auth.json"))}),n.command("whoami","查看当前登录身份").action(()=>{let e=k.getAuth();if(!e){let s=Error("未登录,请先执行 `licell login`");if(m())pn(s,{stage:"auth"});else q($n.red("未登录,请先执行 `licell login`"));process.exitCode=1;return}let t=Ii(e.ak);if(m())T({stage:"auth",action:"whoami",accountId:e.accountId,region:e.region,ak:t});else console.log(`
|
|
499
|
+
accountId: ${$n.cyan(e.accountId)}`),console.log(`region: ${$n.cyan(e.region)}`),console.log(`ak: ${$n.cyan(t)}
|
|
500
|
+
`),q("Done.")}),n.command("switch","切换默认 region").option("--region <region>","目标 region(如 cn-hangzhou)").action(async(e)=>{let t=k.getAuth();if(!t){let c=Error("未登录,请先执行 `licell login`");if(m())pn(c,{stage:"auth"});else q($n.red("未登录,请先执行 `licell login`"));process.exitCode=1;return}let s=I(),r=E(e.region);if(!r&&!s)throw Error("非交互模式下 switch 需要传入 --region");let o=r?Fo(r):Fo(K(await Qs({message:"输入新 region:",initialValue:t.region}),"region"));if(o===t.region){if(m())T({stage:"auth",action:"switch",changed:!1,region:o});else q($n.yellow(`region 未变化,仍为 ${o}`));return}if(k.setAuth({...t,region:o}),m())T({stage:"auth",action:"switch",changed:!0,region:o});else q($n.green(`✅ 默认 region 已切换为 ${o}`))})}var Vs=X(()=>{Q();Lr();Hn();on();fn()});var cc={};bs(cc,{resolvePublicIp:()=>Pp});import{text as Ep,isCancel as qp}from"@clack/prompts";async function Rp(){for(let n of Ip)try{let e=new AbortController,t=setTimeout(()=>e.abort(),5000),s=await fetch(n,{signal:e.signal});if(clearTimeout(t),!s.ok)continue;let r=(await s.text()).trim();if(Ng.test(r))return r}catch{}return null}async function Pp(){let n=await Rp();if(n)return n;if(!I())throw Error("无法自动获取公网 IP,请通过 --ip 参数手动指定");let e=await Ep({message:"无法自动获取公网 IP,请手动输入你的公网 IP 地址:",placeholder:"例如: 1.2.3.4",validate:(t)=>{if(!Ng.test(t.trim()))return"请输入有效的 IPv4 地址"}});if(qp(e))process.exit(0);return String(e).trim()}var Ip,Ng;var fc=X(()=>{on();Ip=["https://ifconfig.me/ip","https://api.ipify.org","https://checkip.amazonaws.com"],Ng=/^(\d{1,3}\.){3}\d{1,3}$/});import{cac as Fb}from"cac";import ps from"picocolors";var zd=new Set(["fn list","fn info","fn invoke","fn rm","oss list","oss info","oss ls","oss upload","oss bucket","db add","db list","db info","db connect","db public-access","db rm","cache add","cache list","cache info","cache connect","cache rotate-password","cache public-access","cache rm","e2e run","e2e cleanup","e2e list","release list","release promote","release rollback","release prune","domain add","domain rm","auth repair","dns records list","dns records add","dns records rm","env list","env set","env rm","env pull","skills init","deploy spec","deploy check","supa add","supa list","supa info","supa connect","supa config","supa whitelist","supa reset-password","supa restart","supa stop","supa start","supa rm"]);function gf(n){return typeof n==="string"&&n.startsWith("-")}function lf(n){if(n.length<4)return n;let e=n.length;for(let t=2;t<n.length;t+=1){if(!gf(n[t]))continue;e=t;break}for(let t=2;t<e;t+=1)for(let s=3;s>=2;s-=1){if(t+s>e)continue;let r=n.slice(t,t+s);if(r.some((c)=>gf(c)))continue;let o=r.join(" ");if(!zd.has(o))continue;return[...n.slice(0,t),o,...n.slice(t+s)]}return n}Vs();Q();dn();on();fn();import{select as tm,text as sm,isCancel as rm}from"@clack/prompts";import qt from"picocolors";ne();import{existsSync as Sn,mkdirSync as Q0,readFileSync as Mi,readdirSync as X0,writeFileSync as Z0}from"fs";import{basename as O0,dirname as J0,join as Tn}from"path";var Gi=new Set(["nodejs20","nodejs22"]),Bi=new Set(["python3.12","python3.13"]),N0=new Set(["docker"]),z0=new Set([".licell",".ali",".git",".DS_Store",".vscode",".idea","node_modules"]);function vi(n=process.cwd()){return O0(n).toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")||"licell-app"}function Ki(n){let e=n.trim().toLowerCase();if(!/^[a-z0-9-]+$/.test(e))throw Error("应用名仅允许小写字母、数字和短横线");if(e.length>128)throw Error("应用名长度不能超过 128 个字符");return e}function nm(n){if(Bi.has(n))return"python";if(N0.has(n))return"docker";if(Gi.has(n))return"node";throw Error(`不支持的 runtime: ${n}`)}function Bo(n){let e=n?nt(n):"nodejs20";return{template:nm(e),runtime:e}}function em(n,e){if(n==="docker")return"docker";if(n==="python"){if(e&&Bi.has(e))return e;return"python3.12"}if(e&&Gi.has(e))return e;return"nodejs20"}function Yi(n,e){let t=em(n,e);if(n==="docker")return[{path:".gitignore",content:`node_modules
|
|
501
501
|
.licell/
|
|
502
502
|
.ali/
|
|
503
503
|
.env
|
|
@@ -680,7 +680,7 @@ ENV PORT=9000
|
|
|
680
680
|
EXPOSE 9000
|
|
681
681
|
|
|
682
682
|
CMD ["bun", "run", "src/index.ts"]
|
|
683
|
-
`},{path:"README.md",content:"# Docker + Bun + Hono API\n\n通过 `licell init --runtime docker` 生成,示例风格与仓库 `examples/docker-bun-hono-api` 对齐。\n\n## 本地运行\n\n```bash\nbun install\nbun run dev\n```\n\n## 部署\n\n```bash\nlicell deploy --type api --runtime docker --target preview\n```\n"}];if(
|
|
683
|
+
`},{path:"README.md",content:"# Docker + Bun + Hono API\n\n通过 `licell init --runtime docker` 生成,示例风格与仓库 `examples/docker-bun-hono-api` 对齐。\n\n## 本地运行\n\n```bash\nbun install\nbun run dev\n```\n\n## 部署\n\n```bash\nlicell deploy --type api --runtime docker --target preview\n```\n"}];if(n==="python")return[{path:".gitignore",content:`__pycache__/
|
|
684
684
|
.venv/
|
|
685
685
|
.licell/
|
|
686
686
|
.ali/
|
|
@@ -1386,25 +1386,25 @@ bun run dev
|
|
|
1386
1386
|
\`\`\`bash
|
|
1387
1387
|
licell deploy --type api --runtime ${t} --entry src/index.ts --target preview
|
|
1388
1388
|
\`\`\`
|
|
1389
|
-
`}]}function
|
|
1389
|
+
`}]}function ji(n=process.cwd()){if(X0(n,{withFileTypes:!0}).map((r)=>r.name).filter((r)=>!z0.has(r)).length>0)return!1;let t=[".gitignore","package.json","pyproject.toml","requirements.txt","Dockerfile","bun.lock","bun.lockb","pnpm-lock.yaml","yarn.lock","package-lock.json","npm-shrinkwrap.json","tsconfig.json","src","app"];for(let r of t)if(Sn(Tn(n,r)))return!1;let s=[".env","README.md"];for(let r of s)if(Sn(Tn(n,r)))return!1;return!0}function Di(n=process.cwd()){if(Sn(Tn(n,"Dockerfile")))return{template:"docker",runtime:"docker"};if(Sn(Tn(n,"requirements.txt"))||Sn(Tn(n,"pyproject.toml"))||Sn(Tn(n,"src","main.py")))return{template:"python",runtime:"python3.12"};if(Sn(Tn(n,"package.json"))||Sn(Tn(n,"bun.lock"))||Sn(Tn(n,"bun.lockb"))||Sn(Tn(n,"pnpm-lock.yaml"))||Sn(Tn(n,"yarn.lock"))||Sn(Tn(n,"package-lock.json"))||Sn(Tn(n,"npm-shrinkwrap.json"))||Sn(Tn(n,"tsconfig.json"))||Sn(Tn(n,"src","index.ts"))||Sn(Tn(n,"src","index.js")))return{template:"node",runtime:"nodejs20"};return{template:"node",runtime:"nodejs20"}}function Wi(n,e,t=!1){let s=[],r=[],o=[];for(let c of e){let f=Tn(n,c.path);if(!Sn(f))continue;if(Mi(f,"utf-8")===c.content){o.push(c.path);continue}if(!t)s.push(c.path)}if(s.length>0)throw Error(`以下文件已存在且内容不同,请使用 --force 覆盖:
|
|
1390
1390
|
${s.join(`
|
|
1391
|
-
`)}`);for(let c of
|
|
1391
|
+
`)}`);for(let c of e){let f=Tn(n,c.path);if(Sn(f)&&Mi(f,"utf-8")===c.content)continue;Q0(J0(f),{recursive:!0}),Z0(f,c.content),r.push(c.path)}return{written:r,skipped:o}}function Vi(n){n.command("init","初始化 FC 项目(空目录生成脚手架,已有项目写入 licell 配置)").option("--runtime <runtime>","默认 runtime:nodejs20/nodejs22/python3.12/python3.13/docker").option("--app <name>","应用名(用于 FC functionName)").option("--force","在已有项目目录生成/覆盖脚手架文件").option("--yes","使用默认值,不进入交互").action(async(e)=>{if(!m())W(qt.bgBlue(qt.white(" ⚡ Licell Project Init ")));else N({stage:"init",action:"init",status:"start"});let t=I(),s=e.yes||!t,r=ji(process.cwd()),o=typeof e.runtime==="string"&&e.runtime.trim().length>0,c=r&&!o&&!s;try{let f=e.runtime;if(!f&&c){let y=await tm({message:"选择默认 runtime:",options:[{value:"nodejs20",label:"nodejs20 (Node TypeScript)"},{value:"nodejs22",label:"nodejs22 (Node 22 Custom Runtime)"},{value:"python3.12",label:"python3.12 (Python Built-in Runtime)"},{value:"python3.13",label:"python3.13 (Python 3.13 Custom Runtime)"},{value:"docker",label:"docker (Bun + TypeScript + Hono)"}]});if(rm(y)){if(m())throw Error("操作已取消");process.exit(0)}f=String(y)}let u=f?Bo(f):r?Bo():Di(process.cwd()),{template:g,runtime:i}=u,l=r||!r&&o&&Boolean(e.force),d=k.getProject(),a=e.app||d.appName||vi();if(!e.app&&!s)a=K(await sm({message:"应用名(用于 FC functionName):",initialValue:a}),"应用名");let p=Ki(a),w=H();w.start(l?"正在生成项目脚手架...":"正在写入 licell 项目配置...");let{written:_,skipped:$}=l?Wi(process.cwd(),Yi(g,i),Boolean(e.force)):{written:[],skipped:[]};if(k.setProject({appName:p,runtime:i}),w.stop(qt.green(l?"✅ 脚手架创建完成":"✅ 配置写入完成")),console.log(`runtime: ${qt.cyan(i)}`),console.log(`appName: ${qt.cyan(p)}`),console.log(`mode: ${qt.cyan(l?"scaffold+config":"config-only")}`),!l)console.log(`
|
|
1392
1392
|
检测到当前目录已有项目文件,已跳过脚手架生成。`),console.log("如需在已有目录生成脚手架,请显式指定 --runtime <runtime> --force。");if(_.length>0){console.log(`
|
|
1393
|
-
已写入文件:`);for(let
|
|
1394
|
-
已跳过(内容相同):`);for(let
|
|
1395
|
-
下一步可直接执行:`),console.log(`- licell deploy --type api --runtime ${i} --target preview`),m())S({stage:"init",runtime:i,appName:p,mode:g?"scaffold+config":"config-only",writtenFiles:_,skippedFiles:$});else q("Done.")}catch(f){if(m())he(f,{stage:"init"});else console.error(P(f));process.exitCode=1}})}j();Xe();fe();import Z from"picocolors";import{spawnSync as _m}from"child_process";function xr(e,n){if(!n||!n.trim())return;let t=_m("sh",["-c",n.trim()],{stdio:"inherit",env:process.env});if(t.status!==0)throw Error(`${e} hook 执行失败(exit=${t.status??"unknown"}): ${n.trim()}`)}function $i(e){if(!e.deploySucceeded)return{};let n={};if(e.cliDomainSuffix&&e.cliDomainSuffix!==e.projectDomainSuffix)n.domainSuffix=e.cliDomainSuffix;if(e.cliRuntime&&e.cliRuntime!==e.projectRuntime)n.runtime=e.cliRuntime;return n}Xe();var Tm=new Set(["static","statis","oss","static-site"]);function Cm(e){if(e===null||e===void 0)return;let n=String(e).trim();return n.length>0?n:void 0}function Bs(e){let n=Cm(e);if(!n)return{};let t=n.toLowerCase();if(Tm.has(t))return{deployTypeHint:"static"};return{deployTypeHint:"api",runtime:Vn(n)}}Ue();oe();ce();j();import{select as Em,text as Sm,isCancel as qm}from"@clack/prompts";function ki(e){return e.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\$/g,"\\$").replace(/`/g,"\\`").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function Rn(e){try{let n=new URL(e);if(n.password)n.password="******";return n.toString()}catch{return e.replace(/:([^@/:]+)@/g,":******@")}}function sn(e){if(typeof e!=="string")return"prod";let n=e.trim().toLowerCase();if(!n)return"prod";if(!/^[a-z0-9-]+$/.test(n))throw Error("发布目标仅允许小写字母、数字和短横线");return n}Wo();oe();ce();function bi(e,n,t,s){return Boolean(e||n||t||s)}async function _i(e){let n=await Y(),t=R(),s=k.getProject();if(!s.appName){if(!t)throw Error("缺少应用名,请先配置 .licell/project.json 的 appName,或在交互终端执行 deploy 初始化");let ue=v(await Sm({message:"为你的应用起个名字 (小写英文):",placeholder:"my-awesome-app"}),"应用名");if(!/^[a-z0-9-]+$/.test(ue))throw Error("应用名仅允许小写字母、数字和短横线");if(ue.length>128)throw Error("应用名长度不能超过 128 个字符");k.setProject({appName:ue}),s=k.getProject()}let o=e.domain?Ls(e.domain):void 0,r=e.domainSuffix?Zn(e.domainSuffix):void 0,c=As(s.domainSuffix),f=As(re(process.env,"DOMAIN_SUFFIX")),u=As(k.getGlobalConfig().domainSuffix),l=Bs(e.runtime),i=l.runtime,g=pt(s.runtime),y=pt(re(process.env,"FC_RUNTIME")),d=e.acrNamespace?Oo(e.acrNamespace):void 0,p=e.type?Nu(e.type):void 0,h;if(p&&l.deployTypeHint&&p!==l.deployTypeHint)throw Error(`--type ${p} 与 --runtime ${e.runtime} 冲突`);if(p)h=p;else if(l.deployTypeHint==="api")h="api";else if(l.deployTypeHint==="static")h="static";else if(t){let ue=await Em({message:"选择部署环境:",options:[{value:"api",label:"\uD83D\uDE80 API 服务 (Node/Python/Docker -> FC 3.0)"},{value:"static",label:"\uD83D\uDCE6 前端静态网站 (直推 OSS 托管)"}]});if(qm(ue)){if(m())throw Error("操作已取消");process.exit(0)}if(ue!=="api"&&ue!=="static")throw Error("未知部署类型");h=ue}else h="api";let _=o?void 0:h==="static"?r:r||c||f||u,$=e.target?sn(e.target):void 0,a=h==="static"&&Boolean(o||_),w=h==="static"?Boolean(e.enableCdn||a):Boolean(e.enableCdn),H=h==="static"?Boolean(e.ssl||a||w):bi(e.ssl,o,w,_),T=Boolean(e.sslForceRenew);if(o&&r)throw Error("--domain 与 --domain-suffix 不能同时使用");if($&&h!=="api")throw Error("--target 仅适用于 API 部署");if(e.enableVpc&&e.disableVpc)throw Error("--enable-vpc 与 --disable-vpc 不能同时使用");if(h!=="api"&&e.enableVpc)throw Error("--enable-vpc 仅适用于 API 部署");if(h!=="api"&&e.disableVpc)throw Error("--disable-vpc 仅适用于 API 部署");if(w&&!o&&!_)throw Error("--enable-cdn 需要域名,请提供 --domain(完整域名)或 --domain-suffix");if(h!=="api"&&i)throw Error("--runtime 的 API 运行时仅适用于 API 部署;静态站请使用 --runtime static");if(h!=="api"&&d)throw Error("--acr-namespace 仅适用于 API Docker 部署");if(T&&!H)throw Error("--ssl-force-renew 需要启用 HTTPS(请使用 --domain 或 --ssl)");if(H&&!o&&!_)throw Error("--ssl 需要域名,请提供 --domain(完整域名)或 --domain-suffix");let F=Boolean(e.preview);if(F&&$)throw Error("--preview 与 --target 不能同时使用");if(F&&!_)throw Error("--preview 需要域名后缀,请先执行 licell deploy --domain-suffix your-domain.com 或 licell config domain your-domain.com");if(F&&o)throw Error("--preview 与 --domain 不能同时使用,preview 会自动生成预览域名");let E=s.appName;if(!E)throw Error("appName 未设置,请检查项目配置");let b=qe(e.memory,"--memory"),x=ti(e.vcpu,"--vcpu"),I=qe(e.instanceConcurrency,"--instance-concurrency"),J=qe(e.timeout,"--timeout"),Ce=b!==void 0||J!==void 0||x!==void 0||I!==void 0?{...b!==void 0?{memorySize:b}:{},...x!==void 0?{cpu:x}:{},...I!==void 0?{instanceConcurrency:I}:{},...J!==void 0?{timeout:J}:{}}:void 0,fn=h==="api"?!Boolean(e.disableVpc):!1;return{appName:E,type:h,releaseTarget:$,cliDomain:o,domainSuffix:_,enableCdn:w,useVpc:fn,enableSSL:H,forceSslRenew:T,preview:F,cliResources:Ce,cliAcrNamespace:d,interactiveTTY:t,auth:n,project:s,cliDomainSuffix:r,projectDomainSuffix:c,cliRuntime:i,projectRuntime:g,envRuntime:y,cliEntry:e.entry,cliDist:e.dist}}j();jn();Ot();Xe();import{confirm as Yi,text as R0,isCancel as Bi}from"@clack/prompts";import{existsSync as P0}from"fs";import Ki from"picocolors";j();Lt();Cn();Fe();import Rm,*as Pe from"@alicloud/alidns20150109";import*as Pn from"@alicloud/fc20230330";import*as Ti from"@alicloud/openapi-client";var Pm=se(Rm,"@alicloud/alidns20150109");function Jn(e){return e.toLowerCase().replace(/^https?:\/\//,"").replace(/\.$/,"")}function Ci(e,n){let t=e.trim().toLowerCase(),s=n.trim().toLowerCase();if(!t)return s?[s]:[];let r=[s==="@"||!s?t:`${s}.${t}`,s];return[...new Set(r.filter((c)=>c.length>0))]}function _t(){let e=k.requireAuth();return new Pm(new Ti.Config({accessKeyId:e.ak,accessKeySecret:e.sk,endpoint:"alidns.aliyuncs.com"}))}function Ei(){return Kn().client}async function Hr(e,n,t){let s=[],o=Ci(n,t);for(let r of o)try{let c=await B(()=>e.describeSubDomainRecords(new Pe.DescribeSubDomainRecordsRequest({domainName:n,subDomain:r,type:"CNAME",pageNumber:1,pageSize:100})));s.push(...c.body?.domainRecords?.record||[])}catch(c){if(ps(c))continue;throw c}return s.find((r)=>{let c=r;return(c.RR||"@")===t&&(c.type||"").toUpperCase()==="CNAME"})}async function Im(e,n,t,s,o){let r=Jn(o),c=await Hr(e,t,s);if(!c?.recordId)try{await B(()=>e.addDomainRecord(new Pe.AddDomainRecordRequest({domainName:t,RR:s,type:"CNAME",value:r})));return}catch(u){if(!N(u))throw u;c=await Hr(e,t,s)}if(!c?.recordId)throw Error(`DNS 记录已存在但无法定位可更新记录: ${n}`);if(Jn(c.value||"")===r)return;await B(()=>e.updateDomainRecord(new Pe.UpdateDomainRecordRequest({recordId:c.recordId,RR:s,type:"CNAME",value:r})))}async function Lr(e,n){let t=e.trim().toLowerCase();if(!t)throw Error("域名不能为空");let{rootDomain:s,subDomain:o}=De(t),r=_t();await Im(r,t,s,o,n)}async function Tt(e,n,t,s={}){let o=k.getProject();if(!o.appName)throw Error("未找到应用名,请先执行 licell deploy");if(!s.skipDnsBind)await Lr(e,n);let r=Ei(),c={routes:[{path:"/*",functionName:o.appName,qualifier:t}]},f=new Pn.CreateCustomDomainInput({domainName:e,protocol:"HTTP",routeConfig:c});try{await B(()=>r.createCustomDomain(new Pn.CreateCustomDomainRequest({body:f})))}catch(u){if(!N(u))throw u;await B(()=>r.updateCustomDomain(e,new Pn.UpdateCustomDomainRequest({body:new Pn.UpdateCustomDomainInput({routeConfig:c})})))}return`http://${e}`}async function Si(e){let n=e.trim().toLowerCase();if(!n)throw Error("域名不能为空");let{rootDomain:t,subDomain:s}=De(n),o=_t(),r=Ei();try{await B(()=>r.deleteCustomDomain(n))}catch(u){if(!ie(u))throw u}let c=new Set,f=Ci(t,s);for(let u of f){let l=[];try{l=(await B(()=>o.describeSubDomainRecords(new Pe.DescribeSubDomainRecordsRequest({domainName:t,subDomain:u,type:"CNAME",pageNumber:1,pageSize:200})))).body?.domainRecords?.record||[]}catch(i){if(ps(i))continue;throw i}for(let i of l){let g=i;if((g.RR||"@")!==s||(g.type||"").toUpperCase()!=="CNAME")continue;let y=g.recordId;if(!y||c.has(y))continue;c.add(y);try{await B(()=>o.deleteDomainRecord(new Pe.DeleteDomainRecordRequest({recordId:y})))}catch(d){if(!ie(d))throw d}}}}async function qi(e,n=200){let t=e.trim().toLowerCase();if(!t)throw Error("域名不能为空");let s=_t(),o=[],r=Math.max(1,Math.min(Math.floor(n),1000)),c=Math.min(100,r);for(let f=1;f<=20&&o.length<r;f+=1){let u=await B(()=>s.describeDomainRecords(new Pe.DescribeDomainRecordsRequest({domainName:t,pageNumber:f,pageSize:c}))),l=u.body?.domainRecords?.record||[];for(let g of l){let y=g.recordId;if(!y)continue;if(o.push({recordId:y,rr:g.RR||"@",type:g.type||"",value:g.value||"",ttl:g.TTL,line:g.line,status:g.status}),o.length>=r)break}let i=u.body?.totalCount||0;if(l.length===0||i>0&&o.length>=i)break}return o}async function Ri(e,n){let t=e.trim().toLowerCase();if(!t)throw Error("域名不能为空");let s=n.rr.trim(),o=n.type.trim().toUpperCase(),r=n.value.trim();if(!s)throw Error("RR 不能为空");if(!o)throw Error("记录类型不能为空");if(!r)throw Error("记录值不能为空");let c=_t(),u=(await B(()=>c.addDomainRecord(new Pe.AddDomainRecordRequest({domainName:t,RR:s,type:o,value:r,TTL:n.ttl,line:n.line||"default"})))).body?.recordId;if(!u)throw Error("添加 DNS 记录失败:未返回 recordId");return u}async function Pi(e){let n=e.trim();if(!n)throw Error("recordId 不能为空");let t=_t();await B(()=>t.deleteDomainRecord(new Pe.DeleteDomainRecordRequest({recordId:n})))}async function Ks(e,n,t){let s=e.trim().toLowerCase(),o=Jn(n),{rootDomain:r,subDomain:c}=De(s),f=c?`*.${c}`:"*",u=c?`*.${c}.${r}`:`*.${r}`,l=_t(),i=await Hr(l,r,f);if(i?.recordId){if(Jn(i.value||"")!==o)await B(()=>l.updateDomainRecord(new Pe.UpdateDomainRecordRequest({recordId:i.recordId,RR:f,type:"CNAME",value:o})));return{created:!1,skipped:!1,wildcardDomain:u,targetValue:o}}if(!t.skipConfirm){if(!t.interactiveTTY)return{created:!1,skipped:!0,wildcardDomain:u,targetValue:o};if(t.onConfirm){if(!await t.onConfirm())return{created:!1,skipped:!0,wildcardDomain:u,targetValue:o}}}return await B(()=>l.addDomainRecord(new Pe.AddDomainRecordRequest({domainName:r,RR:f,type:"CNAME",value:o}))),{created:!0,skipped:!1,wildcardDomain:u,targetValue:o}}j();fe();Cn();import*as In from"@alicloud/openapi-client";import*as Ii from"@alicloud/tea-util";Fe();var Fm=se(In.default,"@alicloud/openapi-client");function xm(){let e=k.requireAuth();return new Fm(new In.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:e.region,endpoint:"cdn.aliyuncs.com"}))}function Hm(e){if(e===null||e===void 0)return;if(typeof e==="boolean")return e?"true":"false";return String(e)}function Lm(e){let n={};for(let[t,s]of Object.entries(e)){let o=Hm(s);if(o===void 0)continue;n[t]=o}return n}async function Vt(e,n){let t=xm(),s=new In.Params({action:e,version:"2018-05-10",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),o=new In.OpenApiRequest({query:Lm(n)});return t.callApi(s,o,new Ii.RuntimeOptions({readTimeout:20000,connectTimeout:8000}))}function dn(e){let n=e.trim().toLowerCase();if(!n)throw Error("域名不能为空");return n}function Fi(e){let n=Jn(e);if(!n)throw Error("CDN 回源域名不能为空");return n}function xi(e){return e==="oss"?"oss":"domain"}function Am(e){return"domestic"}function Hi(e,n){if(e==="domestic"||e==="overseas"||e==="global")return e;return Am(n)}function Um(e){return String(e||"").trim().toLowerCase()||void 0}function vm(e){if(!e||typeof e!=="object")return;let n=e,t=String(n.DomainName||n.domainName||"").trim().toLowerCase();if(!t)return;let s=String(n.Cname||n.cname||"").trim();return{domainName:t,cname:s?Jn(s):void 0,status:Um(n.DomainStatus||n.domainStatus)}}function Gm(e){if(!e||typeof e!=="object")return[];let t=e.Domains;if(!t||typeof t!=="object")return[];let s=t.PageData;if(!Array.isArray(s))return[];return s}function Ar(e){if(typeof e!=="object"||e===null)return!1;let n=String(e.code||"").toLowerCase(),t=String(e.message||"").toLowerCase();return n.includes("invaliddomainstatus")||n.includes("domainnotexist")||t.includes("domain status")||t.includes("processing")||t.includes("not ready")}async function Os(e){let n=dn(e),t=await B(()=>Vt("DescribeUserDomains",{DomainName:n,PageNumber:1,PageSize:50})),s=Gm(t.body);for(let o of s){let r=vm(o);if(!r)continue;if(r.domainName===n)return r}return}async function Mm(e,n,t={}){let s=dn(e),o=Fi(n),r=xi(t.sourceType),c=Hi(t.scope,s),f=JSON.stringify([{content:o,type:r,port:80,priority:"20",weight:"10"}]);await B(()=>Vt("AddCdnDomain",{DomainName:s,CdnType:"web",Scope:c,Sources:f}))}async function Ym(e,n=60,t=3000){let s=dn(e);for(let o=1;o<=n;o+=1){let r=await Os(s);if(r?.cname)return r.cname;await new Promise((c)=>setTimeout(c,t))}throw Error(`CDN 域名已创建,但暂未返回 CNAME: ${s}`)}async function Bm(e,n,t){let s=dn(e),o=n.trim(),r=t.trim();if(!o||!r)return;let c=`licell-cdn-cert-${Date.now()}`;await B(()=>Vt("SetCdnDomainSSLCertificate",{DomainName:s,SSLProtocol:"on",CertType:"upload",CertName:c,SSLPub:o,SSLPri:r}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(f)=>Ee(f)||ie(f)||Ar(f)})}function Km(e){let n=P(e).toLowerCase();return n.includes("aliyuncdnaccessingprivateossrole")||n.includes("private oss")||n.includes("private_oss_auth")||n.includes("l2_oss_key")||n.includes("authorize")}async function Om(e){let n=dn(e),t=JSON.stringify([{functionName:"l2_oss_key",functionArgs:[{argName:"private_oss_auth",argValue:"on"}]}]);try{await B(()=>Vt("BatchSetCdnDomainConfig",{DomainNames:n,Functions:t}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(s)=>Ee(s)||Ar(s)})}catch(s){if(!Km(s))throw s;throw Error("CDN 私有 OSS 回源授权失败,请先在 CDN 控制台完成一次“私有 OSS Bucket 回源授权”,"+`确保服务关联角色 AliyunCDNAccessingPrivateOSSRole 已创建。原始错误: ${P(s)}`)}}async function jm(e){let n=dn(e),t=JSON.stringify([{functionName:"back_to_origin_url_rewrite",functionArgs:[{argName:"source_url",argValue:"^/$"},{argName:"target_url",argValue:"/index.html"},{argName:"flag",argValue:"break"}]}]);await B(()=>Vt("BatchSetCdnDomainConfig",{DomainNames:n,Functions:t}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(s)=>Ee(s)||Ar(s)})}function Wm(e){return e==="online"}function Qm(e){return e==="configure_failed"||e==="check_failed"}async function Dm(e,n=40,t=3000){let s=dn(e);for(let o=1;o<=n;o+=1){let r=await Os(s);if(Wm(r?.status))return;if(Qm(r?.status))throw Error(`CDN 域名状态异常: ${s} (${r?.status})`);await new Promise((c)=>setTimeout(c,t))}throw Error(`CDN 域名长时间未就绪: ${s}`)}async function Vm(e,n,t={}){let s=dn(e),o=Fi(n),r=!1,c=await Os(s);if(!c)try{await Mm(s,o,t),r=!0}catch(u){if(!N(u))throw u;c=await Os(s)}return{cdnCname:c?.cname||await Ym(s),created:r}}async function Xt(e,n,t={}){let s=dn(e),o=xi(t.sourceType),r=Hi(t.scope,s),c=await Vm(s,n,{sourceType:o,scope:r});if(o==="oss"&&t.enablePrivateOssAuth!==!1)await Om(s),await jm(s);let f=!1;if(t.certificate&&t.privateKey)await Bm(s,t.certificate,t.privateKey),f=!0;if(await Lr(s,c.cdnCname),t.waitForOnline)await Dm(s);return{...c,httpsConfigured:f}}j();Lt();Ve();Cn();Fe();import*as hn from"acme-client";import Xm,*as Nn from"@alicloud/alidns20150109";import*as Ws from"@alicloud/fc20230330";import*as vi from"@alicloud/openapi-client";import{createPrivateKey as Zm,X509Certificate as Jm}from"crypto";import{existsSync as Nm,readFileSync as zm,writeFileSync as e0}from"fs";import{dirname as n0,join as t0}from"path";import{homedir as s0}from"os";var js=t0(s0(),".licell-cli","acme-account.pem"),o0=se(Xm,"@alicloud/alidns20150109"),r0=86400000,c0=30,Li=180000,f0=5000,u0=600;function i0(e){let n=e.toString("utf8");try{return Zm(n).export({format:"pem",type:"pkcs1"}).toString()}catch{return n}}async function l0(){if(Nm(js))return zm(js);Ht(n0(js));let e=await hn.crypto.createPrivateKey();return e0(js,e,{mode:384}),e}function g0(e){if(typeof e!=="string")return!1;return e.split(",").map((n)=>n.trim().toUpperCase()).includes("HTTPS")}function y0(e,n=c0){let t=typeof e==="number"?String(e):typeof e==="string"?e.trim():"";if(!/^\d+$/.test(t))return n;let s=Number.parseInt(t,10);return s>0?s:n}function d0(e,n=Date.now()){try{let t=new Jm(e),s=Date.parse(t.validTo);if(!Number.isFinite(s))return null;return Math.floor((s-n)/r0)}catch{return null}}function Ai(e){if(e>=0)return`剩余 ${e} 天`;return`已过期 ${Math.abs(e)} 天`}function h0(e){if(!e)return Li;let n=Number(e.trim());if(!Number.isFinite(n)||n<=0)return Li;return Math.floor(n)}function Ui(e){return e.trim().replace(/^"+|"+$/g,"")}async function Gi(e,n,t){let s=[`${t}.${n}`,t],o=[];for(let r of s)try{let c=await B(()=>e.describeSubDomainRecords(new Nn.DescribeSubDomainRecordsRequest({domainName:n,subDomain:r,type:"TXT",pageNumber:1,pageSize:100})));o.push(...c.body?.domainRecords?.record||[])}catch{}return o}async function a0(e,n,t,s,o){let r=h0(re(process.env,"SSL_DNS_READY_TIMEOUT_MS")),c=Ui(s),f=Date.now();while(!0){if((await Gi(e,n,t)).some((i)=>{if(typeof i.value!=="string")return!1;return Ui(i.value)===c}))return;if(Date.now()-f>r)throw Error(`DNS TXT 记录传播超时: ${t}.${n}`);o.message(`\uD83C\uDF0D 正在等待 DNS 生效 (${t})...`),await ye(f0)}}function m0(e,n,t=d0){if(!e||!g0(e.protocol))return{issue:!0,message:"\uD83D\uDD12 域名尚未开启 HTTPS,开始签发证书..."};if(n.forceRenew)return{issue:!0,message:"\uD83D\uDD01 已启用强制续签,开始重新签发 HTTPS 证书..."};let s=e.certConfig?.certificate;if(typeof s!=="string"||s.trim().length===0)return{issue:!1,message:"\uD83D\uDD10 域名已开启 HTTPS,但无法读取证书有效期,跳过自动续签(可用 --ssl-force-renew 强制续签)。"};let o=t(s);if(o===null)return{issue:!1,message:"\uD83D\uDD10 域名已开启 HTTPS,但无法解析证书有效期,跳过自动续签(可用 --ssl-force-renew 强制续签)。"};if(o>n.renewBeforeDays)return{issue:!1,message:`\uD83D\uDD10 域名已开启 HTTPS,证书${Ai(o)}(续签阈值 ${n.renewBeforeDays} 天),跳过自动续签。`};return{issue:!0,message:`\uD83D\uDD01 检测到证书${Ai(o)}(续签阈值 ${n.renewBeforeDays} 天),开始自动续签...`}}async function Fn(e,n,t){let s=k.requireAuth(),o=t?.bindToFcDomain!==!1,r=new o0(new vi.Config({accessKeyId:s.ak,accessKeySecret:s.sk,endpoint:"alidns.aliyuncs.com",connectTimeout:1e4,readTimeout:600000})),c=o?Kn(s).client:void 0,f={forceRenew:Boolean(t?.forceRenew),renewBeforeDays:y0(t?.renewBeforeDays??re(process.env,"SSL_RENEW_BEFORE_DAYS"))},u=re(process.env,"SSL_SKIP_CHALLENGE_VERIFY")!=="0",l=null;if(c)try{l=(await B(()=>c.getCustomDomain(e))).body}catch{}let i=c?m0(l,f):{issue:!0,message:f.forceRenew?"\uD83D\uDD01 已启用强制续签,开始签发 CDN HTTPS 证书...":"\uD83D\uDD12 正在签发 CDN HTTPS 证书..."};if(n.message(i.message),!i.issue){let T=typeof l?.certConfig?.certificate==="string"?l.certConfig.certificate:void 0,F=typeof l?.certConfig?.privateKey==="string"?l.certConfig.privateKey:void 0;return{url:`https://${e}`,certificate:T?.trim()?T:void 0,privateKey:F?.trim()?F:void 0,reusedExistingCertificate:!0}}let{rootDomain:g,subDomain:y}=De(e),d=[];n.message("\uD83D\uDD12 正在向 Let's Encrypt 注册 ACME 账户并发起证书申请..."),hn.setLogger(()=>{});let p=await l0(),h=new hn.Client({directoryUrl:hn.directory.letsencrypt.production,accountKey:p}),[_,$]=await hn.crypto.createCsr({commonName:e}),a=async(T)=>{let F=new Set,E=await Gi(r,g,T);for(let b of E){if(!b.recordId||F.has(b.recordId))continue;F.add(b.recordId);try{await B(()=>r.deleteDomainRecord(new Nn.DeleteDomainRecordRequest({recordId:b.recordId})))}catch{}}},w=await h.auto({csr:$,email:`admin@${g}`,termsOfServiceAgreed:!0,challengePriority:["dns-01"],skipChallengeVerification:u,challengeCreateFn:async(T,F,E)=>{let b=y==="@"?"_acme-challenge":`_acme-challenge.${y}`,x=E;n.message(`\uD83D\uDCDD 正在自动配置 DNS TXT 记录 (${b}) ...`),await a(b);let I=await B(()=>r.addDomainRecord(new Nn.AddDomainRecordRequest({domainName:g,RR:b,type:"TXT",value:x,TTL:u0})));if(I.body?.recordId)d.push(I.body.recordId);await a0(r,g,b,x,n),n.message(`\uD83C\uDF10 DNS TXT 已就绪,等待 Let's Encrypt 验证 (${b}) ...`)},challengeRemoveFn:async()=>{for(let T of d)try{await B(()=>r.deleteDomainRecord(new Nn.DeleteDomainRecordRequest({recordId:T})))}catch(F){let E=F instanceof Error?F.message:String(F);if(!E.toLowerCase().includes("notfound")&&!E.toLowerCase().includes("not found"))n.message(`⚠️ DNS TXT 记录清理失败 (recordId=${T}): ${E}`)}}}),H=i0(_);if(c)n.message("\uD83D\uDCE6 证书下发成功,正在自动挂载至云端网关开启 HTTPS..."),await B(()=>c.updateCustomDomain(e,new Ws.UpdateCustomDomainRequest({body:new Ws.UpdateCustomDomainInput({protocol:"HTTP,HTTPS",certConfig:{certName:`licell-cert-${Date.now()}`,certificate:w.toString(),privateKey:H}})})));else n.message("\uD83D\uDCE6 证书下发成功,正在用于 CDN 边缘 HTTPS 配置...");return{url:`https://${e}`,certificate:w.toString(),privateKey:H,reusedExistingCertificate:!1}}async function Mi(e,n,t){return(await Fn(e,n,t)).url}Ve();import{request as p0}from"http";import{request as w0}from"https";var $0=["/healthz","/"],k0=4,b0=1500,_0=5000;function T0(e){let t=(e&&e.length>0?e:$0).map((s)=>s.trim()).filter((s)=>s.length>0).map((s)=>s.startsWith("/")?s:`/${s}`);return[...new Set(t)]}function C0(e,n){return`${e.replace(/\/+$/g,"")}${n}`}function E0(e){if(e instanceof Error){if(e.name==="AbortError")return"请求超时";return e.message}return String(e)}async function S0(e,n,t){let s=new AbortController,o=Error("请求超时");o.name="AbortError";let r,c=new Promise((f,u)=>{r=setTimeout(()=>{s.abort(),u(o)},n)});try{return await Promise.race([t(e,{method:"GET",redirect:"manual",signal:s.signal,headers:{"user-agent":"licell-health-check/1.0"}}),c])}finally{if(r)clearTimeout(r)}}async function q0(e,n){let t=new URL(e),s=t.protocol==="https:",o=s?w0:p0;return new Promise((r,c)=>{let f=!1,u,l=o(t,{method:"GET",headers:{"user-agent":"licell-health-check/1.0"},...s?{minVersion:"TLSv1.2",maxVersion:"TLSv1.2"}:{}},(i)=>{if(u)clearTimeout(u);let g=i.statusCode??0;if(i.resume(),i.once("error",()=>{}),!f)f=!0,r(g);i.destroy()});u=setTimeout(()=>{l.destroy(Error("请求超时"))},n),l.on("error",(i)=>{if(u)clearTimeout(u);if(f)return;f=!0,c(i)}),l.end()})}async function an(e,n={}){let t=e.trim();if(!t)return{ok:!1,error:"URL 为空",attempt:1};let s=n.fetchImpl??(typeof globalThis.fetch==="function"?globalThis.fetch.bind(globalThis):void 0),o=T0(n.paths),r=Math.max(1,Math.floor(n.maxAttempts??k0)),c=Math.max(0,Math.floor(n.intervalMs??b0)),f=Math.max(1000,Math.floor(n.timeoutMs??_0)),u=n.allowClientError===!1?400:500,l="未知错误";for(let i=1;i<=r;i+=1){for(let g of o){let y=C0(t,g);try{let d=s?(await S0(y,f,s)).status:await q0(y,f);if(d<u){if(g==="/healthz"&&d===404&&o.includes("/")){l=`GET ${y} 返回 404`;continue}return{ok:!0,checkedUrl:y,statusCode:d,attempt:i}}l=`GET ${y} 返回 ${d}`}catch(d){l=`GET ${y} 请求失败: ${E0(d)}`}}if(i<r&&c>0)await ye(c)}return{ok:!1,error:l,attempt:r}}fe();oe();ce();function Oi(e){let n=[];for(let t of e){let s=t.level==="error"?"ERROR":"WARN";if(n.push(`[${s}] ${t.id}`),n.push(t.message),t.remediation&&t.remediation.length>0)for(let o of t.remediation)n.push(`- ${o}`)}return n}async function ji(e,n){let t=e.cliRuntime||e.projectRuntime||e.envRuntime||Dn;if(t!=="docker"&&!e.cliRuntime&&P0("Dockerfile")&&e.interactiveTTY){let _=await Yi({message:"检测到 Dockerfile,是否使用 Docker 容器部署?"});if(Bi(_)){if(m())throw Error("操作已取消");process.exit(0)}if(_)t="docker"}if(e.cliAcrNamespace&&t!=="docker")throw Error("--acr-namespace 仅适用于 --runtime docker");if(t==="docker"&&e.cliAcrNamespace)k.setProject({acrNamespace:e.cliAcrNamespace});let s=kn(t).defaultEntry,o;if(t==="docker")o=e.cliEntry||"";else if(e.cliEntry)o=v(e.cliEntry,"入口文件路径");else if(e.interactiveTTY)o=v(await R0({message:t.startsWith("python")?"入口文件路径 (Python 需包含 handler 函数):":"入口文件路径 (需导出 handler):",initialValue:s}),"入口文件路径");else o=s;let r=Qt({runtime:t,entry:o,checkDockerDaemon:t==="docker"}),c=r.issues.filter((_)=>_.level==="warning");if(!r.ok){let _=Oi(r.issues),$=ur(r);throw $.message=`${$.message}
|
|
1393
|
+
已写入文件:`);for(let y of _)console.log(`- ${y}`)}if($.length>0){console.log(`
|
|
1394
|
+
已跳过(内容相同):`);for(let y of $)console.log(`- ${y}`)}if(console.log(`
|
|
1395
|
+
下一步可直接执行:`),console.log(`- licell deploy --type api --runtime ${i} --target preview`),m())T({stage:"init",runtime:i,appName:p,mode:l?"scaffold+config":"config-only",writtenFiles:_,skippedFiles:$});else q("Done.")}catch(f){if(m())pn(f,{stage:"init"});else console.error(A(f));process.exitCode=1}})}Q();ne();dn();import z from"picocolors";import{spawnSync as om}from"child_process";function vo(n,e){if(!e||!e.trim())return;let t=om("sh",["-c",e.trim()],{stdio:"inherit",env:process.env});if(t.status!==0)throw Error(`${n} hook 执行失败(exit=${t.status??"unknown"}): ${e.trim()}`)}function Qi(n){if(!n.deploySucceeded)return{};let e={};if(n.cliDomainSuffix&&n.cliDomainSuffix!==n.projectDomainSuffix)e.domainSuffix=n.cliDomainSuffix;if(n.cliRuntime&&n.cliRuntime!==n.projectRuntime)e.runtime=n.cliRuntime;return e}ne();var cm=new Set(["static","statis","oss","static-site"]);function fm(n){if(n===null||n===void 0)return;let e=String(n).trim();return e.length>0?e:void 0}function Zs(n){let e=fm(n);if(!e)return{};let t=e.toLowerCase();if(cm.has(t))return{deployTypeHint:"static"};return{deployTypeHint:"api",runtime:nt(e)}}Hn();on();fn();Q();import{select as um,text as im,isCancel as gm}from"@clack/prompts";function Xi(n){return n.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\$/g,"\\$").replace(/`/g,"\\`").replace(/\r/g,"\\r").replace(/\n/g,"\\n")}function Ae(n){try{let e=new URL(n);if(e.password)e.password="******";return e.toString()}catch{return n.replace(/:([^@/:]+)@/g,":******@")}}function ue(n){if(typeof n!=="string")return"prod";let e=n.trim().toLowerCase();if(!e)return"prod";if(!/^[a-z0-9-]+$/.test(e))throw Error("发布目标仅允许小写字母、数字和短横线");return e}Nr();on();fn();function Zi(n,e,t,s){return Boolean(n||e||t||s)}async function Oi(n){let e=await B(),t=I(),s=k.getProject();if(!s.appName){if(!t)throw Error("缺少应用名,请先配置 .licell/project.json 的 appName,或在交互终端执行 deploy 初始化");let ln=K(await im({message:"为你的应用起个名字 (小写英文):",placeholder:"my-awesome-app"}),"应用名");if(!/^[a-z0-9-]+$/.test(ln))throw Error("应用名仅允许小写字母、数字和短横线");if(ln.length>128)throw Error("应用名长度不能超过 128 个字符");k.setProject({appName:ln}),s=k.getProject()}let r=n.domain?Ys(n.domain):void 0,o=n.domainSuffix?tt(n.domainSuffix):void 0,c=js(s.domainSuffix),f=js(un(process.env,"DOMAIN_SUFFIX")),u=js(k.getGlobalConfig().domainSuffix),g=Zs(n.runtime),i=g.runtime,l=Ct(s.runtime),d=Ct(un(process.env,"FC_RUNTIME")),a=n.acrNamespace?Or(n.acrNamespace):void 0,p=n.type?Ei(n.type):void 0,w;if(p&&g.deployTypeHint&&p!==g.deployTypeHint)throw Error(`--type ${p} 与 --runtime ${n.runtime} 冲突`);if(p)w=p;else if(g.deployTypeHint==="api")w="api";else if(g.deployTypeHint==="static")w="static";else if(t){let ln=await um({message:"选择部署环境:",options:[{value:"api",label:"\uD83D\uDE80 API 服务 (Node/Python/Docker -> FC 3.0)"},{value:"static",label:"\uD83D\uDCE6 前端静态网站 (直推 OSS 托管)"}]});if(gm(ln)){if(m())throw Error("操作已取消");process.exit(0)}if(ln!=="api"&&ln!=="static")throw Error("未知部署类型");w=ln}else w="api";let _=r?void 0:w==="static"?o:o||c||f||u,$=n.target?ue(n.target):void 0,y=w==="static"&&Boolean(r||_),h=w==="static"?Boolean(n.enableCdn||y):Boolean(n.enableCdn),U=w==="static"?Boolean(n.ssl||y||h):Zi(n.ssl,r,h,_),C=Boolean(n.sslForceRenew);if(r&&o)throw Error("--domain 与 --domain-suffix 不能同时使用");if($&&w!=="api")throw Error("--target 仅适用于 API 部署");if(n.enableVpc&&n.disableVpc)throw Error("--enable-vpc 与 --disable-vpc 不能同时使用");if(w!=="api"&&n.enableVpc)throw Error("--enable-vpc 仅适用于 API 部署");if(w!=="api"&&n.disableVpc)throw Error("--disable-vpc 仅适用于 API 部署");if(h&&!r&&!_)throw Error("--enable-cdn 需要域名,请提供 --domain(完整域名)或 --domain-suffix");if(w!=="api"&&i)throw Error("--runtime 的 API 运行时仅适用于 API 部署;静态站请使用 --runtime static");if(w!=="api"&&a)throw Error("--acr-namespace 仅适用于 API Docker 部署");if(C&&!U)throw Error("--ssl-force-renew 需要启用 HTTPS(请使用 --domain 或 --ssl)");if(U&&!r&&!_)throw Error("--ssl 需要域名,请提供 --domain(完整域名)或 --domain-suffix");let L=Boolean(n.preview);if(L&&$)throw Error("--preview 与 --target 不能同时使用");if(L&&!_)throw Error("--preview 需要域名后缀,请先执行 licell deploy --domain-suffix your-domain.com 或 licell config domain your-domain.com");if(L&&r)throw Error("--preview 与 --domain 不能同时使用,preview 会自动生成预览域名");let S=s.appName;if(!S)throw Error("appName 未设置,请检查项目配置");let b=Fn(n.memory,"--memory"),P=Pi(n.vcpu,"--vcpu"),R=Fn(n.instanceConcurrency,"--instance-concurrency"),j=Fn(n.timeout,"--timeout"),gn=b!==void 0||j!==void 0||P!==void 0||R!==void 0?{...b!==void 0?{memorySize:b}:{},...P!==void 0?{cpu:P}:{},...R!==void 0?{instanceConcurrency:R}:{},...j!==void 0?{timeout:j}:{}}:void 0,Xn=w==="api"?!Boolean(n.disableVpc):!1;return{appName:S,type:w,releaseTarget:$,cliDomain:r,domainSuffix:_,enableCdn:h,useVpc:Xn,enableSSL:U,forceSslRenew:C,preview:L,cliResources:gn,cliAcrNamespace:a,interactiveTTY:t,auth:e,project:s,cliDomainSuffix:o,projectDomainSuffix:c,cliRuntime:i,projectRuntime:l,envRuntime:d,cliEntry:n.entry,cliDist:n.dist}}Q();Oe();pt();ne();import{confirm as ag,text as lw,isCancel as hg}from"@clack/prompts";import{existsSync as dw}from"fs";import mg from"picocolors";Q();vt();Pe();xn();import lm,*as Un from"@alicloud/alidns20150109";import*as He from"@alicloud/fc20230330";import*as Ji from"@alicloud/openapi-client";var dm=nn(lm,"@alicloud/alidns20150109");function st(n){return n.toLowerCase().replace(/^https?:\/\//,"").replace(/\.$/,"")}function Ni(n,e){let t=n.trim().toLowerCase(),s=e.trim().toLowerCase();if(!t)return s?[s]:[];let o=[s==="@"||!s?t:`${s}.${t}`,s];return[...new Set(o.filter((c)=>c.length>0))]}function It(){let n=k.requireAuth();return new dm(new Ji.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:"alidns.aliyuncs.com"}))}function zi(){return Qe().client}async function Ko(n,e,t){let s=[],r=Ni(e,t);for(let o of r)try{let c=await D(()=>n.describeSubDomainRecords(new Un.DescribeSubDomainRecordsRequest({domainName:e,subDomain:o,type:"CNAME",pageNumber:1,pageSize:100})));s.push(...c.body?.domainRecords?.record||[])}catch(c){if(Ts(c))continue;throw c}return s.find((o)=>{let c=o;return(c.RR||"@")===t&&(c.type||"").toUpperCase()==="CNAME"})}async function ym(n,e,t,s,r){let o=st(r),c=await Ko(n,t,s);if(!c?.recordId)try{await D(()=>n.addDomainRecord(new Un.AddDomainRecordRequest({domainName:t,RR:s,type:"CNAME",value:o})));return}catch(u){if(!tn(u))throw u;c=await Ko(n,t,s)}if(!c?.recordId)throw Error(`DNS 记录已存在但无法定位可更新记录: ${e}`);if(st(c.value||"")===o)return;await D(()=>n.updateDomainRecord(new Un.UpdateDomainRecordRequest({recordId:c.recordId,RR:s,type:"CNAME",value:o})))}async function Yo(n,e){let t=n.trim().toLowerCase();if(!t)throw Error("域名不能为空");let{rootDomain:s,subDomain:r}=zn(t),o=It();await ym(o,t,s,r,e)}async function Rt(n,e,t,s={}){let r=k.getProject();if(!r.appName)throw Error("未找到应用名,请先执行 licell deploy");if(!s.skipDnsBind)await Yo(n,e);let o=zi(),c={routes:[{path:"/*",functionName:r.appName,qualifier:t}]},f=new He.CreateCustomDomainInput({domainName:n,protocol:"HTTP",routeConfig:c});try{await D(()=>o.createCustomDomain(new He.CreateCustomDomainRequest({body:f})))}catch(u){if(!tn(u))throw u;await D(()=>o.updateCustomDomain(n,new He.UpdateCustomDomainRequest({body:new He.UpdateCustomDomainInput({routeConfig:c})})))}return`http://${n}`}async function ng(n){let e=n.trim().toLowerCase();if(!e)throw Error("域名不能为空");let{rootDomain:t,subDomain:s}=zn(e),r=It(),o=zi();try{await D(()=>o.deleteCustomDomain(e))}catch(u){if(!an(u))throw u}let c=new Set,f=Ni(t,s);for(let u of f){let g=[];try{g=(await D(()=>r.describeSubDomainRecords(new Un.DescribeSubDomainRecordsRequest({domainName:t,subDomain:u,type:"CNAME",pageNumber:1,pageSize:200})))).body?.domainRecords?.record||[]}catch(i){if(Ts(i))continue;throw i}for(let i of g){let l=i;if((l.RR||"@")!==s||(l.type||"").toUpperCase()!=="CNAME")continue;let d=l.recordId;if(!d||c.has(d))continue;c.add(d);try{await D(()=>r.deleteDomainRecord(new Un.DeleteDomainRecordRequest({recordId:d})))}catch(a){if(!an(a))throw a}}}}async function eg(n,e=200){let t=n.trim().toLowerCase();if(!t)throw Error("域名不能为空");let s=It(),r=[],o=Math.max(1,Math.min(Math.floor(e),1000)),c=Math.min(100,o);for(let f=1;f<=20&&r.length<o;f+=1){let u=await D(()=>s.describeDomainRecords(new Un.DescribeDomainRecordsRequest({domainName:t,pageNumber:f,pageSize:c}))),g=u.body?.domainRecords?.record||[];for(let l of g){let d=l.recordId;if(!d)continue;if(r.push({recordId:d,rr:l.RR||"@",type:l.type||"",value:l.value||"",ttl:l.TTL,line:l.line,status:l.status}),r.length>=o)break}let i=u.body?.totalCount||0;if(g.length===0||i>0&&r.length>=i)break}return r}async function tg(n,e){let t=n.trim().toLowerCase();if(!t)throw Error("域名不能为空");let s=e.rr.trim(),r=e.type.trim().toUpperCase(),o=e.value.trim();if(!s)throw Error("RR 不能为空");if(!r)throw Error("记录类型不能为空");if(!o)throw Error("记录值不能为空");let c=It(),u=(await D(()=>c.addDomainRecord(new Un.AddDomainRecordRequest({domainName:t,RR:s,type:r,value:o,TTL:e.ttl,line:e.line||"default"})))).body?.recordId;if(!u)throw Error("添加 DNS 记录失败:未返回 recordId");return u}async function sg(n){let e=n.trim();if(!e)throw Error("recordId 不能为空");let t=It();await D(()=>t.deleteDomainRecord(new Un.DeleteDomainRecordRequest({recordId:e})))}async function Os(n,e,t){let s=n.trim().toLowerCase(),r=st(e),{rootDomain:o,subDomain:c}=zn(s),f=c?`*.${c}`:"*",u=c?`*.${c}.${o}`:`*.${o}`,g=It(),i=await Ko(g,o,f);if(i?.recordId){if(st(i.value||"")!==r)await D(()=>g.updateDomainRecord(new Un.UpdateDomainRecordRequest({recordId:i.recordId,RR:f,type:"CNAME",value:r})));return{created:!1,skipped:!1,wildcardDomain:u,targetValue:r}}if(!t.skipConfirm){if(!t.interactiveTTY)return{created:!1,skipped:!0,wildcardDomain:u,targetValue:r};if(t.onConfirm){if(!await t.onConfirm())return{created:!1,skipped:!0,wildcardDomain:u,targetValue:r}}}return await D(()=>g.addDomainRecord(new Un.AddDomainRecordRequest({domainName:o,RR:f,type:"CNAME",value:r}))),{created:!0,skipped:!1,wildcardDomain:u,targetValue:r}}Q();dn();Pe();import*as Ue from"@alicloud/openapi-client";import*as rg from"@alicloud/tea-util";xn();var am=nn(Ue.default,"@alicloud/openapi-client");function hm(){let n=k.requireAuth();return new am(new Ue.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:"cdn.aliyuncs.com"}))}function mm(n){if(n===null||n===void 0)return;if(typeof n==="boolean")return n?"true":"false";return String(n)}function wm(n){let e={};for(let[t,s]of Object.entries(n)){let r=mm(s);if(r===void 0)continue;e[t]=r}return e}async function zt(n,e){let t=hm(),s=new Ue.Params({action:n,version:"2018-05-10",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),r=new Ue.OpenApiRequest({query:wm(e)});return t.callApi(s,r,new rg.RuntimeOptions({readTimeout:20000,connectTimeout:8000}))}function we(n){let e=n.trim().toLowerCase();if(!e)throw Error("域名不能为空");return e}function og(n){let e=st(n);if(!e)throw Error("CDN 回源域名不能为空");return e}function cg(n){return n==="oss"?"oss":"domain"}function pm(n){return"domestic"}function fg(n,e){if(n==="domestic"||n==="overseas"||n==="global")return n;return pm(e)}function $m(n){return String(n||"").trim().toLowerCase()||void 0}function bm(n){if(!n||typeof n!=="object")return;let e=n,t=String(e.DomainName||e.domainName||"").trim().toLowerCase();if(!t)return;let s=String(e.Cname||e.cname||"").trim();return{domainName:t,cname:s?st(s):void 0,status:$m(e.DomainStatus||e.domainStatus)}}function km(n){if(!n||typeof n!=="object")return[];let t=n.Domains;if(!t||typeof t!=="object")return[];let s=t.PageData;if(!Array.isArray(s))return[];return s}function jo(n){if(typeof n!=="object"||n===null)return!1;let e=String(n.code||"").toLowerCase(),t=String(n.message||"").toLowerCase();return e.includes("invaliddomainstatus")||e.includes("domainnotexist")||t.includes("domain status")||t.includes("processing")||t.includes("not ready")}async function Js(n){let e=we(n),t=await D(()=>zt("DescribeUserDomains",{DomainName:e,PageNumber:1,PageSize:50})),s=km(t.body);for(let r of s){let o=bm(r);if(!o)continue;if(o.domainName===e)return o}return}async function _m(n,e,t={}){let s=we(n),r=og(e),o=cg(t.sourceType),c=fg(t.scope,s),f=JSON.stringify([{content:r,type:o,port:80,priority:"20",weight:"10"}]);await D(()=>zt("AddCdnDomain",{DomainName:s,CdnType:"web",Scope:c,Sources:f}))}async function Cm(n,e=60,t=3000){let s=we(n);for(let r=1;r<=e;r+=1){let o=await Js(s);if(o?.cname)return o.cname;await new Promise((c)=>setTimeout(c,t))}throw Error(`CDN 域名已创建,但暂未返回 CNAME: ${s}`)}async function Sm(n,e,t){let s=we(n),r=e.trim(),o=t.trim();if(!r||!o)return;let c=`licell-cdn-cert-${Date.now()}`;await D(()=>zt("SetCdnDomainSSLCertificate",{DomainName:s,SSLProtocol:"on",CertType:"upload",CertName:c,SSLPub:r,SSLPri:o}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(f)=>Rn(f)||an(f)||jo(f)})}function Tm(n){let e=A(n).toLowerCase();return e.includes("aliyuncdnaccessingprivateossrole")||e.includes("private oss")||e.includes("private_oss_auth")||e.includes("l2_oss_key")||e.includes("authorize")}async function Em(n){let e=we(n),t=JSON.stringify([{functionName:"l2_oss_key",functionArgs:[{argName:"private_oss_auth",argValue:"on"}]}]);try{await D(()=>zt("BatchSetCdnDomainConfig",{DomainNames:e,Functions:t}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(s)=>Rn(s)||jo(s)})}catch(s){if(!Tm(s))throw s;throw Error("CDN 私有 OSS 回源授权失败,请先在 CDN 控制台完成一次“私有 OSS Bucket 回源授权”,"+`确保服务关联角色 AliyunCDNAccessingPrivateOSSRole 已创建。原始错误: ${A(s)}`)}}async function qm(n){let e=we(n),t=JSON.stringify([{functionName:"back_to_origin_url_rewrite",functionArgs:[{argName:"source_url",argValue:"^/$"},{argName:"target_url",argValue:"/index.html"},{argName:"flag",argValue:"break"}]}]);await D(()=>zt("BatchSetCdnDomainConfig",{DomainNames:e,Functions:t}),{maxAttempts:12,baseDelayMs:2000,shouldRetry:(s)=>Rn(s)||jo(s)})}function Im(n){return n==="online"}function Rm(n){return n==="configure_failed"||n==="check_failed"}async function Pm(n,e=40,t=3000){let s=we(n);for(let r=1;r<=e;r+=1){let o=await Js(s);if(Im(o?.status))return;if(Rm(o?.status))throw Error(`CDN 域名状态异常: ${s} (${o?.status})`);await new Promise((c)=>setTimeout(c,t))}throw Error(`CDN 域名长时间未就绪: ${s}`)}async function Fm(n,e,t={}){let s=we(n),r=og(e),o=!1,c=await Js(s);if(!c)try{await _m(s,r,t),o=!0}catch(u){if(!tn(u))throw u;c=await Js(s)}return{cdnCname:c?.cname||await Cm(s),created:o}}async function ns(n,e,t={}){let s=we(n),r=cg(t.sourceType),o=fg(t.scope,s),c=await Fm(s,e,{sourceType:r,scope:o});if(r==="oss"&&t.enablePrivateOssAuth!==!1)await Em(s),await qm(s);let f=!1;if(t.certificate&&t.privateKey)await Sm(s,t.certificate,t.privateKey),f=!0;if(await Yo(s,c.cdnCname),t.waitForOnline)await Pm(s);return{...c,httpsConfigured:f}}Q();vt();Zn();Pe();xn();import*as pe from"acme-client";import xm,*as rt from"@alicloud/alidns20150109";import*as zs from"@alicloud/fc20230330";import*as lg from"@alicloud/openapi-client";import{createPrivateKey as Lm,X509Certificate as Am}from"crypto";import{existsSync as Hm,readFileSync as Um,writeFileSync as Mm}from"fs";import{dirname as Gm,join as Bm}from"path";import{homedir as vm}from"os";var Ns=Bm(vm(),".licell-cli","acme-account.pem"),Km=nn(xm,"@alicloud/alidns20150109"),Ym=86400000,jm=30,ug=180000,Dm=5000,Wm=600;function Vm(n){let e=n.toString("utf8");try{return Lm(e).export({format:"pem",type:"pkcs1"}).toString()}catch{return e}}async function Qm(){if(Hm(Ns))return Um(Ns);Bt(Gm(Ns));let n=await pe.crypto.createPrivateKey();return Mm(Ns,n,{mode:384}),n}function Xm(n){if(typeof n!=="string")return!1;return n.split(",").map((e)=>e.trim().toUpperCase()).includes("HTTPS")}function Zm(n,e=jm){let t=typeof n==="number"?String(n):typeof n==="string"?n.trim():"";if(!/^\d+$/.test(t))return e;let s=Number.parseInt(t,10);return s>0?s:e}function Om(n,e=Date.now()){try{let t=new Am(n),s=Date.parse(t.validTo);if(!Number.isFinite(s))return null;return Math.floor((s-e)/Ym)}catch{return null}}function ig(n){if(n>=0)return`剩余 ${n} 天`;return`已过期 ${Math.abs(n)} 天`}function Jm(n){if(!n)return ug;let e=Number(n.trim());if(!Number.isFinite(e)||e<=0)return ug;return Math.floor(e)}function gg(n){return n.trim().replace(/^"+|"+$/g,"")}async function dg(n,e,t){let s=[`${t}.${e}`,t],r=[];for(let o of s)try{let c=await D(()=>n.describeSubDomainRecords(new rt.DescribeSubDomainRecordsRequest({domainName:e,subDomain:o,type:"TXT",pageNumber:1,pageSize:100})));r.push(...c.body?.domainRecords?.record||[])}catch{}return r}async function Nm(n,e,t,s,r){let o=Jm(un(process.env,"SSL_DNS_READY_TIMEOUT_MS")),c=gg(s),f=Date.now();while(!0){if((await dg(n,e,t)).some((i)=>{if(typeof i.value!=="string")return!1;return gg(i.value)===c}))return;if(Date.now()-f>o)throw Error(`DNS TXT 记录传播超时: ${t}.${e}`);r.message(`\uD83C\uDF0D 正在等待 DNS 生效 (${t})...`),await yn(Dm)}}function zm(n,e,t=Om){if(!n||!Xm(n.protocol))return{issue:!0,message:"\uD83D\uDD12 域名尚未开启 HTTPS,开始签发证书..."};if(e.forceRenew)return{issue:!0,message:"\uD83D\uDD01 已启用强制续签,开始重新签发 HTTPS 证书..."};let s=n.certConfig?.certificate;if(typeof s!=="string"||s.trim().length===0)return{issue:!1,message:"\uD83D\uDD10 域名已开启 HTTPS,但无法读取证书有效期,跳过自动续签(可用 --ssl-force-renew 强制续签)。"};let r=t(s);if(r===null)return{issue:!1,message:"\uD83D\uDD10 域名已开启 HTTPS,但无法解析证书有效期,跳过自动续签(可用 --ssl-force-renew 强制续签)。"};if(r>e.renewBeforeDays)return{issue:!1,message:`\uD83D\uDD10 域名已开启 HTTPS,证书${ig(r)}(续签阈值 ${e.renewBeforeDays} 天),跳过自动续签。`};return{issue:!0,message:`\uD83D\uDD01 检测到证书${ig(r)}(续签阈值 ${e.renewBeforeDays} 天),开始自动续签...`}}async function Me(n,e,t){let s=k.requireAuth(),r=t?.bindToFcDomain!==!1,o=new Km(new lg.Config({accessKeyId:s.ak,accessKeySecret:s.sk,endpoint:"alidns.aliyuncs.com",connectTimeout:1e4,readTimeout:600000})),c=r?Qe(s).client:void 0,f={forceRenew:Boolean(t?.forceRenew),renewBeforeDays:Zm(t?.renewBeforeDays??un(process.env,"SSL_RENEW_BEFORE_DAYS"))},u=un(process.env,"SSL_SKIP_CHALLENGE_VERIFY")!=="0",g=null;if(c)try{g=(await D(()=>c.getCustomDomain(n))).body}catch{}let i=c?zm(g,f):{issue:!0,message:f.forceRenew?"\uD83D\uDD01 已启用强制续签,开始签发 CDN HTTPS 证书...":"\uD83D\uDD12 正在签发 CDN HTTPS 证书..."};if(e.message(i.message),!i.issue){let C=typeof g?.certConfig?.certificate==="string"?g.certConfig.certificate:void 0,L=typeof g?.certConfig?.privateKey==="string"?g.certConfig.privateKey:void 0;return{url:`https://${n}`,certificate:C?.trim()?C:void 0,privateKey:L?.trim()?L:void 0,reusedExistingCertificate:!0}}let{rootDomain:l,subDomain:d}=zn(n),a=[];e.message("\uD83D\uDD12 正在向 Let's Encrypt 注册 ACME 账户并发起证书申请..."),pe.setLogger(()=>{});let p=await Qm(),w=new pe.Client({directoryUrl:pe.directory.letsencrypt.production,accountKey:p}),[_,$]=await pe.crypto.createCsr({commonName:n}),y=async(C)=>{let L=new Set,S=await dg(o,l,C);for(let b of S){if(!b.recordId||L.has(b.recordId))continue;L.add(b.recordId);try{await D(()=>o.deleteDomainRecord(new rt.DeleteDomainRecordRequest({recordId:b.recordId})))}catch{}}},h=await w.auto({csr:$,email:`admin@${l}`,termsOfServiceAgreed:!0,challengePriority:["dns-01"],skipChallengeVerification:u,challengeCreateFn:async(C,L,S)=>{let b=d==="@"?"_acme-challenge":`_acme-challenge.${d}`,P=S;e.message(`\uD83D\uDCDD 正在自动配置 DNS TXT 记录 (${b}) ...`),await y(b);let R=await D(()=>o.addDomainRecord(new rt.AddDomainRecordRequest({domainName:l,RR:b,type:"TXT",value:P,TTL:Wm})));if(R.body?.recordId)a.push(R.body.recordId);await Nm(o,l,b,P,e),e.message(`\uD83C\uDF10 DNS TXT 已就绪,等待 Let's Encrypt 验证 (${b}) ...`)},challengeRemoveFn:async()=>{for(let C of a)try{await D(()=>o.deleteDomainRecord(new rt.DeleteDomainRecordRequest({recordId:C})))}catch(L){let S=L instanceof Error?L.message:String(L);if(!S.toLowerCase().includes("notfound")&&!S.toLowerCase().includes("not found"))e.message(`⚠️ DNS TXT 记录清理失败 (recordId=${C}): ${S}`)}}}),U=Vm(_);if(c)e.message("\uD83D\uDCE6 证书下发成功,正在自动挂载至云端网关开启 HTTPS..."),await D(()=>c.updateCustomDomain(n,new zs.UpdateCustomDomainRequest({body:new zs.UpdateCustomDomainInput({protocol:"HTTP,HTTPS",certConfig:{certName:`licell-cert-${Date.now()}`,certificate:h.toString(),privateKey:U}})})));else e.message("\uD83D\uDCE6 证书下发成功,正在用于 CDN 边缘 HTTPS 配置...");return{url:`https://${n}`,certificate:h.toString(),privateKey:U,reusedExistingCertificate:!1}}async function yg(n,e,t){return(await Me(n,e,t)).url}Zn();import{request as nw}from"http";import{request as ew}from"https";var tw=["/healthz","/"],sw=4,rw=1500,ow=5000;function cw(n){let t=(n&&n.length>0?n:tw).map((s)=>s.trim()).filter((s)=>s.length>0).map((s)=>s.startsWith("/")?s:`/${s}`);return[...new Set(t)]}function fw(n,e){return`${n.replace(/\/+$/g,"")}${e}`}function uw(n){if(n instanceof Error){if(n.name==="AbortError")return"请求超时";return n.message}return String(n)}async function iw(n,e,t){let s=new AbortController,r=Error("请求超时");r.name="AbortError";let o,c=new Promise((f,u)=>{o=setTimeout(()=>{s.abort(),u(r)},e)});try{return await Promise.race([t(n,{method:"GET",redirect:"manual",signal:s.signal,headers:{"user-agent":"licell-health-check/1.0"}}),c])}finally{if(o)clearTimeout(o)}}async function gw(n,e){let t=new URL(n),s=t.protocol==="https:",r=s?ew:nw;return new Promise((o,c)=>{let f=!1,u,g=r(t,{method:"GET",headers:{"user-agent":"licell-health-check/1.0"},...s?{minVersion:"TLSv1.2",maxVersion:"TLSv1.2"}:{}},(i)=>{if(u)clearTimeout(u);let l=i.statusCode??0;if(i.resume(),i.once("error",()=>{}),!f)f=!0,o(l);i.destroy()});u=setTimeout(()=>{g.destroy(Error("请求超时"))},e),g.on("error",(i)=>{if(u)clearTimeout(u);if(f)return;f=!0,c(i)}),g.end()})}async function $e(n,e={}){let t=n.trim();if(!t)return{ok:!1,error:"URL 为空",attempt:1};let s=e.fetchImpl??(typeof globalThis.fetch==="function"?globalThis.fetch.bind(globalThis):void 0),r=cw(e.paths),o=Math.max(1,Math.floor(e.maxAttempts??sw)),c=Math.max(0,Math.floor(e.intervalMs??rw)),f=Math.max(1000,Math.floor(e.timeoutMs??ow)),u=e.allowClientError===!1?400:500,g="未知错误";for(let i=1;i<=o;i+=1){for(let l of r){let d=fw(t,l);try{let a=s?(await iw(d,f,s)).status:await gw(d,f);if(a<u){if(l==="/healthz"&&a===404&&r.includes("/")){g=`GET ${d} 返回 404`;continue}return{ok:!0,checkedUrl:d,statusCode:a,attempt:i}}g=`GET ${d} 返回 ${a}`}catch(a){g=`GET ${d} 请求失败: ${uw(a)}`}}if(i<o&&c>0)await yn(c)}return{ok:!1,error:g,attempt:o}}dn();on();fn();function wg(n){let e=[];for(let t of n){let s=t.level==="error"?"ERROR":"WARN";if(e.push(`[${s}] ${t.id}`),e.push(t.message),t.remediation&&t.remediation.length>0)for(let r of t.remediation)e.push(`- ${r}`)}return e}async function pg(n,e){let t=n.cliRuntime||n.projectRuntime||n.envRuntime||ze;if(t!=="docker"&&!n.cliRuntime&&dw("Dockerfile")&&n.interactiveTTY){let _=await ag({message:"检测到 Dockerfile,是否使用 Docker 容器部署?"});if(hg(_)){if(m())throw Error("操作已取消");process.exit(0)}if(_)t="docker"}if(n.cliAcrNamespace&&t!=="docker")throw Error("--acr-namespace 仅适用于 --runtime docker");if(t==="docker"&&n.cliAcrNamespace)k.setProject({acrNamespace:n.cliAcrNamespace});let s=Te(t).defaultEntry,r;if(t==="docker")r=n.cliEntry||"";else if(n.cliEntry)r=K(n.cliEntry,"入口文件路径");else if(n.interactiveTTY)r=K(await lw({message:t.startsWith("python")?"入口文件路径 (Python 需包含 handler 函数):":"入口文件路径 (需导出 handler):",initialValue:s}),"入口文件路径");else r=s;let o=Jt({runtime:t,entry:r,checkDockerDaemon:t==="docker"}),c=o.issues.filter((_)=>_.level==="warning");if(!o.ok){let _=wg(o.issues),$=mo(o);throw $.message=`${$.message}
|
|
1396
1396
|
${_.join(`
|
|
1397
|
-
`)}`,$}if(c.length>0){let _=
|
|
1397
|
+
`)}`,$}if(c.length>0){let _=wg(c);e.message(`⚠️ 部署前预检通过(含 warning):
|
|
1398
1398
|
${_.join(`
|
|
1399
|
-
`)}`)}let f="\uD83D\uDD28 正在使用 Bun 极速剥离依赖打包,并推送至云端...";if(t==="docker")f="\uD83D\uDC33 正在构建 Docker 镜像并推送至 ACR...";else if(t.startsWith("python"))f="\uD83D\uDC0D 正在打包 Python 源码并推送至云端...";let u=await
|
|
1399
|
+
`)}`)}let f="\uD83D\uDD28 正在使用 Bun 极速剥离依赖打包,并推送至云端...";if(t==="docker")f="\uD83D\uDC33 正在构建 Docker 镜像并推送至 ACR...";else if(t.startsWith("python"))f="\uD83D\uDC0D 正在打包 Python 源码并推送至云端...";let u=await F(e,f,"❌ 部署失败",async()=>{if(n.useVpc&&!n.project.network){e.message("\uD83C\uDF10 正在自动准备 VPC 网络...");try{let b=await Re();k.setProject({network:b}),n.project=k.getProject(),e.message(`✅ VPC 已就绪: ${b.vpcId} / ${b.vswId}`)}catch(b){console.warn(mg.yellow(`⚠️ VPC 自动接入失败,回退公网模式: ${A(b)}`))}}let _=n.useVpc?n.project.network:null,$={...n.cliResources?{resources:n.cliResources}:{},..._!==void 0?{network:_}:{}},y=await yo(n.appName,r,t,Object.keys($).length>0?$:void 0),h=`${n.auth.accountId}.${n.auth.region}.fc.aliyuncs.com`,U,C,L,S;if(n.preview&&n.domainSuffix){e.message("函数部署完成,正在发布预览版本..."),S=await xe(n.appName,`preview at ${new Date().toISOString()}`),L=`${n.appName}-preview-v${S}.${n.domainSuffix}`,e.message(`正在确保通配符 DNS (*.${n.domainSuffix}) 存在...`);let b=await Os(n.domainSuffix,h,{interactiveTTY:n.interactiveTTY,onConfirm:async()=>{let P=await ag({message:`检测到尚未配置通配符 DNS (*.${n.domainSuffix})。
|
|
1400
1400
|
`+`创建后,所有 preview 子域名将自动解析到 FC 网关。
|
|
1401
|
-
`+`已有的精确 DNS 记录(如 ${
|
|
1402
|
-
`+"是否创建?"});if(Bi(x))return!1;return x}});if(b.skipped)n.message(Ki.yellow("⚠️ 已跳过通配符 DNS 创建,preview 域名可能无法访问"));else if(b.created)n.message(`✅ 通配符 DNS 已创建: ${b.wildcardDomain} → ${b.targetValue}`);if(n.message(`正在绑定预览域名 ${F}...`),await Tt(F,w,E,{skipDnsBind:!0}),e.enableSSL)n.message(`预览域名绑定完成,正在签发 HTTPS 证书 (${F})...`),await Fn(F,n,{forceRenew:e.forceSslRenew})}else if(e.releaseTarget)n.message(`函数部署完成,正在发布版本并切流到 ${e.releaseTarget}...`),H=await Sn(e.appName,`deploy ${e.releaseTarget} at ${new Date().toISOString()}`),await qn(e.appName,e.releaseTarget,H,`deployed by licell at ${new Date().toISOString()}`);if(e.domainSuffix){T=`${e.appName}.${e.domainSuffix}`,n.message(`函数部署完成,正在按固定规则绑定域名 ${T}...`),await Tt(T,w,e.releaseTarget,{skipDnsBind:e.enableCdn});let b;if(e.enableSSL){n.message(`固定域名绑定完成,正在签发并挂载 HTTPS 证书 (${T})...`);let x=await Fn(T,n,{forceRenew:e.forceSslRenew});b={certificate:x.certificate,privateKey:x.privateKey}}if(e.enableCdn){n.message(`固定域名绑定完成,正在启用 CDN 加速 (${T})...`);let x=await Xt(T,w,b);if(n.message(x.created?`✅ CDN 加速已启用,CNAME=${x.cdnCname}`:`✅ CDN 加速已存在,已校准 DNS 到 CNAME=${x.cdnCname}`),e.enableSSL&&x.httpsConfigured)n.message("✅ CDN 边缘 HTTPS 已自动配置。");if(e.enableSSL&&!x.httpsConfigured)n.message("⚠️ 未能自动配置 CDN 边缘 HTTPS(未获取到可用证书),请在 CDN 控制台补充证书。")}}if(e.cliDomain){T=e.cliDomain,n.message(`函数部署完成,正在绑定自定义域名 ${T}...`),await Tt(T,w,e.releaseTarget,{skipDnsBind:e.enableCdn});let b;if(e.enableSSL){n.message(`自定义域名绑定完成,正在签发并挂载 HTTPS 证书 (${T})...`);let x=await Fn(T,n,{forceRenew:e.forceSslRenew});b={certificate:x.certificate,privateKey:x.privateKey}}if(e.enableCdn){n.message(`自定义域名绑定完成,正在启用 CDN 加速 (${T})...`);let x=await Xt(T,w,b);if(n.message(x.created?`✅ CDN 加速已启用,CNAME=${x.cdnCname}`:`✅ CDN 加速已存在,已校准 DNS 到 CNAME=${x.cdnCname}`),e.enableSSL&&x.httpsConfigured)n.message("✅ CDN 边缘 HTTPS 已自动配置。");if(e.enableSSL&&!x.httpsConfigured)n.message("⚠️ 未能自动配置 CDN 边缘 HTTPS(未获取到可用证书),请在 CDN 控制台补充证书。")}}return{url:a,promotedVersion:H,fixedDomain:T,previewDomain:F,previewVersion:E}});if(!u)return;let{url:l,promotedVersion:i,fixedDomain:g,previewDomain:y,previewVersion:d}=u;n.message("\uD83E\uDE7A 部署完成,正在做可访问性检测...");let p=[],h=await an(l);if(h.ok)p.push(`✅ 生产地址可访问 (${h.statusCode} ${h.checkedUrl})`);else p.push(`⚠️ 生产地址可访问性检测未通过: ${h.error}`);if(g){let _=`${e.enableSSL?"https":"http"}://${g}`,$=e.enableCdn?10:6,a=e.enableCdn?3000:2000,w=e.enableCdn?6000:5000,H=await an(_,{maxAttempts:$,intervalMs:a,timeoutMs:w});if(H.ok)p.push(`✅ 固定域名可访问 (${H.statusCode} ${H.checkedUrl})`);else p.push(`⚠️ 固定域名检测未通过(可能 DNS 传播中): ${H.error}`)}if(y){let _=`${e.enableSSL?"https":"http"}://${y}`,$=await an(_,{maxAttempts:8,intervalMs:2000,timeoutMs:5000});if($.ok)p.push(`✅ 预览域名可访问 (${$.statusCode} ${$.checkedUrl})`);else p.push(`⚠️ 预览域名检测未通过(可能 DNS 传播中): ${$.error}`)}return{url:l,promotedVersion:i,fixedDomain:g,previewDomain:y,previewVersion:d,healthCheckLogs:p}}j();import{confirm as gp,text as yp,isCancel as dp}from"@clack/prompts";import hp from"picocolors";j();import I0,*as Qe from"@alicloud/oss20190517";import*as xn from"@alicloud/openapi-client";import F0 from"@alicloud/openapi-util";import*as Vi from"@alicloud/tea-util";import{createReadStream as x0,existsSync as H0,lstatSync as L0,readdirSync as A0,realpathSync as Wi,statSync as U0}from"fs";import{isAbsolute as v0,join as G0,relative as M0}from"path";import vr from"mime-types";function Ur(e){if(!Number.isInteger(e)||e<1)throw Error("concurrency must be a positive integer");let n=0,t=[];function s(){if(t.length>0&&n<e)n+=1,t.shift()()}return async function(r){if(n>=e)await new Promise((c)=>t.push(c));else n+=1;try{return await r()}finally{n-=1,s()}}}Cn();Fe();var Di=(()=>{let e=F0;if(typeof e?.query==="function")return e;if(typeof e?.default?.query==="function")return e.default;throw Error("Cannot resolve @alicloud/openapi-util")})(),Y0=10,B0=8000,K0=120000,O0=se(I0,"@alicloud/oss20190517"),j0="application/octet-stream";function Ct(){let e=k.requireAuth(),n=new O0(new xn.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:e.region,endpoint:`oss-${e.region}.aliyuncs.com`})),t=new Vi.RuntimeOptions({connectTimeout:B0,readTimeout:K0});return{auth:e,client:n,runtime:t}}function W0(e){if(!xe(e))return!1;return String(e?.message||"").toLowerCase().includes("put public bucket acl is not allowed")}function Q0(e){if(!String(e?.message||"").toLowerCase().includes("not a valid value for parameter"))return!1;let t=String(e?.stack||"").toLowerCase();return t.includes("gateway-oss")||t.includes("darabonba-map")}async function D0(e,n,t){try{await e.getBucketInfoWithOptions(n,{},t)}catch(s){if(xe(s))throw Error(`OSS Bucket 已被占用且当前账号无权限访问: ${n},请更换 appName 后重试`);throw s}}function Xi(e){if(Array.isArray(e))return e;if(e===void 0||e===null)return[];return[e]}function Zi(e){if(typeof e==="boolean")return e;if(typeof e==="string")return e.trim().toLowerCase()==="true";return!1}function Qi(e){if(typeof e==="number"&&Number.isFinite(e))return e;if(typeof e==="string"){let n=Number(e);if(Number.isFinite(n))return n}return}function ee(e){if(typeof e!=="string")return;let n=e.trim();return n.length>0?n:void 0}async function V0(e,n,t){let s=new xn.OpenApiRequest({headers:{},query:Di.query({...t.marker?{marker:t.marker}:{},"max-keys":t.maxKeys})}),o=new xn.Params({action:"ListBuckets",version:"2019-05-17",protocol:"HTTPS",pathname:"/",method:"GET",authType:"AK",style:"ROA",reqBodyType:"xml",bodyType:"xml"}),c=(await e.execute(o,s,n)).body||{},f=c.Buckets||c.buckets||{};return{rows:Xi(f.Bucket||f.bucket),nextMarker:ee(c.NextMarker)||ee(c.nextMarker),isTruncated:Zi(c.IsTruncated??c.isTruncated)}}async function Ji(e,n,t,s){let o=new xn.OpenApiRequest({hostMap:{bucket:t},headers:{},query:Di.query({...s.prefix?{prefix:s.prefix}:{},...s.continuationToken?{"continuation-token":s.continuationToken}:{},"max-keys":s.maxKeys})}),r=new xn.Params({action:"ListObjectsV2",version:"2019-05-17",protocol:"HTTPS",pathname:"/?list-type=2",method:"GET",authType:"AK",style:"ROA",reqBodyType:"xml",bodyType:"xml"}),f=(await e.execute(r,o,n)).body||{};return{rows:Xi(f.Contents||f.contents),nextContinuationToken:ee(f.NextContinuationToken)||ee(f.nextContinuationToken),isTruncated:Zi(f.IsTruncated??f.isTruncated)}}function Gr(e){if(!e)return;let n=e.trim().replace(/\\/g,"/").replace(/\/{2,}/g,"/").replace(/^\/+|\/+$/g,"");return n.length>0?n:void 0}function X0(e,n){let t=e.replace(/\\/g,"/").replace(/^\/+/,"");if(!t||t===".")throw Error("对象路径不能为空");let s=Gr(n);return s?`${s}/${t}`:t}function Z0(e,n){if(!H0(e)||!U0(e).isDirectory())throw Error(`本地目录不存在或不可读: ${e}`);let t=Wi(e),s=Gr(n),o=new Set([t]),r=[],c=0;function f(u){for(let l of A0(u)){let i=G0(u,l),g=L0(i);if(g.isSymbolicLink()){c+=1;continue}if(g.isDirectory()){let d=Wi(i);if(o.has(d))continue;o.add(d),f(d);continue}if(!g.isFile())continue;let y=M0(t,i).replace(/\\/g,"/");if(!y||y==="."||y.startsWith("..")||v0(y))throw Error(`检测到越界路径,已拒绝上传: ${i}`);r.push({sourceFile:i,objectName:X0(y,s)})}}return f(t),{sourceRoot:t,files:r,skippedSymlinkCount:c}}function J0(e,n){let t=n?vr.lookup(n):!1,s=vr.lookup(e),o=t||s;if(!o)return j0;let r=vr.contentType(o);return r?String(r):String(o)}async function Mr(e,n,t){let{auth:s,client:o,runtime:r}=Ct(),c=e.trim();if(!c)throw Error("bucket 名称不能为空");let f=Gr(t?.targetDir),u=Z0(n,f),l=Number.isFinite(t?.concurrency)&&Number(t?.concurrency||0)>0?Math.floor(Number(t?.concurrency)):Y0,i=Ur(l);return await Promise.all(u.files.map((g)=>i(async()=>{let y=J0(g.sourceFile,g.objectName);await B(()=>o.putObjectWithOptions(c,g.objectName,new Qe.PutObjectRequest({body:x0(g.sourceFile)}),new Qe.PutObjectHeaders({commonHeaders:{"content-type":y}}),r),{maxAttempts:4,baseDelayMs:1000,shouldRetry:Ee})}))),{bucket:c,targetDir:f,uploadedCount:u.files.length,baseUrl:`https://${c}.oss-${s.region}.aliyuncs.com`,skippedSymlinkCount:u.skippedSymlinkCount}}async function Yr(e,n,t){let{auth:s,client:o,runtime:r}=Ct(),c=`licell-${e}-${s.accountId.substring(0,4)}`.toLowerCase();try{await o.putBucketWithOptions(c,new Qe.PutBucketRequest({}),new Qe.PutBucketHeaders({}),r)}catch(l){if(!N(l)&&!Q0(l))throw l;await D0(o,c,r)}let f=!1;try{await o.putBucketAclWithOptions(c,new Qe.PutBucketAclHeaders({acl:"public-read"}),r)}catch(l){if(W0(l))f=!0;if(!f&&xe(l))throw Error(`OSS Bucket 无权限修改 ACL: ${c},请确认该 Bucket 属于当前账号并可写`);if(!f)throw l}return(await Mr(c,n,{targetDir:t?.targetDir})).baseUrl}function Qs(e){let n=k.requireAuth();return`licell-${e}-${n.accountId.substring(0,4)}`.toLowerCase()}async function Ni(e=200){let n=Math.max(1,Math.min(Math.floor(e),1000)),t=Math.min(100,n),{client:s,runtime:o}=Ct(),r=[],c;while(r.length<n){let f=await V0(s,o,{marker:c,maxKeys:t}),u=f.rows;for(let l of u){let i=ee(l.Name)||ee(l.name)||"";if(!i)continue;if(r.push({name:i,location:ee(l.Region)||ee(l.region)||ee(l.Location)||ee(l.location),creationDate:ee(l.CreationDate)||ee(l.creationDate),extranetEndpoint:ee(l.ExtranetEndpoint)||ee(l.extranetEndpoint),intranetEndpoint:ee(l.IntranetEndpoint)||ee(l.intranetEndpoint)}),r.length>=n)break}if(c=f.nextMarker,!f.isTruncated||!c||u.length===0)break}return r}async function zi(e){let{client:n,runtime:t}=Ct(),s=e.trim();if(!s)throw Error("bucket 名称不能为空");let r=(await n.getBucketInfoWithOptions(s,{},t)).body?.bucket;return{name:r?.name||s,location:r?.location,creationDate:r?.creationDate,extranetEndpoint:r?.extranetEndpoint,intranetEndpoint:r?.intranetEndpoint}}async function Ds(e,n,t=200){let{client:s,runtime:o}=Ct(),r=e.trim();if(!r)throw Error("bucket 名称不能为空");let c=Math.max(1,Math.min(Math.floor(t),2000)),f=Math.min(1000,c),u=[],l;while(u.length<c){let i=await Ji(s,o,r,{prefix:n,continuationToken:l,maxKeys:f}),g=i.rows;for(let y of g){let d=ee(y.Key)||ee(y.key);if(!d)continue;if(u.push({name:d,size:Qi(y.Size)??Qi(y.size),lastModified:ee(y.LastModified)||ee(y.lastModified),etag:ee(y.ETag)||ee(y.etag),type:ee(y.Type)||ee(y.type),storageClass:ee(y.StorageClass)||ee(y.storageClass)}),u.length>=c)break}if(l=i.nextContinuationToken,!i.isTruncated||!l||g.length===0)break}return u}async function el(e){let{client:n,runtime:t}=Ct(),s=e.trim();if(!s)throw Error("bucket 名称不能为空");let o=0,r,c=Ur(8);while(!0){let f=[];try{let u=await B(()=>Ji(n,t,s,{continuationToken:r,maxKeys:1000}),{maxAttempts:5,baseDelayMs:800,shouldRetry:Ee});f=u.rows,r=u.nextContinuationToken;let l=f.map((i)=>ee(i.Key)||ee(i.key)||"").filter((i)=>i.length>0);if(l.length>0)await Promise.all(l.map((i)=>c(async()=>{await B(()=>n.deleteObjectWithOptions(s,i,new Qe.DeleteObjectRequest({}),{},t),{maxAttempts:5,baseDelayMs:500,shouldRetry:Ee})}))),o+=l.length;if(!u.isTruncated||!r)break}catch(u){if(ie(u))return{bucket:s,deletedObjects:0,deletedBucket:!1};throw u}}try{return await B(()=>n.deleteBucketWithOptions(s,{},t),{maxAttempts:5,baseDelayMs:800,shouldRetry:Ee}),{bucket:s,deletedObjects:o,deletedBucket:!0}}catch(f){if(ie(f))return{bucket:s,deletedObjects:o,deletedBucket:!1};throw f}}import{existsSync as sl,readdirSync as N0,statSync as ol}from"fs";import{join as Br}from"path";var nl=["dist","build","out","public","www","site",".output/public"];function z0(e){if(!sl(e))return!1;try{return ol(e).isDirectory()}catch{return!1}}function tl(e){let n=Br(e,"index.html");if(!sl(n))return!1;try{return ol(n).isFile()}catch{return!1}}function ep(e){if(!z0(e))return!1;try{return N0(e).length>0}catch{return!1}}function rl(e=process.cwd()){if(tl(e))return".";for(let n of nl){let t=Br(e,n);if(tl(t))return n}for(let n of nl){let t=Br(e,n);if(ep(t))return n}return"dist"}oe();j();fe();Cn();Xn();cr();import*as ve from"@alicloud/fc20230330";import{mkdirSync as np,rmSync as tp,writeFileSync as sp}from"fs";import{tmpdir as op}from"os";import{join as cl}from"path";var rp="-static-proxy",cp=256,fp=30,up=0.25;function ip(e,n){return`
|
|
1401
|
+
`+`已有的精确 DNS 记录(如 ${n.appName}.${n.domainSuffix})不受影响。
|
|
1402
|
+
`+"是否创建?"});if(hg(P))return!1;return P}});if(b.skipped)e.message(mg.yellow("⚠️ 已跳过通配符 DNS 创建,preview 域名可能无法访问"));else if(b.created)e.message(`✅ 通配符 DNS 已创建: ${b.wildcardDomain} → ${b.targetValue}`);if(e.message(`正在绑定预览域名 ${L}...`),await Rt(L,h,S,{skipDnsBind:!0}),n.enableSSL)e.message(`预览域名绑定完成,正在签发 HTTPS 证书 (${L})...`),await Me(L,e,{forceRenew:n.forceSslRenew})}else if(n.releaseTarget)e.message(`函数部署完成,正在发布版本并切流到 ${n.releaseTarget}...`),U=await xe(n.appName,`deploy ${n.releaseTarget} at ${new Date().toISOString()}`),await Le(n.appName,n.releaseTarget,U,`deployed by licell at ${new Date().toISOString()}`);if(n.domainSuffix){C=`${n.appName}.${n.domainSuffix}`,e.message(`函数部署完成,正在按固定规则绑定域名 ${C}...`),await Rt(C,h,n.releaseTarget,{skipDnsBind:n.enableCdn});let b;if(n.enableSSL){e.message(`固定域名绑定完成,正在签发并挂载 HTTPS 证书 (${C})...`);let P=await Me(C,e,{forceRenew:n.forceSslRenew});b={certificate:P.certificate,privateKey:P.privateKey}}if(n.enableCdn){e.message(`固定域名绑定完成,正在启用 CDN 加速 (${C})...`);let P=await ns(C,h,b);if(e.message(P.created?`✅ CDN 加速已启用,CNAME=${P.cdnCname}`:`✅ CDN 加速已存在,已校准 DNS 到 CNAME=${P.cdnCname}`),n.enableSSL&&P.httpsConfigured)e.message("✅ CDN 边缘 HTTPS 已自动配置。");if(n.enableSSL&&!P.httpsConfigured)e.message("⚠️ 未能自动配置 CDN 边缘 HTTPS(未获取到可用证书),请在 CDN 控制台补充证书。")}}if(n.cliDomain){C=n.cliDomain,e.message(`函数部署完成,正在绑定自定义域名 ${C}...`),await Rt(C,h,n.releaseTarget,{skipDnsBind:n.enableCdn});let b;if(n.enableSSL){e.message(`自定义域名绑定完成,正在签发并挂载 HTTPS 证书 (${C})...`);let P=await Me(C,e,{forceRenew:n.forceSslRenew});b={certificate:P.certificate,privateKey:P.privateKey}}if(n.enableCdn){e.message(`自定义域名绑定完成,正在启用 CDN 加速 (${C})...`);let P=await ns(C,h,b);if(e.message(P.created?`✅ CDN 加速已启用,CNAME=${P.cdnCname}`:`✅ CDN 加速已存在,已校准 DNS 到 CNAME=${P.cdnCname}`),n.enableSSL&&P.httpsConfigured)e.message("✅ CDN 边缘 HTTPS 已自动配置。");if(n.enableSSL&&!P.httpsConfigured)e.message("⚠️ 未能自动配置 CDN 边缘 HTTPS(未获取到可用证书),请在 CDN 控制台补充证书。")}}return{url:y,promotedVersion:U,fixedDomain:C,previewDomain:L,previewVersion:S}});if(!u)return;let{url:g,promotedVersion:i,fixedDomain:l,previewDomain:d,previewVersion:a}=u;e.message("\uD83E\uDE7A 部署完成,正在做可访问性检测...");let p=[],w=await $e(g);if(w.ok)p.push(`✅ 生产地址可访问 (${w.statusCode} ${w.checkedUrl})`);else p.push(`⚠️ 生产地址可访问性检测未通过: ${w.error}`);if(l){let _=`${n.enableSSL?"https":"http"}://${l}`,$=n.enableCdn?10:6,y=n.enableCdn?3000:2000,h=n.enableCdn?6000:5000,U=await $e(_,{maxAttempts:$,intervalMs:y,timeoutMs:h});if(U.ok)p.push(`✅ 固定域名可访问 (${U.statusCode} ${U.checkedUrl})`);else p.push(`⚠️ 固定域名检测未通过(可能 DNS 传播中): ${U.error}`)}if(d){let _=`${n.enableSSL?"https":"http"}://${d}`,$=await $e(_,{maxAttempts:8,intervalMs:2000,timeoutMs:5000});if($.ok)p.push(`✅ 预览域名可访问 (${$.statusCode} ${$.checkedUrl})`);else p.push(`⚠️ 预览域名检测未通过(可能 DNS 传播中): ${$.error}`)}return{url:g,promotedVersion:i,fixedDomain:l,previewDomain:d,previewVersion:a,healthCheckLogs:p}}Q();import{confirm as Xw,text as Zw,isCancel as Ow}from"@clack/prompts";import Jw from"picocolors";Q();import yw,*as Nn from"@alicloud/oss20190517";import*as Ge from"@alicloud/openapi-client";import aw from"@alicloud/openapi-util";import*as _g from"@alicloud/tea-util";import{createReadStream as hw,existsSync as mw,lstatSync as ww,readdirSync as pw,realpathSync as $g,statSync as $w}from"fs";import{isAbsolute as bw,join as kw,relative as _w}from"path";import Wo from"mime-types";function Do(n){if(!Number.isInteger(n)||n<1)throw Error("concurrency must be a positive integer");let e=0,t=[];function s(){if(t.length>0&&e<n)e+=1,t.shift()()}return async function(o){if(e>=n)await new Promise((c)=>t.push(c));else e+=1;try{return await o()}finally{e-=1,s()}}}Pe();xn();var kg=(()=>{let n=aw;if(typeof n?.query==="function")return n;if(typeof n?.default?.query==="function")return n.default;throw Error("Cannot resolve @alicloud/openapi-util")})(),Cw=10,Sw=8000,Tw=120000,Ew=nn(yw,"@alicloud/oss20190517"),qw="application/octet-stream";function Pt(){let n=k.requireAuth(),e=new Ew(new Ge.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:`oss-${n.region}.aliyuncs.com`})),t=new _g.RuntimeOptions({connectTimeout:Sw,readTimeout:Tw});return{auth:n,client:e,runtime:t}}function Iw(n){if(!Gn(n))return!1;return String(n?.message||"").toLowerCase().includes("put public bucket acl is not allowed")}function Rw(n){if(!String(n?.message||"").toLowerCase().includes("not a valid value for parameter"))return!1;let t=String(n?.stack||"").toLowerCase();return t.includes("gateway-oss")||t.includes("darabonba-map")}async function Pw(n,e,t){try{await n.getBucketInfoWithOptions(e,{},t)}catch(s){if(Gn(s))throw Error(`OSS Bucket 已被占用且当前账号无权限访问: ${e},请更换 appName 后重试`);throw s}}function Cg(n){if(Array.isArray(n))return n;if(n===void 0||n===null)return[];return[n]}function Sg(n){if(typeof n==="boolean")return n;if(typeof n==="string")return n.trim().toLowerCase()==="true";return!1}function bg(n){if(typeof n==="number"&&Number.isFinite(n))return n;if(typeof n==="string"){let e=Number(n);if(Number.isFinite(e))return e}return}function rn(n){if(typeof n!=="string")return;let e=n.trim();return e.length>0?e:void 0}async function Fw(n,e,t){let s=new Ge.OpenApiRequest({headers:{},query:kg.query({...t.marker?{marker:t.marker}:{},"max-keys":t.maxKeys})}),r=new Ge.Params({action:"ListBuckets",version:"2019-05-17",protocol:"HTTPS",pathname:"/",method:"GET",authType:"AK",style:"ROA",reqBodyType:"xml",bodyType:"xml"}),c=(await n.execute(r,s,e)).body||{},f=c.Buckets||c.buckets||{};return{rows:Cg(f.Bucket||f.bucket),nextMarker:rn(c.NextMarker)||rn(c.nextMarker),isTruncated:Sg(c.IsTruncated??c.isTruncated)}}async function Tg(n,e,t,s){let r=new Ge.OpenApiRequest({hostMap:{bucket:t},headers:{},query:kg.query({...s.prefix?{prefix:s.prefix}:{},...s.continuationToken?{"continuation-token":s.continuationToken}:{},"max-keys":s.maxKeys})}),o=new Ge.Params({action:"ListObjectsV2",version:"2019-05-17",protocol:"HTTPS",pathname:"/?list-type=2",method:"GET",authType:"AK",style:"ROA",reqBodyType:"xml",bodyType:"xml"}),f=(await n.execute(o,r,e)).body||{};return{rows:Cg(f.Contents||f.contents),nextContinuationToken:rn(f.NextContinuationToken)||rn(f.nextContinuationToken),isTruncated:Sg(f.IsTruncated??f.isTruncated)}}function Vo(n){if(!n)return;let e=n.trim().replace(/\\/g,"/").replace(/\/{2,}/g,"/").replace(/^\/+|\/+$/g,"");return e.length>0?e:void 0}function xw(n,e){let t=n.replace(/\\/g,"/").replace(/^\/+/,"");if(!t||t===".")throw Error("对象路径不能为空");let s=Vo(e);return s?`${s}/${t}`:t}function Lw(n,e){if(!mw(n)||!$w(n).isDirectory())throw Error(`本地目录不存在或不可读: ${n}`);let t=$g(n),s=Vo(e),r=new Set([t]),o=[],c=0;function f(u){for(let g of pw(u)){let i=kw(u,g),l=ww(i);if(l.isSymbolicLink()){c+=1;continue}if(l.isDirectory()){let a=$g(i);if(r.has(a))continue;r.add(a),f(a);continue}if(!l.isFile())continue;let d=_w(t,i).replace(/\\/g,"/");if(!d||d==="."||d.startsWith("..")||bw(d))throw Error(`检测到越界路径,已拒绝上传: ${i}`);o.push({sourceFile:i,objectName:xw(d,s)})}}return f(t),{sourceRoot:t,files:o,skippedSymlinkCount:c}}function Aw(n,e){let t=e?Wo.lookup(e):!1,s=Wo.lookup(n),r=t||s;if(!r)return qw;let o=Wo.contentType(r);return o?String(o):String(r)}async function Qo(n,e,t){let{auth:s,client:r,runtime:o}=Pt(),c=n.trim();if(!c)throw Error("bucket 名称不能为空");let f=Vo(t?.targetDir),u=Lw(e,f),g=Number.isFinite(t?.concurrency)&&Number(t?.concurrency||0)>0?Math.floor(Number(t?.concurrency)):Cw,i=Do(g);return await Promise.all(u.files.map((l)=>i(async()=>{let d=Aw(l.sourceFile,l.objectName);await D(()=>r.putObjectWithOptions(c,l.objectName,new Nn.PutObjectRequest({body:hw(l.sourceFile)}),new Nn.PutObjectHeaders({commonHeaders:{"content-type":d}}),o),{maxAttempts:4,baseDelayMs:1000,shouldRetry:Rn})}))),{bucket:c,targetDir:f,uploadedCount:u.files.length,baseUrl:`https://${c}.oss-${s.region}.aliyuncs.com`,skippedSymlinkCount:u.skippedSymlinkCount}}async function Xo(n,e,t){let{auth:s,client:r,runtime:o}=Pt(),c=`licell-${n}-${s.accountId.substring(0,4)}`.toLowerCase();try{await r.putBucketWithOptions(c,new Nn.PutBucketRequest({}),new Nn.PutBucketHeaders({}),o)}catch(g){if(!tn(g)&&!Rw(g))throw g;await Pw(r,c,o)}let f=!1;try{await r.putBucketAclWithOptions(c,new Nn.PutBucketAclHeaders({acl:"public-read"}),o)}catch(g){if(Iw(g))f=!0;if(!f&&Gn(g))throw Error(`OSS Bucket 无权限修改 ACL: ${c},请确认该 Bucket 属于当前账号并可写`);if(!f)throw g}return(await Qo(c,e,{targetDir:t?.targetDir})).baseUrl}function nr(n){let e=k.requireAuth();return`licell-${n}-${e.accountId.substring(0,4)}`.toLowerCase()}async function Eg(n=200){let e=Math.max(1,Math.min(Math.floor(n),1000)),t=Math.min(100,e),{client:s,runtime:r}=Pt(),o=[],c;while(o.length<e){let f=await Fw(s,r,{marker:c,maxKeys:t}),u=f.rows;for(let g of u){let i=rn(g.Name)||rn(g.name)||"";if(!i)continue;if(o.push({name:i,location:rn(g.Region)||rn(g.region)||rn(g.Location)||rn(g.location),creationDate:rn(g.CreationDate)||rn(g.creationDate),extranetEndpoint:rn(g.ExtranetEndpoint)||rn(g.extranetEndpoint),intranetEndpoint:rn(g.IntranetEndpoint)||rn(g.intranetEndpoint)}),o.length>=e)break}if(c=f.nextMarker,!f.isTruncated||!c||u.length===0)break}return o}async function qg(n){let{client:e,runtime:t}=Pt(),s=n.trim();if(!s)throw Error("bucket 名称不能为空");let o=(await e.getBucketInfoWithOptions(s,{},t)).body?.bucket;return{name:o?.name||s,location:o?.location,creationDate:o?.creationDate,extranetEndpoint:o?.extranetEndpoint,intranetEndpoint:o?.intranetEndpoint}}async function er(n,e,t=200){let{client:s,runtime:r}=Pt(),o=n.trim();if(!o)throw Error("bucket 名称不能为空");let c=Math.max(1,Math.min(Math.floor(t),2000)),f=Math.min(1000,c),u=[],g;while(u.length<c){let i=await Tg(s,r,o,{prefix:e,continuationToken:g,maxKeys:f}),l=i.rows;for(let d of l){let a=rn(d.Key)||rn(d.key);if(!a)continue;if(u.push({name:a,size:bg(d.Size)??bg(d.size),lastModified:rn(d.LastModified)||rn(d.lastModified),etag:rn(d.ETag)||rn(d.etag),type:rn(d.Type)||rn(d.type),storageClass:rn(d.StorageClass)||rn(d.storageClass)}),u.length>=c)break}if(g=i.nextContinuationToken,!i.isTruncated||!g||l.length===0)break}return u}async function Ig(n){let{client:e,runtime:t}=Pt(),s=n.trim();if(!s)throw Error("bucket 名称不能为空");let r=0,o,c=Do(8);while(!0){let f=[];try{let u=await D(()=>Tg(e,t,s,{continuationToken:o,maxKeys:1000}),{maxAttempts:5,baseDelayMs:800,shouldRetry:Rn});f=u.rows,o=u.nextContinuationToken;let g=f.map((i)=>rn(i.Key)||rn(i.key)||"").filter((i)=>i.length>0);if(g.length>0)await Promise.all(g.map((i)=>c(async()=>{await D(()=>e.deleteObjectWithOptions(s,i,new Nn.DeleteObjectRequest({}),{},t),{maxAttempts:5,baseDelayMs:500,shouldRetry:Rn})}))),r+=g.length;if(!u.isTruncated||!o)break}catch(u){if(an(u))return{bucket:s,deletedObjects:0,deletedBucket:!1};throw u}}try{return await D(()=>e.deleteBucketWithOptions(s,{},t),{maxAttempts:5,baseDelayMs:800,shouldRetry:Rn}),{bucket:s,deletedObjects:r,deletedBucket:!0}}catch(f){if(an(f))return{bucket:s,deletedObjects:r,deletedBucket:!1};throw f}}import{existsSync as Fg,readdirSync as Hw,statSync as xg}from"fs";import{join as Zo}from"path";var Rg=["dist","build","out","public","www","site",".output/public"];function Uw(n){if(!Fg(n))return!1;try{return xg(n).isDirectory()}catch{return!1}}function Pg(n){let e=Zo(n,"index.html");if(!Fg(e))return!1;try{return xg(e).isFile()}catch{return!1}}function Mw(n){if(!Uw(n))return!1;try{return Hw(n).length>0}catch{return!1}}function Lg(n=process.cwd()){if(Pg(n))return".";for(let e of Rg){let t=Zo(n,e);if(Pg(t))return e}for(let e of Rg){let t=Zo(n,e);if(Mw(t))return e}return"dist"}on();Q();dn();Pe();et();ao();import*as Yn from"@alicloud/fc20230330";import{mkdirSync as Gw,rmSync as Bw,writeFileSync as vw}from"fs";import{tmpdir as Kw}from"os";import{join as Ag}from"path";var Yw="-static-proxy",jw=256,Dw=30,Ww=0.25;function Vw(n,e){return`
|
|
1403
1403
|
const https = require('https');
|
|
1404
1404
|
const crypto = require('crypto');
|
|
1405
1405
|
|
|
1406
|
-
const BUCKET = '${
|
|
1407
|
-
const REGION = '${
|
|
1406
|
+
const BUCKET = '${n}';
|
|
1407
|
+
const REGION = '${e}';
|
|
1408
1408
|
const OSS_HOST = \`\${BUCKET}.oss-\${REGION}.aliyuncs.com\`;
|
|
1409
1409
|
|
|
1410
1410
|
const MIME_TYPES = {
|
|
@@ -1566,86 +1566,86 @@ module.exports.handler = async (request, context) => {
|
|
|
1566
1566
|
};
|
|
1567
1567
|
}
|
|
1568
1568
|
};
|
|
1569
|
-
`.trim()}function
|
|
1569
|
+
`.trim()}function Ug(n){return`${n}${Yw}`}async function Oo(n,e,t){let s=k.requireAuth(),r=k.getProject(),{client:o}=hn(),c=Ug(n),f=Ag(Kw(),`licell-static-proxy-${Date.now()}-${process.pid}`);Gw(f,{recursive:!0});try{let u=Vw(e,s.region);vw(Ag(f,"index.js"),u);let g=lo(f),i=`acs:ram::${s.accountId}:role/AliyunFCDefaultRole`;return await Qw(o,c,g,r.envs||{},t,i,s)}finally{Bw(f,{recursive:!0,force:!0})}}async function Qw(n,e,t,s,r,o,c){let f={runtime:"nodejs20",handler:"index.handler",memorySize:jw,timeout:Dw,cpu:Ww,diskSize:512,instanceConcurrency:10,code:new Yn.InputCodeLocation({zipFile:t})};try{let g={...s,PREVIEW_PATH:r};return await Hg(n,e,{...f,role:o,environmentVariables:g})}catch(g){if(!Gn(g))throw g}let u={...s,PREVIEW_PATH:r,OSS_ACCESS_KEY_ID:c.ak,OSS_ACCESS_KEY_SECRET:c.sk};return await Hg(n,e,{...f,environmentVariables:u})}async function Hg(n,e,t){try{await D(()=>n.createFunction(new Yn.CreateFunctionRequest({body:new Yn.CreateFunctionInput({functionName:e,...t})})))}catch(s){if(!tn(s))throw s;await D(()=>n.updateFunction(e,new Yn.UpdateFunctionRequest({body:new Yn.UpdateFunctionInput(t)})))}return e}async function Jo(n,e){let{client:t}=hn(),s=Ug(n),o=(await D(()=>t.publishFunctionVersion(s,new Yn.PublishFunctionVersionRequest({body:new Yn.PublishVersionInput({description:e||`static preview at ${new Date().toISOString()}`})})))).body?.versionId;if(!o)throw Error("发布版本失败:未返回 versionId");return o}function Nw(n){if(n.cliDomain)return n.cliDomain;if(n.domainSuffix)return`${n.appName}.${n.domainSuffix}`;return}function zw(n){try{return new URL(n).host}catch{throw Error(`无法解析 OSS 源站域名: ${n}`)}}async function Mg(n,e){let t=Lg(),s=n.cliDist?K(n.cliDist,"构建产物目录"):n.interactiveTTY?K(await Zw({message:"前端构建产物目录:",initialValue:t}),"构建产物目录"):t;if(n.preview&&n.domainSuffix)return np(n,e,s);let r=Nw(n),o=await F(e,"☁️ 正在递归上传静态资源到 OSS 边缘节点...","❌ 部署失败",async()=>{let g=await Xo(n.appName,s);if(!r)return{url:g,fixedDomain:void 0};let i=zw(g),l;if(n.enableSSL){e.message(`静态资源上传完成,正在签发 HTTPS 证书 (${r})...`);let a=await Me(r,e,{forceRenew:n.forceSslRenew,bindToFcDomain:!1});l={certificate:a.certificate,privateKey:a.privateKey}}e.message(`静态资源上传完成,正在接入 CDN 并回源 OSS (${r})...`);let d=await ns(r,i,{...l,sourceType:"oss"});if(e.message(d.created?`✅ CDN 加速已启用,CNAME=${d.cdnCname}`:`✅ CDN 加速已存在,已校准 DNS 到 CNAME=${d.cdnCname}`),n.enableSSL&&d.httpsConfigured)e.message("✅ CDN 边缘 HTTPS 已自动配置。");if(n.enableSSL&&!d.httpsConfigured)e.message("⚠️ 未能自动配置 CDN 边缘 HTTPS(未获取到可用证书),请在 CDN 控制台补充证书。");return{url:g,fixedDomain:r}});if(!o)return;let{url:c}=o,f=[];e.message("\uD83E\uDE7A 部署完成,正在做可访问性检测...");let u=await $e(c,{paths:["/"],maxAttempts:5,intervalMs:1500,timeoutMs:5000,allowClientError:!1});if(u.ok)f.push(`✅ OSS 地址可访问 (${u.statusCode} ${u.checkedUrl})`);else f.push(`⚠️ OSS 地址可访问性检测未通过: ${u.error}`);if(o.fixedDomain){let g=`${n.enableSSL?"https":"http"}://${o.fixedDomain}`,i=await $e(g,{paths:["/"],maxAttempts:n.enableCdn?10:6,intervalMs:n.enableCdn?3000:2000,timeoutMs:n.enableCdn?6000:5000,allowClientError:!1});if(i.ok)f.push(`✅ 固定域名可访问 (${i.statusCode} ${i.checkedUrl})`);else f.push(`⚠️ 固定域名检测未通过(可能 DNS/CDN 传播中): ${i.error}`)}return{...o,healthCheckLogs:f}}async function np(n,e,t){let s=k.requireAuth(),r=nr(n.appName),o=`${s.accountId}.${s.region}.fc.aliyuncs.com`,c=await F(e,"☁️ 正在部署静态预览...","❌ 部署失败",async()=>{e.message("正在部署静态代理函数...");let d=await Oo(n.appName,r,"_preview/pending");e.message("正在分配预览版本号...");let p=`_preview/${await Jo(n.appName)}`;e.message(`正在上传静态资源到 OSS (${p})...`);let w=await Xo(n.appName,t,{targetDir:p});e.message("正在更新代理函数并发布最终版本..."),await Oo(n.appName,r,p);let _=await Jo(n.appName),$=`${n.appName}-preview-v${_}.${n.domainSuffix}`;e.message(`正在确保通配符 DNS (*.${n.domainSuffix}) 存在...`);let y=await Os(n.domainSuffix,o,{interactiveTTY:n.interactiveTTY,onConfirm:async()=>{let h=await Xw({message:`检测到尚未配置通配符 DNS (*.${n.domainSuffix})。
|
|
1570
1570
|
`+`创建后,所有 preview 子域名将自动解析到 FC 网关。
|
|
1571
|
-
`+`已有的精确 DNS 记录(如 ${
|
|
1572
|
-
`+"是否创建?"});if(
|
|
1573
|
-
- runtime=${
|
|
1574
|
-
✅ 预检通过`));else{for(let
|
|
1575
|
-
${c} [${
|
|
1576
|
-
⚠️ 预检通过(存在 warning)`));else console.log(
|
|
1577
|
-
❌ 预检失败(存在 error)`))}if(!
|
|
1578
|
-
\uD83C\uDF89 Production URL: ${
|
|
1579
|
-
`),
|
|
1580
|
-
`),console.log(
|
|
1581
|
-
`))}if(
|
|
1582
|
-
`)}if(
|
|
1583
|
-
`);if(!
|
|
1584
|
-
`));if(
|
|
1571
|
+
`+`已有的精确 DNS 记录(如 ${n.appName}.${n.domainSuffix})不受影响。
|
|
1572
|
+
`+"是否创建?"});if(Ow(h))return!1;return h}});if(y.skipped)e.message(Jw.yellow("⚠️ 已跳过通配符 DNS 创建,preview 域名可能无法访问"));else if(y.created)e.message(`✅ 通配符 DNS 已创建: ${y.wildcardDomain} → ${y.targetValue}`);if(e.message(`正在绑定预览域名 ${$}...`),await ep($,o,d,_),n.enableSSL)e.message(`预览域名绑定完成,正在签发 HTTPS 证书 (${$})...`),await Me($,e,{forceRenew:n.forceSslRenew});return{url:w,previewDomain:$,previewVersion:_}});if(!c)return;let{url:f,previewDomain:u,previewVersion:g}=c,i=[];e.message("\uD83E\uDE7A 部署完成,正在做可访问性检测...");let l=await $e(f,{paths:["/"],maxAttempts:5,intervalMs:1500,timeoutMs:5000,allowClientError:!1});if(l.ok)i.push(`✅ OSS 地址可访问 (${l.statusCode} ${l.checkedUrl})`);else i.push(`⚠️ OSS 地址可访问性检测未通过: ${l.error}`);if(u){let d=`${n.enableSSL?"https":"http"}://${u}`,a=await $e(d,{maxAttempts:8,intervalMs:2000,timeoutMs:5000});if(a.ok)i.push(`✅ 预览域名可访问 (${a.statusCode} ${a.checkedUrl})`);else i.push(`⚠️ 预览域名检测未通过(可能 DNS 传播中): ${a.error}`)}return{url:f,previewDomain:u,previewVersion:g,healthCheckLogs:i}}async function ep(n,e,t,s){let{createSharedFcClient:r}=await Promise.resolve().then(() => (xn(),Tf)),o=await import("@alicloud/fc20230330"),{isConflictError:c}=await Promise.resolve().then(() => Ef),{client:f}=r(),u={routes:[{path:"/*",functionName:t,qualifier:s}]},g=new o.CreateCustomDomainInput({domainName:n,protocol:"HTTP",routeConfig:u});try{await f.createCustomDomain(new o.CreateCustomDomainRequest({body:g}))}catch(i){if(!c(i))throw i;await f.updateCustomDomain(n,new o.UpdateCustomDomainRequest({body:new o.UpdateCustomDomainInput({routeConfig:u})}))}}function tp(n){let e=[];if(n.type==="api"){if(e.push("fc"),(n.cliRuntime||n.projectRuntime||n.envRuntime||"").trim().toLowerCase()==="docker")e.push("cr");if(n.useVpc)e.push("vpc")}else e.push("oss");if(n.cliDomain||n.domainSuffix)e.push("dns");if(n.enableCdn)e.push("cdn");return[...new Set(e)]}function No(n){if(n&&n.trim()){let s=Zs(n);if(s.deployTypeHint==="static")throw Error("deploy spec/check 仅适用于 FC API runtime(不要传 static/statis)");if(s.runtime)return s.runtime;throw Error(`无法解析 runtime: ${n}`)}let e=Ct(k.getProject().runtime),t=Ct(un(process.env,"FC_RUNTIME"));return e||t||ze}function sp(n,e){if(m()){let o=e||!n?Ot():{runtime:Zt(No(n))};T({stage:"deploy.spec",...o});return}if(e||!n){let o=Ot();console.log(`${z.bold("FC API Deploy Spec")}`),console.log(`runtime: ${o.runtimes.map((c)=>c.runtime).join(", ")}`),console.log(`defaults: memory=${o.resources.defaults.memoryMb}MB, vcpu=${o.resources.defaults.vcpu}, timeout=${o.resources.defaults.timeoutSeconds}s, instanceConcurrency=${o.resources.defaults.instanceConcurrency}`),console.log(`constraint: ${o.resources.constraints.memoryToVcpuRatio.expression}`);for(let c of o.runtimes){if(console.log(`
|
|
1573
|
+
- runtime=${z.cyan(c.runtime)} (${c.mode})`),console.log(` entry: ${c.defaultEntry||"(按 Dockerfile/项目自动推断)"}`),console.log(` entryRule: ${c.entryRule}`),console.log(` handlerRule: ${c.handlerRule}`),c.handlerContract.signature)console.log(` signature: ${c.handlerContract.signature}`);console.log(` acceptedResponse: ${c.responseSchema.acceptedForms.join(" | ")}`),console.log(` example(pass): ${c.examples.minimalPassExample}`),console.log(` example(fail): ${c.examples.commonFailExample}`);for(let f of c.notes)console.log(` note: ${f}`)}return}let t=No(n),s=Zt(t),r=Ot();if(console.log(`${z.bold("FC API Deploy Spec")}`),console.log(`runtime: ${z.cyan(s.runtime)} (${s.mode})`),console.log(`entry: ${s.defaultEntry||"(按 Dockerfile/项目自动推断)"}`),console.log(`entryRule: ${s.entryRule}`),console.log(`handlerRule: ${s.handlerRule}`),s.handlerContract.signature)console.log(`signature: ${s.handlerContract.signature}`);console.log(`acceptedResponse: ${s.responseSchema.acceptedForms.join(" | ")}`),console.log(`example(pass): ${s.examples.minimalPassExample}`),console.log(`example(fail): ${s.examples.commonFailExample}`);for(let o of s.notes)console.log(`note: ${o}`);console.log(`resources: default memory=${r.resources.defaults.memoryMb}MB, vcpu=${r.resources.defaults.vcpu}, timeout=${r.resources.defaults.timeoutSeconds}s`),console.log(`constraints: ${r.resources.constraints.memoryToVcpuRatio.expression}`)}function rp(n){let e=No(n.runtime),t=Zt(e),s=n.entry?.trim()||t.defaultEntry||void 0,r=Jt({runtime:e,entry:s,checkDockerDaemon:Boolean(n.dockerDaemon)});if(m())T({stage:"deploy.check",...r});else if(console.log(`${z.bold("FC API Deploy Precheck")}`),console.log(`runtime: ${z.cyan(r.runtime)}`),console.log(`entry: ${r.entry||"-"}`),r.issues.length===0)console.log(z.green(`
|
|
1574
|
+
✅ 预检通过`));else{for(let o of r.issues){let c=o.level==="error"?z.red("✖"):z.yellow("⚠");if(console.log(`
|
|
1575
|
+
${c} [${o.level}] ${o.id}`),console.log(o.message),o.remediation&&o.remediation.length>0)for(let f of o.remediation)console.log(` - ${f}`)}if(r.ok)console.log(z.yellow(`
|
|
1576
|
+
⚠️ 预检通过(存在 warning)`));else console.log(z.red(`
|
|
1577
|
+
❌ 预检失败(存在 error)`))}if(!r.ok)process.exitCode=1}function Gg(n){n.command("deploy spec [runtime]","查看 FC API 部署规格(给 Agent/开发者在 deploy 前对照)").option("--all","输出全部 runtime 规格").action((e,t)=>{try{sp(e,t.all)}catch(s){if(m())pn(s,{stage:"deploy.spec"});else console.error(A(s));process.exitCode=1}}),n.command("deploy check","本地预检 FC API 入口与 runtime 约束(建议 deploy 前执行)").option("--runtime <runtime>","FC runtime:nodejs20/nodejs22/python3.12/python3.13/docker").option("--entry <entry>","入口文件路径(默认按 runtime 推断)").option("--docker-daemon","runtime=docker 时额外检测本机 Docker daemon 可用性").action((e)=>{try{rp(e)}catch(t){if(m())pn(t,{stage:"deploy.check"});else console.error(A(t));process.exitCode=1}}),n.command("deploy","一键极速打包部署").option("--type <type>","部署类型:api 或 static(适配 CI 非交互场景)").option("--entry <entry>","API 入口文件(Node 默认 src/index.ts;Python 默认 src/main.py)").option("--dist <dist>","静态站点目录(默认 dist)").option("--runtime <runtime>","运行时(API: nodejs20/nodejs22/python3.12/python3.13/docker;静态站: static/statis)").option("--target <target>","API 部署后自动发布并切流到该 alias(如 prod/preview)").option("--preview","生成预览部署(自动发版 + 绑定预览域名,不影响生产)").option("--domain <domain>","绑定完整自定义域名(如 api.your-domain.xyz)").option("--domain-suffix <suffix>","自动绑定固定子域名后缀(如 your-domain.xyz)").option("--enable-cdn","域名绑定后自动接入 CDN 并将 DNS CNAME 切到 CDN(API 显式开启;Static 提供域名时默认开启)").option("--ssl","启用 HTTPS(API: --domain/--enable-cdn 默认开启;Static: 提供域名时默认开启)").option("--ssl-force-renew","启用 HTTPS 时强制续签证书(忽略到期阈值)").option("--acr-namespace <ns>","Docker 部署时使用的 ACR 命名空间(默认 licell)").option("--enable-vpc","API 部署时启用 VPC 接入(默认启用)").option("--disable-vpc","API 部署时禁用 VPC 接入(使用公网模式)").option("--memory <mb>","函数内存大小(MB,默认 512)").option("--vcpu <n>","函数 vCPU 核数(如 0.5 / 1 / 2,默认 0.5)").option("--instance-concurrency <n>","单实例并发数(默认自动,通常起始 10)").option("--timeout <seconds>","函数超时时间(秒,默认 30)").action(async(e)=>{if(!m())W(z.bgBlue(z.white(" ▲ Deploying to Aliyun ")));else N({stage:"deploy",action:"deploy",status:"start"});let t=H(),s=I();try{await Tt({commandLabel:"licell deploy",interactiveTTY:s});let r=!1;while(!0){let o=await Oi(e),c=k.getAuth(),f=c?`${c.accountId}|${c.region}|${c.ak}`:"";await Et({commandLabel:"licell deploy",interactiveTTY:s,requiredCapabilities:tp(o)});let u=k.getAuth(),g=u?`${u.accountId}|${u.region}|${u.ak}`:"";if(f&&g!==f)continue;try{if(N({stage:"deploy.preflight",action:"resolve-context",status:"info",data:{type:o.type,runtime:o.cliRuntime||o.projectRuntime||o.envRuntime||null,releaseTarget:o.releaseTarget||null,enableCdn:o.enableCdn,enableSSL:o.enableSSL}}),o.project.hooks?.preDeploy)t.start("执行 preDeploy hook..."),vo("preDeploy",o.project.hooks.preDeploy),t.stop(z.green("✅ preDeploy hook 完成"));let i,l,d,a,p,w=[];if(o.type==="api"){N({stage:"deploy.api",action:"execute",status:"start"});let $=await pg(o,t);if(!$)return;N({stage:"deploy.api",action:"execute",status:"ok"}),{url:i,promotedVersion:l,fixedDomain:d,previewDomain:a,previewVersion:p,healthCheckLogs:w}=$}else{N({stage:"deploy.static",action:"execute",status:"start"});let $=await Mg(o,t);if(!$)return;N({stage:"deploy.static",action:"execute",status:"ok"}),{url:i,fixedDomain:d,previewDomain:a,previewVersion:p,healthCheckLogs:w}=$}if(t.stop(z.green("✅ 部署成功!")),console.log(`
|
|
1578
|
+
\uD83C\uDF89 Production URL: ${z.cyan(z.underline(i))}
|
|
1579
|
+
`),a){let $=`${o.enableSSL?"https":"http"}://${a}`;console.log(`\uD83D\uDD0D Preview URL: ${z.cyan(z.underline($))}`),console.log(`\uD83C\uDFF7️ version=${z.cyan(p||"unknown")}
|
|
1580
|
+
`),console.log(z.gray(`\uD83D\uDCA1 验证后运行 ${z.bold(`licell release promote ${p}`)} 发布到生产。
|
|
1581
|
+
`))}if(d){let $=`${o.enableSSL?"https":"http"}://${d}`;console.log(`\uD83C\uDF10 Fixed Domain: ${z.cyan(z.underline($))}
|
|
1582
|
+
`)}if(o.releaseTarget&&l)console.log(`\uD83C\uDFF7️ alias=${z.cyan(o.releaseTarget)} -> version=${z.cyan(l)}
|
|
1583
|
+
`);if(!o.releaseTarget&&!o.preview&&o.type==="api"&&!m())console.log(z.gray(`\uD83D\uDCA1 代码已更新到预览环境。运行 ${z.bold("licell release promote")} 发布到生产。
|
|
1584
|
+
`));if(w.length>0)console.log(`${w.join(`
|
|
1585
1585
|
`)}
|
|
1586
|
-
`);let _
|
|
1587
|
-
function: ${
|
|
1588
|
-
cleanup: triggers=${c.deletedTriggers.length} aliases=${c.deletedAliases.length} versions=${c.deletedVersions.length}`);q("Done.")})})}import{text as
|
|
1589
|
-
name: ${
|
|
1590
|
-
bucket: ${me.cyan(g.bucket)}`),console.log(`prefix: ${me.cyan(g.targetDir||"(root)")}`),console.log(`base: ${me.cyan(g.baseUrl)}`),console.log(`hint: ${me.gray(`${g.baseUrl}/${y}<file>`)}`),g.skippedSymlinkCount>0)console.log(me.yellow(`warning: 已跳过 ${g.skippedSymlinkCount} 个符号链接(为避免目录逃逸与递归风险)`));console.log(""),q("Done.")})})};n("oss upload [bucket]","上传本地目录到 OSS Bucket 指定目录"),n("oss bucket [bucket]","上传本地目录到 OSS Bucket 指定目录(兼容命令,等同 oss upload)")}import{select as Vp,confirm as Xp,isCancel as El}from"@clack/prompts";import L from"picocolors";Ue();j();it();fe();Ve();Ot();import*as ge from"@alicloud/rds20140815";j();Fe();import qp from"@alicloud/rds20140815";import*as hl from"@alicloud/openapi-client";var Rp=60000,Pp=120000,Ip=se(qp,"@alicloud/rds20140815");function on(){let e=k.requireAuth(),n=new Ip(new hl.Config({accessKeyId:e.ak,accessKeySecret:e.sk,endpoint:`rds.${e.region}.aliyuncs.com`,connectTimeout:Rp,readTimeout:Pp}));return{auth:e,client:n}}var Fp=1200000,wl=5000,xp=300000,Wr=5,Hp="pg.n1e.1c.1m",Lp=20,Ap="18.0",Up={postgres:"pg.n2.serverless.1c",mysql:"mysql.n2.serverless.1c"},vp={postgres:20,mysql:20},Qr={postgres:{minCapacity:0.5,maxCapacity:8,autoPause:!0},mysql:{minCapacity:0.5,maxCapacity:2,autoPause:!0}},al={postgres:"AliyunServiceRoleForRdsPgsqlOnEcs",mysql:"AliyunServiceRoleForRds"};function Gp(e,n){if(!n)return!1;if(n===e)return!0;let t=e.split(".")[0],s=n.split(".")[0];return t.length>0&&t===s}async function Mp(e,n,t,s){try{let r=((await e.describeAvailableZones(new ge.DescribeAvailableZonesRequest({regionId:n,engine:t,engineVersion:s,category:"serverless_basic"}))).body?.availableZones||[]).map((c)=>c.zoneId).filter((c)=>typeof c==="string"&&c.length>0);if(r.length>0)return[...new Set(r)]}catch{}try{let r=(await e.describeAvailableZones(new ge.DescribeAvailableZonesRequest({regionId:n,engine:t,engineVersion:s}))).body?.availableZones||[],c=[];for(let u of r){let l=u.zoneId;if(typeof l!=="string"||l.length===0)continue;if((u.supportedEngines||[]).some((g)=>{if((g.engine||"").toLowerCase()!==t.toLowerCase())return!1;return(g.supportedEngineVersions||[]).some((y)=>{if(!Gp(s,y.version))return!1;return(y.supportedCategorys||[]).some((d)=>d.category==="serverless_basic")})}))c.push(l)}if(c.length>0)return[...new Set(c)];let f=r.map((u)=>u.zoneId).filter((u)=>typeof u==="string"&&u.length>0);return[...new Set(f)]}catch{return[]}}async function ml(e,n,t){let s=t==="postgres"?["AliyunServiceRoleForRds",al.postgres]:[al.mysql];for(let o of s){let r=!1;try{let f=((await e.checkServiceLinkedRole(new ge.CheckServiceLinkedRoleRequest({regionId:n,serviceLinkedRole:o}))).body?.hasServiceLinkedRole||"").toString().toLowerCase();r=f==="true"||f==="1"}catch{}if(r)continue;try{await e.createServiceLinkedRole(new ge.CreateServiceLinkedRoleRequest({regionId:n,serviceLinkedRole:o}))}catch(c){if($o(c))continue;throw c}}}async function pl(e,n,t){let s;for(let o=1;o<=Wr;o+=1)try{return await e.createDBInstance(n)}catch(r){if(s=r,!Ee(r)||o===Wr)throw r;t.message(`\uD83C\uDF10 RDS API 网络抖动,${o}/${Wr} 次失败,正在重试...`),await ye(1500*o)}throw s instanceof Error?s:Error("RDS 创建失败")}async function Yp(e,n,t,s){let o=t==="postgres"?"5432":"3306",r=Date.now();while(!0){if(Date.now()-r>xp)throw Error("数据库网络信息未就绪,等待连接地址超时");let f=(await e.describeDBInstanceNetInfo(new ge.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[],u=f.find((y)=>y.IPType==="Private")||f[0],l=u?.connectionString?.trim(),i=u?.port||o;if(l)return{host:l,port:i};let g=f.map((y)=>`${y.IPType||"Unknown"}:${y.connectionString||"-"}`).join(",")||"pending";s.message(`☕ 数据库连接地址初始化中,请稍候... [${g}]`),await ye(wl)}}function $l(e){let n=e.toLowerCase().replace(/[^a-z0-9_]/g,"_");if(!/^[a-z]/.test(n))n=`a_${n}`;if(n=n.replace(/_+/g,"_").replace(/_$/,""),n.length<2||!/[a-z0-9]/.test(n.slice(1)))n="licell_user";return n.slice(0,16)}async function Dr(e,n,t={}){let{auth:s,client:o}=on(),r=k.getProject(),c="main",f=$l(r.appName||"licell_app"),u=He(),l=e==="postgres"?"PostgreSQL":"MySQL",i=e==="postgres",g=t.engineVersion?.trim()||(i?Ap:e==="postgres"?"18.0":"8.0"),y=i?t.category?.trim()||"Basic":t.category?.trim()||"serverless_basic",d=t.storageType?.trim()||"cloud_essd";if(i)n.message("\uD83D\uDD0E 正在查询 RDS PostgreSQL 可用区...");else n.message("\uD83D\uDD0E 正在查询 RDS Serverless 可用区...");let p;if(i)try{p=((await o.describeAvailableZones(new ge.DescribeAvailableZonesRequest({regionId:s.region,engine:l,engineVersion:g,category:y}))).body?.availableZones||[]).map((Ie)=>Ie.zoneId).filter((Ie)=>typeof Ie==="string"&&Ie.length>0),p=[...new Set(p)]}catch{p=[]}else p=await Mp(o,s.region,l,g);n.message("\uD83D\uDD0D 正在探测/拉起专属私有网络平面 (VPC & VSwitch)...");let h=t.zoneId?.trim(),_=t.vpcId?.trim(),$=t.vSwitchId?.trim(),a;if(_||$){if(!_||!$)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!h)throw Error("自定义网络时需提供 --zone");a=await yt({vpcId:_,vswId:$,zoneId:h})}else a=await gt({preferredZoneIds:h?[h]:p});let w=t.instanceClass?.trim()||(i?Hp:Up[e]),H=t.storageGb||(i?Lp:vp[e]);try{let Ie=(await o.describeAvailableClasses(new ge.DescribeAvailableClassesRequest({regionId:s.region,zoneId:a.zoneId,engine:l,engineVersion:g,instanceChargeType:i?"PostPaid":"Serverless",category:y,DBInstanceStorageType:d}))).body?.DBInstanceClasses?.find((rt)=>{if(typeof rt.DBInstanceClass!=="string"||rt.DBInstanceClass.length===0)return!1;if(!t.instanceClass)return!0;return rt.DBInstanceClass===t.instanceClass});if(!t.instanceClass&&typeof Ie?.DBInstanceClass==="string"&&Ie.DBInstanceClass.length>0)w=Ie.DBInstanceClass;let ot=Ie?.DBInstanceStorageRange?.minValue;if(!t.storageGb&&typeof ot==="number"&&Number.isFinite(ot)&&ot>0)H=Math.floor(ot)}catch{n.message("⚠️ 未能查询到可售规格,回退到内置默认规格继续创建...")}n.message("\uD83D\uDD10 正在确保 RDS 服务关联角色已就绪..."),await ml(o,s.region,e),n.message(i?"\uD83D\uDCE6 正在拉起 PostgreSQL (按量付费)...":`\uD83D\uDCE6 正在拉起 Serverless ${e.toUpperCase()} (按量计费)...`);let T={engine:l,engineVersion:g,payType:i?"Postpaid":"Serverless",category:y,regionId:s.region,zoneId:a.zoneId,DBInstanceClass:w,DBInstanceStorage:H,DBInstanceStorageType:d,securityIPList:t.securityIpList?.trim()||a.cidrBlock||"10.0.0.0/8",instanceNetworkType:"VPC",DBInstanceNetType:"Intranet",DBInstanceDescription:t.description?.trim()||`${r.appName||"licell-app"}-${e}`,VPCId:a.vpcId,vSwitchId:a.vswId};if(!i)T.serverlessConfig={minCapacity:t.minCapacity??Qr[e].minCapacity,maxCapacity:t.maxCapacity??Qr[e].maxCapacity,autoPause:t.autoPause??Qr[e].autoPause};let F=t.zoneIdSlave1?.trim(),E=t.zoneIdSlave2?.trim();if(F)T.zoneIdSlave1=F;if(E)T.zoneIdSlave2=E;let b=new ge.CreateDBInstanceRequest(T),x;try{x=await pl(o,b,n)}catch($n){if(!ws($n))throw $n;n.message("\uD83D\uDD10 首次使用检测到缺少服务关联角色,正在自动创建并重试..."),await ml(o,s.region,e);try{x=await pl(o,b,n)}catch(Ie){if(ws(Ie))throw Error("RDS PostgreSQL 仍提示缺少服务关联角色。请先在阿里云控制台开通 RDS PostgreSQL 服务后重试,或先创建 MySQL Serverless 实例。");throw Ie}}let I=x.body?.DBInstanceId;if(!I)throw Error("RDS 创建失败:未返回 DBInstanceId");let J="Creating",Ce=Date.now();while(J!=="Running"){if(Date.now()-Ce>Fp)throw Error(`数据库创建超时,最后状态: ${J}`);if(await ye(wl),J=(await o.describeDBInstances(new ge.DescribeDBInstancesRequest({DBInstanceId:I}))).body?.items?.DBInstance?.[0]?.DBInstanceStatus||"Creating",J==="Deleted"||J==="Failed")throw Error(`数据库创建失败,实例状态: ${J}`);n.message(`☕ 数据库底层初始化中,请稍候... [${J}]`)}if(n.message("\uD83E\uDDF1 正在创建数据库与应用账号..."),await bs(()=>o.createAccount(new ge.CreateAccountRequest({DBInstanceId:I,accountName:f,accountPassword:u,accountType:"Normal",accountDescription:"licell managed account"}))),await bs(()=>o.createDatabase(new ge.CreateDatabaseRequest({DBInstanceId:I,DBName:"main",characterSetName:e==="postgres"?"UTF8":"utf8mb4",ownerAccount:e==="postgres"?f:void 0}))),e!=="postgres")await bs(()=>o.grantAccountPrivilege(new ge.GrantAccountPrivilegeRequest({DBInstanceId:I,DBName:"main",accountName:f,accountPrivilege:"ReadWrite"})));let{host:fn,port:ue}=await Yp(o,I,e,n),Yn=`${e==="postgres"?"postgresql":"mysql"}://${encodeURIComponent(f)}:${encodeURIComponent(u)}@${fn}:${ue}/main`;return r.envs={...r.envs,DATABASE_URL:Yn},r.network=a,r.database={type:e,instanceId:I,user:f,name:"main"},k.setProject(r),n.message("⚠️ DATABASE_URL(含密码)已写入 .licell/project.json,请确认该目录已在 .gitignore 中"),Yn}j();import*as Ze from"@alicloud/rds20140815";function kl(e){if(!e)return null;try{let n=new URL(e),t=n.protocol==="postgresql:"?"postgresql":n.protocol==="mysql:"?"mysql":null;if(!t||!n.hostname)return null;let s=n.port||(t==="postgresql"?"5432":"3306"),o=Number(s);if(!Number.isFinite(o)||o<=0)return null;let r=n.pathname.replace(/^\//,"")||"main";return{protocol:t,host:n.hostname,port:o,database:r,username:decodeURIComponent(n.username||""),password:decodeURIComponent(n.password||"")}}catch{return null}}function bl(e){let n=e.database;if(typeof n!=="object"||n===null)return{};let t=n;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 _l(e){return(e||"").toLowerCase().includes("postgres")?"postgresql":"mysql"}function Tl(e){return{instanceId:e.DBInstanceId||"",description:e.DBInstanceDescription,engine:e.engine,engineVersion:e.engineVersion,status:e.DBInstanceStatus,payType:e.payType,category:e.category,instanceClass:e.DBInstanceClass,zoneId:e.zoneId,vpcId:e.vpcId,vSwitchId:e.vSwitchId}}async function Bp(e){let{auth:n,client:t}=on(),o=(await t.describeDBInstances(new Ze.DescribeDBInstancesRequest({regionId:n.region,DBInstanceId:e,pageNumber:1,pageSize:30}))).body?.items?.DBInstance||[],r=o.find((c)=>c.DBInstanceId===e)||o[0];if(!r?.DBInstanceId)throw Error(`未找到数据库实例: ${e}`);return Tl(r)}async function Vr(e=200){let{auth:n,client:t}=on(),s=[],o=Math.max(1,Math.min(Math.floor(e),500)),r=Math.min(100,o);for(let c=1;c<=20&&s.length<o;c+=1){let f=await t.describeDBInstances(new Ze.DescribeDBInstancesRequest({regionId:n.region,pageNumber:c,pageSize:r})),u=f.body?.items?.DBInstance||[];for(let g of u){let y=Tl(g);if(!y.instanceId)continue;if(s.push(y),s.length>=o)break}let l=f.body||{},i=Number(l.totalRecordCount||l.totalCount||0);if(u.length===0||Number.isFinite(i)&&i>0&&s.length>=i)break}return s}async function Vs(e){let n=e.trim();if(!n)throw Error("instanceId 不能为空");let{client:t}=on(),s=await Bp(n),r=((await t.describeDBInstanceNetInfo(new Ze.DescribeDBInstanceNetInfoRequest({DBInstanceId:n}))).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 Ze.DescribeDatabasesRequest({DBInstanceId:n,pageNumber:1,pageSize:100}))).body?.databases?.database||[]).map((i)=>i.DBName).filter((i)=>typeof i==="string"&&i.length>0),l=((await t.describeAccounts(new Ze.DescribeAccountsRequest({DBInstanceId:n}))).body?.accounts?.DBInstanceAccount||[]).map((i)=>i.accountName).filter((i)=>typeof i==="string"&&i.length>0);return{summary:s,endpoints:r,databases:f,accounts:l}}async function Xs(e){let n=k.getProject(),t=bl(n),s=e?.trim()||t.instanceId||"";if(!s)throw Error("未指定 DB 实例 ID,且当前项目未绑定数据库实例");let o=await Vs(s),r=_l(o.summary.engine),c=o.endpoints.find((F)=>F.ipType==="Private"&&F.host)||o.endpoints.find((F)=>F.host),f=c?.host?.trim();if(!f)throw Error(`未获取到实例 ${s} 的连接地址`);let u=Number(c?.port||(r==="postgresql"?"5432":"3306"));if(!Number.isFinite(u)||u<=0)throw Error(`实例 ${s} 返回了无效端口`);let l=o.endpoints.find((F)=>F.ipType==="Public"&&F.host),g=t.instanceId===s?kl(n.envs?.DATABASE_URL):null,y=g?.username||t.user||"<username>",d=g?.database||t.name||o.databases[0]||"main",p=g?.password||"",h=p.length>0,_=h?encodeURIComponent(p):"<password>",$=y==="<username>"?y:encodeURIComponent(y),a=`${r}://${$}:${_}@${f}:${u}/${d}`,w,H,T;if(l?.host)w=l.host.trim(),H=Number(l.port||u),T=`${r}://${$}:${_}@${w}:${H}/${d}`;return{instanceId:s,engine:r,host:f,port:u,database:d,username:y,passwordKnown:h,connectionString:a,publicHost:w,publicPort:H,publicConnectionString:T}}async function Xr(e){let{client:n}=on();await n.deleteDBInstance(new Ze.DeleteDBInstanceRequest({DBInstanceId:e}))}import*as zn from"@alicloud/rds20140815";fe();var Kp="licell_public";async function Zr(e,n){let{client:t}=on(),r=((await t.describeDBInstanceNetInfo(new zn.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(r?.connectionString)return{host:r.connectionString,port:r.port||"3306"};let c=`${e}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await t.allocateInstancePublicConnection(new zn.AllocateInstancePublicConnectionRequest({DBInstanceId:e,connectionStringPrefix:c,port:"3306"}))}catch(i){let g=P(i);if(g.includes("NetTypeExists")||g.includes("already exists"))n.message("公网地址已存在,正在获取...");else throw i}let l=((await t.describeDBInstanceNetInfo(new zn.DescribeDBInstanceNetInfoRequest({DBInstanceId:e}))).body?.DBInstanceNetInfos?.DBInstanceNetInfo||[]).find((i)=>i.IPType==="Public");if(!l?.connectionString)return null;return{host:l.connectionString,port:l.port||"3306"}}async function Jr(e,n,t){let{client:s}=on(),o=`${n}/32`;try{await s.modifySecurityIps(new zn.ModifySecurityIpsRequest({DBInstanceId:e,DBInstanceIPArrayName:Kp,securityIps:o,modifyMode:"Cover"}))}catch(r){t.message(`⚠️ 公网白名单设置失败: ${P(r)}`)}}oe();ce();function Sl(e){e.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(n)=>{await M({commandLabel:"licell db add",interactiveTTY:R(),requiredCapabilities:["rds"]},async()=>{K(L.bgMagenta(L.white(" \uD83D\uDDC4️ Database Provisioning (IaC) "))),Y();let t=R(),s,o=C(n.type);if(o)s=zu(o);else if(t){let y=await Vp({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(El(y))process.exit(0);s=y}else throw Error("非交互模式下请传入 --type postgresql|mysql");if(s==="serverless-postgresql"){console.log(L.yellow("⏳ Serverless PostgreSQL 即将上线,敬请期待。")),console.log(L.gray(`当前支持的类型:${L.bold("postgresql")}(按量付费)和 ${L.bold("mysql")}(Serverless)`)),q("");return}let r=s,c=qe(n.storage,"storage"),f=Er(n.minRcu,"min-rcu"),u=Er(n.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 l=C(n.autoPause)?ni(n.autoPause):void 0,i=A(),g=await U(i,"正在初始化基础设施编排引擎...","❌ 拉起失败",()=>Dr(r,i,{engineVersion:C(n.engineVersion),category:C(n.category),instanceClass:C(n.class),storageGb:c,storageType:C(n.storageType),minCapacity:f,maxCapacity:u,autoPause:l,zoneId:C(n.zone),zoneIdSlave1:C(n.zoneSlave1),zoneIdSlave2:C(n.zoneSlave2),vpcId:C(n.vpc),vSwitchId:C(n.vsw),securityIpList:C(n.securityIpList),description:C(n.description)}));if(!g)return;if(!m())i.stop(L.green("✅ 数据库实例已就绪并绑定到本工程内网!"));if(m()){S({stage:"db.add",type:s,connectionStringMasked:Rn(g)});return}console.log(`
|
|
1591
|
-
\uD83D\uDD11 内网直连凭证已生成: ${
|
|
1592
|
-
`),q("下次执行 licell deploy 时,将自动作为 process.env.DATABASE_URL 注入!")})}),
|
|
1593
|
-
instanceId: ${
|
|
1594
|
-
instanceId: ${
|
|
1586
|
+
`);let _=Qi({deploySucceeded:!0,cliDomainSuffix:o.cliDomainSuffix,projectDomainSuffix:o.projectDomainSuffix,cliRuntime:o.cliRuntime,projectRuntime:o.projectRuntime});if(Object.keys(_).length>0)k.setProject(_);if(o.project.hooks?.postDeploy)try{vo("postDeploy",o.project.hooks.postDeploy)}catch($){console.warn(z.yellow(`⚠️ postDeploy hook 执行失败,已忽略: ${A($)}`))}if(m())T({stage:"deploy",type:o.type,runtime:o.cliRuntime||o.projectRuntime||o.envRuntime||null,url:i,fixedDomain:d||null,releaseTarget:o.releaseTarget||null,promotedVersion:l||null,healthCheckLogs:w,...!o.releaseTarget&&o.type==="api"?{hint:"运行 licell release promote 发布到生产"}:{}});else q("Done!");return}catch(i){if(!r&&Ho(i)!=="unknown"){if(t.stop(z.yellow("⚠️ 检测到鉴权/权限问题,正在尝试自动修复并重试...")),await Mo(i,{commandLabel:"licell deploy",interactiveTTY:s})){r=!0;continue}}throw i}}}catch(r){if(t.stop(z.red("❌ 部署失败")),m())pn(r,{stage:"deploy"});else console.error(A(r));process.exitCode=1}})}Q();ne();on();Hn();fn();import wn from"picocolors";import{readFileSync as op,realpathSync as cp}from"fs";import{resolve as fp,relative as up,isAbsolute as ip}from"path";function Bg(n){n.command("fn list","查看函数列表").option("--limit <n>","返回数量,默认 20").option("--prefix <prefix>","按函数名前缀过滤").action(async(e)=>{await G({commandLabel:"licell fn list",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let t=An(e.limit,20,200),s=E(e.prefix),r=H(),o=await F(r,"正在拉取函数列表...","❌ 获取函数列表失败",()=>wo(t,s));if(!o)return;if(!m())r.stop(wn.green(`✅ 共获取 ${o.length} 个函数`));if(m()){T({stage:"fn.list",count:o.length,functions:o});return}if(o.length===0){q("当前地域没有函数");return}for(let c of o)console.log(`${wn.cyan(c.functionName)} runtime=${wn.gray(c.runtime||"-")} state=${wn.gray(c.state||"-")} updated=${wn.gray(c.lastModifiedTime||"-")}`);console.log(""),q("Done.")})}),n.command("fn info [name]","查看函数详情").option("--target <target>","指定 alias/version(如 prod/preview/1)").action(async(e,t)=>{await G({commandLabel:"licell fn info",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=H(),f=await F(c,`正在拉取函数 ${r} 详情...`,"❌ 获取函数详情失败",()=>po(r,o||void 0));if(!f)return;if(!m())c.stop(wn.green("✅ 获取成功"));else{T({stage:"fn.info",functionName:f.functionName||r,qualifier:o||null,runtime:f.runtime||null,handler:f.handler||null,state:f.state||null,memorySize:f.memorySize??null,cpu:f.cpu??null,instanceConcurrency:f.instanceConcurrency??null,timeout:f.timeout??null,vpcConfig:f.vpcConfig??null,updatedAt:f.lastModifiedTime||null,envCount:Object.keys(f.environmentVariables||{}).length});return}if(console.log(`
|
|
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
|
+
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
|
+
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)&>>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
|
+
\uD83D\uDD11 内网直连凭证已生成: ${v.cyan(Ae(l))}
|
|
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
|
+
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
|
+
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: ${L.cyan(`${c}/32`)} (分组: licell_public)`),q("Done.")})}),e.command("db rm <instanceId>","删除数据库实例").option("--yes","跳过确认").action(async(n,t)=>{await M({commandLabel:"licell db rm",interactiveTTY:R(),requiredCapabilities:["rds"]},async()=>{K(L.bgRed(L.white(" \uD83D\uDDD1️ Delete Database "))),Y();let s=n.trim();if(!s)throw Error("请提供 instanceId");if(!t.yes&&R()){let r=await Xp({message:`确认删除数据库实例 ${L.red(s)}?此操作不可恢复。`});if(El(r)||!r){q("已取消");return}}let o=A();if(await U(o,`正在删除实例 ${s}...`,"❌ 删除失败",()=>Xr(s)),m()){S({stage:"db.rm",instanceId:s});return}q(`实例 ${s} 已删除`)})})}import{select as lw,confirm as gw,isCancel as Wl}from"@clack/prompts";import G from"picocolors";Ue();j();it();Ve();Ot();import*as rn from"@alicloud/r-kvstore20150101";import{randomUUID as Kl}from"crypto";Fe();import Zp from"@alicloud/r-kvstore20150101";import*as mn from"@alicloud/openapi-client";import*as ql from"@alicloud/tea-util";var Jp=se(Zp,"@alicloud/r-kvstore20150101"),Np=se(mn.default,"@alicloud/openapi-client");function Ge(e){return new Jp(new mn.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:e.region,endpoint:"r-kvstore.aliyuncs.com",connectTimeout:30000,readTimeout:60000}))}function zp(e){return new Np(new mn.Config({accessKeyId:e.ak,accessKeySecret:e.sk,regionId:e.region,endpoint:"r-kvstore.aliyuncs.com"}))}function ew(e){if(e===null||e===void 0)return;if(typeof e==="boolean")return e?"true":"false";return String(e)}function nw(e){let n={};for(let[t,s]of Object.entries(e)){let o=ew(s);if(o===void 0)continue;n[t]=o}return n}async function Rl(e,n,t){let s=zp(e),o=new mn.Params({action:n,version:"2015-01-01",protocol:"HTTPS",pathname:"/",method:"POST",authType:"AK",style:"RPC",reqBodyType:"formData",bodyType:"json"}),r=new mn.OpenApiRequest({query:nw(t)});return s.callApi(o,r,new ql.RuntimeOptions({readTimeout:20000,connectTimeout:8000}))}it();fe();Ve();import*as ke from"@alicloud/r-kvstore20150101";import{randomUUID as tw}from"crypto";fe();function ec(e){return["Error","Released","Inactive","Unavailable","Flushing"].includes(e)}function Je(e,n,t,s){if(!e)return`redis://:${encodeURIComponent(n)}@${t}:${s}`;return`redis://${encodeURIComponent(e)}:${encodeURIComponent(n)}@${t}:${s}`}function Zt(e,n,t,s,o){let r=o?encodeURIComponent(n):"<password>";if(!e)return`redis://:${r}@${t}:${s}`;return`redis://${e==="<username>"?e:encodeURIComponent(e)}:${r}@${t}:${s}`}function Zs(e){if(typeof e!=="object"||e===null)return"";let n="code"in e?String(e.code||""):"";if(n)return n;if("data"in e&&typeof e.data==="object"&&e.data!==null){let t=e.data?.Code;if(t)return String(t)}return""}function Pl(e){return/InvalidInstanceId\.NotFound/i.test(Zs(e)||P(e))}function Il(e){let n=`${Zs(e)} ${P(e)}`;return/NotSupport|Unsupported|InvalidInstanceId|InvalidParameter|OperationDenied|AccessDenied/i.test(n)}function Js(e){let n=new Set;for(let t of e){if(!t)continue;let s=t.trim();if(s)n.add(s)}return[...n]}function et(e){let n=(e||"").trim();if(!n)return null;let t=n.split(",")[0]?.trim();if(!t)return null;let s=/^[a-z][a-z0-9+.-]*:\/\//i.test(t)?t:`redis://${t}`;try{let o=new URL(s),r=o.hostname;if(!r)return null;let c=o.port?Number(o.port):6379;if(!Number.isFinite(c)||c<=0)return null;let f=o.username?decodeURIComponent(o.username):void 0,u=o.password?decodeURIComponent(o.password):void 0,l=o.toString();return{host:r,port:c,accountName:f,password:u,url:l}}catch{return null}}function nt(e){return e.startsWith("r-")}function Jt(e){return e.startsWith("tk-")||e.startsWith("tt-")}function Et(e,n){return{vpcId:n.vpcId,vswId:n.vswId,sgId:n.sgId??e?.sgId,cidrBlock:n.cidrBlock??e?.cidrBlock}}function nc(e){return{instanceId:e.instanceId||"",mode:"classic-redis",instanceName:e.instanceName,status:e.instanceStatus,instanceClass:e.instanceClass,engineVersion:e.engineVersion,host:e.connectionDomain,port:e.port,zoneId:e.zoneId,vpcId:e.vpcId,vSwitchId:e.vSwitchId}}function Fl(e){return{instanceId:e.instanceId||"",mode:"tair-serverless-kv",instanceName:e.instanceName,status:e.instanceStatus,zoneId:e.zoneId,vpcId:e.vpcId,vSwitchId:e.vSwitchId}}var xl=1200000,Hl=5000,Ll=60000,Al=180000,Ns="kvcache.cu.g4b.2",Ul=1;async function Nt(e,n,t){let o=(await e.describeAccounts(new ke.DescribeAccountsRequest({instanceId:n}))).body?.accounts?.account||[],r=o.filter((f)=>f.accountStatus!=="Unavailable");return(r.find((f)=>f.accountType==="Normal")||r[0]||o[0])?.accountName||t||""}async function vl(e,n,t){try{if((await e.describeServiceLinkedRoleExists(new ke.DescribeServiceLinkedRoleExistsRequest({}))).body?.existsServiceLinkedRole)return;t.message("\uD83D\uDD10 正在初始化 Kvstore 服务关联角色..."),await e.initializeKvstorePermission(new ke.InitializeKvstorePermissionRequest({regionId:n}))}catch(s){t.message(`⚠️ 服务关联角色检查失败 (${P(s)}),将继续尝试创建缓存`)}}async function Gl(e,n,t,s,o){let r=s.instanceClass?.trim()||Ns,c=`${o||"licell-app"}-redis`,f=He(),u=s.securityIpList?.trim()||t.cidrBlock||"10.0.0.0/8",i=(await Rl(e,"CreateTairKVCacheInferInstance",{RegionId:e.region,InstanceName:c,InstanceClass:r,ZoneId:t.zoneId,VpcId:t.vpcId,VSwitchId:t.vswId,ChargeType:"PostPaid",AutoPay:!0,Password:f,SecurityIPList:u,ClientToken:tw()})).body||{},g=typeof i.InstanceId==="string"?i.InstanceId:typeof i.instanceId==="string"?i.instanceId:"";if(!g)return null;let y=typeof i.ConnectionString==="string"?i.ConnectionString:typeof i.connectionString==="string"?i.connectionString:"",d=et(y),p=d?.host||"",h=d?.port||6379,_=d?.accountName||"",$=Ge(e);if(!p){let w=await St($,n,[g]);p=w.host,h=w.port,_=w.accountName||_}if(!p)throw Error(`CreateTairKVCacheInferInstance 返回成功但未获取连接地址 (${g})`);let a=Je(_||void 0,f,p,h);return{instanceId:g,host:p,port:h,accountName:_||void 0,password:f,redisUrl:a}}async function zs(e,n){let t=[],s=30;for(let o=1;o<=50;o+=1){let r=await e.describeTairKVCacheInferInstances(new ke.DescribeTairKVCacheInferInstancesRequest({regionId:n,pageNumber:o,pageSize:30})),c=r.body?.instances?.tairInferInstanceDTO||[];t.push(...c);let f=r.body?.totalCount||0;if(c.length===0||t.length>=f)break}return t}function Ml(e,n,t){let s=t?.trim();if(s)return s;let o=e.filter((f)=>{if(!(f.instanceId||"").startsWith("tk-"))return!1;let l=f.instanceStatus||"";if(l&&ec(l))return!1;return!0}),r=o.filter((f)=>{if(f.vpcId&&f.vpcId!==n.vpcId)return!1;if(f.vSwitchId&&f.vSwitchId!==n.vswId)return!1;if(n.zoneId&&f.zoneId&&f.zoneId!==n.zoneId)return!1;return!0}),c=r.length>0?r:o;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 Yl(e,n){try{return(await e.describeTairKVCacheInferInstanceAttribute(new ke.DescribeTairKVCacheInferInstanceAttributeRequest({instanceId:n}))).body?.instances?.DBInstanceAttribute?.[0]}catch(t){if(Pl(t))return null;throw t}}async function St(e,n,t,s={}){let o=Js(t);if(o.length===0)throw Error("未找到可查询的 Tair KVCache 实例 ID");let r=s.waitTimeoutMs||xl,c=Date.now(),f="Creating",u=0;while(!0){if(Date.now()-c>r)throw Error(`Tair Serverless KV 初始化超时,最后状态: ${f}`);let l=!1;for(let i of o){let g=await Yl(e,i);if(!g)continue;l=!0;let y=g.instanceStatus||"Creating";if(f=`${i}:${y}`,ec(y))throw Error(`Tair KVCache 创建失败,实例状态: ${y} (${i})`);let d=et(g.connectionString);if(y==="Normal"&&d?.host)return{...d,sourceInstanceId:i}}if(!l){if(!u)u=Date.now();if(Date.now()-u>=Ll)throw Error(`未查询到可用实例信息,请检查实例 ID 是否正确: ${o.join(", ")}`)}else u=0;n.message(`☕ Tair Serverless KV 初始化中,请稍候... [${f}]`),await ye(Hl)}}async function zt(e,n,t,s){let o=Js(n);for(let r of o)try{let c=await Nt(e,r,s);if(!c)continue;return await e.resetAccountPassword(new ke.ResetAccountPasswordRequest({instanceId:r,accountName:c,accountPassword:t})),{instanceId:r,accountName:c}}catch{continue}return null}async function es(e,n,t){let s=Js(n);for(let o of s)try{return await e.resetTairKVCacheCustomInstancePassword(new ke.ResetTairKVCacheCustomInstancePasswordRequest({instanceId:o,password:t})),{instanceId:o}}catch{continue}return null}async function qt(e,n,t,s){try{await e.modifySecurityIps(new ke.ModifySecurityIpsRequest({instanceId:n,securityIpGroupName:"default",modifyMode:"Append",securityIps:t}))}catch(o){if(N(o)||Il(o)){s.message(`⚠️ 当前实例未应用白名单配置 (${P(o)})`);return}throw o}}async function ns(e,n,t){return((await e.describeInstances(new ke.DescribeInstancesRequest({regionId:n.region,instanceIds:t,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance||[]).find((r)=>r.instanceId===t)||null}async function tc(e,n){return Yl(e,n)}function Bl(e){let n=Zs(e),t=(()=>{if(typeof e!=="object"||e===null)return"";if("data"in e&&typeof e.data==="object"&&e.data!==null){let o=e.data?.RequestId;if(o)return String(o)}return""})(),s=t?`, requestId=${t}`:"";return`⚠️ 直连 API 创建失败 [${n||"Unknown"}] ${P(e)}${s}`}async function sw(e,n,t,s,o,r){let c=o.instanceId?.trim()||"";e.message(`\uD83D\uDD17 正在绑定已有 Redis 实例 (${c})...`);let u=(await n.describeInstances(new rn.DescribeInstancesRequest({regionId:t.region,instanceIds:c,pageNumber:1,pageSize:30}))).body?.instances?.KVStoreInstance?.find((h)=>h.instanceId===c),l=u?.connectionDomain||s.cache?.host,i=u?.port||s.cache?.port||6379;if(!l)throw Error(`未查询到 Redis 连接地址,请确认实例 ${c} 可用`);let g=o.accountName?.trim()||s.cache?.accountName||"";if(!g)g=await Nt(n,c,s.cache?.accountName);let y=o.existingPassword?.trim()||"";if(!y){if(!g)throw Error("未查询到 Redis 账号,请使用 --username 指定实例账号,或使用 --password 直接绑定");let h=He();await n.resetAccountPassword(new rn.ResetAccountPasswordRequest({instanceId:c,accountName:g,accountPassword:h})),y=h}let d=o.securityIpList?.trim()||r.cidrBlock||"10.0.0.0/8";e.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await qt(n,c,d,e);let p=Je(g||void 0,y,l,i);return s.envs={...s.envs,REDIS_URL:p,REDIS_HOST:l,REDIS_PORT:String(i),REDIS_PASSWORD:y,REDIS_USERNAME:g},s.network=Et(s.network,r),s.cache={type:"redis",instanceId:c,host:l,port:i,accountName:g,mode:"classic-redis"},k.setProject(s),p}async function ow(e,n,t,s,o){let r=s.instanceId?.trim()||"";e.message(`\uD83D\uDD17 正在绑定已有 Tair Serverless KV 实例 (${r})...`);let c=await St(n,e,[r,s.vkName,t.cache?.vkName],{waitTimeoutMs:Al});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:r};let f=s.accountName?.trim()||c.accountName||t.cache?.accountName||"",u=s.existingPassword?.trim()||c.password||"";if(!u){e.message("\uD83D\uDD10 未传入 --password,正在自动轮换实例密码...");let g=He(),y=await zt(n,[c.sourceInstanceId,s.vkName,r],g,f);if(y)f=y.accountName,u=g;else{if(!await es(n,[c.sourceInstanceId,r,s.vkName],g))throw Error("未能自动重置已存在实例密码。请使用 --password 传入控制台已设置密码,或先执行 `licell cache rotate-password --instance <id>` 再重试");u=g}}let l=s.securityIpList?.trim()||o.cidrBlock||"10.0.0.0/8";e.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await qt(n,c.sourceInstanceId,l,e);let i=Je(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=Et(t.network,o),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 sc(e,n={}){let t=k.requireAuth(),s=k.getProject();if(n.engineVersion||n.nodeType||n.capacityMb)throw Error("Tair Serverless KV 不支持 --engine-version/--node-type/--capacity 参数");let o=n.zoneId?.trim(),r=n.vpcId?.trim(),c=n.vSwitchId?.trim(),f=await(r||c?(()=>{if(!r||!c)throw Error("自定义网络时需同时提供 --vpc 与 --vsw");if(!o)throw Error("自定义网络时需提供 --zone");return yt({vpcId:r,vswId:c,zoneId:o})})():gt({preferredZoneIds:o?[o]:void 0})),u=Ge(t);await vl(u,t.region,e);let l=n.instanceId?.trim();if(l){if(Jt(l))return ow(e,u,s,n,f);if(nt(l))return sw(e,u,t,s,n,f);throw Error("--instance 仅支持 tt-/tk-(Tair)或 r-(经典 Redis)开头的实例 ID")}let i;try{e.message("⚡ 正在通过直连 API 创建 Tair Serverless KV...");let I=await Gl(t,e,f,n,s.appName);if(I){let J=n.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return e.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await qt(u,I.instanceId,J,e),s.envs={...s.envs,REDIS_URL:I.redisUrl,REDIS_HOST:I.host,REDIS_PORT:String(I.port),REDIS_PASSWORD:I.password,REDIS_USERNAME:I.accountName||""},s.network=Et(s.network,f),s.cache={type:"redis",instanceId:I.instanceId,host:I.host,port:I.port,accountName:I.accountName,mode:"tair-serverless-kv"},k.setProject(s),I.redisUrl}}catch(I){i=I,e.message(Bl(I))}e.message("\uD83D\uDD0E 正在查询可用的 Tair Serverless KV 虚拟集群...");let g=await zs(u,t.region),y=Ml(g,f,n.vkName);if(!y)return e.message("⚠️ Tair Serverless KV 不可用,正在兜底创建云原生 Redis 社区版..."),uw(e,u,t,s,f,n);let d=n.instanceClass?.trim()||Ns,p=n.computeUnitNum||Ul;if(!Number.isInteger(p)||p<=0)throw Error("--compute-unit 必须是正整数");if(p!==1)throw Error("当前阿里云 CreateTairKVCacheVNode 仅支持 --compute-unit 1");let h=`${s.appName||"licell-app"}-redis`;e.message(`⚡ 正在创建 Tair Serverless KV: class=${d}, cu=${p}, vk=${y}`);let _=await u.createTairKVCacheVNode(new rn.CreateTairKVCacheVNodeRequest({regionId:t.region,instanceName:h,instanceClass:d,computeUnitNum:p,zoneId:f.zoneId||o,vSwitchId:f.vswId,vkName:y,clientToken:Kl()})),$=_.body?.instanceId,a=_.body?.vkName||y;if(!$)throw Error("Tair Serverless KV 创建失败:未返回 instanceId");let w=await St(u,e,[a,$]),H=w.host,T=w.port,F=w.accountName||s.cache?.accountName||"",E=w.password||"",b=w.url;if(!E){e.message("\uD83D\uDD10 正在设置 Redis 密码...");let I=He(),J=await zt(u,[w.sourceInstanceId,a,$],I,F);if(J)F=J.accountName,E=I,b=Je(F,E,H,T);else{if(!await es(u,[w.sourceInstanceId,$,a],I))throw Error("未能自动设置 Tair Serverless KV 密码,请在控制台手动设置后重试");E=I,b=Je(F||void 0,E,H,T)}}let x=n.securityIpList?.trim()||f.cidrBlock||"10.0.0.0/8";return e.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await qt(u,w.sourceInstanceId,x,e),s.envs={...s.envs,REDIS_URL:b,REDIS_HOST:H,REDIS_PORT:String(T),REDIS_PASSWORD:E,REDIS_USERNAME:F},s.network=Et(s.network,f),s.cache={type:"redis",instanceId:w.sourceInstanceId,host:H,port:T,accountName:F,vkName:a,mode:"tair-serverless-kv"},k.setProject(s),b}var rw=600000,cw=5000,fw="redis.master.small.default";async function uw(e,n,t,s,o,r){let c=`${s.appName||"licell-app"}-redis`,f=He(),u=r.instanceClass?.trim()||fw;e.message(`\uD83D\uDCE6 正在创建云原生 Redis 社区版 (${u}, 按量付费)...`);let l=await n.createInstance(new rn.CreateInstanceRequest({regionId:t.region,instanceType:"Redis",engineVersion:"5.0",instanceClass:u,instanceName:c,chargeType:"PostPaid",nodeType:"double",networkType:"VPC",vpcId:o.vpcId,vSwitchId:o.vswId,zoneId:o.zoneId,password:f,token:Kl()})),i=l.body?.instanceId;if(!i)throw Error("Redis 创建失败:未返回 instanceId");let g=l.body?.connectionDomain||"",y=l.body?.port||6379,d=Date.now();while(!0){if(Date.now()-d>rw)throw Error("Redis 实例创建超时");await ye(cw);let h=(await n.describeInstanceAttribute(new rn.DescribeInstanceAttributeRequest({instanceId:i}))).body?.instances?.DBInstanceAttribute?.[0],_=h?.instanceStatus||"Creating";if(_==="Normal"){let $=h?.connectionDomain||g,a=h?.port||y;if(!$)throw Error("Redis 实例已就绪但未获取到连接地址");let w=r.securityIpList?.trim()||o.cidrBlock||"10.0.0.0/8";e.message("\uD83D\uDD10 正在配置 Redis 内网白名单..."),await qt(n,i,w,e);let H=Je(void 0,f,$,a);return s.envs={...s.envs,REDIS_URL:H,REDIS_HOST:$,REDIS_PORT:String(a),REDIS_PASSWORD:f,REDIS_USERNAME:""},s.network=Et(s.network,o),s.cache={type:"redis",instanceId:i,host:$,port:a,accountName:void 0,mode:"classic-redis"},k.setProject(s),H}e.message(`☕ Redis 实例初始化中,请稍候... [${_}]`)}}j();it();import*as Ol from"@alicloud/r-kvstore20150101";async function oc(e,n){let t=k.requireAuth(),s=k.getProject(),o=Ge(t),r=n||s.cache?.instanceId;if(!r)throw Error("未找到 Redis 实例 ID,请先执行 licell cache add");if(nt(r)){e.message("\uD83D\uDD0E 正在获取 Redis 账号...");let g=await Nt(o,r,s.cache?.accountName);if(!g)throw Error("未找到可用 Redis 账号,无法轮换密码");let y=He();e.message("\uD83D\uDD10 正在轮换 Redis 密码..."),await o.resetAccountPassword(new Ol.ResetAccountPasswordRequest({instanceId:r,accountName:g,accountPassword:y}));let d=await ns(o,t,r),p=d?.connectionDomain||s.cache?.host,h=d?.port||s.cache?.port||6379;if(!p)throw Error("未查询到 Redis 连接地址");let _=Je(g,y,p,h);return s.envs={...s.envs,REDIS_URL:_,REDIS_HOST:p,REDIS_PORT:String(h),REDIS_PASSWORD:y,REDIS_USERNAME:g},s.cache={...s.cache||{type:"redis",instanceId:r},type:"redis",instanceId:r,host:p,port:h,accountName:g},k.setProject(s),_}e.message("\uD83D\uDD0E 正在解析 Tair Serverless KV 连接地址...");let c=await St(o,e,[r,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:r};let f=He();e.message("\uD83D\uDD10 正在轮换 Tair Serverless KV 密码...");let u=await zt(o,[c.sourceInstanceId,s.cache?.vkName,r],f,s.cache?.accountName),l=u?.accountName||s.cache?.accountName||"";if(!u){if(!await es(o,[c.sourceInstanceId,r,s.cache?.vkName],f))throw Error("轮换密码失败:当前实例不支持自动密码重置")}let i=Je(l||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:l},s.cache={...s.cache||{type:"redis",instanceId:r},type:"redis",instanceId:c.sourceInstanceId,host:c.host,port:c.port,accountName:l,vkName:s.cache?.vkName,mode:"tair-serverless-kv"},k.setProject(s),i}j();import*as pn from"@alicloud/r-kvstore20150101";async function jl(e,n){try{let o=((await e.describeDBInstanceNetInfo(new pn.DescribeDBInstanceNetInfoRequest({instanceId:n}))).body?.netInfoItems?.instanceNetInfo||[]).find((r)=>r.IPType==="Public");if(o?.connectionString)return{host:o.connectionString,port:Number(o.port)||6379}}catch{}return null}async function rc(e=200){let n=k.requireAuth(),t=Ge(n),s=[],o=Math.max(1,Math.min(Math.floor(e),500));for(let r=1;r<=20&&s.length<o;r+=1){let c=await t.describeInstances(new pn.DescribeInstancesRequest({regionId:n.region,pageNumber:r,pageSize:50})),f=c.body?.instances?.KVStoreInstance||[];for(let l of f){let i=nc(l);if(!i.instanceId)continue;if(s.push(i),s.length>=o)break}let u=c.body?.totalCount||0;if(f.length===0||u>0&&s.length>=u)break}if(s.length<o){let r=await zs(t,n.region);for(let c of r){let f=Fl(c);if(!f.instanceId)continue;if(s.some((u)=>u.instanceId===f.instanceId))continue;if(s.push(f),s.length>=o)break}}return s.slice(0,o)}async function cc(e){let n=e.trim();if(!n)throw Error("instanceId 不能为空");let t=k.requireAuth(),s=Ge(t);if(nt(n)){let u=await ns(s,t,n);if(!u?.instanceId)throw Error(`未找到 Redis 实例: ${n}`);let i=((await s.describeAccounts(new pn.DescribeAccountsRequest({instanceId:n}))).body?.accounts?.account||[]).map((g)=>g.accountName).filter((g)=>typeof g==="string"&&g.length>0);return{summary:nc(u),accountNames:i}}if(!Jt(n))throw Error("cache info 仅支持 tt-/tk-/r- 开头的实例 ID");let o=await tc(s,n);if(!o?.instanceId)throw Error(`未找到 Tair 实例: ${n}`);let r=et(o.connectionString),f=((await s.describeAccounts(new pn.DescribeAccountsRequest({instanceId:n}))).body?.accounts?.account||[]).map((u)=>u.accountName).filter((u)=>typeof u==="string"&&u.length>0);return{summary:{instanceId:o.instanceId,mode:"tair-serverless-kv",instanceName:o.instanceName,status:o.instanceStatus,instanceClass:o.instanceClass,host:r?.host,port:r?.port,zoneId:o.zoneId,vpcId:o.vpcId,vSwitchId:o.vSwitchId},accountNames:f}}async function eo(e){let n=k.requireAuth(),t=Ge(n),s=k.getProject(),o=e?.trim()||s.cache?.instanceId||"";if(!o)throw Error("未指定缓存实例 ID,且当前项目未绑定缓存实例");let c=s.cache?.instanceId===o?et(s.envs?.REDIS_URL):null;if(nt(o)){let p=await ns(t,n,o);if(!p?.instanceId)throw Error(`未找到 Redis 实例: ${o}`);let h=p.connectionDomain||s.cache?.host||"",_=p.port||s.cache?.port||6379;if(!h)throw Error(`未获取到实例 ${o} 的连接地址`);let $=c?.accountName||s.cache?.accountName||"<username>",a=c?.password||"",w=a.length>0,H=Zt($==="<username>"?void 0:$,a,h,_,w),T=await jl(t,o);return{instanceId:o,host:h,port:_,username:$==="<username>"?void 0:$,passwordKnown:w,connectionString:H,mode:"classic-redis",...T?{publicHost:T.host,publicPort:T.port,publicConnectionString:Zt($==="<username>"?void 0:$,a,T.host,T.port,w)}:{}}}if(!Jt(o))throw Error("cache connect 仅支持 tt-/tk-/r- 开头的实例 ID");let f=await tc(t,o),u=et(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(`未获取到实例 ${o} 的连接地址`);let l=c?.accountName||u.accountName||s.cache?.accountName,i=c?.password||"",g=i.length>0,y=Zt(l,i,u.host,u.port,g),d=await jl(t,o);return{instanceId:o,host:u.host,port:u.port,username:l,passwordKnown:g,connectionString:y,mode:"tair-serverless-kv",...d?{publicHost:d.host,publicPort:d.port,publicConnectionString:Zt(l,i,d.host,d.port,g)}:{}}}async function fc(e){await Ge().deleteInstance(new pn.DeleteInstanceRequest({instanceId:e}))}j();import*as tt from"@alicloud/r-kvstore20150101";fe();var iw="licell_public";async function uc(e,n){let t=k.requireAuth(),s=Ge(t),c=((await s.describeDBInstanceNetInfo(new tt.DescribeDBInstanceNetInfoRequest({instanceId:e}))).body?.netInfoItems?.instanceNetInfo||[]).find((g)=>g.IPType==="Public");if(c?.connectionString)return{host:c.connectionString,port:Number(c.port)||6379};let f=`${e}-pub`.toLowerCase().replace(/[^a-z0-9]/g,"").slice(0,40);try{await s.allocateInstancePublicConnection(new tt.AllocateInstancePublicConnectionRequest({instanceId:e,connectionStringPrefix:f,port:"6379"}))}catch(g){let y=P(g);if(y.includes("NetTypeExists")||y.includes("already exists"))n.message("公网地址已存在,正在获取...");else throw g}let i=((await s.describeDBInstanceNetInfo(new tt.DescribeDBInstanceNetInfoRequest({instanceId:e}))).body?.netInfoItems?.instanceNetInfo||[]).find((g)=>g.IPType==="Public");if(!i?.connectionString)return null;return{host:i.connectionString,port:Number(i.port)||6379}}async function ic(e,n,t){let s=k.requireAuth(),o=Ge(s),r=`${n}/32`;try{await o.modifySecurityIps(new tt.ModifySecurityIpsRequest({instanceId:e,securityIpGroupName:iw,securityIps:r,modifyMode:"Cover"}))}catch(c){t.message(`⚠️ 公网白名单设置失败: ${P(c)}`)}}oe();ce();function Ql(e){e.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(n)=>{await M({commandLabel:"licell cache add",interactiveTTY:R(),requiredCapabilities:["redis","vpc"]},async()=>{K(G.bgGreen(G.black(" \uD83E\uDDE0 Cache Provisioning (Redis) "))),Y();let t=R(),s=C(n.type)?.toLowerCase();if(!s){if(!t)throw Error("非交互模式下请传入 --type redis");let u=await lw({message:"选择缓存引擎:",options:[{value:"redis",label:"\uD83D\uDFE5 Tair/Redis (VPC 内网)"}]});if(Wl(u))process.exit(0);s=v(u,"缓存类型").toLowerCase()}if(s!=="redis")throw Error("--type 目前仅支持 redis");let o=qe(n.capacity,"capacity"),r=qe(n.computeUnit,"compute-unit"),c=A(),f=await U(c,"正在初始化缓存资源编排...","❌ 缓存拉起失败",()=>sc(c,{instanceId:C(n.instance),existingPassword:C(n.password),accountName:C(n.username),engineVersion:C(n.engineVersion),instanceClass:C(n.class),nodeType:C(n.nodeType),capacityMb:o,vkName:C(n.vkName),computeUnitNum:r,zoneId:C(n.zone),vpcId:C(n.vpc),vSwitchId:C(n.vsw),securityIpList:C(n.securityIpList)}));if(!f)return;if(!m())c.stop(G.green("✅ Redis 缓存已就绪并绑定到本工程内网!"));if(m()){S({stage:"cache.add",type:s,connectionStringMasked:Rn(f)});return}console.log(`
|
|
1597
|
-
\uD83D\uDD11 缓存连接串已生成: ${
|
|
1598
|
-
`),q("下次执行 licell deploy 时,将自动作为 process.env.REDIS_URL 注入!")})}),
|
|
1599
|
-
instanceId: ${
|
|
1600
|
-
instanceId: ${
|
|
1601
|
-
\uD83D\uDD11 新连接串: ${
|
|
1602
|
-
`),q("已同步更新 .licell/project.json 的 REDIS_* 环境变量")})}),
|
|
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(`
|
|
1597
|
+
\uD83D\uDD11 缓存连接串已生成: ${Y.cyan(Ae(f))}
|
|
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
|
+
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(`
|
|
1600
|
+
instanceId: ${Y.cyan(r.instanceId)}`),console.log(`mode: ${Y.cyan(r.mode)}`),console.log(`host: ${Y.cyan(r.host)}`),console.log(`port: ${Y.cyan(String(r.port))}`),console.log(`username: ${Y.cyan(r.username||"<none>")}`),console.log(`password: ${Y.cyan(r.passwordKnown?"<known in project>":"<unknown, please provide manually>")}`),console.log(`url: ${Y.cyan(r.connectionString)}`),r.publicHost)console.log(""),console.log(Y.yellow("── 公网访问 ──")),console.log(`public host: ${Y.cyan(r.publicHost)}`),console.log(`public port: ${Y.cyan(String(r.publicPort))}`),console.log(`public url: ${Y.cyan(r.publicConnectionString)}`);console.log(""),q("Done.")})}),n.command("cache rotate-password","轮换 Redis 密码").option("--instance <instanceId>","指定 Redis 实例 ID,不传则使用当前项目绑定实例").action(async(e)=>{await G({commandLabel:"licell cache rotate-password",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{W(Y.bgGreen(Y.black(" \uD83D\uDD10 Rotate Redis Password "))),B();let t=e.instance?K(e.instance,"实例 ID"):void 0,s=H(),r=await F(s,"正在执行 Redis 密钥轮换...","❌ Redis 密钥轮换失败",()=>dc(s,t));if(!r)return;if(!m())s.stop(Y.green("✅ Redis 密钥轮换完成"));if(m()){T({stage:"cache.rotate-password",instanceId:t||null,connectionStringMasked:Ae(r)});return}console.log(`
|
|
1601
|
+
\uD83D\uDD11 新连接串: ${Y.cyan(Ae(r))}
|
|
1602
|
+
`),q("已同步更新 .licell/project.json 的 REDIS_* 环境变量")})}),n.command("cache public-access [instanceId]","开通 Redis 公网访问并添加当前 IP 到白名单").option("--ip <ip>","手动指定公网 IP(不传则自动获取)").action(async(e,t)=>{await G({commandLabel:"licell cache public-access",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{let{resolvePublicIp:s}=await Promise.resolve().then(() => (fc(),cc));W(Y.bgGreen(Y.black(" \uD83C\uDF10 Cache Public Access "))),B();let r=E(e),o=H();o.start("正在获取公网 IP...");let c=t.ip?.trim()||await s();o.stop(`公网 IP: ${Y.cyan(c)}`);let f=await F(o,"正在解析缓存连接信息...","❌ 连接信息解析失败",()=>ur(r));if(!f)return;await F(o,`正在将 ${c}/32 添加到白名单 (licell_public)...`,"❌ 白名单设置失败",()=>wc(f.instanceId,c,o));let u=await F(o,"正在开通公网访问...","❌ 公网访问开通失败",()=>mc(f.instanceId,o));if(!m())o.stop(Y.green("✅ 公网访问已开通"));else{T({stage:"cache.public-access",instanceId:f.instanceId,publicIp:c,publicHost:u?.host||null,publicPort:u?.port||null});return}if(console.log(""),console.log(Y.yellow("── 内网访问 ──")),console.log(`host: ${Y.cyan(f.host)}`),console.log(`port: ${Y.cyan(String(f.port))}`),console.log(`url: ${Y.cyan(f.connectionString)}`),u){console.log(""),console.log(Y.yellow("── 公网访问 ──")),console.log(`host: ${Y.cyan(u.host)}`),console.log(`port: ${Y.cyan(String(u.port))}`);let g=f.passwordKnown?"<password>":"<password>",i=f.username?`${f.username}:${g}@`:"";console.log(`url: ${Y.cyan(`redis://${i}${u.host}:${u.port}`)}`)}else console.log(Y.yellow(`
|
|
1603
1603
|
⚠️ 公网地址尚未就绪,请稍后通过 cache connect 查看`));console.log(`
|
|
1604
|
-
白名单 IP: ${
|
|
1604
|
+
白名单 IP: ${Y.cyan(`${c}/32`)} (分组: licell_public)`),q("Done.")})}),n.command("cache rm <instanceId>","删除缓存实例").option("--yes","跳过确认").action(async(e,t)=>{await G({commandLabel:"licell cache rm",interactiveTTY:I(),requiredCapabilities:["redis"]},async()=>{W(Y.bgRed(Y.white(" \uD83D\uDDD1️ Delete Cache "))),B();let s=e.trim();if(!s)throw Error("请提供 instanceId");if(!t.yes&&I()){let o=await Xp({message:`确认删除缓存实例 ${Y.red(s)}?此操作不可恢复。`});if($l(o)||!o){q("已取消");return}}let r=H();if(await F(r,`正在删除实例 ${s}...`,"❌ 删除失败",()=>hc(s)),m()){T({stage:"cache.rm",instanceId:s});return}q(`实例 ${s} 已删除`)})})}Hn();Q();import O from"picocolors";import{mkdirSync as Rl,existsSync as _c,readFileSync as Cc,rmSync as e$,writeFileSync as ql}from"fs";import{join as Be,resolve as Pl}from"path";import{spawnSync as Fl}from"child_process";on();import{existsSync as fs,mkdirSync as Zp,readFileSync as Op,readdirSync as kl,statSync as Jp,writeFileSync as Np}from"fs";import{join as _l,resolve as zp}from"path";function Cl(n){let e=(n||"").trim().toLowerCase();if(!e||e==="smoke")return"smoke";if(e==="full")return"full";throw Error("--suite 仅支持 smoke 或 full")}function Sl(n=new Date){let e=(t)=>String(t).padStart(2,"0");return[n.getUTCFullYear(),e(n.getUTCMonth()+1),e(n.getUTCDate()),"-",e(n.getUTCHours()),e(n.getUTCMinutes()),e(n.getUTCSeconds()),"-",String(n.getTime()).slice(-4)].join("")}function pc(n=process.cwd()){return _l(n,".licell","e2e")}function ir(n,e=process.cwd()){return _l(pc(e),`${n}.json`)}function n$(n=process.cwd()){let e=pc(n);if(!fs(e))Zp(e,{recursive:!0});return e}function Dn(n,e=n.projectRoot){n$(e);let t=ir(n.runId,e);return Np(t,JSON.stringify(n,null,2)),t}function $c(n,e=process.cwd()){let t=ir(n,e);if(!fs(t))return null;return JSON.parse(Op(t,"utf8"))}function bc(n=process.cwd()){let e=pc(n);if(!fs(e))return[];let t=kl(e).filter((s)=>s.endsWith(".json")).map((s)=>s.slice(0,-5)).filter((s)=>s.length>0);return t.sort((s,r)=>r.localeCompare(s)),t}function Tl(n=process.cwd()){return bc(n)[0]}function El(n){if(!fs(n))return;if(!Jp(n).isDirectory())throw Error(`路径已存在且不是目录: ${n}`);if(kl(n).length>0)throw Error(`目录非空,请更换 --workspace 或先清理: ${n}`)}function kc(n=process.argv,e=process.execPath,t=process.cwd(),s=fs){let r=n[1];if(r&&typeof r==="string"&&!r.startsWith("-")){let o=zp(t,r);if(s(o))return{command:e,prefixArgs:[o]}}return{command:e,prefixArgs:[]}}vt();dn();fn();function qn(){return new Date().toISOString()}function At(n,e){if(e.length===0)return;console.log(O.bold(n));for(let t of e)console.log(`- ${t}`);console.log("")}function t$(n){let e=[Be(n,".licell","project.json"),Be(n,".ali","project.json")];for(let t of e){if(!_c(t))continue;try{let s=JSON.parse(Cc(t,"utf8"));if(typeof s.appName==="string"&&s.appName.trim().length>0)return s.appName.trim()}catch{}}return}function s$(n){let e=[Be(n,".licell","project.json"),Be(n,".ali","project.json")];for(let t of e){if(!_c(t))continue;try{let r=JSON.parse(Cc(t,"utf8")).network;if(!r||typeof r!=="object")continue;let o=typeof r.vpcId==="string"&&r.vpcId.trim().length>0?r.vpcId.trim():void 0,c=typeof r.vswId==="string"&&r.vswId.trim().length>0?r.vswId.trim():void 0,f=typeof r.sgId==="string"&&r.sgId.trim().length>0?r.sgId.trim():void 0;if(!o||!c)continue;return{vpcId:o,vswId:c,sgId:f}}catch{}}return}function xl(n,e,t){let s=[...n.prefixArgs,...e],r=Fl(n.command,s,{cwd:t,stdio:"inherit",env:process.env});if(r.status!==0){let o=r.signal?` signal=${r.signal}`:"";throw Error(`命令失败: licell ${e.join(" ")} (exit=${String(r.status)}${o})`)}}function r$(n,e,t){let s=Fl(n,e,{cwd:t,stdio:"inherit",env:process.env});if(s.status!==0){let r=s.signal?` signal=${s.signal}`:"";throw Error(`命令失败: ${n} ${e.join(" ")} (exit=${String(s.status)}${r})`)}}function o$(n){return`licell-e2e-${n}`.toLowerCase()}function Il(n,e){let t=Be(n,"e2e-static-dist");return Rl(t,{recursive:!0}),ql(Be(t,"index.html"),`<!doctype html>
|
|
1605
1605
|
<html>
|
|
1606
1606
|
<head><meta charset="UTF-8"><title>licell-e2e</title></head>
|
|
1607
|
-
<body><h1>licell e2e ${
|
|
1607
|
+
<body><h1>licell e2e ${e}</h1></body>
|
|
1608
1608
|
</html>
|
|
1609
|
-
`),
|
|
1610
|
-
`),t}function
|
|
1611
|
-
`),
|
|
1612
|
-
自动进入清理阶段...`));try{await
|
|
1613
|
-
`),await
|
|
1614
|
-
\uD83C\uDFF7️ alias=${
|
|
1615
|
-
`),q("Done.")})}),
|
|
1616
|
-
\uD83C\uDFF7️ alias=${
|
|
1617
|
-
`),q("Done.")})}),
|
|
1618
|
-
保留数量: ${
|
|
1619
|
-
提示: 加上 --apply 才会执行实际删除`));console.log(""),q("Done.");return}if(
|
|
1620
|
-
保留数量: ${
|
|
1621
|
-
提示: 加上 --apply 才会执行实际删除`));console.log(""),q("Done.")})})}import
|
|
1622
|
-
\uD83C\uDFF7️ 域名路由已绑定 alias=${
|
|
1623
|
-
`);q(`\uD83D\uDD17 你的应用现在可通过安全的 ${
|
|
1624
|
-
recordId: ${
|
|
1625
|
-
`),q("Done.")})}),
|
|
1626
|
-
`);try{
|
|
1627
|
-
\uD83D\uDCE1 正在监听云端 [${
|
|
1628
|
-
`));let u=Math.floor(Date.now()/1000)-60,
|
|
1629
|
-
\uD83D\uDC4B 日志流已断开`));process.exit(0)};process.on("SIGINT",
|
|
1630
|
-
`)){let s=t.trim();if(!s)continue;let
|
|
1631
|
-
`)}function
|
|
1632
|
-
[stdout truncated]`:"",
|
|
1633
|
-
[stderr truncated]`:"";return{isError:Boolean(
|
|
1609
|
+
`),ql(Be(t,"health.txt"),`ok:${e}
|
|
1610
|
+
`),t}function us(n,e){n.steps.push(e),n.updatedAt=qn()}function cn(n,e,t){let s=qn(),r=`licell ${t.join(" ")}`;N({stage:`e2e.${e}`,action:e,status:"start",data:{command:r}});try{xl(n.invocation,t,n.workspaceDir),us(n.manifest,{name:e,command:r,status:"ok",startedAt:s,endedAt:qn()}),Dn(n.manifest),N({stage:`e2e.${e}`,action:e,status:"ok"})}catch(o){throw us(n.manifest,{name:e,command:r,status:"failed",startedAt:s,endedAt:qn(),error:A(o)}),Dn(n.manifest),N({stage:`e2e.${e}`,action:e,status:"failed",message:A(o)}),o}}function c$(n,e,t,s){if(!e){us(n.manifest,{name:t,command:`licell ${s.join(" ")}`,status:"skipped",startedAt:qn(),endedAt:qn()}),Dn(n.manifest);return}cn(n,t,s)}function f$(n,e,t,s){let r=qn(),o=`${t} ${s.join(" ")}`.trim();N({stage:`e2e.${e}`,action:e,status:"start",data:{command:o}});try{r$(t,s,n.workspaceDir),us(n.manifest,{name:e,command:o,status:"ok",startedAt:r,endedAt:qn()}),Dn(n.manifest),N({stage:`e2e.${e}`,action:e,status:"ok"})}catch(c){throw us(n.manifest,{name:e,command:o,status:"failed",startedAt:r,endedAt:qn(),error:A(c)}),Dn(n.manifest),N({stage:`e2e.${e}`,action:e,status:"failed",message:A(c)}),c}}function u$(n){let e=["fc","oss","rds","redis","logs"];if(n.domain||n.domainSuffix)e.push("dns");if(n.enableCdn)e.push("cdn");if(n.includeStatic)e.push("oss");if(n.useVpc)e.push("vpc");return[...new Set(e)]}function i$(n,e){return`licell-${n}-${e.substring(0,4)}`.toLowerCase()}async function g$(n){let e=process.cwd(),t=I(),s=Cl(E(n.suite)),r=E(n.runId)||Sl(),o=o$(r),c=E(n.runtime)||"nodejs22",f=E(n.target)||"preview",u=Boolean(n.enableVpc),g=E(n.domain),i=E(n.domainSuffix),l=E(n.dbInstance),d=E(n.cacheInstance),a=Boolean(n.skipStatic),p=g?Ys(g):void 0,w=i?tt(i):void 0;if(p&&w)throw Error("--domain 与 --domain-suffix 不能同时使用");let _=Boolean(n.enableCdn);if(_&&!p&&!w)throw Error("--enable-cdn 需要配合 --domain 或 --domain-suffix");let $=Boolean(n.preview);if($&&!w)throw Error("--preview 需要配合 --domain-suffix");let y=Boolean(n.cleanup),h=Boolean(n.yes),U=Pl(E(n.workspace)||Be(e,".licell","e2e-work",r));El(U),Rl(U,{recursive:!0});let C={runId:r,suite:s,status:"running",createdAt:qn(),updatedAt:qn(),projectRoot:e,workspaceDir:U,target:f,runtime:c,resources:{appName:o,...p?{domain:p}:{},...w?{domainSuffix:w}:{}},steps:[],cleanup:{status:"pending",details:[],errors:[]}},L=Dn(C,e),S=kc(),b={invocation:S,workspaceDir:U,manifest:C,state:{hasDeployedApi:!1,hasDeployedStatic:!1}};W(O.bgBlue(O.white(" \uD83E\uDDEA Licell E2E Runner "))),console.log(`runId: ${O.cyan(r)}`),console.log(`suite: ${O.cyan(s)}`),console.log(`workspace: ${O.cyan(U)}`),console.log(`manifest: ${O.cyan(L)}
|
|
1611
|
+
`),At("执行计划",[`runtime: ${c}`,`target: ${f}`,`api function: ${o}`,`network: ${u?"vpc(shared licell-vpc)":"public(no-vpc)"}`,...p?[`fixed domain: ${p}`]:[],...w?[`domain suffix: ${w}`]:[],..._?["cdn: enabled"]:[],...$?["preview deploy: enabled"]:[],...s==="full"&&!a?["static deploy: enabled"]:["static deploy: skipped"]]);let P;N({stage:"e2e",action:"run",status:"start",data:{runId:r,suite:s,runtime:c,target:f,workspaceDir:U}});try{await G({commandLabel:"licell e2e run",interactiveTTY:t,requiredCapabilities:u$({domain:p,domainSuffix:w,enableCdn:_,includeStatic:s==="full"&&!a,useVpc:u})},async()=>{cn(b,"init",["init","--runtime",c,"--app",o,"--yes"]);let R=t$(U);if(!R)throw Error("init 成功后未检测到 appName");if(c.startsWith("nodejs"))f$(b,"bun-install","bun",["install"]);if(C.resources.appName=R,!C.resources.domain&&C.resources.domainSuffix)C.resources.domain=`${R}.${C.resources.domainSuffix}`;C.updatedAt=qn(),Dn(C,e),At("创建资源",[`fc function: ${R}`,...C.resources.domain?[`domain: ${C.resources.domain}`]:[]]);let j=["deploy","--type","api","--runtime",c,"--target",f];if(j.push(u?"--enable-vpc":"--disable-vpc"),p)j.push("--domain",p);if(w)j.push("--domain-suffix",w);if(_)j.push("--enable-cdn");cn(b,"deploy-api",j),b.state.hasDeployedApi=!0;let gn=s$(U);if(gn){if(C.resources.vpcId=gn.vpcId,C.resources.vswId=gn.vswId,gn.sgId)C.resources.sgId=gn.sgId;Dn(C,e)}if(cn(b,"fn-list",["fn","list","--prefix",R,"--limit","20"]),cn(b,"fn-info",["fn","info",R,"--target",f]),cn(b,"fn-invoke",["fn","invoke",R,"--target",f,"--payload",JSON.stringify({runId:r,ping:"pong"})]),cn(b,"env-set",["env","set","LICELL_E2E_RUN_ID",r]),cn(b,"env-list",["env","list","--target",f]),cn(b,"env-pull",["env","pull","--target",f]),cn(b,"env-rm",["env","rm","LICELL_E2E_RUN_ID","--yes"]),cn(b,"release-list",["release","list","--limit","5"]),cn(b,"release-promote",["release","promote","--target",f]),$&&w){let ln=["deploy","--type","api","--runtime",c,"--preview"];if(ln.push(u?"--enable-vpc":"--disable-vpc"),ln.push("--domain-suffix",w),cn(b,"deploy-api-preview",ln),s==="full"&&!a){let We=["deploy","--type","static","--dist",Il(U,`${r}-preview`),"--preview"];We.push("--domain-suffix",w),cn(b,"deploy-static-preview",We)}cn(b,"release-prune-preview",["release","prune","--preview","--keep","2"])}if(cn(b,"logs-once",["logs","--once","--window","180","--lines","200"]),cn(b,"oss-list",["oss","list","--limit","5"]),cn(b,"db-list",["db","list","--limit","5"]),cn(b,"cache-list",["cache","list","--limit","5"]),l)cn(b,"db-info",["db","info",l]),cn(b,"db-connect",["db","connect",l]);if(d)cn(b,"cache-info",["cache","info",d]),cn(b,"cache-connect",["cache","connect",d]);let Xn=(()=>{let ln=C.resources.domain;if(!ln)return;return zn(ln).rootDomain})();if(c$(b,Boolean(Xn),"dns-records-list",["dns","records","list",Xn||"","--limit","20"]),s==="full"){if(cn(b,"whoami",["whoami"]),!a){let ln=k.requireAuth(),De=Il(U,r);cn(b,"deploy-static",["deploy","--type","static","--dist",De]),b.state.hasDeployedStatic=!0,C.resources.staticBucket=i$(R,ln.accountId),Dn(C,e),At("静态资源",[`oss bucket: ${C.resources.staticBucket}`,`upload prefix: e2e-upload-${r}`]),cn(b,"oss-upload",["oss","upload","--bucket",C.resources.staticBucket,"--source-dir",De,"--target-dir",`e2e-upload-${r}`]),cn(b,"oss-ls-uploaded",["oss","ls",C.resources.staticBucket,`e2e-upload-${r}`,"--limit","20"])}}}),C.status="succeeded",C.updatedAt=qn(),Dn(C,e),At("E2E 结果",[`runId: ${r}`,`status: ${C.status}`,...C.resources.appName?[`fc function: ${C.resources.appName}`]:[],...C.resources.domain?[`domain: ${C.resources.domain}`]:[],...C.resources.staticBucket?[`oss bucket: ${C.resources.staticBucket}`]:[],...C.resources.vpcId?[`vpc: ${C.resources.vpcId}/${C.resources.vswId||"-"}`]:[]]),console.log(O.green(`✅ E2E run 完成(${r})`)),T({stage:"e2e",runId:r,suite:s,status:C.status,appName:C.resources.appName||null,domain:C.resources.domain||null,staticBucket:C.resources.staticBucket||null,workspaceDir:U})}catch(R){P=R,C.status="failed",C.updatedAt=qn(),C.notes=[...C.notes||[],A(R)],Dn(C,e)}if(y){console.log(O.gray(`
|
|
1612
|
+
自动进入清理阶段...`));try{await Ll(C,{yes:h,keepWorkspace:!1,invocation:S,interactiveTTY:t})}catch(R){if(!P)throw R;console.warn(O.yellow(`⚠️ 自动清理失败: ${A(R)}`))}}else console.log(O.gray(`可执行清理命令: licell e2e cleanup ${r}`));if(P)throw P;q("Done.")}async function Ll(n,e){let t=n.status,s=e.interactiveTTY??I();await Qn(`清理 E2E 运行 ${n.runId} 相关云资源`,{yes:e.yes,interactiveTTY:s});let r=e.invocation||kc(),o=[],c=[],f=n.workspaceDir,u=n.resources.appName,g=n.resources.domain,i=n.resources.staticBucket,l=n.resources.vpcId,d=n.resources.vswId,a=(p,w,_)=>{try{xl(r,w,f),c.push(`${p}: ok`)}catch($){let y=A($),h=y.toLowerCase();if((_?.ignoreErrorPatterns||[]).some((C)=>h.includes(C))){c.push(`${p}: skipped (${y})`);return}o.push(`${p}: ${y}`),c.push(`${p}: failed`)}};if(n.cleanup=n.cleanup||{},n.cleanup.attemptedAt=qn(),n.cleanup.status="pending",n.cleanup.details=c,n.cleanup.errors=o,n.updatedAt=qn(),Dn(n,n.projectRoot),N({stage:"e2e.cleanup",action:"cleanup",status:"start",data:{runId:n.runId,appName:u||null,domain:g||null,staticBucket:i||null}}),At("清理目标",[...u?[`fc function: ${u}`]:[],...g?[`domain binding: ${g}`]:[],...i?[`oss bucket: ${i}`]:[],...l?[`vpc network: ${l}/${d||"-"} (shared, keep)`]:[],...e.keepWorkspace?["workspace: keep"]:[`workspace: ${f}`]]),await G({commandLabel:"licell e2e cleanup",interactiveTTY:s,requiredCapabilities:["fc",...g?["dns"]:[],...i?["oss"]:[]]},async()=>{if(g)console.log(O.gray(`清理 domain: ${g}`)),a("domain-rm",["domain","rm",g,"--yes"]);if(u){console.log(O.gray(`清理 preview 域名: ${u}`)),a("release-prune-preview",["release","prune","--preview","--keep","0","--apply","--yes"],{ignoreErrorPatterns:["not found","no preview"]}),console.log(O.gray(`清理 function: ${u}`)),a("fn-rm",["fn","rm",u,"--force","--yes"],{ignoreErrorPatterns:["functionnotfound","does not exist","not found"]});let p=`${u}-static-proxy`;console.log(O.gray(`清理 static proxy function: ${p}`)),a("fn-rm-static-proxy",["fn","rm",p,"--force","--yes"],{ignoreErrorPatterns:["functionnotfound","does not exist","not found"]})}if(i){console.log(O.gray(`清理 oss bucket: ${i}`));try{let p=await Ig(i);c.push(`oss-bucket-rm: ok (${p.bucket}, objects=${p.deletedObjects}, bucketDeleted=${p.deletedBucket})`),console.log(O.green(`oss 清理完成: ${p.bucket} (objects=${p.deletedObjects})`))}catch(p){o.push(`oss-bucket-rm: ${A(p)}`),c.push("oss-bucket-rm: failed"),console.warn(O.yellow(`oss 清理失败: ${A(p)}`))}}if(l)c.push(`vpc-rm: skipped (${l} 为共享网络,e2e 默认不自动删除)`)}),!e.keepWorkspace)try{e$(f,{recursive:!0,force:!0}),c.push("workspace-rm: ok"),console.log(O.green(`workspace 已清理: ${f}`))}catch(p){o.push(`workspace-rm: ${A(p)}`),c.push("workspace-rm: failed"),console.warn(O.yellow(`workspace 清理失败: ${A(p)}`))}if(n.cleanup.finishedAt=qn(),n.cleanup.status=o.length>0?"partial":"done",o.length>0)n.status="partial_cleaned";else n.status=t==="failed"?"failed":"cleaned";if(n.updatedAt=qn(),Dn(n,n.projectRoot),o.length>0){console.warn(O.yellow(`⚠️ 清理存在 ${o.length} 个失败项:`));for(let p of o)console.warn(O.yellow(`- ${p}`))}else At("清理结果",c.map((p)=>p.replace(/^([^:]+): /,"$1 => "))),console.log(O.green(`✅ 清理完成: ${n.runId}`));T({stage:"e2e.cleanup",runId:n.runId,status:n.cleanup.status||"unknown",details:c,errors:o})}function Al(n){n.command("e2e run","执行固定 E2E 套件(默认 smoke)").option("--suite <suite>","套件:smoke/full(默认 smoke)").option("--run-id <id>","指定 runId(默认自动生成)").option("--runtime <runtime>","部署 runtime(默认 nodejs22)").option("--target <alias>","部署 target alias(默认 preview)").option("--enable-vpc","API 部署启用 VPC(默认关闭,便于无残留清理)").option("--domain <domain>","固定完整域名(可选)").option("--domain-suffix <suffix>","固定域名后缀(可选)").option("--db-instance <instanceId>","full 套件时附加验证 db info/connect(复用已有实例)").option("--cache-instance <instanceId>","full 套件时附加验证 cache info/connect(复用已有实例)").option("--skip-static","full 套件时跳过 static + oss upload 场景").option("--enable-cdn","部署时启用 CDN(需配合 domain/domain-suffix)").option("--preview","测试 preview 部署流程(需配合 --domain-suffix)").option("--cleanup","执行完后自动清理").option("--workspace <dir>","指定 E2E 工作目录(默认 .licell/e2e-work/<runId>)").option("--yes","自动清理时跳过二次确认").action(async(e)=>{try{await g$(e)}catch(t){if(m())pn(t,{stage:"e2e"});else console.error(O.red(A(t)));process.exitCode=1}}),n.command("e2e cleanup [runId]","清理指定 E2E run 产生的资源").option("--manifest <path>","直接指定 manifest 文件路径").option("--keep-workspace","保留本地 workspace 目录").option("--yes","跳过二次确认(危险)").action(async(e,t)=>{try{let s=process.cwd(),r=E(t.manifest),o=null;if(r){let c=Pl(r);if(!_c(c))throw Error(`manifest 不存在: ${c}`);o=JSON.parse(Cc(c,"utf8"))}else{let c=E(e)||Tl(s);if(!c)throw Error("未找到任何 e2e manifest,请先执行 `licell e2e run`");if(o=$c(c,s),!o)throw Error(`未找到 runId=${c} 的 manifest`)}W(O.bgBlue(O.white(" \uD83E\uDDF9 Licell E2E Cleanup "))),console.log(`runId: ${O.cyan(o.runId)}`),console.log(`workspace: ${O.cyan(o.workspaceDir)}`),console.log(`manifest: ${O.cyan(ir(o.runId,o.projectRoot))}
|
|
1613
|
+
`),await Ll(o,{yes:Boolean(t.yes),keepWorkspace:Boolean(t.keepWorkspace)}),q("Done.")}catch(s){if(m())pn(s,{stage:"e2e.cleanup"});else console.error(O.red(A(s)));process.exitCode=1}}),n.command("e2e list","查看本项目 e2e 运行记录").action(()=>{let e=bc(process.cwd());if(e.length===0){q("当前项目暂无 e2e 记录");return}for(let t of e){let s=$c(t,process.cwd());if(!s)continue;console.log(`${O.cyan(t)} suite=${O.gray(s.suite)} status=${O.gray(s.status)} workspace=${O.gray(s.workspaceDir)}`)}console.log(""),q("Done.")})}import Z from"picocolors";Hn();ne();on();Q();fn();Q();Pe();et();import*as Hl from"@alicloud/fc20230330";xn();import l$,*as Ul from"@alicloud/oss20190517";import*as Ml from"@alicloud/openapi-client";import*as Gl from"@alicloud/tea-util";var d$=nn(l$,"@alicloud/oss20190517");function y$(){let n=k.requireAuth(),e=new d$(new Ml.Config({accessKeyId:n.ak,accessKeySecret:n.sk,regionId:n.region,endpoint:`oss-${n.region}.aliyuncs.com`})),t=new Gl.RuntimeOptions({connectTimeout:8000,readTimeout:120000});return{auth:n,client:e,runtime:t}}async function a$(n){let{client:e}=hn(),t=[],s=new RegExp(`^${n}-preview-v\\d+\\.`),r;while(!0){let o=await D(()=>e.listCustomDomains(new Hl.ListCustomDomainsRequest({limit:100,nextToken:r}))),c=o.body?.customDomains||[];for(let f of c){let u=f.domainName;if(u&&s.test(u))t.push(u)}if(r=o.body?.nextToken,!r||c.length===0)break}return t}function h$(n,e){let t=n.match(new RegExp(`^${e}-preview-v(\\d+)\\.`));if(!t)return null;return parseInt(t[1],10)}async function m$(n,e){try{await D(()=>n.deleteCustomDomain(e))}catch(t){if(!an(t))throw t}}async function w$(n,e){let{client:t,runtime:s}=y$(),r=await er(n,e,1000),o=0;for(let c of r)try{await D(()=>t.deleteObjectWithOptions(n,c.name,new Ul.DeleteObjectRequest({}),{},s)),o++}catch(f){if(!an(f))throw f}return o}async function Bl(n,e,t){let s=await a$(n),r=s.map((i)=>({domain:i,version:h$(i,n)})).filter((i)=>i.version!==null).sort((i,l)=>(l.version||0)-(i.version||0)),o=r.slice(0,e),c=r.slice(e),f={keep:e,totalPreviewDomains:s.length,candidates:c.map((i)=>i.domain),deletedDomains:[],deletedOssPaths:[],failed:[]};if(!t)return f;let u=nr(n),{client:g}=hn();for(let i of c)try{if(await m$(g,i.domain),f.deletedDomains.push(i.domain),i.version!==null)for(let l of[i.version,i.version-1]){if(l<=0)continue;let d=`_preview/${l}/`;try{if(await w$(u,d)>0)f.deletedOssPaths.push(d)}catch{}}}catch(l){f.failed.push({domain:i.domain,reason:l instanceof Error?l.message:String(l)})}return f}function vl(n){n.command("release list","查看函数版本列表").option("--limit <n>","返回版本数量,默认 20").action(async(e)=>{await G({commandLabel:"licell release list",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{W(Z.bgBlue(Z.white(" \uD83D\uDCDA Function Versions "))),B();let t=k.getProject();Kn(t);let s=An(e.limit,20,100),r=H(),o=await F(r,"正在拉取函数版本列表...","❌ 获取版本列表失败",()=>kt(t.appName,s));if(!o)return;if(!m())r.stop(Z.green(`✅ 共获取 ${o.length} 个版本`));if(m()){T({stage:"release.list",appName:t.appName,count:o.length,versions:o});return}if(o.length===0){q("当前函数还没有已发布版本");return}for(let c of o){let f=c.versionId||"unknown",u=c.createdTime||"-",g=c.description||"-";console.log(`${Z.cyan(f)} ${Z.gray(u)} ${g}`)}q("Done.")})}),n.command("release promote [versionId]","发布并切流到目标别名").option("--target <target>","目标别名,默认 prod").action(async(e,t)=>{await G({commandLabel:"licell release promote",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{W(Z.bgBlue(Z.white(" \uD83D\uDE80 Promote Release "))),B();let s=k.getProject();Kn(s);let r=ue(t.target),o=H(),c=await F(o,`正在准备发布到别名 ${r}...`,"❌ 切流失败",async()=>{let f=e?K(e,"versionId"):"";if(!f){o.message("未指定 versionId,正在发布当前函数代码为新版本...");try{f=await xe(s.appName,`promote ${r} at ${new Date().toISOString()}`)}catch(u){if(!Ds(u))throw u;o.message("检测到当前代码无变更,复用最新已发布版本..."),f=await Ws(s.appName)}}return await Le(s.appName,r,f,`promoted by licell at ${new Date().toISOString()}`),f});if(!c)return;if(!m())o.stop(Z.green("✅ 别名切流完成"));if(m()){T({stage:"release.promote",appName:s.appName,target:r,versionId:c});return}console.log(`
|
|
1614
|
+
\uD83C\uDFF7️ alias=${Z.cyan(r)} -> version=${Z.cyan(c)}
|
|
1615
|
+
`),q("Done.")})}),n.command("release rollback <versionId>","回滚到指定函数版本").option("--target <target>","目标别名,默认 prod").action(async(e,t)=>{await G({commandLabel:"licell release rollback",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{W(Z.bgBlue(Z.white(" ↩ Rollback Release "))),B();let s=k.getProject();Kn(s);let r=ue(t.target),o=K(e,"versionId"),c=H();if(!await F(c,`正在回滚 ${r} 到版本 ${o}...`,"❌ 回滚失败",async()=>{return await Le(s.appName,r,o,`rollback by licell at ${new Date().toISOString()}`),!0}))return;if(!m())c.stop(Z.green("✅ 回滚完成"));if(m()){T({stage:"release.rollback",appName:s.appName,target:r,versionId:o});return}console.log(`
|
|
1616
|
+
\uD83C\uDFF7️ alias=${Z.cyan(r)} -> version=${Z.cyan(o)}
|
|
1617
|
+
`),q("Done.")})}),n.command("release prune","清理历史函数版本(默认仅预览)").option("--keep <n>","保留最近 N 个版本,默认 10").option("--apply","执行删除,未传则仅预览").option("--yes","跳过二次确认(危险)").option("--preview","清理预览域名绑定(而非函数版本)").action(async(e)=>{await G({commandLabel:"licell release prune",interactiveTTY:I(),requiredCapabilities:e.preview?["fc","dns"]:["fc"]},async()=>{W(Z.bgBlue(Z.white(e.preview?" \uD83E\uDDF9 Prune Preview Domains ":" \uD83E\uDDF9 Prune Function Versions "))),B();let t=k.getProject();Kn(t);let s=Fn(e.keep,"keep")||(e.preview?3:10),r=Boolean(e.apply);if(e.preview){if(r)await Qn(`清理预览域名绑定(保留最近 ${s} 个)`,{yes:Boolean(e.yes)});let f=H(),u=await F(f,r?"正在清理预览域名...":"正在预览可清理的预览域名...","❌ 清理失败",()=>Bl(t.appName,s,r));if(!u)return;if(!m())f.stop(Z.green(r?"✅ 清理任务完成":"✅ 预览完成"));if(m()){T({stage:"release.prune.preview",appName:t.appName,keepRequested:s,applyRequested:r,...u});return}if(console.log(`
|
|
1618
|
+
保留数量: ${Z.cyan(String(u.keep))}`),console.log(`发现预览域名: ${Z.cyan(String(u.totalPreviewDomains))}`),console.log(`候选删除: ${Z.cyan(String(u.candidates.length))}`),u.candidates.length>0)console.log(`候选: ${u.candidates.join(", ")}`);if(r){if(console.log(`已删除域名绑定: ${Z.cyan(String(u.deletedDomains.length))}`),console.log(`已删除 OSS 路径: ${Z.cyan(String(u.deletedOssPaths.length))}`),u.failed.length>0){console.log(Z.yellow(`删除失败: ${u.failed.length}`));for(let g of u.failed)console.log(Z.yellow(`- ${g.domain}: ${g.reason}`))}}else console.log(Z.gray(`
|
|
1619
|
+
提示: 加上 --apply 才会执行实际删除`));console.log(""),q("Done.");return}if(r)await Qn(`清理函数历史版本(保留最近 ${s} 个)`,{yes:Boolean(e.yes)});let o=H(),c=await F(o,r?"正在清理历史版本...":"正在预览可清理版本...","❌ 清理失败",()=>Co(t.appName,s,r));if(!c)return;if(!m())o.stop(Z.green(r?"✅ 清理任务完成":"✅ 预览完成"));if(m()){T({stage:"release.prune",appName:t.appName,keepRequested:s,applyRequested:r,...c});return}if(console.log(`
|
|
1620
|
+
保留数量: ${Z.cyan(String(c.keep))}`),console.log(`总发布版本: ${Z.cyan(String(c.totalVersions))}`),console.log(`Alias 保护版本: ${Z.cyan(String(c.aliasProtectedVersions.length))}`),console.log(`候选删除版本: ${Z.cyan(String(c.candidates.length))}`),c.candidates.length>0)console.log(`候选: ${c.candidates.join(", ")}`);if(r){if(console.log(`已删除: ${Z.cyan(String(c.deleted.length))}`),c.failed.length>0){console.log(Z.yellow(`删除失败: ${c.failed.length}`));for(let f of c.failed)console.log(Z.yellow(`- ${f.versionId}: ${f.reason}`))}}else console.log(Z.gray(`
|
|
1621
|
+
提示: 加上 --apply 才会执行实际删除`));console.log(""),q("Done.")})})}import le from"picocolors";ne();Hn();on();Q();fn();function Kl(n){n.command("domain add <domain>","绑定自定义域名").option("--ssl","自动配置 Let's Encrypt 免费证书开启 HTTPS").option("--ssl-force-renew","配合 --ssl 强制续签证书(忽略到期阈值)").option("--target <target>","将域名路由到指定 FC alias(如 prod/preview)").action(async(e,t)=>{await G({commandLabel:"licell domain add",interactiveTTY:I(),requiredCapabilities:["fc","dns"]},async()=>{W(le.bgCyan(le.black(" \uD83C\uDF10 Domain & SSL Configuration ")));let s=await B(),r=K(e,"域名"),o=ue(t.target);if(t.sslForceRenew&&!t.ssl)throw Error("--ssl-force-renew 需要与 --ssl 一起使用");let c=k.getProject();Kn(c);let f=H(),u=await F(f,`正在配置云解析 DNS,将 ${r} 指向应用...`,"❌ 配置流程中断",async()=>{let g=`${s.accountId}.${s.region}.fc.aliyuncs.com`;await Rt(r,g,o);try{f.message(`正在确保别名 ${o} 存在...`);let i;try{i=await xe(c.appName,`domain bind ${o} at ${new Date().toISOString()}`)}catch(l){if(!Ds(l))throw l;i=await Ws(c.appName)}await Le(c.appName,o,i,`domain bind by licell at ${new Date().toISOString()}`)}catch{if(!m())console.warn(le.yellow(`⚠️ 未能自动创建别名 ${o},请先 deploy 后执行 licell release promote`))}if(t.ssl)return f.message("DNS CNAME 配置成功。正在接管 Let's Encrypt 签发流程..."),yg(r,f,{forceRenew:Boolean(t.sslForceRenew)});return`http://${r}`});if(!u)return;if(!m())f.stop(le.green("✅ 域名绑定与网络平面配置大功告成!"));if(m()){T({stage:"domain.add",domain:r,releaseTarget:o||null,ssl:Boolean(t.ssl),finalUrl:u});return}if(o)console.log(`
|
|
1622
|
+
\uD83C\uDFF7️ 域名路由已绑定 alias=${le.cyan(o)}
|
|
1623
|
+
`);q(`\uD83D\uDD17 你的应用现在可通过安全的 ${le.cyan(le.underline(u))} 访问`)})}),n.command("domain rm <domain>","解绑自定义域名并清理 DNS CNAME").option("--yes","跳过二次确认(危险)").action(async(e,t)=>{await G({commandLabel:"licell domain rm",interactiveTTY:I(),requiredCapabilities:["fc","dns"]},async()=>{W(le.bgCyan(le.black(" \uD83C\uDF10 Domain Removal "))),await B();let s=K(e,"域名").toLowerCase();await Qn(`解绑域名 ${s}`,{yes:Boolean(t.yes)});let r=H();if(!await F(r,`正在解绑域名 ${s}...`,"❌ 域名解绑失败",async()=>{return await ng(s),!0}))return;if(!m())r.stop(le.green("✅ 域名已解绑并完成 DNS 清理")),q("Done.");else T({stage:"domain.rm",domain:s,removed:!0})})})}import{text as p$,isCancel as $$}from"@clack/prompts";import _e from"picocolors";Hn();on();fn();function Yl(n){n.command("dns records list [domain]","查看域名解析记录").option("--limit <n>","返回数量,默认 100").action(async(e,t)=>{await G({commandLabel:"licell dns records list",interactiveTTY:I(),requiredCapabilities:["dns"]},async()=>{B();let s=e;if(!s){if(!I())throw Error("缺少域名参数,请使用:licell dns records list <domain>");let u=await p$({message:"请输入要查看的域名:",placeholder:"example.com"});if($$(u))process.exit(0);s=K(u,"域名")}let r=K(s,"域名").toLowerCase(),o=An(t.limit,100,500),c=H(),f=await F(c,`正在拉取 ${r} 的解析记录...`,"❌ 获取 DNS 记录失败",()=>eg(r,o));if(!f)return;if(!m())c.stop(_e.green(`✅ 共获取 ${f.length} 条记录`));if(m()){T({stage:"dns.records.list",domain:r,count:f.length,records:f});return}if(f.length===0){q("当前域名无解析记录");return}for(let u of f)console.log(`${_e.cyan(u.recordId)} ${_e.gray(u.rr)} ${_e.gray(u.type)} ${_e.gray(u.value)} ttl=${_e.gray(String(u.ttl||"-"))}`);console.log(""),q("Done.")})}),n.command("dns records add <domain>","添加域名解析记录").option("--rr <rr>","主机记录,如 @/www/api").option("--type <type>","记录类型,如 A/CNAME/TXT").option("--value <value>","记录值").option("--ttl <ttl>","TTL 秒,默认 600").option("--line <line>","线路,默认 default").action(async(e,t)=>{await G({commandLabel:"licell dns records add",interactiveTTY:I(),requiredCapabilities:["dns"]},async()=>{B();let s=K(e,"域名").toLowerCase(),r=E(t.rr),o=E(t.type),c=E(t.value);if(!r||!o||!c)throw Error("dns records add 需要提供 --rr --type --value");let f=Fn(t.ttl,"ttl"),u=E(t.line)||"default",g=H(),i=await F(g,"正在添加 DNS 记录...","❌ DNS 记录创建失败",()=>tg(s,{rr:r,type:o,value:c,ttl:f,line:u}));if(!i)return;if(!m())g.stop(_e.green("✅ DNS 记录已创建"));if(m()){T({stage:"dns.records.add",domain:s,recordId:i,rr:r,type:o,value:c,ttl:f||600,line:u});return}console.log(`
|
|
1624
|
+
recordId: ${_e.cyan(i)}
|
|
1625
|
+
`),q("Done.")})}),n.command("dns records rm <recordId>","删除域名解析记录").option("--yes","跳过二次确认(危险)").action(async(e,t)=>{await G({commandLabel:"licell dns records rm",interactiveTTY:I(),requiredCapabilities:["dns"]},async()=>{B();let s=K(e,"recordId");await Qn(`删除 DNS 记录 ${s}`,{yes:Boolean(t.yes)});let r=H();if(!await F(r,`正在删除记录 ${s}...`,"❌ DNS 记录删除失败",async()=>{return await sg(s),!0}))return;if(!m())r.stop(_e.green("✅ DNS 记录已删除")),q("Done.");else T({stage:"dns.records.rm",recordId:s,removed:!0})})})}import Ht from"picocolors";import{writeFileSync as jl}from"fs";Hn();ne();on();Q();fn();function Dl(n){n.command("env list","查看云端环境变量").option("--target <target>","查看指定 FC alias 的环境变量(如 prod/preview)").option("--show-values","显示完整变量值(默认隐藏)").action(async(e)=>{await G({commandLabel:"licell env list",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let t=k.getProject();Kn(t);let s=e.target?ue(e.target):void 0,r=H(),o=await F(r,s?`正在拉取 alias=${s} 的环境变量...`:"正在拉取云端环境变量...","❌ 获取环境变量失败",()=>bt(t.appName,s));if(!o)return;if(!m())r.stop(Ht.green(`✅ 共 ${Object.keys(o).length} 个环境变量`));let c=Object.entries(o).sort(([u],[g])=>u.localeCompare(g)),f=Boolean(e.showValues);if(m()){let u=f?Object.fromEntries(c):Object.fromEntries(c.map(([g,i])=>[g,`<hidden:${String(i).length} chars>`]));T({stage:"env.list",qualifier:s||null,count:c.length,showValues:f,envs:u});return}if(c.length===0){q("云端当前无环境变量");return}for(let[u,g]of c){let i=f?g:`<hidden:${String(g).length} chars>`;console.log(`${Ht.cyan(u)}=${i}`)}console.log(""),q("Done.")})}),n.command("env set <key> <value>","设置云端环境变量(并同步本地 .licell/project.json)").action(async(e,t)=>{await G({commandLabel:"licell env set",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let s=k.getProject();Kn(s);let r=xo(K(e,"环境变量名")),o=K(t,"环境变量值"),c=H(),f=await F(c,`正在写入环境变量 ${r}...`,"❌ 环境变量写入失败",()=>ko(s.appName,r,o));if(!f)return;if(k.setProject({envs:f},{replaceEnvs:!0}),!m())c.stop(Ht.green("✅ 环境变量已写入云端并同步到本地配置")),q("Done.");else T({stage:"env.set",key:r,updatedCount:Object.keys(f).length})})}),n.command("env rm <key>","删除云端环境变量(并同步本地 .licell/project.json)").option("--yes","跳过二次确认(危险)").action(async(e,t)=>{await G({commandLabel:"licell env rm",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let s=k.getProject();Kn(s);let r=xo(K(e,"环境变量名"));await Qn(`删除环境变量 ${r}`,{yes:Boolean(t.yes)});let o=H(),c=await F(o,`正在删除环境变量 ${r}...`,"❌ 环境变量删除失败",()=>_o(s.appName,r));if(!c)return;if(k.setProject({envs:c},{replaceEnvs:!0}),!m())o.stop(Ht.green("✅ 环境变量已从云端移除(若存在)并同步本地配置")),q("Done.");else T({stage:"env.rm",key:r,updatedCount:Object.keys(c).length})})}),n.command("env pull","拉取云端环境变量").option("--target <target>","从指定 FC alias 拉取环境变量(如 prod/preview)").action(async(e)=>{await G({commandLabel:"licell env pull",interactiveTTY:I(),requiredCapabilities:["fc"]},async()=>{B();let t=k.getProject();Kn(t);let s=e.target?ue(e.target):void 0,r=H(),o=await F(r,s?`正在拉取 alias=${s} 的环境变量...`:"正在拉取云端环境变量...","❌ 环境变量拉取失败",()=>bt(t.appName,s));if(!o)return;k.setProject({envs:o},{replaceEnvs:!0});let c=Object.entries(o);if(c.length===0){try{jl(".env","",{mode:384})}catch(u){throw Error(`写入 .env 文件失败: ${u instanceof Error?u.message:String(u)}`)}if(!m())r.stop(Ht.yellow("云端无环境变量,已清空本地 .env"));T({stage:"env.pull",qualifier:s||null,count:0,envFile:".env",emptied:!0});return}let f=c.map(([u,g])=>`${u}="${Xi(String(g))}"`).join(`
|
|
1626
|
+
`);try{jl(".env",f,{mode:384})}catch(u){throw Error(`写入 .env 文件失败: ${u instanceof Error?u.message:String(u)}`)}if(Ti(),!m())r.stop(Ht.green(`✅ 已拉取 ${c.length} 个环境变量并写入 .env`));T({stage:"env.pull",qualifier:s||null,count:c.length,envFile:".env",emptied:!1})})})}import Xl from"picocolors";Q();Zn();xn();dn();import b$,*as Sc from"@alicloud/sls20201230";import*as Vl from"@alicloud/openapi-client";import ve from"picocolors";var k$=nn(b$,"@alicloud/sls20201230");function _$(n){return n.replace(/['"\\*?:|\[\]{}()&!^~]/g,"")}function C$(n){let e=A(n).toLowerCase();return e.includes("projectnotexist")||e.includes("logstorenotexist")}function Wl(n,e,t=!1){let s=[];return n.sort((r,o)=>parseInt(r.__time__||"0",10)-parseInt(o.__time__||"0",10)).forEach((r)=>{let o=`${r.__time__||""}|${r.__source__||""}|${r.message||r.content||""}`;if(e?.has(o))return;if(e){if(e.add(o),e.size>5000){let g=[...e];e.clear();for(let i of g.slice(-2500))e.add(i)}}let c=new Date(parseInt(r.__time__||"0",10)*1000).toLocaleTimeString(),f=String(r.message||r.content||JSON.stringify(r)).trim();if(f.toLowerCase().includes("error"))f=ve.red(f);let u=`${ve.gray(`[${c}]`)} ${f}`;if(s.push(u),!t)console.log(u)}),s}async function Ql(n,e={}){let t=k.requireAuth(),s=new k$(new Vl.Config({accessKeyId:t.ak,accessKeySecret:t.sk,endpoint:`${t.region}.log.aliyuncs.com`})),r=`aliyun-fc-${t.region}-${t.accountId}`,o="function-log",c=_$(n),f=e.lineLimit&&e.lineLimit>0?Math.floor(e.lineLimit):1000;if(e.once){let a=e.windowSeconds&&e.windowSeconds>0?Math.floor(e.windowSeconds):120,p=Math.floor(Date.now()/1000),w=Math.max(0,p-a);try{let $=(await s.getLogs(r,"function-log",new Sc.GetLogsRequest({from:w,to:p,query:`* and functionName: "${c}"`,line:f}))).body||[];if($.length===0){if(!e.silent)console.log(ve.gray(`最近 ${a}s 无日志`));return{mode:"once",logs:[],lines:[]}}let y=Wl($,void 0,Boolean(e.silent));return{mode:"once",logs:$,lines:y}}catch(_){if(C$(_)){if(!e.silent)console.log(ve.yellow(`⚠️ 日志服务尚未就绪,已跳过: ${A(_)}`));return{mode:"once",logs:[],lines:[]}}throw _}}if(!e.silent)console.log(ve.gray(`
|
|
1627
|
+
\uD83D\uDCE1 正在监听云端 [${ve.cyan(n)}] 的实时日志流 (Ctrl+C 退出)...
|
|
1628
|
+
`));let u=Math.floor(Date.now()/1000)-60,g=new Set,i=0,l=!0,d=()=>{if(l=!1,!e.silent)console.log(ve.gray(`
|
|
1629
|
+
\uD83D\uDC4B 日志流已断开`));process.exit(0)};process.on("SIGINT",d),process.on("SIGTERM",d);while(l){try{let a=Math.floor(Date.now()/1000);if(a<=u){await yn(1500);continue}let w=(await s.getLogs(r,"function-log",new Sc.GetLogsRequest({from:u,to:a,query:`* and functionName: "${c}"`,line:f}))).body||[];if(Wl(w,g,Boolean(e.silent)),w.length>0){let _=parseInt(w[w.length-1].__time__||`${u}`,10);if(Number.isFinite(_)&&_>0)u=_}}catch(a){let p=Date.now();if(p-i>1e4){let w=A(a);if(!e.silent)console.log(ve.yellow(`⚠️ 日志拉取失败,10 秒后重试: ${w}`));i=p}}await yn(1500)}}Hn();on();Q();fn();function Zl(n){n.command("logs","查看云端日志(默认实时流式)").option("--once","仅拉取一次最近日志并退出").option("--window <seconds>","一次拉取模式的时间窗(默认 120 秒)").option("--lines <n>","每次请求最大日志条数(默认 1000)").action(async(e)=>{await G({commandLabel:"licell logs",interactiveTTY:I(),requiredCapabilities:["fc","logs"]},async()=>{W(Xl.bgBlue(Xl.white(" \uD83D\uDCE1 Serverless Log Stream "))),B();let t=k.getProject();Kn(t,"当前目录下没有找到绑定的云端项目");let s=m()?!0:Boolean(e.once),r=await Ql(t.appName,{once:s,windowSeconds:Fn(e.window,"--window"),lineLimit:Fn(e.lines,"--lines"),silent:m()});if(m())T({stage:"logs",appName:t.appName,once:s,lines:r&&"lines"in r?r.lines:[],count:r&&"logs"in r?r.logs.length:0})})})}on();fn();import Ut from"picocolors";import{createHash as S$}from"crypto";import{spawnSync as Nl}from"child_process";import{mkdtempSync as T$,rmSync as E$,writeFileSync as q$}from"fs";import{join as Ol}from"path";import{tmpdir as I$}from"os";var Tc="agents-infrastructure/licell",R$=/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;function P$(n){if(n.scriptUrl)return n.scriptUrl;let e=n.repo||Tc;if(!R$.test(e))throw Error("无效的仓库格式,必须是 owner/repo");if(n.version)return`https://github.com/${e}/releases/download/${n.version}/install.sh`;return`https://github.com/${e}/releases/latest/download/install.sh`}function F$(n){let e=n.lastIndexOf("/");if(e<0)return null;return`${n.substring(0,e)}/SHA256SUMS.txt`}function x$(n,e){for(let t of n.split(`
|
|
1630
|
+
`)){let s=t.trim();if(!s)continue;let r=s.match(/^([a-f0-9]{64})\s+(.+)$/);if(r&&r[2].trim()===e)return r[1]}return null}function L$(n,e){return S$("sha256").update(n,"utf8").digest("hex")===e}function Jl(n,e){let t=Nl("curl",["-fsSL",n],{encoding:"utf8",maxBuffer:20971520});if(t.status!==0||!t.stdout){let s=(t.stderr||"").trim();throw Error(s?`下载${e}失败: ${s}`:`下载${e}失败`)}return t.stdout}function zl(n){n.command("upgrade","升级到最新 release 版本").option("--version <tag>","指定版本(如 v0.9.6)").option("--repo <owner/repo>",`GitHub 仓库(默认 ${Tc})`).option("--script-url <url>","覆盖 install.sh 地址(调试用途,需配合 --skip-checksum)").option("--skip-checksum","跳过 SHA256 完整性校验(不推荐)").option("--dry-run","只输出将使用的安装脚本地址").action(async(e)=>{W(Ut.bgBlue(Ut.white(" ⬆ Licell Upgrade ")));let t=E(e.version),s=E(e.repo)||Tc,r=E(e.scriptUrl),o=Boolean(e.skipChecksum);if(r&&!o)throw Error("使用 --script-url 时必须同时指定 --skip-checksum 以确认跳过完整性校验");let c=P$({repo:s,version:t,scriptUrl:r});if(Boolean(e.dryRun)){if(m())T({stage:"upgrade",dryRun:!0,scriptUrl:c});else console.log(c),q("Done.");return}let f=H();f.start("正在下载升级脚本...");let u=Jl(c,"安装脚本");if(!o){let l=F$(c);if(l){f.message("正在校验脚本完整性...");try{let d=Jl(l,"SHA256SUMS"),a=x$(d,"install.sh");if(a){if(!L$(u,a))throw Error("install.sh SHA256 校验失败,脚本可能被篡改。如需跳过校验请使用 --skip-checksum")}else if(m())N({stage:"upgrade",action:"checksum",status:"info",message:"SHA256SUMS 未包含 install.sh,已跳过校验"});else console.error(Ut.yellow("⚠️ SHA256SUMS 中未找到 install.sh 条目,跳过校验"))}catch(d){if(d instanceof Error&&d.message.includes("SHA256 校验失败"))throw d;if(m())N({stage:"upgrade",action:"checksum",status:"info",message:"无法下载 SHA256SUMS,已跳过校验"});else console.error(Ut.yellow("⚠️ 无法下载 SHA256SUMS 校验文件,跳过校验"))}}}let g=T$(Ol(I$(),"licell-upgrade-")),i=Ol(g,"install.sh");try{if(q$(i,u,{mode:448}),!m())f.stop(Ut.green("✅ 脚本下载完成,开始安装"));let l=Nl("bash",[i],{stdio:m()?"pipe":"inherit",encoding:"utf8",env:{...process.env,LICELL_SKIP_RUN_CHECK:"1"}});if(m()){let d=typeof l.stdout==="string"?l.stdout.trim():"",a=typeof l.stderr==="string"?l.stderr.trim():"";if(d)N({stage:"upgrade.install",action:"stdout",status:"info",message:d});if(a)N({stage:"upgrade.install",action:"stderr",status:"info",message:a})}if(l.status!==0)throw Error(`升级安装失败(exit=${l.status??"unknown"})`);if(m())T({stage:"upgrade",dryRun:!1,scriptUrl:c,checksumSkipped:o});else q(Ut.green("✅ 升级完成"))}finally{E$(g,{recursive:!0,force:!0})}})}import ds from"picocolors";import{existsSync as lr,readFileSync as sd,writeFileSync as Rc,mkdirSync as rd}from"fs";import{join as Pc,dirname as od}from"path";import{homedir as cd}from"os";dn();fn();import{spawn as A$}from"child_process";import{createInterface as H$}from"readline";import{isAbsolute as U$,resolve as Ec}from"path";function gr(n){return typeof n==="object"&&n!==null&&!Array.isArray(n)}function M$(n){process.stdout.write(`${JSON.stringify(n)}
|
|
1631
|
+
`)}function ed(n){M$(n)}function is(n,e,t,s){ed({jsonrpc:"2.0",id:n,error:s?{code:e,message:t,data:s}:{code:e,message:t}})}function Mt(n,e){ed({jsonrpc:"2.0",id:n,result:e})}function G$(){let n=process.execPath,e=[],t=process.argv[1];if(typeof t==="string"){let s=t.toLowerCase();if(s.endsWith(".js")||s.endsWith(".cjs")||s.endsWith(".mjs")||s.endsWith(".ts"))e.push(t)}return{command:n,baseArgs:e}}function B$(n,e){if(typeof e!=="string")return n;let t=e.trim();if(!t)return n;let s=U$(t)?Ec(t):Ec(n,t),r=Ec(n);if(s===r)return s;if(!s.startsWith(`${r}/`))throw Error(`cwd 必须在 projectRoot 内部: cwd=${s}, projectRoot=${r}`);return s}function nd(n){let e="",t=0,s=!1;return{push(r){if(s)return;let o=t+r.length;if(o<=n){e+=r.toString("utf8"),t=o;return}let c=Math.max(0,n-t);if(c>0)e+=r.subarray(0,c).toString("utf8");t=n,s=!0},get(){return{text:e,truncated:s}}}}function M(n){if(typeof n!=="string")return;let e=n.trim();return e.length>0?e:void 0}function _n(n){if(typeof n==="boolean")return n;return}function Ce(n){if(typeof n!=="number")return;if(!Number.isFinite(n))return;return n}function gs(n,e){if(n!==!0)throw Error(e)}function v$(n){for(let e=0;e<n.length;e+=1){let t=n[e];if(t==="--")break;if(t==="--output")return!0;if(t.startsWith("--output="))return!0}return!1}async function K$(n){if(n.argv.length===0)throw Error("argv 不能为空");if(n.argv[0]==="mcp"||n.argv.includes("mcp"))throw Error("禁止在 MCP 中递归调用 licell mcp(请直接调用其它 licell 命令)");let e=B$(n.projectRoot,n.cwd),t=typeof n.timeoutMs==="number"?n.timeoutMs:void 0,s=Number.isFinite(t)&&t&&t>0?Math.min(Math.max(1000,Math.floor(t)),1800000):600000,{command:r,baseArgs:o}=G$(),c=v$(n.argv)?[...n.argv]:[...n.argv,"--output","json"],f=[...o,...c],u=1048576,g=nd(u),i=nd(u),l={...process.env,CI:"1",NO_COLOR:"1",LICELL_MCP:"1"},d=A$(r,f,{cwd:e,env:l,stdio:["ignore","pipe","pipe"]});d.stdout?.on("data",(y)=>g.push(y)),d.stderr?.on("data",(y)=>i.push(y));let a=!1,p=setTimeout(()=>{a=!0,d.kill("SIGTERM"),setTimeout(()=>d.kill("SIGKILL"),2000).unref()},s);p.unref();let w=await new Promise((y,h)=>{d.on("error",h),d.on("close",(U)=>y(U))}).finally(()=>clearTimeout(p)),_=g.get(),$=i.get();return{exitCode:w,timedOut:a,stdout:_,stderr:$,command:[r,...f].join(" ")}}function Y$(n){let e={exitCode:n.exitCode,timedOut:n.timedOut,command:n.command,stdout:n.stdout.text,stdoutTruncated:n.stdout.truncated,stderr:n.stderr.text,stderrTruncated:n.stderr.truncated,records:Ci(n.stdout.text)},t=n.timedOut?`timed out: ${e.command}`:`exit=${e.exitCode??"null"}: ${e.command}`,s=n.stdout.truncated?`
|
|
1632
|
+
[stdout truncated]`:"",r=n.stderr.truncated?`
|
|
1633
|
+
[stderr truncated]`:"";return{isError:Boolean(n.timedOut||typeof n.exitCode==="number"&&n.exitCode!==0),content:[{type:"text",text:`${t}
|
|
1634
1634
|
|
|
1635
1635
|
[stdout]
|
|
1636
|
-
${
|
|
1636
|
+
${e.stdout}${s}
|
|
1637
1637
|
|
|
1638
1638
|
[stderr]
|
|
1639
|
-
${
|
|
1640
|
-
`)},t=process.env.LICELL_MCP_DEBUG==="1"||process.env.LICELL_MCP_DEBUG==="true",s={licell_cli:{name:"licell_cli",title:"Deploy & manage Aliyun services (licell)",description:"Use licell CLI to deploy API/static services to Alibaba Cloud and manage related resources (FC, custom domains, SSL, DNS, CDN, logs, etc.). Returns stdout/stderr.",inputSchema:{type:"object",additionalProperties:!1,properties:{argv:{type:"array",items:{type:"string"},minItems:1,description:'licell arguments, e.g. ["deploy","--type","static","--dist","dist"]'},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)"},timeoutMs:{type:"number",description:"Command timeout in milliseconds (default: 600000, max: 1800000)"}},required:["argv"]},annotations:{openWorldHint:!0,destructiveHint:!0}},licell_deploy:{name:"licell_deploy",title:"Deploy service (API/Static) to Aliyun",description:"Deploy current project. API deploys to Function Compute (FC 3.0); Static deploys to OSS hosting. For API, Agent should call licell_fc_deploy_spec + licell_fc_deploy_check before deploy.",inputSchema:{type:"object",additionalProperties:!1,properties:{type:{type:"string",enum:["api","static"],description:"Deployment type."},runtime:{type:"string",description:"API runtime: nodejs20/nodejs22/python3.12/python3.13/docker; Static: static/statis."},entry:{type:"string",description:"API entry file (default depends on runtime)."},dist:{type:"string",description:"Static site directory (default: dist)."},target:{type:"string",description:"FC alias target (e.g. prod/preview). API only."},domain:{type:"string",description:"Full custom domain (e.g. api.example.com). API/Static supported; implies SSL. Static will auto-enable CDN."},domainSuffix:{type:"string",description:"Domain suffix (e.g. example.com) to bind <appName>.<suffix>. API/Static supported."},enableCdn:{type:"boolean",description:"Enable CDN after domain bind (API optional; Static with domain already auto-enables). Implies SSL."},ssl:{type:"boolean",description:"Enable HTTPS (Let's Encrypt). If domain/enableCdn is set, SSL is implied."},sslForceRenew:{type:"boolean",description:"Force renew certificate when SSL enabled."},acrNamespace:{type:"string",description:"ACR namespace for docker runtime."},enableVpc:{type:"boolean",description:"Enable VPC integration (API only)."},disableVpc:{type:"boolean",description:"Disable VPC integration (API only, public mode)."},memory:{type:"number",description:"Memory size (MB)."},vcpu:{type:"number",description:"vCPU cores (e.g. 0.5/1/2)."},instanceConcurrency:{type:"number",description:"Instance concurrency."},timeout:{type:"number",description:"Timeout seconds."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}},required:["type"]}},licell_fc_deploy_spec:{name:"licell_fc_deploy_spec",title:"Get FC API deploy spec",description:"Return machine-readable FC API runtime specs (handlerContract/eventSchema/responseSchema/examples/validationRules and resource constraints) for agent planning.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"Optional runtime filter: nodejs20/nodejs22/python3.12/python3.13/docker."},all:{type:"boolean",description:"Return all runtime specs."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_fc_deploy_check:{name:"licell_fc_deploy_check",title:"Precheck FC API deploy readiness",description:"Read-only validation before FC API deployment. Returns actionable issues (missing handler, wrong entry, Docker prerequisites, etc.) and does not modify project files.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"Runtime to validate (default from project/env or nodejs20)."},entry:{type:"string",description:"Optional entry path override."},dockerDaemon:{type:"boolean",description:"When runtime=docker, also check local Docker daemon availability."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_init:{name:"licell_init",title:"Initialize licell project",description:"Initialize current directory: write .licell/project.json, and optionally generate scaffold files for supported runtimes.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"nodejs20/nodejs22/python3.12/python3.13/docker."},app:{type:"string",description:"appName (FC functionName)."},force:{type:"boolean",description:"Overwrite/generate scaffold in non-empty dir."},yes:{type:"boolean",description:"Non-interactive mode (recommended for MCP). Default true."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_release_promote:{name:"licell_release_promote",title:"Promote FC release (alias switch)",description:"Publish (if needed) and switch an FC alias (e.g. prod/preview) to a version.",inputSchema:{type:"object",additionalProperties:!1,properties:{versionId:{type:"string",description:"Optional versionId. If omitted, licell will publish current code or reuse latest published."},target:{type:"string",description:"Alias target (default: prod)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_release_rollback:{name:"licell_release_rollback",title:"Rollback FC release (alias switch)",description:"Switch an FC alias to a specific versionId.",inputSchema:{type:"object",additionalProperties:!1,properties:{versionId:{type:"string",description:"VersionId to rollback to."},target:{type:"string",description:"Alias target (default: prod)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}},required:["versionId"]}},licell_release_prune:{name:"licell_release_prune",title:"Prune FC historical versions (dangerous)",description:"Preview or delete old FC published versions. Destructive when apply=true (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{keep:{type:"number",description:"Keep latest N versions (default 10)."},apply:{type:"boolean",description:"If true, perform deletion. If false/omitted, preview only."},yes:{type:"boolean",description:"Required when apply=true (non-interactive double-confirm)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}},annotations:{destructiveHint:!0}},licell_fn_list:{name:"licell_fn_list",title:"List functions",description:"List FC functions in current region.",inputSchema:{type:"object",additionalProperties:!1,properties:{limit:{type:"number",description:"Max items (default 20)."},prefix:{type:"string",description:"Filter by function name prefix."},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_info:{name:"licell_fn_info",title:"Get function info",description:"Get FC function details.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},target:{type:"string",description:"Qualifier alias/version (e.g. prod/preview/1)."},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_invoke:{name:"licell_fn_invoke",title:"Invoke function",description:"Invoke FC function synchronously with an optional payload.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},target:{type:"string",description:"Qualifier alias/version (e.g. prod/preview/1)."},payload:{type:"string",description:"Raw payload text."},payloadJson:{type:"object",description:"JSON payload object (will be JSON.stringify-ed).",additionalProperties:!0},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_rm:{name:"licell_fn_rm",title:"Remove function (dangerous)",description:"Delete FC function. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},force:{type:"boolean",description:"Cascade delete triggers/aliases/versions."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}}},annotations:{destructiveHint:!0}},licell_domain_add:{name:"licell_domain_add",title:"Bind custom domain (and optional SSL)",description:"Bind a custom domain to current FC app and optionally enable HTTPS.",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Full domain, e.g. api.example.com."},ssl:{type:"boolean",description:"Enable HTTPS (Let's Encrypt)."},sslForceRenew:{type:"boolean",description:"Force renew certificate."},target:{type:"string",description:"Route to FC alias (prod/preview)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]}},licell_domain_rm:{name:"licell_domain_rm",title:"Unbind custom domain (dangerous)",description:"Unbind custom domain and cleanup DNS record. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Full domain, e.g. api.example.com."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]},annotations:{destructiveHint:!0}},licell_dns_records_list:{name:"licell_dns_records_list",title:"List DNS records",description:"List DNS records for a domain (Alidns).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Root domain, e.g. example.com."},limit:{type:"number",description:"Max items (default 100)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]}},licell_dns_records_add:{name:"licell_dns_records_add",title:"Add DNS record",description:"Add a DNS record (Alidns).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Root domain, e.g. example.com."},rr:{type:"string",description:"RR host, e.g. @/www/api."},type:{type:"string",description:"Record type, e.g. A/CNAME/TXT."},value:{type:"string",description:"Record value."},ttl:{type:"number",description:"TTL seconds (default 600)."},line:{type:"string",description:"Line (default: default)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain","rr","type","value"]}},licell_dns_records_rm:{name:"licell_dns_records_rm",title:"Remove DNS record (dangerous)",description:"Remove a DNS record by recordId. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{recordId:{type:"string",description:"RecordId from list."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["recordId"]},annotations:{destructiveHint:!0}}},o=["2025-03-26","2025-06-18","2025-11-25"],r=!1,c=o[o.length-1],f=Nw({input:process.stdin,crlfDelay:1/0}),u=new Set,l=(d)=>{u.add(d),d.finally(()=>u.delete(d)).catch(()=>{})},i=async(d)=>{if(t)n(`[mcp] <= notification ${d.method}`);if(d.method==="notifications/initialized")return;if(d.method==="exit")process.exit(0)},g=async(d)=>{let{id:p,method:h}=d;if(t)n(`[mcp] <= request ${h} id=${p}`);if(!r&&h!=="initialize"&&h!=="ping"){os(p,-32002,"Server not initialized. Call 'initialize' first.");return}if(h==="ping"){Ft(p,{});return}if(h==="initialize"){let _=to(d.params)?d.params:{};c=(typeof _.protocolVersion==="string"?_.protocolVersion:"")||c,r=!0,Ft(p,{protocolVersion:c,capabilities:{tools:{}},serverInfo:{name:"licell",version:e.serverVersion},instructions:`This MCP server is for deploying and managing services on Alibaba Cloud via licell, scoped to projectRoot.
|
|
1641
|
-
Destructive commands require explicit --yes in non-interactive mode.`});return}if(
|
|
1642
|
-
`,{encoding:"utf8"})}function
|
|
1643
|
-
`,
|
|
1639
|
+
${e.stderr}${r}`.trim()}],structuredContent:e}}async function qc(n){let e=(a)=>{process.stderr.write(`${a}
|
|
1640
|
+
`)},t=process.env.LICELL_MCP_DEBUG==="1"||process.env.LICELL_MCP_DEBUG==="true",s={licell_cli:{name:"licell_cli",title:"Deploy & manage Aliyun services (licell)",description:"Use licell CLI to deploy API/static services to Alibaba Cloud and manage related resources (FC, custom domains, SSL, DNS, CDN, logs, etc.). Returns stdout/stderr.",inputSchema:{type:"object",additionalProperties:!1,properties:{argv:{type:"array",items:{type:"string"},minItems:1,description:'licell arguments, e.g. ["deploy","--type","static","--dist","dist"]'},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)"},timeoutMs:{type:"number",description:"Command timeout in milliseconds (default: 600000, max: 1800000)"}},required:["argv"]},annotations:{openWorldHint:!0,destructiveHint:!0}},licell_deploy:{name:"licell_deploy",title:"Deploy service (API/Static) to Aliyun",description:"Deploy current project. API deploys to Function Compute (FC 3.0); Static deploys to OSS hosting. For API, Agent should call licell_fc_deploy_spec + licell_fc_deploy_check before deploy.",inputSchema:{type:"object",additionalProperties:!1,properties:{type:{type:"string",enum:["api","static"],description:"Deployment type."},runtime:{type:"string",description:"API runtime: nodejs20/nodejs22/python3.12/python3.13/docker; Static: static/statis."},entry:{type:"string",description:"API entry file (default depends on runtime)."},dist:{type:"string",description:"Static site directory (default: dist)."},target:{type:"string",description:"FC alias target (e.g. prod/preview). API only."},domain:{type:"string",description:"Full custom domain (e.g. api.example.com). API/Static supported; implies SSL. Static will auto-enable CDN."},domainSuffix:{type:"string",description:"Domain suffix (e.g. example.com) to bind <appName>.<suffix>. API/Static supported."},enableCdn:{type:"boolean",description:"Enable CDN after domain bind (API optional; Static with domain already auto-enables). Implies SSL."},ssl:{type:"boolean",description:"Enable HTTPS (Let's Encrypt). If domain/enableCdn is set, SSL is implied."},sslForceRenew:{type:"boolean",description:"Force renew certificate when SSL enabled."},acrNamespace:{type:"string",description:"ACR namespace for docker runtime."},enableVpc:{type:"boolean",description:"Enable VPC integration (API only)."},disableVpc:{type:"boolean",description:"Disable VPC integration (API only, public mode)."},memory:{type:"number",description:"Memory size (MB)."},vcpu:{type:"number",description:"vCPU cores (e.g. 0.5/1/2)."},instanceConcurrency:{type:"number",description:"Instance concurrency."},timeout:{type:"number",description:"Timeout seconds."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}},required:["type"]}},licell_fc_deploy_spec:{name:"licell_fc_deploy_spec",title:"Get FC API deploy spec",description:"Return machine-readable FC API runtime specs (handlerContract/eventSchema/responseSchema/examples/validationRules and resource constraints) for agent planning.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"Optional runtime filter: nodejs20/nodejs22/python3.12/python3.13/docker."},all:{type:"boolean",description:"Return all runtime specs."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_fc_deploy_check:{name:"licell_fc_deploy_check",title:"Precheck FC API deploy readiness",description:"Read-only validation before FC API deployment. Returns actionable issues (missing handler, wrong entry, Docker prerequisites, etc.) and does not modify project files.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"Runtime to validate (default from project/env or nodejs20)."},entry:{type:"string",description:"Optional entry path override."},dockerDaemon:{type:"boolean",description:"When runtime=docker, also check local Docker daemon availability."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_init:{name:"licell_init",title:"Initialize licell project",description:"Initialize current directory: write .licell/project.json, and optionally generate scaffold files for supported runtimes.",inputSchema:{type:"object",additionalProperties:!1,properties:{runtime:{type:"string",description:"nodejs20/nodejs22/python3.12/python3.13/docker."},app:{type:"string",description:"appName (FC functionName)."},force:{type:"boolean",description:"Overwrite/generate scaffold in non-empty dir."},yes:{type:"boolean",description:"Non-interactive mode (recommended for MCP). Default true."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_release_promote:{name:"licell_release_promote",title:"Promote FC release (alias switch)",description:"Publish (if needed) and switch an FC alias (e.g. prod/preview) to a version.",inputSchema:{type:"object",additionalProperties:!1,properties:{versionId:{type:"string",description:"Optional versionId. If omitted, licell will publish current code or reuse latest published."},target:{type:"string",description:"Alias target (default: prod)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}}},licell_release_rollback:{name:"licell_release_rollback",title:"Rollback FC release (alias switch)",description:"Switch an FC alias to a specific versionId.",inputSchema:{type:"object",additionalProperties:!1,properties:{versionId:{type:"string",description:"VersionId to rollback to."},target:{type:"string",description:"Alias target (default: prod)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}},required:["versionId"]}},licell_release_prune:{name:"licell_release_prune",title:"Prune FC historical versions (dangerous)",description:"Preview or delete old FC published versions. Destructive when apply=true (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{keep:{type:"number",description:"Keep latest N versions (default 10)."},apply:{type:"boolean",description:"If true, perform deletion. If false/omitted, preview only."},yes:{type:"boolean",description:"Required when apply=true (non-interactive double-confirm)."},cwd:{type:"string",description:"Working directory relative to projectRoot (default: projectRoot)."},timeoutMs:{type:"number",description:"Command timeout in milliseconds."}}},annotations:{destructiveHint:!0}},licell_fn_list:{name:"licell_fn_list",title:"List functions",description:"List FC functions in current region.",inputSchema:{type:"object",additionalProperties:!1,properties:{limit:{type:"number",description:"Max items (default 20)."},prefix:{type:"string",description:"Filter by function name prefix."},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_info:{name:"licell_fn_info",title:"Get function info",description:"Get FC function details.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},target:{type:"string",description:"Qualifier alias/version (e.g. prod/preview/1)."},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_invoke:{name:"licell_fn_invoke",title:"Invoke function",description:"Invoke FC function synchronously with an optional payload.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},target:{type:"string",description:"Qualifier alias/version (e.g. prod/preview/1)."},payload:{type:"string",description:"Raw payload text."},payloadJson:{type:"object",description:"JSON payload object (will be JSON.stringify-ed).",additionalProperties:!0},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_fn_rm:{name:"licell_fn_rm",title:"Remove function (dangerous)",description:"Delete FC function. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Function name. If omitted, uses project appName."},force:{type:"boolean",description:"Cascade delete triggers/aliases/versions."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}}},annotations:{destructiveHint:!0}},licell_domain_add:{name:"licell_domain_add",title:"Bind custom domain (and optional SSL)",description:"Bind a custom domain to current FC app and optionally enable HTTPS.",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Full domain, e.g. api.example.com."},ssl:{type:"boolean",description:"Enable HTTPS (Let's Encrypt)."},sslForceRenew:{type:"boolean",description:"Force renew certificate."},target:{type:"string",description:"Route to FC alias (prod/preview)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]}},licell_domain_rm:{name:"licell_domain_rm",title:"Unbind custom domain (dangerous)",description:"Unbind custom domain and cleanup DNS record. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Full domain, e.g. api.example.com."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]},annotations:{destructiveHint:!0}},licell_dns_records_list:{name:"licell_dns_records_list",title:"List DNS records",description:"List DNS records for a domain (Alidns).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Root domain, e.g. example.com."},limit:{type:"number",description:"Max items (default 100)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain"]}},licell_dns_records_add:{name:"licell_dns_records_add",title:"Add DNS record",description:"Add a DNS record (Alidns).",inputSchema:{type:"object",additionalProperties:!1,properties:{domain:{type:"string",description:"Root domain, e.g. example.com."},rr:{type:"string",description:"RR host, e.g. @/www/api."},type:{type:"string",description:"Record type, e.g. A/CNAME/TXT."},value:{type:"string",description:"Record value."},ttl:{type:"number",description:"TTL seconds (default 600)."},line:{type:"string",description:"Line (default: default)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["domain","rr","type","value"]}},licell_dns_records_rm:{name:"licell_dns_records_rm",title:"Remove DNS record (dangerous)",description:"Remove a DNS record by recordId. Destructive (requires yes=true).",inputSchema:{type:"object",additionalProperties:!1,properties:{recordId:{type:"string",description:"RecordId from list."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["recordId"]},annotations:{destructiveHint:!0}},licell_supa_list:{name:"licell_supa_list",title:"List Supabase instances",description:"List RDS Supabase instances in current region.",inputSchema:{type:"object",additionalProperties:!1,properties:{limit:{type:"number",description:"Max items (default 20)."},cwd:{type:"string"},timeoutMs:{type:"number"}}}},licell_supa_add:{name:"licell_supa_add",title:"Create Supabase instance",description:"Provision a new RDS Supabase instance (creates PG, waits until Running, saves env vars). Long-running (~5-10 min).",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"App name for the instance."},vsw:{type:"string",description:"VSwitch ID (auto-detected if omitted)."},class:{type:"string",description:"Instance class (default rdsai.supabase.basic)."},dbInstance:{type:"string",description:"Existing RDS PostgreSQL instance ID to associate."},dashboardUser:{type:"string",description:"Dashboard username (default supabase)."},dashboardPassword:{type:"string",description:"Dashboard password (auto-generated if omitted)."},dbPassword:{type:"string",description:"Database password (auto-generated if omitted)."},publicNetwork:{type:"boolean",description:"Enable public NAT gateway."},cwd:{type:"string"},timeoutMs:{type:"number",description:"Command timeout in milliseconds (default 900000 for long provision)."}}}},licell_supa_info:{name:"licell_supa_info",title:"Get Supabase instance details",description:"Get detailed attributes of a Supabase instance.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]}},licell_supa_connect:{name:"licell_supa_connect",title:"Get Supabase connection info",description:"Get Supabase endpoints, DB endpoints, and API keys (anon key, service key, JWT secret).",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]}},licell_supa_config:{name:"licell_supa_config",title:"View/modify Supabase config",description:"View or modify Supabase instance configuration (auth/storage/RAG). Without modification flags, shows current config.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},setAuth:{type:"string",description:"Set auth config: KEY=VALUE (e.g. GOTRUE_SITE_URL=http://example.com)."},setStorage:{type:"string",description:"Set storage config: KEY=VALUE."},rag:{type:"string",enum:["on","off"],description:"Enable/disable RAG Agent."},setRag:{type:"string",description:"Set RAG config: KEY=VALUE."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]}},licell_supa_whitelist:{name:"licell_supa_whitelist",title:"Manage Supabase IP whitelist",description:"View or modify Supabase instance IP whitelist. Without modification flags, shows current whitelist.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},set:{type:"string",description:"Set whitelist IPs (cover mode, comma-separated)."},add:{type:"string",description:"Append whitelist IPs (comma-separated)."},remove:{type:"string",description:"Remove whitelist IPs (comma-separated)."},group:{type:"string",description:"Whitelist group name (default: default)."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]}},licell_supa_reset_password:{name:"licell_supa_reset_password",title:"Reset Supabase password",description:"Reset Supabase dashboard or database password.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},dashboardPassword:{type:"string",description:"New dashboard password."},dbPassword:{type:"string",description:"New database password."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]}},licell_supa_lifecycle:{name:"licell_supa_lifecycle",title:"Restart/stop/start Supabase instance",description:"Manage Supabase instance lifecycle: restart, stop, or start.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},action:{type:"string",enum:["restart","stop","start"],description:"Lifecycle action."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName","action"]}},licell_supa_rm:{name:"licell_supa_rm",title:"Delete Supabase instance (dangerous)",description:"Delete a Supabase instance. Destructive and irreversible (requires yes=true). Associated PG instance and NAT gateway need manual cleanup.",inputSchema:{type:"object",additionalProperties:!1,properties:{instanceName:{type:"string",description:"Supabase instance name."},yes:{type:"boolean",description:"Required in non-interactive mode."},cwd:{type:"string"},timeoutMs:{type:"number"}},required:["instanceName"]},annotations:{destructiveHint:!0}}},r=["2025-03-26","2025-06-18","2025-11-25"],o=!1,c=r[r.length-1],f=H$({input:process.stdin,crlfDelay:1/0}),u=new Set,g=(a)=>{u.add(a),a.finally(()=>u.delete(a)).catch(()=>{})},i=async(a)=>{if(t)e(`[mcp] <= notification ${a.method}`);if(a.method==="notifications/initialized")return;if(a.method==="exit")process.exit(0)},l=async(a)=>{let{id:p,method:w}=a;if(t)e(`[mcp] <= request ${w} id=${p}`);if(!o&&w!=="initialize"&&w!=="ping"){is(p,-32002,"Server not initialized. Call 'initialize' first.");return}if(w==="ping"){Mt(p,{});return}if(w==="initialize"){let _=gr(a.params)?a.params:{};c=(typeof _.protocolVersion==="string"?_.protocolVersion:"")||c,o=!0,Mt(p,{protocolVersion:c,capabilities:{tools:{}},serverInfo:{name:"licell",version:n.serverVersion},instructions:`This MCP server is for deploying and managing services on Alibaba Cloud via licell, scoped to projectRoot.
|
|
1641
|
+
Destructive commands require explicit --yes in non-interactive mode.`});return}if(w==="shutdown"){Mt(p,null);return}if(w==="tools/list"){let _=[];for(let $ of Object.values(s)){let y={name:$.name,title:$.title,description:$.description,inputSchema:$.inputSchema};if("annotations"in $&&$.annotations)y.annotations=$.annotations;if(c<"2025-03-26")y.input_schema=$.inputSchema;_.push(y)}Mt(p,{tools:_});return}if(w==="tools/call"){let _=gr(a.params)?a.params:{},$=typeof _.name==="string"?_.name:"",y=gr(_.arguments)?_.arguments:{};try{let h=null,U=y.cwd,C=y.timeoutMs;if($==="licell_cli"){let S=y.argv;if(!Array.isArray(S)||S.some((b)=>typeof b!=="string"||b.trim()===""))throw Error("Invalid arguments: argv must be a non-empty string[]");h=S.map((b)=>b.trim())}else if($==="licell_deploy"){let S=M(y.type);if(S!=="api"&&S!=="static")throw Error('type must be "api" or "static"');h=["deploy","--type",S];let b=M(y.runtime),P=M(y.entry),R=M(y.dist),j=M(y.target),gn=M(y.domain),Xn=M(y.domainSuffix),ln=M(y.acrNamespace),De=_n(y.enableCdn),We=_n(y.ssl),Se=_n(y.sslForceRenew),Mn=_n(y.enableVpc),gt=_n(y.disableVpc),lt=Ce(y.memory),sf=Ce(y.vcpu),rf=Ce(y.instanceConcurrency),of=Ce(y.timeout);if(b)h.push("--runtime",b);if(P)h.push("--entry",P);if(R)h.push("--dist",R);if(j)h.push("--target",j);if(gn)h.push("--domain",gn);if(Xn)h.push("--domain-suffix",Xn);if(De)h.push("--enable-cdn");if(We)h.push("--ssl");if(Se)h.push("--ssl-force-renew");if(ln)h.push("--acr-namespace",ln);if(Mn)h.push("--enable-vpc");if(gt)h.push("--disable-vpc");if(lt!==void 0)h.push("--memory",String(lt));if(sf!==void 0)h.push("--vcpu",String(sf));if(rf!==void 0)h.push("--instance-concurrency",String(rf));if(of!==void 0)h.push("--timeout",String(of))}else if($==="licell_fc_deploy_spec"){h=["deploy","spec"];let S=M(y.runtime),b=_n(y.all);if(S)h.push(S);if(b)h.push("--all")}else if($==="licell_fc_deploy_check"){h=["deploy","check"];let S=M(y.runtime),b=M(y.entry),P=_n(y.dockerDaemon);if(S)h.push("--runtime",S);if(b)h.push("--entry",b);if(P)h.push("--docker-daemon")}else if($==="licell_init"){h=["init"];let S=M(y.runtime),b=M(y.app),P=_n(y.force),R=_n(y.yes);if(S)h.push("--runtime",S);if(b)h.push("--app",b);if(P)h.push("--force");if(R!==!1)h.push("--yes")}else if($==="licell_release_promote"){let S=M(y.versionId),b=M(y.target);if(h=["release","promote"],S)h.push(S);if(b)h.push("--target",b)}else if($==="licell_release_rollback"){let S=M(y.versionId);if(!S)throw Error("versionId is required");let b=M(y.target);if(h=["release","rollback",S],b)h.push("--target",b)}else if($==="licell_release_prune"){let S=Ce(y.keep),b=_n(y.apply),P=_n(y.yes);if(h=["release","prune"],S!==void 0)h.push("--keep",String(S));if(b)gs(P,"apply=true is destructive; set yes=true to confirm"),h.push("--apply","--yes")}else if($==="licell_fn_list"){h=["fn","list"];let S=Ce(y.limit),b=M(y.prefix);if(S!==void 0)h.push("--limit",String(S));if(b)h.push("--prefix",b)}else if($==="licell_fn_info"){h=["fn","info"];let S=M(y.name),b=M(y.target);if(S)h.push(S);if(b)h.push("--target",b)}else if($==="licell_fn_invoke"){h=["fn","invoke"];let S=M(y.name),b=M(y.target),P=M(y.payload),R=y.payloadJson;if(P&&R!==void 0)throw Error("Provide only one of payload or payloadJson");if(S)h.push(S);if(b)h.push("--target",b);if(P)h.push("--payload",P);if(R!==void 0)h.push("--payload",JSON.stringify(R))}else if($==="licell_fn_rm"){h=["fn","rm"];let S=M(y.name),b=_n(y.force),P=_n(y.yes);if(gs(P,"fn rm is destructive; set yes=true to confirm"),S)h.push(S);if(b)h.push("--force");h.push("--yes")}else if($==="licell_domain_add"){let S=M(y.domain);if(!S)throw Error("domain is required");let b=M(y.target),P=_n(y.ssl),R=_n(y.sslForceRenew);if(h=["domain","add",S],P)h.push("--ssl");if(R)h.push("--ssl-force-renew");if(b)h.push("--target",b)}else if($==="licell_domain_rm"){let S=M(y.domain);if(!S)throw Error("domain is required");let b=_n(y.yes);gs(b,"domain rm is destructive; set yes=true to confirm"),h=["domain","rm",S,"--yes"]}else if($==="licell_dns_records_list"){let S=M(y.domain);if(!S)throw Error("domain is required");let b=Ce(y.limit);if(h=["dns","records","list",S],b!==void 0)h.push("--limit",String(b))}else if($==="licell_dns_records_add"){let S=M(y.domain),b=M(y.rr),P=M(y.type),R=M(y.value);if(!S||!b||!P||!R)throw Error("domain, rr, type, value are required");let j=Ce(y.ttl),gn=M(y.line);if(h=["dns","records","add",S,"--rr",b,"--type",P,"--value",R],j!==void 0)h.push("--ttl",String(j));if(gn)h.push("--line",gn)}else if($==="licell_dns_records_rm"){let S=M(y.recordId);if(!S)throw Error("recordId is required");let b=_n(y.yes);gs(b,"dns records rm is destructive; set yes=true to confirm"),h=["dns","records","rm",S,"--yes"]}else if($==="licell_supa_list"){h=["supa","list"];let S=Ce(y.limit);if(S!==void 0)h.push("--limit",String(S))}else if($==="licell_supa_add"){h=["supa","add"];let S=M(y.name),b=M(y.vsw),P=M(y.class),R=M(y.dbInstance),j=M(y.dashboardUser),gn=M(y.dashboardPassword),Xn=M(y.dbPassword),ln=_n(y.publicNetwork);if(S)h.push("--name",S);if(b)h.push("--vsw",b);if(P)h.push("--class",P);if(R)h.push("--db-instance",R);if(j)h.push("--dashboard-user",j);if(gn)h.push("--dashboard-password",gn);if(Xn)h.push("--db-password",Xn);if(ln)h.push("--public-network")}else if($==="licell_supa_info"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");h=["supa","info",S]}else if($==="licell_supa_connect"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");h=["supa","connect",S]}else if($==="licell_supa_config"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");h=["supa","config",S];let b=M(y.setAuth),P=M(y.setStorage),R=M(y.rag),j=M(y.setRag);if(b)h.push("--set-auth",b);if(P)h.push("--set-storage",P);if(R)h.push("--rag",R);if(j)h.push("--set-rag",j)}else if($==="licell_supa_whitelist"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");h=["supa","whitelist",S];let b=M(y.set),P=M(y.add),R=M(y.remove),j=M(y.group);if(b)h.push("--set",b);if(P)h.push("--add",P);if(R)h.push("--remove",R);if(j)h.push("--group",j)}else if($==="licell_supa_reset_password"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");h=["supa","reset-password",S];let b=M(y.dashboardPassword),P=M(y.dbPassword);if(!b&&!P)throw Error("Provide dashboardPassword or dbPassword");if(b)h.push("--dashboard-password",b);if(P)h.push("--db-password",P)}else if($==="licell_supa_lifecycle"){let S=M(y.instanceName),b=M(y.action);if(!S)throw Error("instanceName is required");if(b!=="restart"&&b!=="stop"&&b!=="start")throw Error("action must be restart, stop, or start");h=["supa",b,S]}else if($==="licell_supa_rm"){let S=M(y.instanceName);if(!S)throw Error("instanceName is required");let b=_n(y.yes);gs(b,"supa rm is destructive; set yes=true to confirm"),h=["supa","rm",S,"--yes"]}else throw Error(`Unknown tool: ${$}`);if(!h)throw Error(`Unknown tool: ${$}`);if(t)e(`[mcp] tool ${$} starting...`);let L=await K$({projectRoot:n.projectRoot,argv:h,cwd:U,timeoutMs:C});if(t)e(`[mcp] tool ${$} done (exit=${L.exitCode}, timedOut=${L.timedOut})`);Mt(p,Y$(L))}catch(h){Mt(p,{isError:!0,content:[{type:"text",text:A(h)}]})}return}is(p,-32601,`Method not found: ${w}`)},d=(a)=>{if(!gr(a)||a.jsonrpc!=="2.0"){is(null,-32600,"Invalid Request");return}if(typeof a.method!=="string"){is(a.id??null,-32600,"Invalid Request");return}let{method:p,id:w}=a;if(w!==void 0&&w!==null){g(l(a).catch(($)=>e(`[mcp] request handler error: ${A($)}`)));return}g(i(a).catch(($)=>e(`[mcp] notification handler error: ${A($)}`)))};if(f.on("line",(a)=>{let p=a.trim();if(!p)return;let w;try{w=JSON.parse(p)}catch(_){is(null,-32700,"Parse error",{message:A(_)});return}if(Array.isArray(w)){for(let _ of w)d(_);return}d(w)}),e(`[mcp] server started: ${n.serverTitle} (projectRoot=${n.projectRoot}, protocol=${c})`),await new Promise((a)=>f.once("close",a)),u.size>0)await Promise.race([Promise.allSettled([...u]).then(()=>{return}),new Promise((a)=>setTimeout(a,2000))])}Hn();on();import{existsSync as j$,readFileSync as D$}from"fs";import{resolve as Ic}from"path";var __dirname="/Users/wyatt/work/licell/src/utils",W$=process.env.LICELL_VERSION;function ls(n){if(typeof n!=="string")return null;let e=n.trim();return e.length>0?e:null}function V$(){let n=[];if(typeof __dirname==="string"&&__dirname.length>0)n.push(Ic(__dirname,"../../package.json")),n.push(Ic(__dirname,"../package.json"));n.push(Ic(process.cwd(),"package.json"));for(let e of n){if(!j$(e))continue;try{let t=JSON.parse(D$(e,"utf-8")),s=ls(typeof t.version==="string"?t.version:null);if(s)return s}catch{}}return null}function it(n){let e=n?.env??process.env,t=ls(e.LICELL_VERSION);if(t)return t;let s=ls(n?.bundledVersion??W$);if(s)return s;let r=ls(e.npm_package_version);if(r)return r;let c=Object.prototype.hasOwnProperty.call(n??{},"packageVersion")?n?.packageVersion??null:V$(),f=ls(c);if(f)return f;return"dev"}fn();var td=["fc","dns","oss","rds","redis","cdn","vpc","cr","logs"];function fd(n){if(!lr(n))return null;return JSON.parse(sd(n,"utf8"))}function dr(n){return typeof n==="object"&&n!==null&&!Array.isArray(n)}function ud(n,e){Rc(n,`${JSON.stringify(e,null,2)}
|
|
1642
|
+
`,{encoding:"utf8"})}function id(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Q$(n,e){let t=new RegExp(`^[ \\t]*${id(e)}[ \\t]*=[ \\t]*\\[([\\s\\S]*?)\\][ \\t]*$`,"m"),s=n.match(t);if(!s)return null;return s[1].split(",").map((c)=>c.trim()).filter(Boolean).map((c)=>{let f=c.match(/^"(.*)"$/);return f?f[1]:c})}function X$(n,e){let t=new RegExp(`^[ \\t]*\\[${id(e)}\\][ \\t]*$`,"m"),s=n.match(t);if(!s||s.index===void 0)return null;let r=s.index,o=n.indexOf(`
|
|
1643
|
+
`,r),c=o===-1?n.length:o+1,f=n.slice(c).match(/^[ \t]*\[[^\]]+\][ \t]*$/m),u=f&&f.index!==void 0?c+f.index:n.length;return{start:r,end:u}}function Z$(n){let e=n.match(/^[ \t]*command[ \t]*=[ \t]*"([^"]+)"[ \t]*$/m);if(!e||e[1]!=="licell")return!1;let t=Q$(n,"args");if(!t||t.length!==2)return!1;return t[0]==="mcp"&&t[1]==="serve"}function O$(n){return`[mcp_servers.${n}]
|
|
1644
1644
|
command = "licell"
|
|
1645
1645
|
args = ["mcp", "serve"]
|
|
1646
|
-
`}function
|
|
1646
|
+
`}function yr(n){let e=Pc(n.projectRoot,".mcp.json"),t=fd(e),s=dr(t)?{...t}:{},o={...dr(s.mcpServers)?s.mcpServers:{}},c={command:"licell",args:["mcp","serve"]},f=o[n.serverName];if(JSON.stringify(f)===JSON.stringify(c))return{configPath:e,updated:!1};return o[n.serverName]=c,s.mcpServers=o,ud(e,s),{configPath:e,updated:!0}}function gd(n){let e=n?.serverName||"licell",t=Pc(cd(),".claude","settings.local.json"),s=od(t);if(!lr(s))rd(s,{recursive:!0});let r=fd(t),o=dr(r)?{...r}:{},f={...dr(o.mcpServers)?o.mcpServers:{}},u={command:"licell",args:["mcp","serve"]},g=f[e];if(JSON.stringify(g)===JSON.stringify(u))return{configPath:t,updated:!1};return f[e]=u,o.mcpServers=f,ud(t,o),{configPath:t,updated:!0}}function ld(n){let e=n?.serverName||"licell",t=Pc(cd(),".codex","config.toml"),s=od(t);if(!lr(s))rd(s,{recursive:!0});let r=lr(t)?sd(t,"utf8"):"",o=`mcp_servers.${e}`,c=O$(e),f=X$(r,o);if(f){let i=r.slice(f.start,f.end);if(Z$(i))return{configPath:t,updated:!1};let l=`${r.slice(0,f.start)}${c}${r.slice(f.end)}`;return Rc(t,l,"utf8"),{configPath:t,updated:!0}}let u=r.trimEnd(),g=u.length>0?`${u}
|
|
1647
1647
|
|
|
1648
|
-
${c}`:c;return
|
|
1648
|
+
${c}`:c;return Rc(t,g,"utf8"),{configPath:t,updated:!0}}function dd(n){n.command("mcp [action]","MCP:让 Agent 通过 licell 执行部署/发布/运维(初始化 .mcp.json 或启动 stdio server)").option("--project-root <path>","项目根目录(默认当前目录)").option("--server-name <name>","写入 .mcp.json 的 server 名称(默认 licell)").action(async(e,t)=>{let s=(e||"").trim().toLowerCase(),r=typeof t.projectRoot==="string"&&t.projectRoot.trim()?t.projectRoot.trim():process.cwd(),o=typeof t.serverName==="string"&&t.serverName.trim()?t.serverName.trim():"licell",c=I();if(!s){await Tt({commandLabel:"licell mcp",interactiveTTY:c}),await Et({commandLabel:"licell mcp",interactiveTTY:c,requiredCapabilities:td});let{configPath:f,updated:u}=yr({projectRoot:r,serverName:o});if(m()){T({stage:"mcp",action:"init",configPath:f,updated:u,next:"run `licell mcp serve` without --output json to start stdio server"});return}console.log(ds.green(`✅ MCP 配置已就绪: ${f}${u?" (updated)":""}`)),console.log(ds.gray('现在启动 MCP 服务(stdio)。用于 Claude Code/Cursor 等客户端时,请在 .mcp.json 中使用 args: ["mcp","serve"]。')),console.log(ds.gray("提示:删除/清理类命令在 MCP 非交互模式下仍需要显式传 --yes。")),console.log(""),await qc({projectRoot:r,serverVersion:it(),serverTitle:`licell ${it()}`});return}if(s==="init"){let{configPath:f,updated:u}=yr({projectRoot:r,serverName:o});if(m())T({stage:"mcp",action:"init",configPath:f,updated:u});else console.log(ds.green(`✅ 已写入 MCP 配置: ${f}${u?"":" (no changes)"}`)),console.log(ds.gray("下一步:在支持 MCP 的客户端中启用该项目的 .mcp.json(例如 Claude Code)。"));return}if(s==="serve"){if(m())throw Error("mcp serve 使用 stdio JSON-RPC 协议,不支持 --output json");await Tt({commandLabel:"licell mcp serve",interactiveTTY:c}),await Et({commandLabel:"licell mcp serve",interactiveTTY:c,requiredCapabilities:td}),await qc({projectRoot:r,serverVersion:it(),serverTitle:`licell ${it()}`});return}throw Error(`未知 mcp action: ${e||""}(支持: init / serve)`)})}var ar=["-h","--help","-v","--version","--output"],J$=["login","logout","whoami","switch","init","deploy","fn","oss","db","cache","e2e","release","domain","dns","env","logs","upgrade","mcp","auth","completion"],yd={auth:["repair"],deploy:["spec","check"],fn:["list","info","invoke","rm"],oss:["list","info","ls","upload","bucket"],db:["add","list","info","connect"],cache:["add","list","info","connect","rotate-password"],e2e:["run","cleanup","list"],release:["list","promote","rollback","prune"],domain:["add","rm"],dns:["records"],env:["list","set","rm","pull"],mcp:["init","serve"],completion:["bash","zsh"]},ad={"dns records":["list","add","rm"]},Fc={login:["--account-id","--ak","--sk","--region","--bootstrap-ram","--bootstrap-user","--bootstrap-policy"],"auth repair":["--account-id","--ak","--sk","--region","--bootstrap-user","--bootstrap-policy"],switch:["--region"],init:["--runtime","--app","--force","--yes"],deploy:["--type","--entry","--dist","--runtime","--target","--domain","--domain-suffix","--enable-cdn","--ssl","--ssl-force-renew","--acr-namespace","--enable-vpc","--disable-vpc","--memory","--vcpu","--instance-concurrency","--timeout"],"deploy spec":["--all"],"deploy check":["--runtime","--entry","--docker-daemon"],"fn list":["--limit","--prefix"],"fn info":["--target"],"fn invoke":["--target","--payload","--file"],"fn rm":["--force","--yes"],"oss list":["--limit"],"oss ls":["--limit"],"oss upload":["--bucket","--source-dir","--target-dir"],"oss bucket":["--bucket","--source-dir","--target-dir"],"db add":["--type","--engine-version","--category","--class","--storage","--storage-type","--min-rcu","--max-rcu","--auto-pause","--zone","--zone-slave1","--zone-slave2","--vpc","--vsw","--security-ip-list","--description"],"db list":["--limit"],"db public-access":["--ip"],"cache add":["--type","--instance","--password","--username","--engine-version","--class","--node-type","--capacity","--vk-name","--compute-unit","--zone","--vpc","--vsw","--security-ip-list"],"cache list":["--limit"],"cache rotate-password":["--instance"],"cache public-access":["--ip"],"e2e run":["--suite","--run-id","--runtime","--target","--enable-vpc","--domain","--domain-suffix","--db-instance","--cache-instance","--skip-static","--enable-cdn","--cleanup","--workspace","--yes"],"e2e cleanup":["--manifest","--keep-workspace","--yes"],"release list":["--limit"],"release promote":["--target"],"release rollback":["--target"],"release prune":["--keep","--apply","--yes"],"domain add":["--ssl","--ssl-force-renew","--target"],"domain rm":["--yes"],"dns records list":["--limit"],"dns records add":["--rr","--type","--value","--ttl","--line"],"dns records rm":["--yes"],"env list":["--target","--show-values"],"env rm":["--yes"],"env pull":["--target"],mcp:["--project-root","--server-name"],upgrade:["--version","--repo","--script-url","--skip-checksum","--dry-run"],completion:["--engine"]};function ys(n){return[...new Set(n)]}function N$(n){if(!n)return[];let e=n.trim();if(!e)return[];return e.split(/\s+/).filter(Boolean)}function z$(n){let e=n[0];if(!e||e.startsWith("-"))return[];let t=yd[e];if(!t)return[e];let s=n[1];if(!s||s.startsWith("-")||!t.includes(s))return[e];let r=`${e} ${s}`,o=ad[r];if(!o)return[e,s];let c=n[2];if(!c||c.startsWith("-")||!o.includes(c))return[e,s];return[e,s,c]}function nb(n){if(n.length===0)return ys([...J$,...ar]);if(n.length===1){let t=n[0];return ys([...yd[t]||[],...Fc[t]||[],...ar])}if(n.length===2){let t=`${n[0]} ${n[1]}`;return ys([...ad[t]||[],...Fc[t]||[],...ar])}let e=`${n[0]} ${n[1]} ${n[2]}`;return ys([...Fc[e]||[],...ar])}function hd(n){let e=N$(n.compWords),t=Number(n.compCword);if(!Number.isFinite(t))t=e.length-1;let s=[...e];if(s[0]==="licell")s.shift(),t-=1;if(t<0)t=0;let r=n.compCur??s[t]??"",o=s.slice(0,Math.max(0,Math.min(t,s.length))),c=z$(o),f=nb(c),u=r.startsWith("-"),g=f.filter((i)=>{if(u&&!i.startsWith("-"))return!1;return i.startsWith(r)});return ys(g).sort()}function md(n){let e=(n||"").trim().toLowerCase();if(e==="bash"||e==="zsh")return e;throw Error("shell 仅支持 bash 或 zsh")}function wd(n){let e=(n||"").toLowerCase();if(e.includes("zsh"))return"zsh";if(e.includes("bash"))return"bash";return}function pd(n){if(n==="bash")return`# bash completion for licell
|
|
1649
1649
|
_licell_completion() {
|
|
1650
1650
|
local cur
|
|
1651
1651
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
@@ -1671,13 +1671,13 @@ _licell_completion() {
|
|
|
1671
1671
|
(( \${#suggestions[@]} )) && compadd -a suggestions
|
|
1672
1672
|
}
|
|
1673
1673
|
compdef _licell_completion licell
|
|
1674
|
-
`}
|
|
1674
|
+
`}fn();function $d(n){n.command("completion [shell]","输出 shell 补全脚本(bash/zsh)").option("--engine","内部补全引擎(供 shell completion 调用)").action((e,t)=>{if(t.engine){let c=hd({compWords:process.env.COMP_WORDS,compCword:process.env.COMP_CWORD,compCur:process.env.COMP_CUR});if(m()){T({stage:"completion.engine",count:c.length,candidates:c});return}if(c.length>0)process.stdout.write(`${c.join(`
|
|
1675
1675
|
`)}
|
|
1676
|
-
`);return}let s=
|
|
1676
|
+
`);return}let s=wd(process.env.SHELL),r=md(e||s||"bash"),o=pd(r);if(m())T({stage:"completion.script",shell:r,script:o});else process.stdout.write(o)})}on();dn();fn();import{select as cb,isCancel as fb}from"@clack/prompts";import Ke from"picocolors";import{existsSync as Lc,mkdirSync as eb,readFileSync as bd,writeFileSync as hr}from"fs";import{dirname as tb,join as as,isAbsolute as sb}from"path";import{homedir as rb}from"os";var xc="- licell: Deploy and manage Alibaba Cloud Serverless applications using the licell CLI. Covers deploy, release, functions, env vars, domains, DNS, logs, OSS, database, cache, Supabase, and MCP. (file: .claude/skills/licell/SKILL.md)";function kd(){return`---
|
|
1677
1677
|
name: licell
|
|
1678
1678
|
description: >-
|
|
1679
1679
|
Deploy and manage Alibaba Cloud Serverless applications using the licell CLI.
|
|
1680
|
-
Covers deploy, release, functions, env vars, domains, DNS, logs, OSS, database, cache, and MCP.
|
|
1680
|
+
Covers deploy, release, functions, env vars, domains, DNS, logs, OSS, database, cache, Supabase, and MCP.
|
|
1681
1681
|
metadata:
|
|
1682
1682
|
author: licell
|
|
1683
1683
|
version: "1.0"
|
|
@@ -1703,22 +1703,33 @@ licell release promote --target prod # 发布到生产
|
|
|
1703
1703
|
\`\`\`
|
|
1704
1704
|
|
|
1705
1705
|
<!-- PLACEHOLDER_COMMAND_REFERENCE -->
|
|
1706
|
-
`}function
|
|
1707
|
-
`,"")+
|
|
1708
|
-
`,"")+
|
|
1706
|
+
`}function _d(){return"## Command Reference\n\n### Deploy (`licell deploy`)\n\n一键打包部署到阿里云函数计算。\n\n| 选项 | 说明 |\n|------|------|\n| `--type <type>` | 部署类型:`api` 或 `static` |\n| `--entry <entry>` | API 入口文件(Node 默认 src/index.ts;Python 默认 src/main.py) |\n| `--dist <dist>` | 静态站点目录(默认 dist) |\n| `--runtime <runtime>` | 运行时:nodejs20/nodejs22/python3.12/python3.13/docker/static |\n| `--target <target>` | 部署后自动发布到指定 alias(如 prod/preview) |\n| `--domain <domain>` | 绑定完整自定义域名(如 api.example.com) |\n| `--domain-suffix <suffix>` | 自动绑定子域名后缀(如 example.com) |\n| `--enable-cdn` | 接入 CDN 并将 DNS CNAME 切到 CDN |\n| `--ssl` | 启用 HTTPS(Let's Encrypt 自动证书) |\n| `--enable-vpc` / `--disable-vpc` | 启用/禁用 VPC 接入 |\n| `--memory <mb>` | 函数内存(MB,默认 512) |\n| `--vcpu <n>` | vCPU 核数(默认 0.5) |\n| `--instance-concurrency <n>` | 单实例并发数 |\n| `--timeout <seconds>` | 函数超时(秒,默认 30) |\n| `--acr-namespace <ns>` | Docker 部署的 ACR 命名空间(默认 licell) |\n\n### FC API Spec & Precheck(Agent 推荐)\n\n| 命令 | 说明 |\n|------|------|\n| `licell deploy spec [runtime]` | 查看 FC API runtime 规格(entry/handler/资源约束) |\n| `licell deploy spec --all` | 查看全部 runtime 规格 |\n| `licell deploy check --runtime <runtime> --entry <entry>` | 部署前本地预检(缺少 handler/入口错误会提前报出) |\n| `licell deploy check --runtime docker --docker-daemon` | Docker 预检并检测本机 Docker daemon |\n\n**Agent 最佳实践:**\n\n```bash\n# 1) 先读规格(知道该 runtime 的硬性要求)\nlicell deploy spec nodejs22\n\n# 2) 再做预检(拿到可执行修复建议)\nlicell deploy check --runtime nodejs22 --entry src/index.ts\n\n# 3) 预检通过后再部署\nlicell deploy --type api --runtime nodejs22 --entry src/index.ts --target preview\n```\n\n`deploy spec --output json` 包含 `handlerContract`、`eventSchema`、`responseSchema`、`examples`、`validationRules`,可直接供 Agent 规划与校验。\n\n**常见用法:**\n\n```bash\n# API 部署到 preview\nlicell deploy --type api --target preview\n\n# 静态站点部署\nlicell deploy --type static --dist dist --domain-suffix example.com\n\n# Docker 部署\nlicell deploy --type api --runtime docker --target preview\n```\n\n### Release Management (`licell release`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell release list [--limit <n>]` | 查看函数版本列表 |\n| `licell release promote [versionId] --target <alias>` | 发布并切流到目标别名(默认 prod) |\n| `licell release prune --keep <n> [--apply]` | 清理历史版本(不传 --apply 仅预览) |\n\n### Function Management (`licell fn`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell fn list [--limit <n>] [--prefix <p>]` | 查看函数列表 |\n| `licell fn info [name] [--target <alias>]` | 查看函数详情 |\n| `licell fn invoke [name] [--target <alias>] [--payload <json>]` | 调用云端函数 |\n| `licell fn rm <name> [--force] [--yes]` | 删除函数(--force 级联删除触发器/alias/版本) |\n\n### Environment Variables (`licell env`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell env list [--target <alias>] [--show-values]` | 查看云端环境变量 |\n| `licell env set <key> <value>` | 设置环境变量(同步本地配置) |\n| `licell env rm <key> [--yes]` | 删除环境变量 |\n| `licell env pull [--target <alias>]` | 拉取云端环境变量到本地 |\n\n### Domain (`licell domain`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell domain add <domain> [--ssl] [--target <alias>]` | 绑定自定义域名 |\n| `licell domain rm <domain> [--yes]` | 解绑域名并清理 DNS |\n\n### DNS Records (`licell dns`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell dns records list [domain] [--limit <n>]` | 查看解析记录 |\n| `licell dns records add <domain> --rr <rr> --type <type> --value <val>` | 添加解析记录 |\n| `licell dns records rm <recordId> [--yes]` | 删除解析记录 |\n\n### Logs (`licell logs`)\n\n| 选项 | 说明 |\n|------|------|\n| `--once` | 仅拉取一次最近日志并退出 |\n| `--window <seconds>` | 一次拉取的时间窗(默认 120 秒) |\n| `--lines <n>` | 每次最大日志条数(默认 1000) |\n\n### OSS (`licell oss`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell oss list [--limit <n>]` | 查看 Bucket 列表 |\n| `licell oss info <bucket>` | 查看 Bucket 详情 |\n| `licell oss objects [bucket] [--prefix <p>] [--limit <n>]` | 查看对象列表 |\n| `licell oss upload [bucket] --source-dir <dir> [--target-dir <dir>]` | 上传目录到 OSS |\n\n### Database (`licell db`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell db add --type <postgres\\|mysql>` | 分配 Serverless 数据库 |\n| `licell db list [--limit <n>]` | 查看数据库实例列表 |\n| `licell db info [instanceId]` | 查看实例详情 |\n| `licell db connect [instanceId]` | 输出连接信息 |\n\n`db add` 高级选项:`--engine-version`、`--class`、`--storage`、`--min-rcu`、`--max-rcu`、`--auto-pause`、`--zone`、`--vpc`、`--vsw`、`--security-ip-list`\n\n### Cache (`licell cache`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell cache add --type redis` | 分配 Redis/Tair 缓存 |\n| `licell cache list [--limit <n>]` | 查看实例列表 |\n| `licell cache info [instanceId]` | 查看实例详情 |\n| `licell cache connect [instanceId]` | 输出连接信息 |\n| `licell cache rotate-password [--instance <id>]` | 轮换 Redis 密码 |\n\n`cache add` 高级选项:`--instance`、`--password`、`--class`、`--zone`、`--vpc`、`--vsw`、`--security-ip-list`\n\n### Supabase (`licell supa`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell supa list [--limit <n>]` | 查看 Supabase 实例列表 |\n| `licell supa add [--name <name>]` | 创建 Supabase 实例(含 PG、等待就绪、保存凭证) |\n| `licell supa info <instanceName>` | 查看实例详情 |\n| `licell supa connect <instanceName>` | 查看连接信息(Endpoints、API Keys) |\n| `licell supa config <instanceName>` | 查看/修改配置(Auth/Storage/RAG) |\n| `licell supa whitelist <instanceName>` | 查看/修改 IP 白名单 |\n| `licell supa reset-password <instanceName>` | 重置 Dashboard 或数据库密码 |\n| `licell supa restart <instanceName>` | 重启实例 |\n| `licell supa stop <instanceName>` | 停止实例 |\n| `licell supa start <instanceName>` | 启动实例 |\n| `licell supa rm <instanceName> [--yes]` | 删除实例(不可恢复) |\n\n`supa add` 高级选项:`--vsw`、`--class`、`--db-instance`、`--dashboard-user`、`--dashboard-password`、`--db-password`、`--public-network`\n\n`supa config` 修改选项:`--set-auth KEY=VALUE`、`--set-storage KEY=VALUE`、`--rag on|off`、`--set-rag KEY=VALUE`\n\n`supa whitelist` 修改选项:`--set <ips>`(覆盖)、`--add <ips>`(追加)、`--remove <ips>`(删除)、`--group <name>`\n\n### MCP Server (`licell mcp`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell mcp` | 初始化 .mcp.json 并启动 stdio server |\n| `licell mcp init` | 仅写入 .mcp.json 配置 |\n| `licell mcp serve` | 启动 MCP stdio server |\n\n### Auth (`licell login` / `licell whoami` / `licell switch`)\n\n| 命令 | 说明 |\n|------|------|\n| `licell login` | 配置阿里云凭证(交互式或 --ak/--sk/--account-id) |\n| `licell login --bootstrap-ram` | 自动创建最小权限 RAM 用户 |\n| `licell whoami` | 查看当前登录信息 |\n| `licell switch --region <region>` | 切换默认 region |\n\n## Common Patterns\n\n### 典型部署流程\n\n```bash\n# 1. 部署到 preview 环境\nlicell deploy --type api --target preview\n\n# 2. 验证 preview 环境\nlicell fn invoke --target preview --payload '{\"path\":\"/health\"}'\n\n# 3. 发布到生产\nlicell release promote --target prod\n\n# 4. 如有问题,回滚\nlicell release rollback --target prod\n```\n\n### 环境变量管理\n\n```bash\nlicell env set DATABASE_URL \"postgresql://...\"\nlicell env set REDIS_URL \"redis://...\"\nlicell deploy --type api --target preview # 重新部署使变量生效\n```\n\n### 自定义域名 + HTTPS + CDN\n\n```bash\nlicell deploy --type api --target prod --domain api.example.com --ssl --enable-cdn\n```\n\n### 数据库 + 缓存 + VPC 全栈部署\n\n```bash\nlicell db add --type postgres\nlicell cache add --type redis\nlicell deploy --type api --target preview --enable-vpc\n```\n\n### Supabase 全栈部署\n\n```bash\nlicell supa add --name my-app\nlicell supa connect <instanceName> # 获取 URL 和 API Keys\nlicell deploy --type api --target preview --enable-vpc\n```\n\n## Error Handling\n\n- 认证失败:运行 `licell login` 重新配置凭证\n- 部署失败:检查 `licell logs --once` 查看错误日志\n- 域名冲突:使用 `licell domain rm <domain>` 清理后重试\n- 版本清理:`licell release prune --keep 5 --apply` 清理旧版本\n- 破坏性操作(rm/prune)需要 `--yes` 跳过确认,或在交互模式下手动确认\n"}function ob(n){let e=rb();if(n==="claude")return as(e,".claude","skills","licell");return as(e,".agents","skills","licell")}function mr(n){let e=kd().replace(`<!-- PLACEHOLDER_COMMAND_REFERENCE -->
|
|
1707
|
+
`,"")+_d();if(n==="claude")return[{path:".claude/skills/licell/SKILL.md",content:e}];return[{path:"codex.md",content:e}]}function Cd(n){let e=kd().replace(`<!-- PLACEHOLDER_COMMAND_REFERENCE -->
|
|
1708
|
+
`,"")+_d(),t=ob(n);return[{path:as(t,"SKILL.md"),content:e}]}function wr(n){let e=as(n,"AGENTS.md");if(!Lc(e)){let c=`# AGENTS.md
|
|
1709
1709
|
|
|
1710
1710
|
## Available Skills
|
|
1711
1711
|
|
|
1712
|
-
${
|
|
1713
|
-
`;return
|
|
1714
|
-
${
|
|
1712
|
+
${xc}
|
|
1713
|
+
`;return hr(e,c,"utf8"),{filePath:e,updated:!0}}let t=bd(e,"utf8");if(t.includes(".claude/skills/licell/SKILL.md"))return{filePath:e,updated:!1};let r=/^(#{2,3}\s+Available\s+[Ss]kills\s*)$/m.exec(t);if(r&&r.index!==void 0){let c=r.index+r[0].length,f=`${t.slice(0,c)}
|
|
1714
|
+
${xc}${t.slice(c)}`;return hr(e,f,"utf8"),{filePath:e,updated:!0}}let o=`${t.trimEnd()}
|
|
1715
1715
|
|
|
1716
1716
|
## Available Skills
|
|
1717
1717
|
|
|
1718
|
-
${
|
|
1719
|
-
`;return
|
|
1720
|
-
已写入文件:`);for(let
|
|
1721
|
-
已跳过(内容相同):`);for(let g of l)console.log(` ${Un.gray("=")} ${g}`)}if(i.updated)console.log(` ${Un.green("+")} AGENTS.md`);else console.log(` ${Un.gray("=")} AGENTS.md(已包含 licell 条目)`);if(m())S({stage:"skills",agent:r,projectRoot:o,writtenFiles:u,skippedFiles:l,agentsMdUpdated:i.updated});else q("Done.")}catch(r){if(m())he(r,{stage:"skills"});else console.error(P(r));process.exitCode=1}})}oe();fe();ce();import{select as Jg,confirm as S$,isCancel as Sc}from"@clack/prompts";import Ne from"picocolors";var q$=new Set(["claude","codex"]);async function qc(e={}){let n=R(),t=m(),s=typeof e.projectRoot==="string"&&e.projectRoot.trim()?e.projectRoot.trim():process.cwd();try{let o=()=>{if(t)S({stage:"setup",cancelled:!0});else q("已取消")},r;if(e.agent&&q$.has(e.agent))r=e.agent;else if(e.agent)throw Error(`不支持的 agent: ${e.agent}(支持: claude / codex)`);else if(n){let h=await Jg({message:"选择目标 Agent:",options:[{value:"claude",label:"Claude Code"},{value:"codex",label:"OpenAI Codex"}]});if(Sc(h)){o();return}r=h}else throw Error("非交互模式下必须指定 --agent 参数(claude / codex)");let c;if(e.global)c="global";else if(n){let h=await Jg({message:"配置范围:",options:[{value:"global",label:"全局(所有项目生效)"},{value:"project",label:"当前项目"}]});if(Sc(h)){o();return}c=h}else c="global";let f=A();if(!t)f.start("正在生成 Skills...");let u=c==="global"?Xg(r):io(r),l=c==="global"?"":s,{written:i,skipped:g}=go(l,u,Boolean(e.force));if(c==="project")lo(s);if(!t)f.stop(Ne.green("✅ Skills 生成完成"));if(!t&&i.length>0)for(let h of i)console.log(` ${Ne.green("+")} ${h}`);if(!t&&g.length>0)for(let h of g)console.log(` ${Ne.gray("=")} ${h}(已存在)`);let y=!1,d=null,p=null;if(n){let h=await S$({message:"是否配置 MCP(让 Agent 能调用 licell)?"});if(Sc(h)){o();return}y=h===!0}if(y)if(c==="global"&&r==="claude"){let{configPath:h,updated:_}=Ug();if(d=h,p=_,!t)console.log(` ${_?Ne.green("+"):Ne.gray("=")} ${h}${_?"":"(已存在)"}`)}else if(c==="global"&&r==="codex"){let{configPath:h,updated:_}=vg();if(d=h,p=_,!t)console.log(` ${_?Ne.green("+"):Ne.gray("=")} ${h}${_?"":"(已存在)"}`)}else{let{configPath:h,updated:_}=co({projectRoot:s,serverName:"licell"});if(d=h,p=_,!t)console.log(` ${_?Ne.green("+"):Ne.gray("=")} ${h}${_?"":"(已存在)"}`)}if(t)S({stage:"setup",agent:r,scope:c,projectRoot:s,writtenFiles:i,skippedFiles:g,mcpConfigured:y,mcpConfigPath:d,mcpConfigUpdated:p});else q("Done.")}catch(o){if(t)he(o,{stage:"setup"});else console.error(P(o));process.exitCode=1}}function Ng(e){e.command("setup","安装后引导:配置 AI Agent Skills 和 MCP").option("--agent <agent>","目标 Agent(claude / codex)").option("--global","全局配置(所有项目生效)").option("--project-root <path>","项目目录(默认当前目录)").option("--force","覆盖已有文件").action(async(n)=>{if(!m())K(Ne.bgBlue(Ne.white(" \uD83D\uDEE0 Licell Setup ")));else X({stage:"setup",action:"setup",status:"start"});await qc(n)})}j();oe();oe();ce();import vn from"picocolors";function zg(e){e.command("config domain [suffix]","查看或设置全局默认域名后缀").option("--unset","清除已设置的全局域名后缀").action((n,t)=>{let s=k.getGlobalConfig();if(t.unset){if(k.setGlobalConfig({domainSuffix:void 0}),m())S({stage:"config.domain",domainSuffix:null,action:"unset"});else K(vn.bgMagenta(vn.white(" ⚙ Config "))),q(vn.green("已清除全局域名后缀"));return}let o=C(n);if(!o){let c=s.domainSuffix||null;if(m())S({stage:"config.domain",domainSuffix:c});else if(c)console.log(`全局域名后缀: ${vn.cyan(c)}`);else console.log(vn.gray("未设置全局域名后缀。用法: licell config domain <suffix>"));return}let r=Zn(o);if(k.setGlobalConfig({domainSuffix:r}),m())S({stage:"config.domain",domainSuffix:r,action:"set"});else K(vn.bgMagenta(vn.white(" ⚙ Config "))),console.log(`全局域名后缀已设置为: ${vn.cyan(r)}`),q("后续 deploy/domain 命令将自动使用此域名后缀")})}import{existsSync as ny,readFileSync as R$,writeFileSync as P$,mkdirSync as I$}from"fs";import{homedir as F$}from"os";import{join as ty}from"path";import is from"picocolors";var Rc=ty(F$(),".licell-cli"),Pc=ty(Rc,"update-check.json"),x$=43200000,H$=2000,L$="https://api.github.com/repos/agents-infrastructure/licell/releases/latest";function ey(e,n){let t=e.split(".").map(Number),s=n.split(".").map(Number),o=Math.max(t.length,s.length);for(let r=0;r<o;r++){let c=t[r]??0,f=s[r]??0;if(c!==f)return c-f}return 0}function A$(){try{if(!ny(Pc))return null;let e=JSON.parse(R$(Pc,"utf-8"));if(typeof e.latestVersion!=="string"||typeof e.checkedAt!=="number")return null;return e}catch{return null}}function U$(e){try{if(!ny(Rc))I$(Rc,{recursive:!0});P$(Pc,JSON.stringify(e),"utf-8")}catch{}}async function v$(){try{let e=new AbortController,n=setTimeout(()=>e.abort(),H$),t=await fetch(L$,{signal:e.signal,headers:{Accept:"application/vnd.github+json"}});if(clearTimeout(n),!t.ok)return null;let s=await t.json();if(typeof s.tag_name!=="string")return null;return s.tag_name.replace(/^v/,"")}catch{return null}}async function sy(e){if(!e||e==="dev")return null;let n=A$();if(n&&Date.now()-n.checkedAt<x$){if(ey(n.latestVersion,e)>0)return{currentVersion:e,latestVersion:n.latestVersion};return null}let t=await v$();if(!t)return null;if(U$({latestVersion:t,checkedAt:Date.now()}),ey(t,e)>0)return{currentVersion:e,latestVersion:t};return null}function oy(e){let n=`新版本可用: ${e.currentVersion} → ${e.latestVersion}`,t="licell upgrade",s=Math.max(n.length,18),o=(r)=>r+" ".repeat(s-r.replace(/\x1b\[[0-9;]*m/g,"").length);console.error(""),console.error(is.yellow(`╭${"─".repeat(s+4)}╮`)),console.error(is.yellow(`│ ${o(n)} │`)),console.error(is.yellow(`│ ${o(`运行 ${is.bold("licell upgrade")} 升级`)} │`)),console.error(is.yellow(`╰${"─".repeat(s+4)}╯`))}fe();j();oe();Gs();import{confirm as ry,isCancel as cy}from"@clack/prompts";import Gn from"picocolors";fe();async function fy(){console.log(Gn.cyan(`
|
|
1722
|
-
|
|
1723
|
-
`))
|
|
1724
|
-
|
|
1718
|
+
${xc}
|
|
1719
|
+
`;return hr(e,o,"utf8"),{filePath:e,updated:!0}}function pr(n,e,t=!1){let s=[],r=[];for(let o of e){let c=sb(o.path)?o.path:as(n,o.path),f=tb(c);if(!Lc(f))eb(f,{recursive:!0});if(Lc(c)){if(bd(c,"utf8")===o.content){r.push(o.path);continue}if(!t)throw Error(`文件已存在且内容不同: ${o.path}(使用 --force 覆盖)`)}hr(c,o.content,"utf8"),s.push(o.path)}return{written:s,skipped:r}}var ub=new Set(["claude","codex"]);function Sd(n){n.command("skills init [agent]","为 AI Agent 生成 licell skills(claude / codex)").option("--project-root <path>","目标项目目录(默认当前目录)").option("--force","覆盖已有文件").action(async(e,t)=>{if(!m())W(Ke.bgBlue(Ke.white(" \uD83D\uDEE0 Licell Skills Init ")));else N({stage:"skills",action:"skills init",status:"start"});let s=I(),r=typeof t.projectRoot==="string"&&t.projectRoot.trim()?t.projectRoot.trim():process.cwd();try{let o;if(e&&ub.has(e))o=e;else if(e)throw Error(`不支持的 agent: ${e}(支持: claude / codex)`);else if(s){let l=await cb({message:"选择目标 Agent:",options:[{value:"claude",label:"Claude Code (.claude/skills/ + AGENTS.md)"},{value:"codex",label:"OpenAI Codex (codex.md + AGENTS.md)"}]});if(fb(l)){if(m())throw Error("操作已取消");process.exit(0)}o=l}else throw Error("非交互模式下必须指定 agent 参数(claude / codex)");let c=H();c.start(`正在生成 ${o} skills...`);let f=mr(o),{written:u,skipped:g}=pr(r,f,Boolean(t.force)),i=wr(r);if(c.stop(Ke.green("✅ Skills 生成完成")),console.log(`agent: ${Ke.cyan(o)}`),u.length>0){console.log(`
|
|
1720
|
+
已写入文件:`);for(let l of u)console.log(` ${Ke.green("+")} ${l}`)}if(g.length>0){console.log(`
|
|
1721
|
+
已跳过(内容相同):`);for(let l of g)console.log(` ${Ke.gray("=")} ${l}`)}if(i.updated)console.log(` ${Ke.green("+")} AGENTS.md`);else console.log(` ${Ke.gray("=")} AGENTS.md(已包含 licell 条目)`);if(m())T({stage:"skills",agent:o,projectRoot:r,writtenFiles:u,skippedFiles:g,agentsMdUpdated:i.updated});else q("Done.")}catch(o){if(m())pn(o,{stage:"skills"});else console.error(A(o));process.exitCode=1}})}on();dn();fn();import{select as Td,confirm as ib,isCancel as Ac}from"@clack/prompts";import se from"picocolors";var gb=new Set(["claude","codex"]);async function Hc(n={}){let e=I(),t=m(),s=typeof n.projectRoot==="string"&&n.projectRoot.trim()?n.projectRoot.trim():process.cwd();try{let r=()=>{if(t)T({stage:"setup",cancelled:!0});else q("已取消")},o;if(n.agent&&gb.has(n.agent))o=n.agent;else if(n.agent)throw Error(`不支持的 agent: ${n.agent}(支持: claude / codex)`);else if(e){let w=await Td({message:"选择目标 Agent:",options:[{value:"claude",label:"Claude Code"},{value:"codex",label:"OpenAI Codex"}]});if(Ac(w)){r();return}o=w}else throw Error("非交互模式下必须指定 --agent 参数(claude / codex)");let c;if(n.global)c="global";else if(e){let w=await Td({message:"配置范围:",options:[{value:"global",label:"全局(所有项目生效)"},{value:"project",label:"当前项目"}]});if(Ac(w)){r();return}c=w}else c="global";let f=H();if(!t)f.start("正在生成 Skills...");let u=c==="global"?Cd(o):mr(o),g=c==="global"?"":s,{written:i,skipped:l}=pr(g,u,Boolean(n.force));if(c==="project")wr(s);if(!t)f.stop(se.green("✅ Skills 生成完成"));if(!t&&i.length>0)for(let w of i)console.log(` ${se.green("+")} ${w}`);if(!t&&l.length>0)for(let w of l)console.log(` ${se.gray("=")} ${w}(已存在)`);let d=!1,a=null,p=null;if(e){let w=await ib({message:"是否配置 MCP(让 Agent 能调用 licell)?"});if(Ac(w)){r();return}d=w===!0}if(d)if(c==="global"&&o==="claude"){let{configPath:w,updated:_}=gd();if(a=w,p=_,!t)console.log(` ${_?se.green("+"):se.gray("=")} ${w}${_?"":"(已存在)"}`)}else if(c==="global"&&o==="codex"){let{configPath:w,updated:_}=ld();if(a=w,p=_,!t)console.log(` ${_?se.green("+"):se.gray("=")} ${w}${_?"":"(已存在)"}`)}else{let{configPath:w,updated:_}=yr({projectRoot:s,serverName:"licell"});if(a=w,p=_,!t)console.log(` ${_?se.green("+"):se.gray("=")} ${w}${_?"":"(已存在)"}`)}if(t)T({stage:"setup",agent:o,scope:c,projectRoot:s,writtenFiles:i,skippedFiles:l,mcpConfigured:d,mcpConfigPath:a,mcpConfigUpdated:p});else q("Done.")}catch(r){if(t)pn(r,{stage:"setup"});else console.error(A(r));process.exitCode=1}}function Ed(n){n.command("setup","安装后引导:配置 AI Agent Skills 和 MCP").option("--agent <agent>","目标 Agent(claude / codex)").option("--global","全局配置(所有项目生效)").option("--project-root <path>","项目目录(默认当前目录)").option("--force","覆盖已有文件").action(async(e)=>{if(!m())W(se.bgBlue(se.white(" \uD83D\uDEE0 Licell Setup ")));else N({stage:"setup",action:"setup",status:"start"});await Hc(e)})}Q();on();on();fn();import Ye from"picocolors";function qd(n){n.command("config domain [suffix]","查看或设置全局默认域名后缀").option("--unset","清除已设置的全局域名后缀").action((e,t)=>{let s=k.getGlobalConfig();if(t.unset){if(k.setGlobalConfig({domainSuffix:void 0}),m())T({stage:"config.domain",domainSuffix:null,action:"unset"});else W(Ye.bgMagenta(Ye.white(" ⚙ Config "))),q(Ye.green("已清除全局域名后缀"));return}let r=E(e);if(!r){let c=s.domainSuffix||null;if(m())T({stage:"config.domain",domainSuffix:c});else if(c)console.log(`全局域名后缀: ${Ye.cyan(c)}`);else console.log(Ye.gray("未设置全局域名后缀。用法: licell config domain <suffix>"));return}let o=tt(r);if(k.setGlobalConfig({domainSuffix:o}),m())T({stage:"config.domain",domainSuffix:o,action:"set"});else W(Ye.bgMagenta(Ye.white(" ⚙ Config "))),console.log(`全局域名后缀已设置为: ${Ye.cyan(o)}`),q("后续 deploy/domain 命令将自动使用此域名后缀")})}Hn();import{confirm as $b,isCancel as bb}from"@clack/prompts";import x from"picocolors";Q();Zn();pt();import{randomInt as hs,randomUUID as wb}from"crypto";import*as de from"@alicloud/rdsai20250507";Q();xn();import lb from"@alicloud/rdsai20250507";import*as Id from"@alicloud/openapi-client";var db=60000,yb=120000,ab=nn(lb,"@alicloud/rdsai20250507"),hb={"cn-chengdu":"rdsai.cn-chengdu.aliyuncs.com","ap-northeast-1":"rdsai.ap-northeast-1.aliyuncs.com","ap-southeast-1":"rdsai.ap-southeast-1.aliyuncs.com","ap-southeast-3":"rdsai.ap-southeast-3.aliyuncs.com","ap-southeast-5":"rdsai.ap-southeast-5.aliyuncs.com","us-west-1":"rdsai.us-west-1.aliyuncs.com","eu-central-1":"rdsai.eu-central-1.aliyuncs.com"};function mb(n){return hb[n]||"rdsai.aliyuncs.com"}function Cn(){let n=k.requireAuth(),e=new ab(new Id.Config({accessKeyId:n.ak,accessKeySecret:n.sk,endpoint:mb(n.region),connectTimeout:db,readTimeout:yb}));return{auth:n,client:e}}var Uc=900000,Rd=8000,Pd="rdsai.supabase.basic";var Mc="abcdefghjkmnpqrstuvwxyz",Gc="ABCDEFGHJKLMNPQRSTUVWXYZ",Bc="23456789",Fd=`${Mc}${Gc}${Bc}_`;function xd(n=20){let e=[Mc[hs(Mc.length)],Gc[hs(Gc.length)],Bc[hs(Bc.length)],"_"];while(e.length<n)e.push(Fd[hs(Fd.length)]);for(let t=e.length-1;t>0;t-=1){let s=hs(t+1);[e[t],e[s]]=[e[s],e[t]]}return e.join("")}async function vc(n,e={}){let{auth:t,client:s}=Cn(),r=k.getProject();n.message("\uD83D\uDD27 正在准备网络环境...");let o=e.vSwitchId?.trim()||"";if(!o)o=(await Re()).vswId;let c=e.appName?.trim()||`licell-supa-${Date.now()}`,f=e.dashboardUsername?.trim()||"supabase",u=e.dashboardPassword?.trim()||xd(),g=e.databasePassword?.trim()||xd(),i=e.instanceClass?.trim()||Pd,l=e.dbInstanceClass?.trim()||"pg.n2.2c.1m",d=e.dbInstanceStorage??20,a=wb();n.message("\uD83D\uDE80 正在创建 Supabase 实例...");let w=(await s.createAppInstance(new de.CreateAppInstanceRequest({regionId:t.region,appType:"supabase",vSwitchId:o,instanceClass:i,appName:c,dashboardUsername:f,dashboardPassword:u,databasePassword:g,publicEndpointEnabled:e.publicEndpointEnabled??!0,publicNetworkAccessEnabled:e.publicNetworkAccessEnabled??!1,dBInstanceName:e.dbInstanceName?.trim()||void 0,clientToken:a,DBInstanceConfig:new de.CreateAppInstanceRequestDBInstanceConfig({DBInstanceClass:l,DBInstanceStorage:d,payType:"Postpaid"})}))).body?.instanceName;if(!w)throw Error("创建 Supabase 实例失败:未返回 instanceName");n.message(`⏳ 等待实例 ${w} 就绪...`);let _=Date.now()+Uc,$="";while(Date.now()<_){await yn(Rd);try{let R=await s.describeAppInstanceAttribute(new de.DescribeAppInstanceAttributeRequest({regionId:t.region,instanceName:w}));if($=String(R.body?.Status||R.body?.status||""),n.message(`⏳ 实例 ${w} 状态: ${$}`),$==="Running")break;if($.includes("Failed")||$.includes("Error"))throw Error(`Supabase 实例创建失败,状态: ${$}`)}catch(R){let j=R instanceof Error?R.message:String(R);if(j.includes("Failed")||j.includes("Error"))throw R}}if($!=="Running")throw Error(`Supabase 实例 ${w} 在 ${Uc/60000} 分钟内未就绪 (当前状态: ${$})`);n.message("\uD83D\uDD17 正在获取连接信息...");let h=(await s.describeInstanceEndpoints(new de.DescribeInstanceEndpointsRequest({regionId:t.region,instanceName:w}))).body||{},U=h.instanceEndpoints||h.InstanceEndpoints||[],C=U.find((R)=>(R.IpType||R.ipType)==="public"),L=U.find((R)=>(R.IpType||R.ipType)==="vpc"),S=C?String(C.ConnectionString||C.connectionString||""):L?String(L.ConnectionString||L.connectionString||""):"";n.message("\uD83D\uDD11 正在获取认证信息...");let b="",P="";try{let j=(await s.describeInstanceAuthInfo(new de.DescribeInstanceAuthInfoRequest({regionId:t.region,instanceName:w}))).body||{},gn=j.apiKeys||j.ApiKeys||{};b=String(gn.AnonKey||gn.anonKey||""),P=String(gn.ServiceKey||gn.serviceKey||"")}catch{}return r.envs={...r.envs,SUPABASE_URL:S?`http://${S}`:"",SUPABASE_ANON_KEY:b,SUPABASE_SERVICE_ROLE_KEY:P,SUPABASE_INSTANCE_NAME:w,SUPABASE_DASHBOARD_USERNAME:f,SUPABASE_DASHBOARD_PASSWORD:u,SUPABASE_DB_PASSWORD:g},k.setProject(r),{instanceName:w,appName:c,supabaseUrl:S,anonKey:b,serviceKey:P,dashboardUsername:f,dashboardPassword:u,databasePassword:g}}import*as V from"@alicloud/rdsai20250507";function pb(n){return{instanceName:String(n.InstanceName||n.instanceName||""),appName:n.AppName??n.appName,appType:n.AppType??n.appType,status:n.Status??n.status,instanceClass:n.InstanceClass??n.instanceClass,regionId:n.RegionId??n.regionId,dbInstanceName:n.DBInstanceName??n.dBInstanceName,vSwitchId:n.VSwitchId??n.vSwitchId,publicConnectionString:n.PublicConnectionString??n.publicConnectionString,vpcConnectionString:n.VpcConnectionString??n.vpcConnectionString}}async function Kc(n=50){let{auth:e,client:t}=Cn(),s=[],r=Math.max(1,Math.min(Math.floor(n),200)),o=Math.min(50,r);for(let c=1;c<=20&&s.length<r;c+=1){let f=await t.describeAppInstances(new V.DescribeAppInstancesRequest({regionId:e.region,appType:"supabase",pageSize:o,pageNumber:c})),u=f.body?.instances||[];for(let i of u){let l=pb(i);if(!l.instanceName)continue;if(s.push(l),s.length>=r)break}let g=Number(f.body?.totalCount||0);if(u.length===0||Number.isFinite(g)&&g>0&&s.length>=g)break}return s}async function Yc(n){let e=n.trim();if(!e)throw Error("instanceName 不能为空");let{auth:t,client:s}=Cn(),o=(await s.describeAppInstanceAttribute(new V.DescribeAppInstanceAttributeRequest({regionId:t.region,instanceName:e}))).body||{};return{instanceName:String(o.InstanceName||o.instanceName||e),appName:o.AppName??o.appName,appType:o.AppType??o.appType,status:o.Status??o.status,instanceClass:o.InstanceClass??o.instanceClass,regionId:o.RegionId??o.regionId,dbInstanceName:o.DBInstanceName??o.dBInstanceName,vSwitchId:o.VSwitchId??o.vSwitchId,publicConnectionString:o.PublicConnectionString??o.publicConnectionString,vpcConnectionString:o.VpcConnectionString??o.vpcConnectionString,instanceMinorVersion:o.InstanceMinorVersion??o.instanceMinorVersion,zoneId:o.ZoneId??o.zoneId,eipStatus:o.EipStatus,natStatus:o.NatStatus,natGatewayId:o.NatGatewayId,natCreatedBy:o.NatCreatedBy,eipId:o.EipId}}async function jc(n){let{auth:e,client:t}=Cn(),r=(await t.describeInstanceEndpoints(new V.DescribeInstanceEndpointsRequest({regionId:e.region,instanceName:n}))).body||{},o=(r.instanceEndpoints||r.InstanceEndpoints||[]).map((f)=>({connectionString:f.ConnectionString??f.connectionString,ipType:f.IpType??f.ipType,ip:f.IP??f.ip,port:f.Port??f.port})),c=(r.dBInstanceEndpoints||r.DBInstanceEndpoints||[]).map((f)=>({connectionString:f.ConnectionString??f.connectionString,ipType:f.IpType??f.ipType,port:f.Port??f.port}));return{instanceEndpoints:o,dbInstanceEndpoints:c}}async function $r(n){let{auth:e,client:t}=Cn(),r=(await t.describeInstanceAuthInfo(new V.DescribeInstanceAuthInfoRequest({regionId:e.region,instanceName:n}))).body||{},o=r.apiKeys||r.ApiKeys||{},c=(r.configList||r.ConfigList||[]).map((f)=>({name:String(f.Name||f.name||""),value:String(f.Value||f.value||"")}));return{jwtSecret:r.JwtSecret??r.jwtSecret,anonKey:o.AnonKey??o.anonKey,serviceKey:o.ServiceKey??o.serviceKey,configList:c}}async function Dc(n){let{auth:e,client:t}=Cn(),r=(await t.describeInstanceStorageConfig(new V.DescribeInstanceStorageConfigRequest({regionId:e.region,instanceName:n}))).body||{};return(r.configList||r.ConfigList||[]).map((o)=>({name:String(o.Name||o.name||""),value:String(o.Value||o.value||"")}))}async function Wc(n){let{auth:e,client:t}=Cn(),r=(await t.describeInstanceRAGConfig(new V.DescribeInstanceRAGConfigRequest({regionId:e.region,instanceName:n}))).body||{},o=(r.configList||r.ConfigList||[]).map((c)=>({name:String(c.Name||c.name||""),value:String(c.Value||c.value||"")}));return{status:r.Status===!0||r.status===!0||r.Status==="true"||r.status==="true",configList:o}}async function Vc(n){let{auth:e,client:t}=Cn(),r=(await t.describeInstanceIpWhitelist(new V.DescribeInstanceIpWhitelistRequest({regionId:e.region,instanceName:n}))).body||{};return(r.ipWhiteListGroups||r.IpWhiteListGroups||[]).map((o)=>({groupName:String(o.GroupName||o.groupName||"default"),ipWhitelist:String(o.IpWhitelist||o.ipWhitelist||"")}))}async function Qc(n,e){let{auth:t,client:s}=Cn();await s.modifyInstanceAuthConfig(new V.ModifyInstanceAuthConfigRequest({regionId:t.region,instanceName:n,configList:e.map((r)=>new V.ModifyInstanceAuthConfigRequestConfigList({name:r.name,value:r.value}))}))}async function Xc(n,e){let{auth:t,client:s}=Cn();await s.modifyInstanceStorageConfig(new V.ModifyInstanceStorageConfigRequest({regionId:t.region,instanceName:n,configList:e.map((r)=>new V.ModifyInstanceStorageConfigRequestConfigList({name:r.name,value:r.value}))}))}async function Zc(n,e,t){let{auth:s,client:r}=Cn();await r.modifyInstanceRAGConfig(new V.ModifyInstanceRAGConfigRequest({regionId:s.region,instanceName:n,status:e,configList:t?.map((o)=>new V.ModifyInstanceRAGConfigRequestConfigList({name:o.name,value:o.value}))}))}async function ms(n,e,t,s){let{auth:r,client:o}=Cn();await o.modifyInstanceIpWhitelist(new V.ModifyInstanceIpWhitelistRequest({regionId:r.region,instanceName:n,ipWhitelist:e,modifyMode:t||"Cover",groupName:s||"default"}))}async function Oc(n,e,t){let{auth:s,client:r}=Cn();await r.resetInstancePassword(new V.ResetInstancePasswordRequest({regionId:s.region,instanceName:n,dashboardPassword:e,databasePassword:t}))}async function Jc(n){let{auth:e,client:t}=Cn();await t.restartInstance(new V.RestartInstanceRequest({regionId:e.region,instanceName:n}))}async function Nc(n){let{auth:e,client:t}=Cn();await t.stopInstance(new V.StopInstanceRequest({regionId:e.region,instanceName:n}))}async function zc(n){let{auth:e,client:t}=Cn();await t.startInstance(new V.StartInstanceRequest({regionId:e.region,instanceName:n}))}async function nf(n){let{auth:e,client:t}=Cn();await t.deleteAppInstance(new V.DeleteAppInstanceRequest({regionId:e.region,instanceName:n}))}on();fn();function Ld(n){n.command("supa add","创建 RDS Supabase 实例").option("--name <name>","应用名称").option("--vsw <vSwitchId>","指定 VSwitch ID").option("--class <instanceClass>","实例规格(默认 rdsai.supabase.basic)").option("--db-instance <dbInstanceName>","关联已有 RDS PostgreSQL 实例 ID").option("--dashboard-user <user>","Dashboard 用户名(默认 supabase)").option("--dashboard-password <password>","Dashboard 密码(自动生成)").option("--db-password <password>","数据库密码(自动生成)").option("--public-network","开启公网 NAT 网关").action(async(e)=>{await G({commandLabel:"licell supa add",interactiveTTY:I(),requiredCapabilities:["rdsai","vpc"]},async()=>{W(x.bgGreen(x.black(" \uD83D\uDFE2 Supabase Provisioning "))),B();let t=H(),s=await F(t,"正在创建 Supabase 实例...","❌ 创建失败",()=>vc(t,{appName:E(e.name),vSwitchId:E(e.vsw),instanceClass:E(e.class),dbInstanceName:E(e.dbInstance),dashboardUsername:E(e.dashboardUser),dashboardPassword:E(e.dashboardPassword),databasePassword:E(e.dbPassword),publicNetworkAccessEnabled:e.publicNetwork??!1}));if(!s)return;if(m()){T({stage:"supa.add",...s});return}if(t.stop(x.green("✅ Supabase 实例已就绪!")),console.log(`
|
|
1722
|
+
instanceName: ${x.cyan(s.instanceName)}`),console.log(`appName: ${x.cyan(s.appName)}`),s.supabaseUrl)console.log(`url: ${x.cyan(s.supabaseUrl)}`);if(console.log(`dashboard: ${x.cyan(s.dashboardUsername)} / ${x.cyan(s.dashboardPassword)}`),console.log(`db password: ${x.cyan(s.databasePassword)}`),s.anonKey)console.log(`anon key: ${x.gray(s.anonKey.slice(0,30))}...`);if(s.serviceKey)console.log(`service key: ${x.gray(s.serviceKey.slice(0,30))}...`);console.log(""),q("凭证已保存到项目环境变量 (SUPABASE_URL, SUPABASE_ANON_KEY 等)")})}),n.command("supa list","查看 Supabase 实例列表").option("--limit <n>","返回数量,默认 20").action(async(e)=>{await G({commandLabel:"licell supa list",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let t=An(e.limit,20,200),s=H(),r=await F(s,"正在拉取 Supabase 实例列表...","❌ 获取失败",()=>Kc(t));if(!r)return;if(m()){T({stage:"supa.list",count:r.length,instances:r});return}if(s.stop(x.green(`✅ 共获取 ${r.length} 个实例`)),r.length===0){q("当前地域没有 Supabase 实例");return}for(let o of r)console.log(`${x.cyan(o.instanceName)} app=${x.gray(o.appName||"-")} status=${x.gray(o.status||"-")} pg=${x.gray(o.dbInstanceName||"-")}`);console.log(""),q("Done.")})}),n.command("supa info <instanceName>","查看 Supabase 实例详情").action(async(e)=>{await G({commandLabel:"licell supa info",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let t=e.trim(),s=H(),r=await F(s,`正在拉取实例 ${t} 详情...`,"❌ 获取失败",()=>Yc(t));if(!r)return;if(m()){T({stage:"supa.info",detail:r});return}if(s.stop(x.green("✅ 获取成功")),console.log(`
|
|
1723
|
+
instanceName: ${x.cyan(r.instanceName)}`),console.log(`appName: ${x.cyan(r.appName||"-")}`),console.log(`status: ${x.cyan(r.status||"-")}`),console.log(`class: ${x.cyan(r.instanceClass||"-")}`),console.log(`region/zone: ${x.cyan(`${r.regionId||"-"} / ${r.zoneId||"-"}`)}`),console.log(`pgInstance: ${x.cyan(r.dbInstanceName||"-")}`),console.log(`vSwitch: ${x.cyan(r.vSwitchId||"-")}`),r.vpcConnectionString)console.log(`vpc url: ${x.cyan(r.vpcConnectionString)}`);if(r.publicConnectionString)console.log(`public url: ${x.cyan(r.publicConnectionString)}`);console.log(""),q("Done.")})}),n.command("supa connect <instanceName>","查看 Supabase 连接信息和 API Keys").action(async(e)=>{await G({commandLabel:"licell supa connect",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let t=e.trim(),s=H(),[r,o]=await F(s,"正在获取连接信息...","❌ 获取失败",()=>Promise.all([jc(t),$r(t)]))||[null,null];if(!r||!o)return;if(m()){T({stage:"supa.connect",instanceName:t,endpoints:r,authInfo:o});return}s.stop(x.green("✅ 连接信息")),console.log(x.yellow(`
|
|
1724
|
+
── Supabase Endpoints ──`));for(let c of r.instanceEndpoints)console.log(` ${x.gray(c.ipType||"-")}: ${x.cyan(c.connectionString||"-")}`);if(r.dbInstanceEndpoints.length>0){console.log(x.yellow(`
|
|
1725
|
+
── DB Endpoints ──`));for(let c of r.dbInstanceEndpoints)console.log(` ${x.gray(c.ipType||"-")}: ${x.cyan(c.connectionString||"-")}:${x.cyan(c.port||"-")}`)}if(console.log(x.yellow(`
|
|
1726
|
+
── API Keys ──`)),o.jwtSecret)console.log(`jwt secret: ${x.gray(o.jwtSecret.slice(0,20))}...`);if(o.anonKey)console.log(`anon key: ${x.gray(o.anonKey.slice(0,40))}...`);if(o.serviceKey)console.log(`service key: ${x.gray(o.serviceKey.slice(0,40))}...`);if(o.configList.length>0){console.log(x.yellow(`
|
|
1727
|
+
── Auth Config ──`));for(let c of o.configList)console.log(` ${x.gray(c.name)}: ${x.cyan(c.value)}`)}console.log(""),q("Done.")})}),n.command("supa config <instanceName>","查看 Supabase 实例配置(auth/storage/rag)").option("--set-auth <key=value>","修改 Auth 配置(如 GOTRUE_SITE_URL=http://example.com)").option("--set-storage <key=value>","修改 Storage 配置(如 TENANT_ID=my-prefix)").option("--rag <on|off>","开启/关闭 RAG Agent").option("--set-rag <key=value>","修改 RAG 配置(如 LLM_MODEL=qwen-flash)").action(async(e,t)=>{await G({commandLabel:"licell supa config",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let s=e.trim(),r=H();if(t.setAuth){let[o,...c]=t.setAuth.split("="),f=c.join("=");if(!o||f===void 0)throw Error("格式: --set-auth KEY=VALUE");if(await F(r,`正在修改 Auth 配置 ${o}...`,"❌ 修改失败",()=>Qc(s,[{name:o,value:f}])),!m())r.stop(x.green(`✅ Auth 配置 ${o} 已更新`));else{T({stage:"supa.config",action:"set-auth",key:o,value:f});return}}if(t.setStorage){let[o,...c]=t.setStorage.split("="),f=c.join("=");if(!o||f===void 0)throw Error("格式: --set-storage KEY=VALUE");if(await F(r,`正在修改 Storage 配置 ${o}...`,"❌ 修改失败",()=>Xc(s,[{name:o,value:f}])),!m())r.stop(x.green(`✅ Storage 配置 ${o} 已更新`));else{T({stage:"supa.config",action:"set-storage",key:o,value:f});return}}if(t.rag||t.setRag){let o=t.rag==="on"?!0:t.rag==="off"?!1:void 0,c;if(t.setRag){let[f,...u]=t.setRag.split("="),g=u.join("=");if(!f||g===void 0)throw Error("格式: --set-rag KEY=VALUE");c=[{name:f,value:g}]}if(await F(r,"正在修改 RAG 配置...","❌ 修改失败",()=>Zc(s,o,c)),!m())r.stop(x.green("✅ RAG 配置已更新"));else{T({stage:"supa.config",action:"set-rag",ragStatus:o,ragConfig:c});return}}if(!t.setAuth&&!t.setStorage&&!t.rag&&!t.setRag){let[o,c,f]=await F(r,"正在获取配置...","❌ 获取失败",()=>Promise.all([$r(s),Dc(s),Wc(s)]))||[null,null,null];if(!o||!c||!f)return;if(m()){T({stage:"supa.config",instanceName:s,authInfo:o,storageConfig:c,ragConfig:f});return}if(r.stop(x.green("✅ 配置信息")),o.configList.length>0){console.log(x.yellow(`
|
|
1728
|
+
── Auth Config ──`));for(let u of o.configList)console.log(` ${x.gray(u.name)}: ${x.cyan(u.value)}`)}if(c.length>0){console.log(x.yellow(`
|
|
1729
|
+
── Storage Config (OSS) ──`));for(let u of c)console.log(` ${x.gray(u.name)}: ${x.cyan(u.value)}`)}if(console.log(x.yellow(`
|
|
1730
|
+
── RAG Agent ──`)),console.log(` status: ${f.status?x.green("enabled"):x.gray("disabled")}`),f.configList.length>0)for(let u of f.configList)console.log(` ${x.gray(u.name)}: ${x.cyan(u.value)}`);console.log(""),q("使用 --set-auth / --set-storage / --rag / --set-rag 修改配置")}})}),n.command("supa whitelist <instanceName>","查看/修改 Supabase IP 白名单").option("--set <ips>","设置白名单 IP(覆盖模式,逗号分隔)").option("--add <ips>","追加白名单 IP(逗号分隔)").option("--remove <ips>","删除白名单 IP(逗号分隔)").option("--group <name>","白名单分组名称(默认 default)").action(async(e,t)=>{await G({commandLabel:"licell supa whitelist",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let s=e.trim(),r=t.group?.trim()||"default",o=H();if(t.set){if(await F(o,"正在设置白名单...","❌ 设置失败",()=>ms(s,t.set,"Cover",r)),m()){T({stage:"supa.whitelist",action:"set",ips:t.set});return}o.stop(x.green("✅ 白名单已更新"))}else if(t.add){if(await F(o,"正在追加白名单...","❌ 追加失败",()=>ms(s,t.add,"Append",r)),m()){T({stage:"supa.whitelist",action:"add",ips:t.add});return}o.stop(x.green("✅ 白名单已追加"))}else if(t.remove){if(await F(o,"正在删除白名单...","❌ 删除失败",()=>ms(s,t.remove,"Delete",r)),m()){T({stage:"supa.whitelist",action:"remove",ips:t.remove});return}o.stop(x.green("✅ 白名单已删除"))}else{let c=await F(o,"正在获取白名单...","❌ 获取失败",()=>Vc(s));if(!c)return;if(m()){T({stage:"supa.whitelist",instanceName:s,groups:c});return}o.stop(x.green("✅ IP 白名单"));for(let f of c)console.log(`
|
|
1731
|
+
${x.yellow(`── ${f.groupName} ──`)}`),console.log(` ${x.cyan(f.ipWhitelist||"(empty)")}`);console.log(""),q("使用 --set / --add / --remove 修改白名单")}})}),n.command("supa reset-password <instanceName>","重置 Supabase Dashboard 或数据库密码").option("--dashboard-password <password>","新的 Dashboard 密码").option("--db-password <password>","新的数据库密码").action(async(e,t)=>{await G({commandLabel:"licell supa reset-password",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let s=e.trim();if(!t.dashboardPassword&&!t.dbPassword)throw Error("请指定 --dashboard-password 或 --db-password");let r=H();if(await F(r,"正在重置密码...","❌ 重置失败",()=>Oc(s,t.dashboardPassword,t.dbPassword)),m()){T({stage:"supa.reset-password",instanceName:s});return}r.stop(x.green("✅ 密码已重置")),q("Done.")})});for(let[e,t,s]of[["supa restart","重启",Jc],["supa stop","暂停",Nc],["supa start","启动",zc]])n.command(`${e} <instanceName>`,`${t} Supabase 实例`).action(async(r)=>{await G({commandLabel:`licell ${e}`,interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{B();let o=r.trim(),c=H();if(await F(c,`正在${t}实例 ${o}...`,`❌ ${t}失败`,()=>s(o)),m()){T({stage:`supa.${e.split(" ")[1]}`,instanceName:o});return}c.stop(x.green(`✅ 实例 ${o} 已${t}`)),q("Done.")})});n.command("supa rm <instanceName>","删除 Supabase 实例").option("--yes","跳过确认").action(async(e,t)=>{await G({commandLabel:"licell supa rm",interactiveTTY:I(),requiredCapabilities:["rdsai"]},async()=>{W(x.bgRed(x.white(" \uD83D\uDDD1️ Delete Supabase Instance "))),B();let s=e.trim();if(!s)throw Error("请提供 instanceName");if(!t.yes&&I()){let o=await $b({message:`确认删除 Supabase 实例 ${x.red(s)}?此操作不可恢复。
|
|
1732
|
+
⚠️ 注意:关联的 RDS PostgreSQL 实例和 NAT 网关需要手动删除。`});if(bb(o)||!o){q("已取消");return}}let r=H();if(await F(r,`正在删除实例 ${s}...`,"❌ 删除失败",()=>nf(s)),m()){T({stage:"supa.rm",instanceName:s});return}q(`实例 ${s} 已删除。⚠️ 关联的 PG 实例和 NAT 网关需手动清理。`)})})}import{existsSync as Hd,readFileSync as kb,writeFileSync as _b,mkdirSync as Cb}from"fs";import{homedir as Sb}from"os";import{join as Ud}from"path";import ws from"picocolors";var ef=Ud(Sb(),".licell-cli"),tf=Ud(ef,"update-check.json"),Tb=43200000,Eb=2000,qb="https://api.github.com/repos/agents-infrastructure/licell/releases/latest";function Ad(n,e){let t=n.split(".").map(Number),s=e.split(".").map(Number),r=Math.max(t.length,s.length);for(let o=0;o<r;o++){let c=t[o]??0,f=s[o]??0;if(c!==f)return c-f}return 0}function Ib(){try{if(!Hd(tf))return null;let n=JSON.parse(kb(tf,"utf-8"));if(typeof n.latestVersion!=="string"||typeof n.checkedAt!=="number")return null;return n}catch{return null}}function Rb(n){try{if(!Hd(ef))Cb(ef,{recursive:!0});_b(tf,JSON.stringify(n),"utf-8")}catch{}}async function Pb(){try{let n=new AbortController,e=setTimeout(()=>n.abort(),Eb),t=await fetch(qb,{signal:n.signal,headers:{Accept:"application/vnd.github+json"}});if(clearTimeout(e),!t.ok)return null;let s=await t.json();if(typeof s.tag_name!=="string")return null;return s.tag_name.replace(/^v/,"")}catch{return null}}async function Md(n){if(!n||n==="dev")return null;let e=Ib();if(e&&Date.now()-e.checkedAt<Tb){if(Ad(e.latestVersion,n)>0)return{currentVersion:n,latestVersion:e.latestVersion};return null}let t=await Pb();if(!t)return null;if(Rb({latestVersion:t,checkedAt:Date.now()}),Ad(t,n)>0)return{currentVersion:n,latestVersion:t};return null}function Gd(n){let e=`新版本可用: ${n.currentVersion} → ${n.latestVersion}`,t="licell upgrade",s=Math.max(e.length,18),r=(o)=>o+" ".repeat(s-o.replace(/\x1b\[[0-9;]*m/g,"").length);console.error(""),console.error(ws.yellow(`╭${"─".repeat(s+4)}╮`)),console.error(ws.yellow(`│ ${r(e)} │`)),console.error(ws.yellow(`│ ${r(`运行 ${ws.bold("licell upgrade")} 升级`)} │`)),console.error(ws.yellow(`╰${"─".repeat(s+4)}╯`))}dn();Q();on();Vs();import{confirm as Bd,isCancel as vd}from"@clack/prompts";import je from"picocolors";dn();async function Kd(){console.log(je.cyan(`
|
|
1733
|
+
\uD83D\uDC4B 欢迎使用 Licell CLI!`)),console.log(je.gray(`检测到您尚未配置登录信息。本向导将协助您完成初始设置。
|
|
1734
|
+
`));let n=await Bd({message:"是否现在配置阿里云登录凭证?(支持全自动高权限转最小权限)",initialValue:!0});if(vd(n)){console.log(je.gray("已取消初始化向导。稍后可通过 `licell login` 重新配置。"));return}if(n)try{await Xs()}catch(t){console.error(je.red(`
|
|
1735
|
+
❌ 登录配置失败: ${A(t)}`)),console.log(je.gray("您可以稍后通过 `licell login` 重试登录。"))}else console.log(je.gray("跳过登录配置。您可以通过 `licell login` 随时进行配置。"));console.log();let e=await Bd({message:"是否配置 AI Agent Skills 和 MCP(推荐,让 AI 更好地使用 licell)?",initialValue:!0});if(vd(e)){console.log(je.gray("已完成向导。稍后可通过 `licell setup` 重新配置 AI 助手。"));return}if(e)await Hc();else console.log(je.gray("跳过 AI 助手配置。您可以通过 `licell setup` 随时配置。"))}fn();var jd=it(),en=Fb("licell");en.version(jd);en.option("--output <mode>","输出格式:text|json(json 更适合 Agent/MCP 解析)",{default:"text"});Go(en);Vi(en);Gg(en);Bg(en);Kg(en);nl(en);bl(en);Al(en);vl(en);Kl(en);Yl(en);Dl(en);Zl(en);zl(en);dd(en);$d(en);Sd(en);Ed(en);qd(en);Ld(en);en.help();en.on("command:*",()=>{let n=en.args.join(" ");if(m())pn(Error(`未知命令: ${n}`),{stage:"parse",details:{command:n}}),process.exit(1);console.error(`未知命令: ${n}`),en.outputHelp(),process.exit(1)});var Dd=lf(process.argv),$s=Dd;try{let n=mi(Dd);$s=n.argv,wi(n.mode,$s),bi()}catch(n){let e=A(n);console.error(ps.red(e)),process.exit(1)}function xb(n){let e=A(n),t=e.match(/missing required args for command `(.+?)`/),s=t||typeof n==="object"&&n!==null&&"name"in n&&String(n.name||"")==="CACError";if(m())pn(n,{stage:s?"parse":"runtime",...t?{details:{usage:t[1]}}:{}}),process.exit(1);if(t)console.error(ps.red("命令参数不完整。")),console.error(ps.gray(`用法: licell ${t[1]}`)),en.outputHelp(),process.exit(1);console.error(ps.red(e)),process.exit(1)}var Yd=!1;function Wd(n,e){if(Yd)return;if(Yd=!0,m())pn(n,{stage:e});else console.error(ps.red(A(n)));process.exit(1)}process.on("unhandledRejection",(n)=>{Wd(n,"unhandled_rejection")});process.on("uncaughtException",(n)=>{Wd(n,"uncaught_exception")});process.once("beforeExit",(n)=>{if(n!==0)return;if(!m())return;if(ki()||_i())return;T({stage:"runtime",completed:!0})});var Lb=$s.some((n)=>n==="upgrade"),Ab=!m()&&!Lb?Md(jd).catch(()=>null):Promise.resolve(null);Promise.resolve().then(async()=>{if($s.length<=2){if(pi()==="json")T({stage:"help",help:"请执行 licell <command> --help 查看命令说明"}),process.exit(0);if(I()&&!m()&&!k.getAuth())await Kd();else en.outputHelp();process.exit(0)}}).then(()=>en.parse($s)).then(async()=>{let n=await Ab;if(n)Gd(n)}).catch(xb);
|