@vibecontrols/vibe-plugin-gitops-azdevops 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.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. 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-azdevops",KEY_PAT="pat:default";class AzureDevOpsProvider{name="azdevops";host;log;token=null;organisation=null;apiHost="https://dev.azure.com";cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-azdevops")}async init(){let s=await this.loadAuth();if(s)this.token=s.token,this.organisation=s.meta?.organization??null,this.apiHost=this.resolveApiHost(s.meta),this.log.info("Loaded persisted AzDO PAT")}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}}requireOrg(){if(!this.organisation)throw new GitOpsError("INVALID","meta.organization missing \u2014 call saveCredentials with org first");return this.organisation}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-azdevops/0.1",Accept:"application/json"});if(this.token)h.set("Authorization","Basic "+btoa(":"+this.token));return h}mapStatus(s,body){if(s===401||s===203)return new GitOpsError("AUTH","AzDO: unauthorized");if(s===403)return new GitOpsError("FORBIDDEN","AzDO: forbidden");if(s===404)return new GitOpsError("NOT_FOUND","AzDO: not found");if(s===429)return new GitOpsError("RATE_LIMITED","AzDO: rate-limited");return new GitOpsError("UPSTREAM","AzDO: "+s+" "+body.slice(0,200))}resolveApiHost(meta){let override=meta?.baseUrl??meta?.host;return override?override.replace(/\/$/,""):"https://dev.azure.com"}async rest(path,project){let org=this.requireOrg(),url=path.startsWith("http")?path:`${this.apiHost}/${encodeURIComponent(org)}${project?"/"+encodeURIComponent(project):""}${path}`,res=await fetch(url,{headers:this.headers()});if(res.status===203){let text=await res.text().catch(()=>"");throw this.mapStatus(203,text)}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})}parseFqn(fqn){let parts=fqn.split("/");if(parts.length===3)return{project:parts[1],repo:parts[2]};if(parts.length===2)return{project:parts[0],repo:parts[1]};throw new GitOpsError("INVALID",`Invalid AzDO FQN: ${fqn} (expected org/project/repo)`)}mapRepo(r){return{fqn:`${this.organisation}/${r.project.name}/${r.name}`,provider:"azdevops",visibility:"private",defaultBranch:(r.defaultBranch??"refs/heads/main").replace(/^refs\/heads\//,""),isArchived:!!r.isDisabled,isFork:!!r.isFork,size:r.size,url:r.webUrl,createdAt:"",updatedAt:""}}mapPr(p){let state=p.status==="completed"?"merged":p.status==="abandoned"?"closed":"open",reviewers=(p.reviewers??[]).map((r)=>r.displayName??r.uniqueName??"").filter(Boolean),decision=(()=>{let votes=(p.reviewers??[]).map((r)=>r.vote??0);if(votes.some((v)=>v<0))return"CHANGES_REQUESTED";if(votes.length&&votes.every((v)=>v>=10))return"APPROVED";return})();return{id:String(p.pullRequestId),number:p.pullRequestId,title:p.title,state,isDraft:!!p.isDraft,author:p.createdBy?.displayName??p.createdBy?.uniqueName??"unknown",reviewers,reviewDecision:decision,createdAt:p.creationDate,updatedAt:p.creationDate,mergedAt:p.closedDate,durationOpenSeconds:p.closedDate?Math.floor((Date.parse(p.closedDate)-Date.parse(p.creationDate))/1000):void 0,url:p._links?.web?.href??"",labels:[]}}mapRun(r){return{id:String(r.id),pipelineName:r.pipeline?.name??r.name??"pipeline",branch:(r.resources?.repositories?.self?.refName??"").replace(/^refs\/heads\//,""),status:r.state==="inProgress"?"running":r.state==="notStarted"?"queued":"completed",conclusion:r.result==="succeeded"?"success":r.result==="failed"?"failure":r.result==="canceled"?"cancelled":void 0,startedAt:r.createdDate,completedAt:r.finishedDate,durationSeconds:r.finishedDate?Math.max(0,Math.floor((Date.parse(r.finishedDate)-Date.parse(r.createdDate))/1000)):void 0,url:r._links?.web?.href??"",actor:"unknown",commitSha:r.resources?.repositories?.self?.version}}async saveCredentials(input){if(!input.meta?.organization)throw new GitOpsError("INVALID","meta.organization is required for Azure DevOps");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.organisation=input.meta.organization,this.apiHost=this.resolveApiHost(input.meta),this.cache.clear()}async validateCredentials(){if(!this.token){let s=await this.loadAuth();if(!s)return{ok:!1,message:"No PAT stored"};this.token=s.token,this.organisation=s.meta?.organization??null,this.apiHost=this.resolveApiHost(s.meta)}try{return{ok:!0,account:(await this.rest("/_apis/connectionData?api-version=7.1")).authenticatedUser?.providerDisplayName??this.organisation??""}}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.organisation=null,this.cache.clear()}async healthCheck(){try{return await this.rest("/_apis/connectionData?api-version=7.1"),{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));if(opts.org)return{items:(await this.rest("/_apis/git/repositories?api-version=7.1",opts.org)).value.slice(0,limit).map((r)=>this.mapRepo(r))};let projects=await this.rest("/_apis/projects?api-version=7.1"),items=[];for(let proj of projects.value.slice(0,5))try{let data=await this.rest("/_apis/git/repositories?api-version=7.1",proj.name);for(let r of data.value)if(items.push(this.mapRepo(r)),items.length>=limit)break;if(items.length>=limit)break}catch{}return{items}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let{project,repo}=this.parseFqn(fqn),r=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}?api-version=7.1`,project);return this.mapRepo(r)})}async listBranches(fqn){let{project,repo}=this.parseFqn(fqn);return(await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/refs?filter=heads/&api-version=7.1`,project)).value.map((b)=>({name:b.name.replace(/^refs\/heads\//,""),isProtected:!!b.isLocked,lastCommitSha:b.objectId}))}async listLanguages(_fqn){return{}}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),{project,repo}=this.parseFqn(fqn);try{let data=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/commits?$top=${limit}&api-version=7.1`,project),counts=new Map;for(let c of data.value){let login=c.author?.name;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{project,repo}=this.parseFqn(fqn),status={open:"active",closed:"completed",all:"all"}[opts?.state??"open"]??"active",limit=Math.max(1,Math.min(opts?.limit??30,100));return(await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests?searchCriteria.status=${status}&$top=${limit}&api-version=7.1`,project)).value.map((p)=>this.mapPr(p))}async getPullRequest(fqn,id){let{project,repo}=this.parseFqn(fqn),p=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${id}?api-version=7.1`,project);return this.mapPr(p)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:100}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:100})).filter((p)=>p.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.filter((p)=>!p.reviewDecision),awaitingApproval:open.filter((p)=>p.reviewDecision==="REVIEW_REQUIRED")}}async listIssues(_fqn){return[]}async labelStats(_fqn){return{}}async listPipelines(fqn){let{project}=this.parseFqn(fqn);return(await this.rest("/_apis/pipelines?api-version=7.1",project)).value.map((p)=>({id:String(p.id),name:p.name,state:"active",path:p.folder}))}async listRecentRuns(fqn,opts){let{project}=this.parseFqn(fqn),limit=Math.max(1,Math.min(opts?.limit??30,100)),pipelines=await this.listPipelines(fqn),runs=[];for(let pipe of pipelines.slice(0,5))try{let data=await this.rest(`/_apis/pipelines/${pipe.id}/runs?$top=${limit}&api-version=7.1`,project);for(let r of data.value){let mapped=this.mapRun({...r,pipeline:{id:Number(pipe.id),name:pipe.name}});if(opts?.branch&&mapped.branch!==opts.branch)continue;if(runs.push(mapped),runs.length>=limit)break}if(runs.length>=limit)break}catch{}return runs.slice(0,limit)}async getRun(fqn,runId){let{project}=this.parseFqn(fqn),data=await this.rest(`/_apis/build/builds/${encodeURIComponent(runId)}?api-version=7.1`,project);return this.mapRun(data)}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:[],totalRunsLast30Days:runs.length}}async listEnvironments(_fqn){return[]}async listSecurityAlerts(_fqn,_opts){return[]}async orgRollup(_org){let items=(await this.listRepos({limit:100})).items;return{totalRepos:items.length,byVisibility:{public:0,private:items.length,internal:0},byLanguage:{},archived:items.filter((r)=>r.isArchived).length,stale30d:0,totalOpenPRs:0,totalOpenIssues:0}}async listWebhooks(_fqn){return[]}}var PLUGIN_NAME="gitops-azdevops",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="azdevops",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 AzureDevOpsProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("Azure DevOps gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"Azure DevOps provider for the GitOps meta plugin (REST 7.1). meta.organization required in auth.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{src_default as default,createPlugin,AzureDevOpsProvider};
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-azdevops",KEY_PAT="pat:default";class AzureDevOpsProvider{name="azdevops";host;log;token=null;organisation=null;apiHost="https://dev.azure.com";cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-azdevops")}async init(){let s=await this.loadAuth();if(s)this.token=s.token,this.organisation=s.meta?.organization??null,this.apiHost=this.resolveApiHost(s.meta),this.log.info("Loaded persisted AzDO PAT")}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}}requireOrg(){if(!this.organisation)throw new GitOpsError("INVALID","meta.organization missing \u2014 call saveCredentials with org first");return this.organisation}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-azdevops/0.1",Accept:"application/json"});if(this.token)h.set("Authorization","Basic "+btoa(":"+this.token));return h}mapStatus(s,body){if(s===401||s===203)return new GitOpsError("AUTH","AzDO: unauthorized");if(s===403)return new GitOpsError("FORBIDDEN","AzDO: forbidden");if(s===404)return new GitOpsError("NOT_FOUND","AzDO: not found");if(s===429)return new GitOpsError("RATE_LIMITED","AzDO: rate-limited");return new GitOpsError("UPSTREAM","AzDO: "+s+" "+body.slice(0,200))}resolveApiHost(meta){let override=meta?.baseUrl??meta?.host;return override?override.replace(/\/$/,""):"https://dev.azure.com"}async rest(path,project){let org=this.requireOrg(),url=path.startsWith("http")?path:`${this.apiHost}/${encodeURIComponent(org)}${project?"/"+encodeURIComponent(project):""}${path}`,res=await fetch(url,{headers:this.headers()});if(res.status===203){let text=await res.text().catch(()=>"");throw this.mapStatus(203,text)}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))}parseFqn(fqn){let parts=fqn.split("/");if(parts.length===3)return{project:parts[1],repo:parts[2]};if(parts.length===2)return{project:parts[0],repo:parts[1]};throw new GitOpsError("INVALID",`Invalid AzDO FQN: ${fqn} (expected org/project/repo)`)}mapRepo(r){return{fqn:`${this.organisation}/${r.project.name}/${r.name}`,provider:"azdevops",visibility:"private",defaultBranch:(r.defaultBranch??"refs/heads/main").replace(/^refs\/heads\//,""),isArchived:!!r.isDisabled,isFork:!!r.isFork,size:r.size,url:r.webUrl,createdAt:"",updatedAt:""}}mapPr(p){let state=p.status==="completed"?"merged":p.status==="abandoned"?"closed":"open",reviewers=(p.reviewers??[]).map((r)=>r.displayName??r.uniqueName??"").filter(Boolean),decision=(()=>{let votes=(p.reviewers??[]).map((r)=>r.vote??0);if(votes.some((v)=>v<0))return"CHANGES_REQUESTED";if(votes.length&&votes.every((v)=>v>=10))return"APPROVED";return})();return{id:String(p.pullRequestId),number:p.pullRequestId,title:p.title,state,isDraft:!!p.isDraft,author:p.createdBy?.displayName??p.createdBy?.uniqueName??"unknown",reviewers,reviewDecision:decision,createdAt:p.creationDate,updatedAt:p.creationDate,mergedAt:p.closedDate,durationOpenSeconds:p.closedDate?Math.floor((Date.parse(p.closedDate)-Date.parse(p.creationDate))/1000):void 0,url:p._links?.web?.href??"",labels:[]}}mapRun(r){return{id:String(r.id),pipelineName:r.pipeline?.name??r.name??"pipeline",branch:(r.resources?.repositories?.self?.refName??"").replace(/^refs\/heads\//,""),status:r.state==="inProgress"?"running":r.state==="notStarted"?"queued":"completed",conclusion:r.result==="succeeded"?"success":r.result==="failed"?"failure":r.result==="canceled"?"cancelled":void 0,startedAt:r.createdDate,completedAt:r.finishedDate,durationSeconds:r.finishedDate?Math.max(0,Math.floor((Date.parse(r.finishedDate)-Date.parse(r.createdDate))/1000)):void 0,url:r._links?.web?.href??"",actor:"unknown",commitSha:r.resources?.repositories?.self?.version}}async saveCredentials(input){if(!input.meta?.organization)throw new GitOpsError("INVALID","meta.organization is required for Azure DevOps");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.organisation=input.meta.organization,this.apiHost=this.resolveApiHost(input.meta),this.cache.clear()}async validateCredentials(){if(!this.token){let s=await this.loadAuth();if(!s)return{ok:!1,message:"No PAT stored"};this.token=s.token,this.organisation=s.meta?.organization??null,this.apiHost=this.resolveApiHost(s.meta)}try{return{ok:!0,account:(await this.rest("/_apis/connectionData?api-version=7.1")).authenticatedUser?.providerDisplayName??this.organisation??""}}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.organisation=null,this.cache.clear()}async healthCheck(){try{return await this.rest("/_apis/connectionData?api-version=7.1"),{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));if(opts.org)return{items:(await this.rest("/_apis/git/repositories?api-version=7.1",opts.org)).value.slice(0,limit).map((r)=>this.mapRepo(r))};let projects=await this.rest("/_apis/projects?api-version=7.1"),items=[];for(let proj of projects.value.slice(0,5))try{let data=await this.rest("/_apis/git/repositories?api-version=7.1",proj.name);for(let r of data.value)if(items.push(this.mapRepo(r)),items.length>=limit)break;if(items.length>=limit)break}catch{}return{items}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let{project,repo}=this.parseFqn(fqn),r=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}?api-version=7.1`,project);return this.mapRepo(r)})}async listBranches(fqn){let{project,repo}=this.parseFqn(fqn);return(await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/refs?filter=heads/&api-version=7.1`,project)).value.map((b)=>({name:b.name.replace(/^refs\/heads\//,""),isProtected:!!b.isLocked,lastCommitSha:b.objectId}))}async listLanguages(_fqn){return{}}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),{project,repo}=this.parseFqn(fqn);try{let data=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/commits?$top=${limit}&api-version=7.1`,project),counts=new Map;for(let c of data.value){let login=c.author?.name;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{project,repo}=this.parseFqn(fqn),status={open:"active",closed:"completed",all:"all"}[opts?.state??"open"]??"active",limit=Math.max(1,Math.min(opts?.limit??30,100));return(await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests?searchCriteria.status=${status}&$top=${limit}&api-version=7.1`,project)).value.map((p)=>this.mapPr(p))}async getPullRequest(fqn,id){let{project,repo}=this.parseFqn(fqn),p=await this.rest(`/_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${id}?api-version=7.1`,project);return this.mapPr(p)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:100}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:100})).filter((p)=>p.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.filter((p)=>!p.reviewDecision),awaitingApproval:open.filter((p)=>p.reviewDecision==="REVIEW_REQUIRED")}}async listIssues(_fqn){return[]}async labelStats(_fqn){return{}}async listPipelines(fqn){let{project}=this.parseFqn(fqn);return(await this.rest("/_apis/pipelines?api-version=7.1",project)).value.map((p)=>({id:String(p.id),name:p.name,state:"active",path:p.folder}))}async listRecentRuns(fqn,opts){let{project}=this.parseFqn(fqn),limit=Math.max(1,Math.min(opts?.limit??30,100)),pipelines=await this.listPipelines(fqn),runs=[];for(let pipe of pipelines.slice(0,5))try{let data=await this.rest(`/_apis/pipelines/${pipe.id}/runs?$top=${limit}&api-version=7.1`,project);for(let r of data.value){let mapped=this.mapRun({...r,pipeline:{id:Number(pipe.id),name:pipe.name}});if(opts?.branch&&mapped.branch!==opts.branch)continue;if(runs.push(mapped),runs.length>=limit)break}if(runs.length>=limit)break}catch{}return runs.slice(0,limit)}async getRun(fqn,runId){let{project}=this.parseFqn(fqn),data=await this.rest(`/_apis/build/builds/${encodeURIComponent(runId)}?api-version=7.1`,project);return this.mapRun(data)}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:[],totalRunsLast30Days:runs.length}}async listEnvironments(_fqn){return[]}async listSecurityAlerts(_fqn,_opts){return[]}async orgRollup(_org){let items=(await this.listRepos({limit:100})).items;return{totalRepos:items.length,byVisibility:{public:0,private:items.length,internal:0},byLanguage:{},archived:items.filter((r)=>r.isArchived).length,stale30d:0,totalOpenPRs:0,totalOpenIssues:0}}async listWebhooks(_fqn){return[]}}var PLUGIN_NAME="gitops-azdevops",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="azdevops",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 AzureDevOpsProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("Azure DevOps gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"Azure DevOps provider for the GitOps meta plugin (REST 7.1). meta.organization required in auth.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{AzureDevOpsProvider,createPlugin,src_default as default};
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@vibecontrols/vibe-plugin-gitops-azdevops",
3
- "version": "2026.731.1",
3
+ "version": "2026.1108.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "description": "Azure DevOps provider for VibeControls GitOps meta plugin (REST 7.1)",
7
7
  "engines": {
8
- "bun": ">=1.3.14"
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 --emitDeclarationOnly --declaration --outDir ./dist",
17
- "dev": "tsc --watch",
18
- "lint": "eslint ./src",
19
- "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
20
- "format": "bunx prettier . --write",
21
- "format:check": "bunx prettier . --check",
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": "bash scripts/build-limits.sh --gate bash -c 'bun run format:check && bun run lint:sanity && bun run type:check && bun run build'"
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
- "@eslint/js": "^10.0.1",
35
- "@types/node": "^22.0.0",
36
- "bun-types": "^1.3.9",
37
- "eslint": "^9.30.1",
38
- "globals": "^17.3.0",
39
- "prettier": "^3.6.2",
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/algoshred/vibe-plugin-gitops-azdevops.git"
64
+ "url": "git+https://github.com/Algoshred/vibe-plugin-gitops-azdevops.git"
67
65
  },
68
66
  "bugs": {
69
- "url": "https://github.com/algoshred/vibe-plugin-gitops-azdevops/issues"
67
+ "url": "https://github.com/Algoshred/vibe-plugin-gitops-azdevops/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
  }