@vibecontrols/vibe-plugin-gitops-bitbucket 2026.731.1 → 2026.1108.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/package.json +34 -22
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import{Elysia}from"elysia";function createLifecycleHooks(spec){let{name,onInit,onShutdown,telemetryEventName,skipPlatforms}=spec;return{onServerStart:async(_app,hostServices)=>{if(skipPlatforms&&skipPlatforms.includes(process.platform)){process.stderr.write(`[${name}] skipping init on unsupported platform '${process.platform}'
|
|
3
|
-
`);return}if(onInit)await onInit(hostServices);if(telemetryEventName)hostServices.telemetry?.emit(telemetryEventName,{plugin:name})},onServerStop:async(hostServices)=>{if(onShutdown)await onShutdown(hostServices)}}}var TelemetryEmitter=class{constructor(pluginName,pluginVersion,hostServices){this.pluginName=pluginName,this.pluginVersion=pluginVersion,this.hostServices=hostServices}pluginName;pluginVersion;hostServices;emit(eventName,payload){let target=this.hostServices?.telemetry;if(!target)return;target.emit(eventName,{plugin:this.pluginName,version:this.pluginVersion,timestamp:new Date().toISOString(),...payload??{}})}emitReady(context){this.emit(`${this.pluginName}.ready`,context)}emitError(error,context){this.emit(`${this.pluginName}.error`,{message:error.message,...context??{}})}emitEvent(type,payload){this.emit(type,payload)}},BoundLogger=class{constructor(logger,source){this.logger=logger,this.source=source}logger;source;info(message,meta){this.logger?.info?.(this.source,message,meta)}warn(message,meta){this.logger?.warn?.(this.source,message,meta)}error(message,meta){this.logger?.error?.(this.source,message,meta)}debug(message,meta){this.logger?.debug?.(this.source,message,meta)}};var CONTEXT_PROVIDER_TYPE="context",contextProviders=new Map;function registerContextProvider(provider,hostServices){if(!provider||typeof provider.name!=="string"||provider.name.length===0)throw Error("ContextProvider.name is required");if(typeof provider.getContext!=="function")throw Error("ContextProvider.getContext must be a function");contextProviders.set(provider.name,provider),hostServices?.serviceRegistry?.registerProvider?.(CONTEXT_PROVIDER_TYPE,provider,provider.name)}function listContextProviders(){return Array.from(contextProviders.values())}function getContextProvider(name){return contextProviders.get(name)}var ProviderRegistry=class{constructor(hostServices){this.hostServices=hostServices}hostServices;registerContextProvider(provider){registerContextProvider(provider,this.hostServices)}listContextProviders(){return listContextProviders()}getContextProvider(name){return getContextProvider(name)}getServiceRegistry(){return this.hostServices?.serviceRegistry}registerProvider(type,name,provider){this.hostServices?.serviceRegistry?.registerProvider?.(type,provider,name)}getProvider(type,name){let reg=this.hostServices?.serviceRegistry;if(!reg)return;if(name===void 0)return reg.getProvider?.(type);if(!reg.listProvidersForType)return;if(!(reg.listProvidersForType(type)??[]).some((entry)=>typeof entry==="string"?entry===name:entry.pluginName===name))return;return reg.getProvider?.(type)}listProviders(type){return(this.hostServices?.serviceRegistry?.listProvidersForType?.(type)??[]).map((entry)=>typeof entry==="string"?entry:entry.pluginName)}withCliContribution(contribution){let contributors=this.hostServices?.cliContributors;if(!contributors)return;for(let section of contribution.statusSections??[])contributors.addStatusSection?.(section);for(let check of contribution.doctorChecks??[])contributors.addDoctorCheck?.(check)}};class GitOpsError extends Error{code;upstreamStatus;retryAfterSeconds;constructor(code,message,opts){super(message);this.name="GitOpsError",this.code=code,this.upstreamStatus=opts?.upstreamStatus,this.retryAfterSeconds=opts?.retryAfterSeconds}}var STORAGE_NS="gitops-bitbucket",KEY_PAT="pat:default",API_BASE="https://api.bitbucket.org/2.0";function resolveApiBase(meta){let override=meta?.baseUrl??meta?.host;return override?override.replace(/\/$/,""):API_BASE}class BitbucketProvider{name="bitbucket";host;log;token=null;apiBase=API_BASE;cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-bitbucket")}async init(){let s=await this.loadAuth();if(s)this.token=s.token,this.apiBase=resolveApiBase(s.meta),this.log.info("Loaded persisted Bitbucket app-password")}async loadAuth(){let raw=await this.host.storage?.get(STORAGE_NS,KEY_PAT);if(!raw)return null;try{return typeof raw==="string"?JSON.parse(raw):raw}catch{return null}}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-bitbucket/0.1",Accept:"application/json"});if(this.token)h.set("Authorization","Basic "+btoa(this.token));return h}mapStatus(s,body){if(s===401)return new GitOpsError("AUTH","Bitbucket: unauthorized");if(s===403)return new GitOpsError("FORBIDDEN","Bitbucket: forbidden");if(s===404)return new GitOpsError("NOT_FOUND","Bitbucket: not found");if(s===429)return new GitOpsError("RATE_LIMITED","Bitbucket: rate-limited");return new GitOpsError("UPSTREAM","Bitbucket: "+s+" "+body.slice(0,200))}async rest(path){let url=path.startsWith("http")?path:`${this.apiBase}${path}`,res=await fetch(url,{headers:this.headers()});if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapStatus(res.status,text)}return await res.json()}cached(key,ttlMs,fn){let e=this.cache.get(key);if(e&&Date.now()-e.ts<e.ttlMs)return Promise.resolve(e.value);return fn().then((v)=>{return this.cache.set(key,{ts:Date.now(),ttlMs,value:v}),v})}mapRepo(r){return{fqn:r.full_name,provider:"bitbucket",visibility:r.is_private?"private":"public",defaultBranch:r.mainbranch?.name??"main",description:r.description??void 0,language:r.language,isArchived:!1,isFork:!1,size:r.size,url:r.links?.html?.href??"",createdAt:r.created_on,updatedAt:r.updated_on,pushedAt:r.updated_on}}mapPr(p){let state=p.state==="MERGED"?"merged":p.state==="DECLINED"||p.state==="SUPERSEDED"?"closed":"open";return{id:String(p.id),number:p.id,title:p.title,state,isDraft:!!p.draft,author:p.author?.display_name??p.author?.nickname??"unknown",reviewers:(p.reviewers??[]).map((r)=>r.display_name??r.nickname??""),createdAt:p.created_on,updatedAt:p.updated_on,mergedAt:state==="merged"?p.closed_on:void 0,durationOpenSeconds:p.closed_on?Math.floor((Date.parse(p.closed_on)-Date.parse(p.created_on))/1000):void 0,url:p.links?.html?.href??"",labels:[]}}mapPipeline(p){let state=p.state?.name??"",result=p.state?.result?.name??"";return{id:p.uuid,pipelineName:`build-${p.build_number??p.uuid}`,branch:p.target?.ref_name??"",status:state==="IN_PROGRESS"?"running":state==="PENDING"?"queued":"completed",conclusion:result==="SUCCESSFUL"?"success":result==="FAILED"?"failure":result==="STOPPED"?"cancelled":void 0,startedAt:p.created_on,completedAt:p.completed_on,durationSeconds:p.duration_in_seconds,url:"",actor:p.creator?.display_name??"unknown",commitSha:p.target?.commit?.hash}}async saveCredentials(input){let env={kind:input.kind,token:input.token,meta:input.meta,savedAt:new Date().toISOString()};await this.host.storage?.set(STORAGE_NS,KEY_PAT,JSON.stringify(env)),this.token=input.token,this.apiBase=resolveApiBase(input.meta),this.cache.clear()}async validateCredentials(){if(!this.token){let s=await this.loadAuth();if(!s)return{ok:!1,message:"No credentials stored"};this.token=s.token}try{let u=await this.rest("/user");return{ok:!0,account:u.username??u.display_name??"unknown",scopes:["repository:read","pullrequest:read","pipeline:read"]}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}async rotateCredentials(input){return await this.saveCredentials(input),this.validateCredentials()}async revokeCredentials(){await this.host.storage?.delete(STORAGE_NS,KEY_PAT),this.token=null,this.cache.clear()}async healthCheck(){try{return await this.rest("/user"),{ok:!0}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}async listRepos(opts){let limit=Math.max(1,Math.min(opts.limit??30,100)),path=opts.cursor??(opts.org?`/repositories/${encodeURIComponent(opts.org)}?pagelen=${limit}&sort=-updated_on`:`/repositories?role=member&pagelen=${limit}&sort=-updated_on`),data=await this.rest(path);return{items:data.values.map((r)=>this.mapRepo(r)),nextCursor:data.next}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let r=await this.rest(`/repositories/${fqn}`);return this.mapRepo(r)})}async listBranches(fqn){return(await this.rest(`/repositories/${fqn}/refs/branches?pagelen=100`)).values.map((b)=>({name:b.name,isProtected:!1,lastCommitSha:b.target.hash}))}async listLanguages(fqn){return this.cached(`langs:${fqn}`,300000,async()=>{let r=await this.rest(`/repositories/${fqn}`);return r.language?{[r.language]:1}:{}})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100));try{let data=await this.rest(`/repositories/${fqn}/commits?pagelen=${limit}`),counts=new Map;for(let c of data.values){let login=c.author?.user?.display_name??c.author?.user?.nickname;if(!login)continue;counts.set(login,(counts.get(login)??0)+1)}return[...counts.entries()].sort((a,b)=>b[1]-a[1]).map(([login,contributions])=>({login,contributions}))}catch{return[]}}async listPullRequests(fqn,opts){let state={open:"OPEN",closed:"MERGED,DECLINED",all:"OPEN,MERGED,DECLINED,SUPERSEDED"}[opts?.state??"open"]??"OPEN",limit=Math.max(1,Math.min(opts?.limit??30,50));return(await this.rest(`/repositories/${fqn}/pullrequests?state=${state}&pagelen=${limit}`)).values.map((p)=>this.mapPr(p))}async getPullRequest(fqn,id){let p=await this.rest(`/repositories/${fqn}/pullrequests/${id}`);return this.mapPr(p)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:50}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:50})).filter((m)=>m.state==="merged")].filter((m)=>typeof m.durationOpenSeconds==="number").sort((a,b)=>(a.durationOpenSeconds??0)-(b.durationOpenSeconds??0)),median=sorted.length?(sorted[Math.floor(sorted.length/2)]?.durationOpenSeconds??0)/3600:0;return{slowest:sorted.slice(-5).reverse(),fastest:sorted.slice(0,5),medianAgeHours:Math.round(median*10)/10,awaitingReview:open,awaitingApproval:[]}}async listIssues(fqn,opts){try{let limit=Math.max(1,Math.min(opts?.limit??30,50));return(await this.rest(`/repositories/${fqn}/issues?pagelen=${limit}`)).values.filter((i)=>{let s=opts?.state;if(!s)return!0;if(s==="open")return i.state!=="closed"&&i.state!=="resolved";return i.state==="closed"||i.state==="resolved"}).map((i)=>({id:String(i.id),number:i.id,title:i.title,state:i.state,labels:[],url:i.links?.html?.href??"",createdAt:i.created_on}))}catch{return[]}}async labelStats(_fqn){return{}}async listPipelines(_fqn){return[{id:"default",name:"Pipelines",state:"active"}]}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&target.branch=${encodeURIComponent(opts.branch)}`:"";return(await this.rest(`/repositories/${fqn}/pipelines?pagelen=${limit}&sort=-created_on${branchQs}`)).values.map((p)=>this.mapPipeline(p))}async getRun(fqn,runId){let p=await this.rest(`/repositories/${fqn}/pipelines/${runId}`);return this.mapPipeline(p)}async pipelineAnalytics(fqn){let runs=await this.listRecentRuns(fqn,{limit:100}),completed=runs.filter((r)=>r.status==="completed"),succ=completed.filter((r)=>r.conclusion==="success").length,durations=completed.map((r)=>r.durationSeconds??0).filter((d)=>d>0).sort((a,b)=>a-b),pct=(p)=>durations.length===0?0:durations[Math.min(durations.length-1,Math.floor(durations.length*p))]??0;return{successRate:completed.length?succ/completed.length:0,durationP50:pct(0.5),durationP95:pct(0.95),slowest:[...completed].sort((a,b)=>(b.durationSeconds??0)-(a.durationSeconds??0)).slice(0,5),fastest:[...completed].filter((r)=>(r.durationSeconds??0)>0).sort((a,b)=>(a.durationSeconds??0)-(b.durationSeconds??0)).slice(0,5),running:runs.filter((r)=>r.status==="running"),queued:runs.filter((r)=>r.status==="queued"),pendingApproval:runs.filter((r)=>r.status==="waiting"),totalRunsLast30Days:runs.length}}async listEnvironments(fqn){try{return(await this.rest(`/repositories/${fqn}/environments`)).values.map((e)=>({name:e.name,state:e.type??"active"}))}catch{return[]}}async listSecurityAlerts(_fqn,_opts){return[]}async orgRollup(org){return this.cached(`rollup:${org}`,300000,async()=>{let first=await this.listRepos({org,limit:100}),items=[...first.items],cursor=first.nextCursor,pages=1;while(cursor&&pages<5){let next=await this.listRepos({cursor});items.push(...next.items),cursor=next.nextCursor,pages++}let byVis={public:0,private:0,internal:0},byLang={},now=Date.now(),stale30d=0;for(let r of items){if(byVis[r.visibility]+=1,r.language)byLang[r.language]=(byLang[r.language]??0)+1;let pushed=Date.parse(r.pushedAt??r.updatedAt);if(now-pushed>2592000000)stale30d++}return{totalRepos:items.length,byVisibility:byVis,byLanguage:byLang,archived:0,stale30d,totalOpenPRs:0,totalOpenIssues:0}})}async listWebhooks(fqn){try{return(await this.rest(`/repositories/${fqn}/hooks`)).values.map((h)=>({id:h.uuid,url:h.url,events:h.events,active:h.active}))}catch{return[]}}}var PLUGIN_NAME="gitops-bitbucket",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="bitbucket",provider=null,createPlugin=(_ctx)=>{let telemetry=new TelemetryEmitter(PLUGIN_NAME,PLUGIN_VERSION),lifecycle=createLifecycleHooks({name:PLUGIN_NAME,telemetryEventName:"gitops.provider.ready",onInit:async(hostServices)=>{let log=new BoundLogger(hostServices.logger,PLUGIN_NAME);provider=new BitbucketProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("Bitbucket gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"Bitbucket Cloud provider for the GitOps meta plugin (REST v2, app-password auth).",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{src_default as default,createPlugin,BitbucketProvider};
|
|
3
|
+
`);return}if(onInit)await onInit(hostServices);if(telemetryEventName)hostServices.telemetry?.emit(telemetryEventName,{plugin:name})},onServerStop:async(hostServices)=>{if(onShutdown)await onShutdown(hostServices)}}}var TelemetryEmitter=class{constructor(pluginName,pluginVersion,hostServices){this.pluginName=pluginName,this.pluginVersion=pluginVersion,this.hostServices=hostServices}pluginName;pluginVersion;hostServices;emit(eventName,payload){let target=this.hostServices?.telemetry;if(!target)return;target.emit(eventName,{plugin:this.pluginName,version:this.pluginVersion,timestamp:new Date().toISOString(),...payload??{}})}emitReady(context){this.emit(`${this.pluginName}.ready`,context)}emitError(error,context){this.emit(`${this.pluginName}.error`,{message:error.message,...context??{}})}emitEvent(type,payload){this.emit(type,payload)}},BoundLogger=class{constructor(logger,source){this.logger=logger,this.source=source}logger;source;info(message,meta){this.logger?.info?.(this.source,message,meta)}warn(message,meta){this.logger?.warn?.(this.source,message,meta)}error(message,meta){this.logger?.error?.(this.source,message,meta)}debug(message,meta){this.logger?.debug?.(this.source,message,meta)}};var CONTEXT_PROVIDER_TYPE="context",contextProviders=new Map;function registerContextProvider(provider,hostServices){if(!provider||typeof provider.name!=="string"||provider.name.length===0)throw Error("ContextProvider.name is required");if(typeof provider.getContext!=="function")throw Error("ContextProvider.getContext must be a function");contextProviders.set(provider.name,provider),hostServices?.serviceRegistry?.registerProvider?.(CONTEXT_PROVIDER_TYPE,provider,provider.name)}function listContextProviders(){return Array.from(contextProviders.values())}function getContextProvider(name){return contextProviders.get(name)}var ProviderRegistry=class{constructor(hostServices){this.hostServices=hostServices}hostServices;registerContextProvider(provider){registerContextProvider(provider,this.hostServices)}listContextProviders(){return listContextProviders()}getContextProvider(name){return getContextProvider(name)}getServiceRegistry(){return this.hostServices?.serviceRegistry}registerProvider(type,name,provider){this.hostServices?.serviceRegistry?.registerProvider?.(type,provider,name)}getProvider(type,name){let reg=this.hostServices?.serviceRegistry;if(!reg)return;if(name===void 0)return reg.getProvider?.(type);if(!reg.listProvidersForType)return;if(!(reg.listProvidersForType(type)??[]).some((entry)=>typeof entry==="string"?entry===name:entry.pluginName===name))return;return reg.getProvider?.(type)}listProviders(type){return(this.hostServices?.serviceRegistry?.listProvidersForType?.(type)??[]).map((entry)=>typeof entry==="string"?entry:entry.pluginName)}withCliContribution(contribution){let contributors=this.hostServices?.cliContributors;if(!contributors)return;for(let section of contribution.statusSections??[])contributors.addStatusSection?.(section);for(let check of contribution.doctorChecks??[])contributors.addDoctorCheck?.(check)}};class GitOpsError extends Error{code;upstreamStatus;retryAfterSeconds;constructor(code,message,opts){super(message);this.name="GitOpsError",this.code=code,this.upstreamStatus=opts?.upstreamStatus,this.retryAfterSeconds=opts?.retryAfterSeconds}}var STORAGE_NS="gitops-bitbucket",KEY_PAT="pat:default",API_BASE="https://api.bitbucket.org/2.0";function resolveApiBase(meta){let override=meta?.baseUrl??meta?.host;return override?override.replace(/\/$/,""):API_BASE}class BitbucketProvider{name="bitbucket";host;log;token=null;apiBase=API_BASE;cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-bitbucket")}async init(){let s=await this.loadAuth();if(s)this.token=s.token,this.apiBase=resolveApiBase(s.meta),this.log.info("Loaded persisted Bitbucket app-password")}async loadAuth(){let raw=await this.host.storage?.get(STORAGE_NS,KEY_PAT);if(!raw)return null;try{return typeof raw==="string"?JSON.parse(raw):raw}catch{return null}}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-bitbucket/0.1",Accept:"application/json"});if(this.token)h.set("Authorization","Basic "+btoa(this.token));return h}mapStatus(s,body){if(s===401)return new GitOpsError("AUTH","Bitbucket: unauthorized");if(s===403)return new GitOpsError("FORBIDDEN","Bitbucket: forbidden");if(s===404)return new GitOpsError("NOT_FOUND","Bitbucket: not found");if(s===429)return new GitOpsError("RATE_LIMITED","Bitbucket: rate-limited");return new GitOpsError("UPSTREAM","Bitbucket: "+s+" "+body.slice(0,200))}async rest(path){let url=path.startsWith("http")?path:`${this.apiBase}${path}`,res=await fetch(url,{headers:this.headers()});if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapStatus(res.status,text)}return await res.json()}cached(key,ttlMs,fn){let e=this.cache.get(key);if(e&&Date.now()-e.ts<e.ttlMs)return Promise.resolve(e.value);return fn().then((v)=>(this.cache.set(key,{ts:Date.now(),ttlMs,value:v}),v))}mapRepo(r){return{fqn:r.full_name,provider:"bitbucket",visibility:r.is_private?"private":"public",defaultBranch:r.mainbranch?.name??"main",description:r.description??void 0,language:r.language,isArchived:!1,isFork:!1,size:r.size,url:r.links?.html?.href??"",createdAt:r.created_on,updatedAt:r.updated_on,pushedAt:r.updated_on}}mapPr(p){let state=p.state==="MERGED"?"merged":p.state==="DECLINED"||p.state==="SUPERSEDED"?"closed":"open";return{id:String(p.id),number:p.id,title:p.title,state,isDraft:!!p.draft,author:p.author?.display_name??p.author?.nickname??"unknown",reviewers:(p.reviewers??[]).map((r)=>r.display_name??r.nickname??""),createdAt:p.created_on,updatedAt:p.updated_on,mergedAt:state==="merged"?p.closed_on:void 0,durationOpenSeconds:p.closed_on?Math.floor((Date.parse(p.closed_on)-Date.parse(p.created_on))/1000):void 0,url:p.links?.html?.href??"",labels:[]}}mapPipeline(p){let state=p.state?.name??"",result=p.state?.result?.name??"";return{id:p.uuid,pipelineName:`build-${p.build_number??p.uuid}`,branch:p.target?.ref_name??"",status:state==="IN_PROGRESS"?"running":state==="PENDING"?"queued":"completed",conclusion:result==="SUCCESSFUL"?"success":result==="FAILED"?"failure":result==="STOPPED"?"cancelled":void 0,startedAt:p.created_on,completedAt:p.completed_on,durationSeconds:p.duration_in_seconds,url:"",actor:p.creator?.display_name??"unknown",commitSha:p.target?.commit?.hash}}async saveCredentials(input){let env={kind:input.kind,token:input.token,meta:input.meta,savedAt:new Date().toISOString()};await this.host.storage?.set(STORAGE_NS,KEY_PAT,JSON.stringify(env)),this.token=input.token,this.apiBase=resolveApiBase(input.meta),this.cache.clear()}async validateCredentials(){if(!this.token){let s=await this.loadAuth();if(!s)return{ok:!1,message:"No credentials stored"};this.token=s.token}try{let u=await this.rest("/user");return{ok:!0,account:u.username??u.display_name??"unknown",scopes:["repository:read","pullrequest:read","pipeline:read"]}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}async rotateCredentials(input){return await this.saveCredentials(input),this.validateCredentials()}async revokeCredentials(){await this.host.storage?.delete(STORAGE_NS,KEY_PAT),this.token=null,this.cache.clear()}async healthCheck(){try{return await this.rest("/user"),{ok:!0}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}async listRepos(opts){let limit=Math.max(1,Math.min(opts.limit??30,100)),path=opts.cursor??(opts.org?`/repositories/${encodeURIComponent(opts.org)}?pagelen=${limit}&sort=-updated_on`:`/repositories?role=member&pagelen=${limit}&sort=-updated_on`),data=await this.rest(path);return{items:data.values.map((r)=>this.mapRepo(r)),nextCursor:data.next}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let r=await this.rest(`/repositories/${fqn}`);return this.mapRepo(r)})}async listBranches(fqn){return(await this.rest(`/repositories/${fqn}/refs/branches?pagelen=100`)).values.map((b)=>({name:b.name,isProtected:!1,lastCommitSha:b.target.hash}))}async listLanguages(fqn){return this.cached(`langs:${fqn}`,300000,async()=>{let r=await this.rest(`/repositories/${fqn}`);return r.language?{[r.language]:1}:{}})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100));try{let data=await this.rest(`/repositories/${fqn}/commits?pagelen=${limit}`),counts=new Map;for(let c of data.values){let login=c.author?.user?.display_name??c.author?.user?.nickname;if(!login)continue;counts.set(login,(counts.get(login)??0)+1)}return[...counts.entries()].sort((a,b)=>b[1]-a[1]).map(([login,contributions])=>({login,contributions}))}catch{return[]}}async listPullRequests(fqn,opts){let state={open:"OPEN",closed:"MERGED,DECLINED",all:"OPEN,MERGED,DECLINED,SUPERSEDED"}[opts?.state??"open"]??"OPEN",limit=Math.max(1,Math.min(opts?.limit??30,50));return(await this.rest(`/repositories/${fqn}/pullrequests?state=${state}&pagelen=${limit}`)).values.map((p)=>this.mapPr(p))}async getPullRequest(fqn,id){let p=await this.rest(`/repositories/${fqn}/pullrequests/${id}`);return this.mapPr(p)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:50}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:50})).filter((m)=>m.state==="merged")].filter((m)=>typeof m.durationOpenSeconds==="number").sort((a,b)=>(a.durationOpenSeconds??0)-(b.durationOpenSeconds??0)),median=sorted.length?(sorted[Math.floor(sorted.length/2)]?.durationOpenSeconds??0)/3600:0;return{slowest:sorted.slice(-5).reverse(),fastest:sorted.slice(0,5),medianAgeHours:Math.round(median*10)/10,awaitingReview:open,awaitingApproval:[]}}async listIssues(fqn,opts){try{let limit=Math.max(1,Math.min(opts?.limit??30,50));return(await this.rest(`/repositories/${fqn}/issues?pagelen=${limit}`)).values.filter((i)=>{let s=opts?.state;if(!s)return!0;if(s==="open")return i.state!=="closed"&&i.state!=="resolved";return i.state==="closed"||i.state==="resolved"}).map((i)=>({id:String(i.id),number:i.id,title:i.title,state:i.state,labels:[],url:i.links?.html?.href??"",createdAt:i.created_on}))}catch{return[]}}async labelStats(_fqn){return{}}async listPipelines(_fqn){return[{id:"default",name:"Pipelines",state:"active"}]}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&target.branch=${encodeURIComponent(opts.branch)}`:"";return(await this.rest(`/repositories/${fqn}/pipelines?pagelen=${limit}&sort=-created_on${branchQs}`)).values.map((p)=>this.mapPipeline(p))}async getRun(fqn,runId){let p=await this.rest(`/repositories/${fqn}/pipelines/${runId}`);return this.mapPipeline(p)}async pipelineAnalytics(fqn){let runs=await this.listRecentRuns(fqn,{limit:100}),completed=runs.filter((r)=>r.status==="completed"),succ=completed.filter((r)=>r.conclusion==="success").length,durations=completed.map((r)=>r.durationSeconds??0).filter((d)=>d>0).sort((a,b)=>a-b),pct=(p)=>durations.length===0?0:durations[Math.min(durations.length-1,Math.floor(durations.length*p))]??0;return{successRate:completed.length?succ/completed.length:0,durationP50:pct(0.5),durationP95:pct(0.95),slowest:[...completed].sort((a,b)=>(b.durationSeconds??0)-(a.durationSeconds??0)).slice(0,5),fastest:[...completed].filter((r)=>(r.durationSeconds??0)>0).sort((a,b)=>(a.durationSeconds??0)-(b.durationSeconds??0)).slice(0,5),running:runs.filter((r)=>r.status==="running"),queued:runs.filter((r)=>r.status==="queued"),pendingApproval:runs.filter((r)=>r.status==="waiting"),totalRunsLast30Days:runs.length}}async listEnvironments(fqn){try{return(await this.rest(`/repositories/${fqn}/environments`)).values.map((e)=>({name:e.name,state:e.type??"active"}))}catch{return[]}}async listSecurityAlerts(_fqn,_opts){return[]}async orgRollup(org){return this.cached(`rollup:${org}`,300000,async()=>{let first=await this.listRepos({org,limit:100}),items=[...first.items],cursor=first.nextCursor,pages=1;while(cursor&&pages<5){let next=await this.listRepos({cursor});items.push(...next.items),cursor=next.nextCursor,pages++}let byVis={public:0,private:0,internal:0},byLang={},now=Date.now(),stale30d=0;for(let r of items){if(byVis[r.visibility]+=1,r.language)byLang[r.language]=(byLang[r.language]??0)+1;let pushed=Date.parse(r.pushedAt??r.updatedAt);if(now-pushed>2592000000)stale30d++}return{totalRepos:items.length,byVisibility:byVis,byLanguage:byLang,archived:0,stale30d,totalOpenPRs:0,totalOpenIssues:0}})}async listWebhooks(fqn){try{return(await this.rest(`/repositories/${fqn}/hooks`)).values.map((h)=>({id:h.uuid,url:h.url,events:h.events,active:h.active}))}catch{return[]}}}var PLUGIN_NAME="gitops-bitbucket",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="bitbucket",provider=null,createPlugin=(_ctx)=>{let telemetry=new TelemetryEmitter(PLUGIN_NAME,PLUGIN_VERSION),lifecycle=createLifecycleHooks({name:PLUGIN_NAME,telemetryEventName:"gitops.provider.ready",onInit:async(hostServices)=>{let log=new BoundLogger(hostServices.logger,PLUGIN_NAME);provider=new BitbucketProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("Bitbucket gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"Bitbucket Cloud provider for the GitOps meta plugin (REST v2, app-password auth).",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{BitbucketProvider,createPlugin,src_default as default};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecontrols/vibe-plugin-gitops-bitbucket",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1108.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"description": "Bitbucket Cloud provider for VibeControls GitOps meta plugin (REST v2)",
|
|
7
7
|
"engines": {
|
|
8
|
-
"bun": ">=1.
|
|
8
|
+
"bun": ">=1.4.4"
|
|
9
9
|
},
|
|
10
10
|
"author": {
|
|
11
11
|
"name": "Vignesh T.V",
|
|
@@ -13,33 +13,31 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun --minify-syntax --minify-whitespace --external elysia && tsc
|
|
17
|
-
"dev": "tsc --watch",
|
|
18
|
-
"lint": "eslint
|
|
19
|
-
"
|
|
20
|
-
"format": "
|
|
21
|
-
"
|
|
22
|
-
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
16
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --minify-syntax --minify-whitespace --external elysia && tsc -p tsconfig.build.json",
|
|
17
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
18
|
+
"lint": "eslint .",
|
|
19
|
+
"format": "bun fmt",
|
|
20
|
+
"format:check": "bun fmt --check",
|
|
21
|
+
"type:check": "boff gate -- bun check --no-pretty",
|
|
23
22
|
"test": "bun test",
|
|
24
23
|
"clean": "rimraf dist",
|
|
25
24
|
"prebuild": "bun run clean",
|
|
26
25
|
"prepublishOnly": "bun run build",
|
|
27
|
-
"sanity": "
|
|
26
|
+
"sanity": "boff sanity",
|
|
27
|
+
"sanity:force": "boff sanity --force",
|
|
28
|
+
"audit": "bun audit --audit-level=high"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@vibecontrols/plugin-sdk": "2026.522.1",
|
|
31
32
|
"elysia": "^1.4.28"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@
|
|
35
|
-
"@types/
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"rimraf": "^6.0.1",
|
|
41
|
-
"typescript": "^5.9.3",
|
|
42
|
-
"typescript-eslint": "^8.56.0"
|
|
35
|
+
"@burdenoff/toolchain": "2026.1107.14",
|
|
36
|
+
"@types/bun": "^1.4.4",
|
|
37
|
+
"eslint": "^9.39.5",
|
|
38
|
+
"knip": "^6.44.1",
|
|
39
|
+
"rimraf": "^6.1.4",
|
|
40
|
+
"typescript": "6.0.3"
|
|
43
41
|
},
|
|
44
42
|
"peerDependencies": {
|
|
45
43
|
"@vibecontrols/agent": ">=2.0.0",
|
|
@@ -63,10 +61,10 @@
|
|
|
63
61
|
],
|
|
64
62
|
"repository": {
|
|
65
63
|
"type": "git",
|
|
66
|
-
"url": "git+https://github.com/
|
|
64
|
+
"url": "git+https://github.com/Algoshred/vibe-plugin-gitops-bitbucket.git"
|
|
67
65
|
},
|
|
68
66
|
"bugs": {
|
|
69
|
-
"url": "https://github.com/
|
|
67
|
+
"url": "https://github.com/Algoshred/vibe-plugin-gitops-bitbucket/issues"
|
|
70
68
|
},
|
|
71
69
|
"homepage": "https://vibecontrols.com",
|
|
72
70
|
"publishConfig": {
|
|
@@ -78,5 +76,19 @@
|
|
|
78
76
|
"!dist/**/*.map",
|
|
79
77
|
"README.md",
|
|
80
78
|
"LICENSE"
|
|
81
|
-
]
|
|
79
|
+
],
|
|
80
|
+
"packageManager": "bun@1.4.4",
|
|
81
|
+
"boff": {
|
|
82
|
+
"type": "plugin",
|
|
83
|
+
"sanity": {
|
|
84
|
+
"steps": [
|
|
85
|
+
"format",
|
|
86
|
+
"lint",
|
|
87
|
+
"typecheck",
|
|
88
|
+
"build",
|
|
89
|
+
"test",
|
|
90
|
+
"knip"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
82
94
|
}
|