@vibecontrols/vibe-plugin-gitops-gitlab 2026.601.2 → 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-gitlab",KEY_PAT="pat:default",DEFAULT_HOST="https://gitlab.com";class GitLabProvider{name="gitlab";host;log;token=null;apiBase=`${DEFAULT_HOST}/api/v4`;cache=new Map;rateLimit=null;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-gitlab")}async init(){let stored=await this.loadAuth();if(stored)this.token=stored.token,this.apiBase=`${stored.meta?.host??DEFAULT_HOST}/api/v4`,this.log.info("Loaded persisted GitLab 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}}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-gitlab/0.1",Accept:"application/json"});if(this.token)h.set("PRIVATE-TOKEN",this.token);return h}mapStatus(status,body){if(status===401)return new GitOpsError("AUTH","GitLab: unauthorized");if(status===403)return new GitOpsError("FORBIDDEN","GitLab: forbidden");if(status===404)return new GitOpsError("NOT_FOUND","GitLab: not found");if(status===429)return new GitOpsError("RATE_LIMITED","GitLab: rate-limited");return new GitOpsError("UPSTREAM","GitLab: "+status+" "+body.slice(0,200))}async rest(path,init){let url=path.startsWith("http")?path:`${this.apiBase}${path}`,res=await fetch(url,{method:init?.method??"GET",headers:this.headers(),body:init?.body!==void 0?JSON.stringify(init.body):void 0}),remaining=res.headers.get("ratelimit-remaining"),reset=res.headers.get("ratelimit-reset");if(remaining&&reset)this.rateLimit={remaining:parseInt(remaining,10),resetAt:new Date(parseInt(reset,10)*1000).toISOString()};if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapStatus(res.status,text)}return{data:await res.json(),headers:res.headers}}cached(key,ttlMs,fn){let entry=this.cache.get(key);if(entry&&Date.now()-entry.ts<entry.ttlMs)return Promise.resolve(entry.value);return fn().then((value)=>{return this.cache.set(key,{ts:Date.now(),ttlMs,value}),value})}projectId(fqn){return encodeURIComponent(fqn)}mapRepo(p){return{fqn:p.path_with_namespace,provider:"gitlab",visibility:p.visibility==="internal"?"internal":p.visibility==="private"?"private":"public",defaultBranch:p.default_branch??"main",description:p.description??void 0,topics:p.topics??[],isArchived:p.archived,isFork:!1,stars:p.star_count,forks:p.forks_count,url:p.web_url,createdAt:p.created_at,updatedAt:p.last_activity_at??p.created_at,pushedAt:p.last_activity_at}}mapMr(m){let merged=m.state==="merged";return{id:String(m.id),number:m.iid,title:m.title,state:merged?"merged":m.state,isDraft:!!m.draft,author:m.author?.username??"unknown",reviewers:(m.reviewers??[]).map((r)=>r.username),createdAt:m.created_at,updatedAt:m.updated_at,mergedAt:m.merged_at??void 0,durationOpenSeconds:m.merged_at?Math.floor((Date.parse(m.merged_at)-Date.parse(m.created_at))/1000):void 0,url:m.web_url,labels:m.labels??[]}}mapPipeline(p){return{id:String(p.id),pipelineName:p.source??"pipeline",branch:p.ref,status:p.status==="running"?"running":p.status==="pending"||p.status==="created"?"queued":p.status==="manual"?"waiting":"completed",conclusion:p.status==="success"?"success":p.status==="failed"?"failure":p.status==="canceled"?"cancelled":p.status==="skipped"?"skipped":void 0,startedAt:p.started_at,completedAt:p.finished_at,durationSeconds:p.duration,url:p.web_url,actor:p.user?.username??"unknown",commitSha:p.sha}}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=`${input.meta?.host??DEFAULT_HOST}/api/v4`,this.cache.clear()}async validateCredentials(){if(!this.token){let stored=await this.loadAuth();if(!stored)return{ok:!1,message:"No PAT stored"};this.token=stored.token,this.apiBase=`${stored.meta?.host??DEFAULT_HOST}/api/v4`}try{let{data}=await this.rest("/user");return{ok:!0,account:data.username,scopes:["api"]}}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("/version"),{ok:!0,rateLimit:this.rateLimit??void 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?`/groups/${encodeURIComponent(opts.org)}/projects?per_page=${limit}&order_by=last_activity_at`:`/projects?membership=true&per_page=${limit}&order_by=last_activity_at`),{data,headers}=await this.rest(path),items=data.map((p)=>this.mapRepo(p)),nextPage=headers.get("x-next-page"),baseUrl=path.startsWith("http")?path:`${this.apiBase}${path}`,nextCursor;if(nextPage){let u=new URL(baseUrl);u.searchParams.set("page",nextPage),nextCursor=u.toString()}return{items,nextCursor}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let{data}=await this.rest(`/projects/${this.projectId(fqn)}`);return this.mapRepo(data)})}async listBranches(fqn){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/repository/branches?per_page=100`);return data.map((b)=>({name:b.name,isProtected:b.protected,lastCommitSha:b.commit.id}))}async listLanguages(fqn){return this.cached(`langs:${fqn}`,300000,async()=>{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/languages`);return data})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/repository/contributors?per_page=${limit}`);return data.map((c)=>({login:c.name,contributions:c.commits}))}async listPullRequests(fqn,opts){let state={open:"opened",closed:"closed",all:"all"}[opts?.state??"open"]??"opened",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/merge_requests?state=${state}&per_page=${limit}&order_by=updated_at`);return data.map((m)=>this.mapMr(m))}async getPullRequest(fqn,id){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/merge_requests/${id}`);return this.mapMr(data)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:100}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:100})).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){let state=opts?.state==="closed"?"closed":"opened",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/issues?state=${state}&per_page=${limit}`);return data.map((i)=>({id:String(i.id),number:i.iid,title:i.title,state:i.state,labels:i.labels,url:i.web_url,createdAt:i.created_at}))}async labelStats(fqn){let issues=await this.listIssues(fqn,{state:"open",limit:100}),out={};for(let i of issues)for(let l of i.labels)out[l]=(out[l]??0)+1;return out}async listPipelines(fqn){let runs=await this.listRecentRuns(fqn,{limit:50});return[...new Set(runs.map((r)=>r.pipelineName))].map((s)=>({id:s,name:s,state:"active"}))}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&ref=${encodeURIComponent(opts.branch)}`:"",{data}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines?per_page=${limit}${branchQs}`);return data.map((p)=>this.mapPipeline(p))}async getRun(fqn,runId){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines/${runId}`),run=this.mapPipeline(data);try{let{data:jobs}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines/${runId}/jobs`);run.jobs=jobs.map((j)=>({id:String(j.id),name:j.name,status:j.status,conclusion:j.status==="success"?"success":j.status==="failed"?"failure":void 0,startedAt:j.started_at,completedAt:j.finished_at,durationSeconds:j.duration}))}catch{}return run}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{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/environments`);return data.map((e)=>({name:e.name,state:e.state,lastDeployedAt:e.updated_at,url:e.external_url}))}catch{return[]}}async listSecurityAlerts(fqn,_opts){try{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/vulnerability_findings?per_page=50`);return data.map((v)=>({id:String(v.id),type:v.report_type==="secret_detection"?"secret-scan":v.report_type==="sast"?"code-scan":v.report_type==="dependency_scanning"?"dependency":"advisory",severity:(v.severity??"info").toLowerCase(),state:v.state==="resolved"?"fixed":v.state==="dismissed"?"dismissed":"open",title:v.name,url:v.links?.[0]?.url??"",cve:v.identifiers?.find((i)=>i.type==="cve")?.external_id,createdAt:v.created_at}))}catch{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={},archived=0,stale30d=0,now=Date.now();for(let r of items){if(byVis[r.visibility]+=1,r.isArchived)archived++;let pushed=Date.parse(r.pushedAt??r.updatedAt);if(now-pushed>2592000000)stale30d++}return{totalRepos:items.length,byVisibility:byVis,byLanguage:byLang,archived,stale30d,totalOpenPRs:0,totalOpenIssues:0}})}async listWebhooks(fqn){try{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/hooks`);return data.map((h)=>({id:String(h.id),url:h.url,events:h.push_events?["push"]:[],active:!0}))}catch{return[]}}}var PLUGIN_NAME="gitops-gitlab",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="gitlab",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 GitLabProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("GitLab gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"GitLab provider for the GitOps meta plugin. Supports gitlab.com + self-hosted via REST v4.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{src_default as default,createPlugin,GitLabProvider};
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-gitlab",KEY_PAT="pat:default",DEFAULT_HOST="https://gitlab.com";class GitLabProvider{name="gitlab";host;log;token=null;apiBase=`${DEFAULT_HOST}/api/v4`;cache=new Map;rateLimit=null;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-gitlab")}async init(){let stored=await this.loadAuth();if(stored)this.token=stored.token,this.apiBase=`${stored.meta?.host??DEFAULT_HOST}/api/v4`,this.log.info("Loaded persisted GitLab 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}}headers(){let h=new Headers({"User-Agent":"vibecontrols-gitops-gitlab/0.1",Accept:"application/json"});if(this.token)h.set("PRIVATE-TOKEN",this.token);return h}mapStatus(status,body){if(status===401)return new GitOpsError("AUTH","GitLab: unauthorized");if(status===403)return new GitOpsError("FORBIDDEN","GitLab: forbidden");if(status===404)return new GitOpsError("NOT_FOUND","GitLab: not found");if(status===429)return new GitOpsError("RATE_LIMITED","GitLab: rate-limited");return new GitOpsError("UPSTREAM","GitLab: "+status+" "+body.slice(0,200))}async rest(path,init){let url=path.startsWith("http")?path:`${this.apiBase}${path}`,res=await fetch(url,{method:init?.method??"GET",headers:this.headers(),body:init?.body!==void 0?JSON.stringify(init.body):void 0}),remaining=res.headers.get("ratelimit-remaining"),reset=res.headers.get("ratelimit-reset");if(remaining&&reset)this.rateLimit={remaining:parseInt(remaining,10),resetAt:new Date(parseInt(reset,10)*1000).toISOString()};if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapStatus(res.status,text)}return{data:await res.json(),headers:res.headers}}cached(key,ttlMs,fn){let entry=this.cache.get(key);if(entry&&Date.now()-entry.ts<entry.ttlMs)return Promise.resolve(entry.value);return fn().then((value)=>(this.cache.set(key,{ts:Date.now(),ttlMs,value}),value))}projectId(fqn){return encodeURIComponent(fqn)}mapRepo(p){return{fqn:p.path_with_namespace,provider:"gitlab",visibility:p.visibility==="internal"?"internal":p.visibility==="private"?"private":"public",defaultBranch:p.default_branch??"main",description:p.description??void 0,topics:p.topics??[],isArchived:p.archived,isFork:!1,stars:p.star_count,forks:p.forks_count,url:p.web_url,createdAt:p.created_at,updatedAt:p.last_activity_at??p.created_at,pushedAt:p.last_activity_at}}mapMr(m){let merged=m.state==="merged";return{id:String(m.id),number:m.iid,title:m.title,state:merged?"merged":m.state,isDraft:!!m.draft,author:m.author?.username??"unknown",reviewers:(m.reviewers??[]).map((r)=>r.username),createdAt:m.created_at,updatedAt:m.updated_at,mergedAt:m.merged_at??void 0,durationOpenSeconds:m.merged_at?Math.floor((Date.parse(m.merged_at)-Date.parse(m.created_at))/1000):void 0,url:m.web_url,labels:m.labels??[]}}mapPipeline(p){return{id:String(p.id),pipelineName:p.source??"pipeline",branch:p.ref,status:p.status==="running"?"running":p.status==="pending"||p.status==="created"?"queued":p.status==="manual"?"waiting":"completed",conclusion:p.status==="success"?"success":p.status==="failed"?"failure":p.status==="canceled"?"cancelled":p.status==="skipped"?"skipped":void 0,startedAt:p.started_at,completedAt:p.finished_at,durationSeconds:p.duration,url:p.web_url,actor:p.user?.username??"unknown",commitSha:p.sha}}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=`${input.meta?.host??DEFAULT_HOST}/api/v4`,this.cache.clear()}async validateCredentials(){if(!this.token){let stored=await this.loadAuth();if(!stored)return{ok:!1,message:"No PAT stored"};this.token=stored.token,this.apiBase=`${stored.meta?.host??DEFAULT_HOST}/api/v4`}try{let{data}=await this.rest("/user");return{ok:!0,account:data.username,scopes:["api"]}}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("/version"),{ok:!0,rateLimit:this.rateLimit??void 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?`/groups/${encodeURIComponent(opts.org)}/projects?per_page=${limit}&order_by=last_activity_at`:`/projects?membership=true&per_page=${limit}&order_by=last_activity_at`),{data,headers}=await this.rest(path),items=data.map((p)=>this.mapRepo(p)),nextPage=headers.get("x-next-page"),baseUrl=path.startsWith("http")?path:`${this.apiBase}${path}`,nextCursor;if(nextPage){let u=new URL(baseUrl);u.searchParams.set("page",nextPage),nextCursor=u.toString()}return{items,nextCursor}}async getRepo(fqn){return this.cached(`repo:${fqn}`,60000,async()=>{let{data}=await this.rest(`/projects/${this.projectId(fqn)}`);return this.mapRepo(data)})}async listBranches(fqn){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/repository/branches?per_page=100`);return data.map((b)=>({name:b.name,isProtected:b.protected,lastCommitSha:b.commit.id}))}async listLanguages(fqn){return this.cached(`langs:${fqn}`,300000,async()=>{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/languages`);return data})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/repository/contributors?per_page=${limit}`);return data.map((c)=>({login:c.name,contributions:c.commits}))}async listPullRequests(fqn,opts){let state={open:"opened",closed:"closed",all:"all"}[opts?.state??"open"]??"opened",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/merge_requests?state=${state}&per_page=${limit}&order_by=updated_at`);return data.map((m)=>this.mapMr(m))}async getPullRequest(fqn,id){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/merge_requests/${id}`);return this.mapMr(data)}async pullRequestAnalytics(fqn){let open=await this.listPullRequests(fqn,{state:"open",limit:100}),sorted=[...(await this.listPullRequests(fqn,{state:"closed",limit:100})).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){let state=opts?.state==="closed"?"closed":"opened",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.rest(`/projects/${this.projectId(fqn)}/issues?state=${state}&per_page=${limit}`);return data.map((i)=>({id:String(i.id),number:i.iid,title:i.title,state:i.state,labels:i.labels,url:i.web_url,createdAt:i.created_at}))}async labelStats(fqn){let issues=await this.listIssues(fqn,{state:"open",limit:100}),out={};for(let i of issues)for(let l of i.labels)out[l]=(out[l]??0)+1;return out}async listPipelines(fqn){let runs=await this.listRecentRuns(fqn,{limit:50});return[...new Set(runs.map((r)=>r.pipelineName))].map((s)=>({id:s,name:s,state:"active"}))}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&ref=${encodeURIComponent(opts.branch)}`:"",{data}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines?per_page=${limit}${branchQs}`);return data.map((p)=>this.mapPipeline(p))}async getRun(fqn,runId){let{data}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines/${runId}`),run=this.mapPipeline(data);try{let{data:jobs}=await this.rest(`/projects/${this.projectId(fqn)}/pipelines/${runId}/jobs`);run.jobs=jobs.map((j)=>({id:String(j.id),name:j.name,status:j.status,conclusion:j.status==="success"?"success":j.status==="failed"?"failure":void 0,startedAt:j.started_at,completedAt:j.finished_at,durationSeconds:j.duration}))}catch{}return run}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{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/environments`);return data.map((e)=>({name:e.name,state:e.state,lastDeployedAt:e.updated_at,url:e.external_url}))}catch{return[]}}async listSecurityAlerts(fqn,_opts){try{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/vulnerability_findings?per_page=50`);return data.map((v)=>({id:String(v.id),type:v.report_type==="secret_detection"?"secret-scan":v.report_type==="sast"?"code-scan":v.report_type==="dependency_scanning"?"dependency":"advisory",severity:(v.severity??"info").toLowerCase(),state:v.state==="resolved"?"fixed":v.state==="dismissed"?"dismissed":"open",title:v.name,url:v.links?.[0]?.url??"",cve:v.identifiers?.find((i)=>i.type==="cve")?.external_id,createdAt:v.created_at}))}catch{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={},archived=0,stale30d=0,now=Date.now();for(let r of items){if(byVis[r.visibility]+=1,r.isArchived)archived++;let pushed=Date.parse(r.pushedAt??r.updatedAt);if(now-pushed>2592000000)stale30d++}return{totalRepos:items.length,byVisibility:byVis,byLanguage:byLang,archived,stale30d,totalOpenPRs:0,totalOpenIssues:0}})}async listWebhooks(fqn){try{let{data}=await this.rest(`/projects/${this.projectId(fqn)}/hooks`);return data.map((h)=>({id:String(h.id),url:h.url,events:h.push_events?["push"]:[],active:!0}))}catch{return[]}}}var PLUGIN_NAME="gitops-gitlab",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="gitlab",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 GitLabProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("GitLab gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"GitLab provider for the GitOps meta plugin. Supports gitlab.com + self-hosted via REST v4.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{GitLabProvider,createPlugin,src_default as default};
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@vibecontrols/vibe-plugin-gitops-gitlab",
3
- "version": "2026.601.2",
3
+ "version": "2026.1108.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "description": "GitLab provider for VibeControls GitOps meta plugin (REST v4)",
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": "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": "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-gitlab.git"
64
+ "url": "git+https://github.com/Algoshred/vibe-plugin-gitops-gitlab.git"
67
65
  },
68
66
  "bugs": {
69
- "url": "https://github.com/algoshred/vibe-plugin-gitops-gitlab/issues"
67
+ "url": "https://github.com/Algoshred/vibe-plugin-gitops-gitlab/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
  }