@vibecontrols/vibe-plugin-gitops-github 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 DEFAULT_BASE_REST="https://api.github.com",DEFAULT_BASE_GQL="https://api.github.com/graphql";class GitHubClient{token=null;rateLimit=null;baseRest;baseGql;userAgent;constructor(opts={}){this.baseRest=opts.baseUrlRest??DEFAULT_BASE_REST,this.baseGql=opts.baseUrlGraphql??DEFAULT_BASE_GQL,this.userAgent=opts.userAgent??"vibecontrols-gitops-github/0.1 (+https://vibecontrols.com)"}setToken(token){this.token=token}hasToken(){return!!this.token}getRateLimit(){return this.rateLimit}updateRateLimit(headers){let remaining=headers.get("x-ratelimit-remaining"),reset=headers.get("x-ratelimit-reset");if(remaining&&reset)this.rateLimit={remaining:parseInt(remaining,10),resetAt:new Date(parseInt(reset,10)*1000).toISOString()}}mapHttpError(status,body){if(status===401)return new GitOpsError("AUTH","GitHub: unauthorized",{upstreamStatus:status});if(status===403){if(/rate limit/i.test(body)||/api rate limit exceeded/i.test(body))return new GitOpsError("RATE_LIMITED","GitHub: rate-limited",{upstreamStatus:status});return new GitOpsError("FORBIDDEN","GitHub: forbidden",{upstreamStatus:status})}if(status===404)return new GitOpsError("NOT_FOUND","GitHub: not found",{upstreamStatus:status});if(status===429)return new GitOpsError("RATE_LIMITED","GitHub: too many requests",{upstreamStatus:status});if(status>=500)return new GitOpsError("UPSTREAM","GitHub: "+status,{upstreamStatus:status});return new GitOpsError("UPSTREAM","GitHub: "+status+" "+body.slice(0,200),{upstreamStatus:status})}authHeaders(extra){let h=new Headers({"User-Agent":this.userAgent,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28",...extra??{}});if(this.token)h.set("Authorization","Bearer "+this.token);return h}async rest(path,init){let url=path.startsWith("http")?path:this.baseRest+path,method=init?.method??"GET";for(let attempt=0;attempt<2;attempt++){let res=await fetch(url,{method,headers:this.authHeaders(init?.headers),body:init?.body!==void 0?JSON.stringify(init.body):void 0});if(this.updateRateLimit(res.headers),res.status===429||res.status===403&&res.headers.get("retry-after")){if(attempt===0){let retryAfter=parseInt(res.headers.get("retry-after")??"1",10);await new Promise((r)=>setTimeout(r,Math.min(retryAfter,5)*1000));continue}}if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapHttpError(res.status,text)}return{data:(res.headers.get("content-type")??"").includes("json")?await res.json():await res.text(),headers:res.headers}}throw new GitOpsError("RATE_LIMITED","GitHub: exhausted retries")}async graphql(query,variables={}){let res=await fetch(this.baseGql,{method:"POST",headers:this.authHeaders({"Content-Type":"application/json"}),body:JSON.stringify({query,variables})});if(this.updateRateLimit(res.headers),!res.ok){let text=await res.text().catch(()=>"");throw this.mapHttpError(res.status,text)}let json=await res.json();if(json.errors&&json.errors.length>0)throw new GitOpsError("UPSTREAM","GitHub GraphQL: "+json.errors.map((e)=>e.message).join("; "));if(!json.data)throw new GitOpsError("UPSTREAM","GitHub GraphQL: empty data");return json.data}parseNextLink(headers){let link=headers.get("link");if(!link)return null;return/<([^>]+)>;\s*rel="next"/.exec(link)?.[1]??null}}function mapRepo(r){let visibility=r.visibility??(r.private?"private":"public");return{fqn:r.full_name,provider:"github",visibility:visibility==="private"?"private":visibility==="internal"?"internal":"public",defaultBranch:r.default_branch,description:r.description??void 0,language:r.language??void 0,topics:r.topics??[],isArchived:!!r.archived,isFork:!!r.fork,size:r.size,stars:r.stargazers_count,forks:r.forks_count,watchers:r.watchers_count,url:r.html_url,createdAt:r.created_at,updatedAt:r.updated_at,pushedAt:r.pushed_at??void 0}}function mapPullRequest(p){let merged=!!p.merged_at;return{id:String(p.id),number:p.number,title:p.title,state:merged?"merged":p.state,isDraft:!!p.draft,author:p.user?.login??"unknown",reviewers:(p.requested_reviewers??[]).map((r)=>r.login),mergeState:mapMergeState(p.mergeable_state),createdAt:p.created_at,updatedAt:p.updated_at,mergedAt:p.merged_at??void 0,durationOpenSeconds:computeDuration(p.created_at,p.merged_at??p.updated_at),url:p.html_url,labels:(p.labels??[]).map((l)=>l.name)}}function mapMergeState(s){if(!s)return;switch(s){case"clean":case"dirty":case"blocked":case"unstable":case"behind":return s;default:return}}function computeDuration(start,end){if(!end)return;let dt=Date.parse(end)-Date.parse(start);return dt>0?Math.floor(dt/1000):void 0}function mapPipeline(w){return{id:String(w.id),name:w.name,state:w.state==="active"?"active":"disabled",path:w.path}}function mapRun(r){let startedAt=r.run_started_at,completedAt=r.status==="completed"?r.updated_at??void 0:void 0,duration=startedAt&&completedAt?Math.max(0,Math.floor((Date.parse(completedAt)-Date.parse(startedAt))/1000)):void 0;return{id:String(r.id),pipelineName:r.name??r.display_title??"ci",branch:r.head_branch??"unknown",status:mapRunStatus(r.status),conclusion:mapConclusion(r.conclusion),event:r.event,startedAt,completedAt,durationSeconds:duration,url:r.html_url,actor:r.actor?.login??"unknown",commitSha:r.head_sha,commitMessage:r.head_commit?.message}}function mapRunStatus(s){if(s==="queued")return"queued";if(s==="in_progress"||s==="running")return"running";if(s==="waiting")return"waiting";return"completed"}function mapConclusion(c){if(!c)return;switch(c){case"success":case"failure":case"cancelled":case"skipped":case"timed_out":case"neutral":return c;default:return}}function mapJob(j){return{id:String(j.id),name:j.name,status:j.status,conclusion:j.conclusion??void 0,startedAt:j.started_at,completedAt:j.completed_at,durationSeconds:j.started_at&&j.completed_at?Math.max(0,Math.floor((Date.parse(j.completed_at)-Date.parse(j.started_at))/1000)):void 0}}function mapBranch(b){return{name:b.name,isProtected:b.protected,lastCommitSha:b.commit.sha}}function mapContributor(c){return{login:c.login,contributions:c.contributions,avatarUrl:c.avatar_url}}function mapIssue(i){if(i.pull_request)return null;return{id:String(i.id),number:i.number,title:i.title,state:i.state,labels:(i.labels??[]).map((l)=>typeof l==="string"?l:l.name),url:i.html_url,createdAt:i.created_at}}function mapDependabotAlert(a){let sev=(a.security_advisory?.severity??"info").toLowerCase();return{id:String(a.number),type:"dependency",severity:sev,state:a.fixed_at?"fixed":a.dismissed_at?"dismissed":"open",title:a.security_advisory?.summary??`${a.security_vulnerability?.package?.name??"(unknown)"} vulnerability`,url:a.html_url,ecosystem:a.security_vulnerability?.package?.ecosystem,cve:a.security_advisory?.cve_id??void 0,createdAt:a.created_at}}function mapCodeScanAlert(a){let sev=(a.rule?.severity??"info").toLowerCase();return{id:String(a.number),type:"code-scan",severity:sev,state:a.state==="fixed"||a.state==="closed"?"fixed":a.state==="dismissed"?"dismissed":"open",title:a.rule?.description??a.rule?.id??"Code scanning alert",url:a.html_url,ruleId:a.rule?.id,createdAt:a.created_at}}function mapSecretScanAlert(a){return{id:String(a.number),type:"secret-scan",severity:"high",state:a.state==="resolved"?"fixed":"open",title:a.secret_type_display_name??a.secret_type??"Secret detected",url:a.html_url,createdAt:a.created_at}}function mapWebhook(h){return{id:String(h.id),url:h.config?.url??"",events:h.events??[],active:!!h.active}}var STORAGE_NS="gitops-github",KEY_PAT="pat:default";class GitHubProvider{name="github";host;log;client;cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-github"),this.client=new GitHubClient}async init(){let stored=await this.loadAuth();if(stored)this.client.setToken(stored.token),this.log.info("Loaded persisted GitHub 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}}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.client.setToken(input.token),this.cache.clear()}async validateCredentials(){if(!this.client.hasToken()){let stored=await this.loadAuth();if(!stored)return{ok:!1,message:"No PAT stored"};this.client.setToken(stored.token)}try{let{data,headers}=await this.client.rest("/user"),scopes=(headers.get("x-oauth-scopes")??"").split(",").map((s)=>s.trim()).filter(Boolean);return{ok:!0,account:data.login,scopes}}catch(err){return{ok:!1,message:(err instanceof GitOpsError?err:null)?.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.client.setToken(null),this.cache.clear()}async healthCheck(){if(!this.client.hasToken()){let stored=await this.loadAuth();if(stored)this.client.setToken(stored.token)}try{let{data}=await this.client.rest("/rate_limit");return{ok:!0,rateLimit:{remaining:data.resources.core.remaining,resetAt:new Date(data.resources.core.reset*1000).toISOString()}}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}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})}async listRepos(opts){let limit=Math.max(1,Math.min(opts.limit??30,100)),path=opts.cursor??(opts.org?`/orgs/${encodeURIComponent(opts.org)}/repos?per_page=${limit}&sort=pushed`:`/user/repos?per_page=${limit}&sort=pushed&affiliation=owner,collaborator,organization_member`),{data,headers}=await this.client.rest(path);return{items:data.map(mapRepo),nextCursor:this.client.parseNextLink(headers)??void 0}}async getRepo(fqn){let cacheKey=`repo:${fqn}`;return this.cached(cacheKey,60000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}`);return mapRepo(data)})}async listBranches(fqn){let{data}=await this.client.rest(`/repos/${fqn}/branches?per_page=100`);return data.map(mapBranch)}async listLanguages(fqn){let cacheKey=`langs:${fqn}`;return this.cached(cacheKey,300000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/languages`);return data})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),cacheKey=`contrib:${fqn}:${limit}`;return this.cached(cacheKey,300000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/contributors?per_page=${limit}`);return data.map(mapContributor)})}async listPullRequests(fqn,opts){let state=opts?.state??"open",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.client.rest(`/repos/${fqn}/pulls?state=${state}&per_page=${limit}&sort=updated&direction=desc`);return data.map(mapPullRequest)}async getPullRequest(fqn,id){let{data}=await this.client.rest(`/repos/${fqn}/pulls/${id}`);return mapPullRequest(data)}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((p)=>typeof p.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,opts){let state=opts?.state??"open",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.client.rest(`/repos/${fqn}/issues?state=${state}&per_page=${limit}`);return data.map(mapIssue).filter((i)=>!!i)}async labelStats(fqn){let open=await this.listIssues(fqn,{state:"open",limit:100}),out={};for(let i of open)for(let l of i.labels)out[l]=(out[l]??0)+1;return out}async listPipelines(fqn){let cacheKey=`workflows:${fqn}`;return this.cached(cacheKey,60000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/actions/workflows`);return data.workflows.map(mapPipeline)})}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&branch=${encodeURIComponent(opts.branch)}`:"",{data}=await this.client.rest(`/repos/${fqn}/actions/runs?per_page=${limit}${branchQs}`);return data.workflow_runs.map(mapRun)}async getRun(fqn,runId){let{data}=await this.client.rest(`/repos/${fqn}/actions/runs/${runId}`),run=mapRun(data);try{let{data:jobs}=await this.client.rest(`/repos/${fqn}/actions/runs/${runId}/jobs`);run.jobs=jobs.jobs.map(mapJob)}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.client.rest(`/repos/${fqn}/environments`);return(data.environments??[]).map((e)=>({name:e.name,state:"active",lastDeployedAt:e.updated_at,url:e.html_url}))}catch{return[]}}async listSecurityAlerts(fqn,opts){let kinds=opts?.kind?[opts.kind]:["dependency","code-scan","secret-scan"],out=[];for(let k of kinds)try{if(k==="dependency"){let{data}=await this.client.rest(`/repos/${fqn}/dependabot/alerts?state=open&per_page=50`);out.push(...data.map(mapDependabotAlert))}else if(k==="code-scan"){let{data}=await this.client.rest(`/repos/${fqn}/code-scanning/alerts?state=open&per_page=50`);out.push(...data.map(mapCodeScanAlert))}else if(k==="secret-scan"){let{data}=await this.client.rest(`/repos/${fqn}/secret-scanning/alerts?state=open&per_page=50`);out.push(...data.map(mapSecretScanAlert))}}catch(err){if(err instanceof GitOpsError&&err.code==="FORBIDDEN")continue}return out}async orgRollup(org){let cacheKey=`rollup:${org}`;return this.cached(cacheKey,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.language)byLang[r.language]=(byLang[r.language]??0)+1;if(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.client.rest(`/repos/${fqn}/hooks`);return data.map(mapWebhook)}catch{return[]}}}var PLUGIN_NAME="gitops-github",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="github",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 GitHubProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("GitHub gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"GitHub provider for the GitOps meta plugin. Repo / PR / Actions / Dependabot data over REST+GraphQL v4.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{src_default as default,createPlugin,GitHubProvider};
|
|
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 DEFAULT_BASE_REST="https://api.github.com",DEFAULT_BASE_GQL="https://api.github.com/graphql";class GitHubClient{token=null;rateLimit=null;baseRest;baseGql;userAgent;constructor(opts={}){this.baseRest=opts.baseUrlRest??DEFAULT_BASE_REST,this.baseGql=opts.baseUrlGraphql??DEFAULT_BASE_GQL,this.userAgent=opts.userAgent??"vibecontrols-gitops-github/0.1 (+https://vibecontrols.com)"}setToken(token){this.token=token}hasToken(){return!!this.token}getRateLimit(){return this.rateLimit}updateRateLimit(headers){let remaining=headers.get("x-ratelimit-remaining"),reset=headers.get("x-ratelimit-reset");if(remaining&&reset)this.rateLimit={remaining:parseInt(remaining,10),resetAt:new Date(parseInt(reset,10)*1000).toISOString()}}mapHttpError(status,body){if(status===401)return new GitOpsError("AUTH","GitHub: unauthorized",{upstreamStatus:status});if(status===403){if(/rate limit/i.test(body)||/api rate limit exceeded/i.test(body))return new GitOpsError("RATE_LIMITED","GitHub: rate-limited",{upstreamStatus:status});return new GitOpsError("FORBIDDEN","GitHub: forbidden",{upstreamStatus:status})}if(status===404)return new GitOpsError("NOT_FOUND","GitHub: not found",{upstreamStatus:status});if(status===429)return new GitOpsError("RATE_LIMITED","GitHub: too many requests",{upstreamStatus:status});if(status>=500)return new GitOpsError("UPSTREAM","GitHub: "+status,{upstreamStatus:status});return new GitOpsError("UPSTREAM","GitHub: "+status+" "+body.slice(0,200),{upstreamStatus:status})}authHeaders(extra){let h=new Headers({"User-Agent":this.userAgent,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28",...extra??{}});if(this.token)h.set("Authorization","Bearer "+this.token);return h}async rest(path,init){let url=path.startsWith("http")?path:this.baseRest+path,method=init?.method??"GET";for(let attempt=0;attempt<2;attempt++){let res=await fetch(url,{method,headers:this.authHeaders(init?.headers),body:init?.body!==void 0?JSON.stringify(init.body):void 0});if(this.updateRateLimit(res.headers),res.status===429||res.status===403&&res.headers.get("retry-after")){if(attempt===0){let retryAfter=parseInt(res.headers.get("retry-after")??"1",10);await new Promise((r)=>setTimeout(r,Math.min(retryAfter,5)*1000));continue}}if(!res.ok){let text=await res.text().catch(()=>"");throw this.mapHttpError(res.status,text)}return{data:(res.headers.get("content-type")??"").includes("json")?await res.json():await res.text(),headers:res.headers}}throw new GitOpsError("RATE_LIMITED","GitHub: exhausted retries")}async graphql(query,variables={}){let res=await fetch(this.baseGql,{method:"POST",headers:this.authHeaders({"Content-Type":"application/json"}),body:JSON.stringify({query,variables})});if(this.updateRateLimit(res.headers),!res.ok){let text=await res.text().catch(()=>"");throw this.mapHttpError(res.status,text)}let json=await res.json();if(json.errors&&json.errors.length>0)throw new GitOpsError("UPSTREAM","GitHub GraphQL: "+json.errors.map((e)=>e.message).join("; "));if(!json.data)throw new GitOpsError("UPSTREAM","GitHub GraphQL: empty data");return json.data}parseNextLink(headers){let link=headers.get("link");if(!link)return null;return/<([^>]+)>;\s*rel="next"/.exec(link)?.[1]??null}}function mapRepo(r){let visibility=r.visibility??(r.private?"private":"public");return{fqn:r.full_name,provider:"github",visibility:visibility==="private"?"private":visibility==="internal"?"internal":"public",defaultBranch:r.default_branch,description:r.description??void 0,language:r.language??void 0,topics:r.topics??[],isArchived:!!r.archived,isFork:!!r.fork,size:r.size,stars:r.stargazers_count,forks:r.forks_count,watchers:r.watchers_count,url:r.html_url,createdAt:r.created_at,updatedAt:r.updated_at,pushedAt:r.pushed_at??void 0}}function mapPullRequest(p){let merged=!!p.merged_at;return{id:String(p.id),number:p.number,title:p.title,state:merged?"merged":p.state,isDraft:!!p.draft,author:p.user?.login??"unknown",reviewers:(p.requested_reviewers??[]).map((r)=>r.login),mergeState:mapMergeState(p.mergeable_state),createdAt:p.created_at,updatedAt:p.updated_at,mergedAt:p.merged_at??void 0,durationOpenSeconds:computeDuration(p.created_at,p.merged_at??p.updated_at),url:p.html_url,labels:(p.labels??[]).map((l)=>l.name)}}function mapMergeState(s){if(!s)return;switch(s){case"clean":case"dirty":case"blocked":case"unstable":case"behind":return s;default:return}}function computeDuration(start,end){if(!end)return;let dt=Date.parse(end)-Date.parse(start);return dt>0?Math.floor(dt/1000):void 0}function mapPipeline(w){return{id:String(w.id),name:w.name,state:w.state==="active"?"active":"disabled",path:w.path}}function mapRun(r){let startedAt=r.run_started_at,completedAt=r.status==="completed"?r.updated_at??void 0:void 0,duration=startedAt&&completedAt?Math.max(0,Math.floor((Date.parse(completedAt)-Date.parse(startedAt))/1000)):void 0;return{id:String(r.id),pipelineName:r.name??r.display_title??"ci",branch:r.head_branch??"unknown",status:mapRunStatus(r.status),conclusion:mapConclusion(r.conclusion),event:r.event,startedAt,completedAt,durationSeconds:duration,url:r.html_url,actor:r.actor?.login??"unknown",commitSha:r.head_sha,commitMessage:r.head_commit?.message}}function mapRunStatus(s){if(s==="queued")return"queued";if(s==="in_progress"||s==="running")return"running";if(s==="waiting")return"waiting";return"completed"}function mapConclusion(c){if(!c)return;switch(c){case"success":case"failure":case"cancelled":case"skipped":case"timed_out":case"neutral":return c;default:return}}function mapJob(j){return{id:String(j.id),name:j.name,status:j.status,conclusion:j.conclusion??void 0,startedAt:j.started_at,completedAt:j.completed_at,durationSeconds:j.started_at&&j.completed_at?Math.max(0,Math.floor((Date.parse(j.completed_at)-Date.parse(j.started_at))/1000)):void 0}}function mapBranch(b){return{name:b.name,isProtected:b.protected,lastCommitSha:b.commit.sha}}function mapContributor(c){return{login:c.login,contributions:c.contributions,avatarUrl:c.avatar_url}}function mapIssue(i){if(i.pull_request)return null;return{id:String(i.id),number:i.number,title:i.title,state:i.state,labels:(i.labels??[]).map((l)=>typeof l==="string"?l:l.name),url:i.html_url,createdAt:i.created_at}}function mapDependabotAlert(a){let sev=(a.security_advisory?.severity??"info").toLowerCase();return{id:String(a.number),type:"dependency",severity:sev,state:a.fixed_at?"fixed":a.dismissed_at?"dismissed":"open",title:a.security_advisory?.summary??`${a.security_vulnerability?.package?.name??"(unknown)"} vulnerability`,url:a.html_url,ecosystem:a.security_vulnerability?.package?.ecosystem,cve:a.security_advisory?.cve_id??void 0,createdAt:a.created_at}}function mapCodeScanAlert(a){let sev=(a.rule?.severity??"info").toLowerCase();return{id:String(a.number),type:"code-scan",severity:sev,state:a.state==="fixed"||a.state==="closed"?"fixed":a.state==="dismissed"?"dismissed":"open",title:a.rule?.description??a.rule?.id??"Code scanning alert",url:a.html_url,ruleId:a.rule?.id,createdAt:a.created_at}}function mapSecretScanAlert(a){return{id:String(a.number),type:"secret-scan",severity:"high",state:a.state==="resolved"?"fixed":"open",title:a.secret_type_display_name??a.secret_type??"Secret detected",url:a.html_url,createdAt:a.created_at}}function mapWebhook(h){return{id:String(h.id),url:h.config?.url??"",events:h.events??[],active:!!h.active}}var STORAGE_NS="gitops-github",KEY_PAT="pat:default";class GitHubProvider{name="github";host;log;client;cache=new Map;constructor(host){this.host=host,this.log=new BoundLogger(host.logger,"gitops-github"),this.client=new GitHubClient}async init(){let stored=await this.loadAuth();if(stored)this.client.setToken(stored.token),this.log.info("Loaded persisted GitHub 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}}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.client.setToken(input.token),this.cache.clear()}async validateCredentials(){if(!this.client.hasToken()){let stored=await this.loadAuth();if(!stored)return{ok:!1,message:"No PAT stored"};this.client.setToken(stored.token)}try{let{data,headers}=await this.client.rest("/user"),scopes=(headers.get("x-oauth-scopes")??"").split(",").map((s)=>s.trim()).filter(Boolean);return{ok:!0,account:data.login,scopes}}catch(err){return{ok:!1,message:(err instanceof GitOpsError?err:null)?.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.client.setToken(null),this.cache.clear()}async healthCheck(){if(!this.client.hasToken()){let stored=await this.loadAuth();if(stored)this.client.setToken(stored.token)}try{let{data}=await this.client.rest("/rate_limit");return{ok:!0,rateLimit:{remaining:data.resources.core.remaining,resetAt:new Date(data.resources.core.reset*1000).toISOString()}}}catch(err){return{ok:!1,message:err instanceof Error?err.message:String(err)}}}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))}async listRepos(opts){let limit=Math.max(1,Math.min(opts.limit??30,100)),path=opts.cursor??(opts.org?`/orgs/${encodeURIComponent(opts.org)}/repos?per_page=${limit}&sort=pushed`:`/user/repos?per_page=${limit}&sort=pushed&affiliation=owner,collaborator,organization_member`),{data,headers}=await this.client.rest(path);return{items:data.map(mapRepo),nextCursor:this.client.parseNextLink(headers)??void 0}}async getRepo(fqn){let cacheKey=`repo:${fqn}`;return this.cached(cacheKey,60000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}`);return mapRepo(data)})}async listBranches(fqn){let{data}=await this.client.rest(`/repos/${fqn}/branches?per_page=100`);return data.map(mapBranch)}async listLanguages(fqn){let cacheKey=`langs:${fqn}`;return this.cached(cacheKey,300000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/languages`);return data})}async listContributors(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??20,100)),cacheKey=`contrib:${fqn}:${limit}`;return this.cached(cacheKey,300000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/contributors?per_page=${limit}`);return data.map(mapContributor)})}async listPullRequests(fqn,opts){let state=opts?.state??"open",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.client.rest(`/repos/${fqn}/pulls?state=${state}&per_page=${limit}&sort=updated&direction=desc`);return data.map(mapPullRequest)}async getPullRequest(fqn,id){let{data}=await this.client.rest(`/repos/${fqn}/pulls/${id}`);return mapPullRequest(data)}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((p)=>typeof p.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,opts){let state=opts?.state??"open",limit=Math.max(1,Math.min(opts?.limit??30,100)),{data}=await this.client.rest(`/repos/${fqn}/issues?state=${state}&per_page=${limit}`);return data.map(mapIssue).filter((i)=>!!i)}async labelStats(fqn){let open=await this.listIssues(fqn,{state:"open",limit:100}),out={};for(let i of open)for(let l of i.labels)out[l]=(out[l]??0)+1;return out}async listPipelines(fqn){let cacheKey=`workflows:${fqn}`;return this.cached(cacheKey,60000,async()=>{let{data}=await this.client.rest(`/repos/${fqn}/actions/workflows`);return data.workflows.map(mapPipeline)})}async listRecentRuns(fqn,opts){let limit=Math.max(1,Math.min(opts?.limit??30,100)),branchQs=opts?.branch?`&branch=${encodeURIComponent(opts.branch)}`:"",{data}=await this.client.rest(`/repos/${fqn}/actions/runs?per_page=${limit}${branchQs}`);return data.workflow_runs.map(mapRun)}async getRun(fqn,runId){let{data}=await this.client.rest(`/repos/${fqn}/actions/runs/${runId}`),run=mapRun(data);try{let{data:jobs}=await this.client.rest(`/repos/${fqn}/actions/runs/${runId}/jobs`);run.jobs=jobs.jobs.map(mapJob)}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.client.rest(`/repos/${fqn}/environments`);return(data.environments??[]).map((e)=>({name:e.name,state:"active",lastDeployedAt:e.updated_at,url:e.html_url}))}catch{return[]}}async listSecurityAlerts(fqn,opts){let kinds=opts?.kind?[opts.kind]:["dependency","code-scan","secret-scan"],out=[];for(let k of kinds)try{if(k==="dependency"){let{data}=await this.client.rest(`/repos/${fqn}/dependabot/alerts?state=open&per_page=50`);out.push(...data.map(mapDependabotAlert))}else if(k==="code-scan"){let{data}=await this.client.rest(`/repos/${fqn}/code-scanning/alerts?state=open&per_page=50`);out.push(...data.map(mapCodeScanAlert))}else if(k==="secret-scan"){let{data}=await this.client.rest(`/repos/${fqn}/secret-scanning/alerts?state=open&per_page=50`);out.push(...data.map(mapSecretScanAlert))}}catch(err){if(err instanceof GitOpsError&&err.code==="FORBIDDEN")continue}return out}async orgRollup(org){let cacheKey=`rollup:${org}`;return this.cached(cacheKey,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.language)byLang[r.language]=(byLang[r.language]??0)+1;if(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.client.rest(`/repos/${fqn}/hooks`);return data.map(mapWebhook)}catch{return[]}}}var PLUGIN_NAME="gitops-github",PLUGIN_VERSION="0.1.0",PROVIDER_NAME="github",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 GitHubProvider(hostServices),await provider.init(),new ProviderRegistry(hostServices).registerProvider("gitops",PROVIDER_NAME,provider),telemetry.emit("gitops.provider.ready",{provider:PROVIDER_NAME}),log.info("GitHub gitops provider registered")},onShutdown:async()=>{provider=null}});return{capabilities:{storage:"rw",secrets:"rw",telemetry:!0,audit:!0},name:PLUGIN_NAME,version:PLUGIN_VERSION,description:"GitHub provider for the GitOps meta plugin. Repo / PR / Actions / Dependabot data over REST+GraphQL v4.",tags:["backend","provider","integration"],onServerStart:lifecycle.onServerStart,onServerStop:lifecycle.onServerStop}},src_default=createPlugin;export{GitHubProvider,createPlugin,src_default as default};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecontrols/vibe-plugin-gitops-github",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1108.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"description": "GitHub provider for VibeControls GitOps meta plugin (REST v3 + GraphQL v4)",
|
|
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",
|
|
@@ -64,10 +62,10 @@
|
|
|
64
62
|
],
|
|
65
63
|
"repository": {
|
|
66
64
|
"type": "git",
|
|
67
|
-
"url": "git+https://github.com/
|
|
65
|
+
"url": "git+https://github.com/Algoshred/vibe-plugin-gitops-github.git"
|
|
68
66
|
},
|
|
69
67
|
"bugs": {
|
|
70
|
-
"url": "https://github.com/
|
|
68
|
+
"url": "https://github.com/Algoshred/vibe-plugin-gitops-github/issues"
|
|
71
69
|
},
|
|
72
70
|
"homepage": "https://vibecontrols.com",
|
|
73
71
|
"publishConfig": {
|
|
@@ -79,5 +77,19 @@
|
|
|
79
77
|
"!dist/**/*.map",
|
|
80
78
|
"README.md",
|
|
81
79
|
"LICENSE"
|
|
82
|
-
]
|
|
80
|
+
],
|
|
81
|
+
"packageManager": "bun@1.4.4",
|
|
82
|
+
"boff": {
|
|
83
|
+
"type": "plugin",
|
|
84
|
+
"sanity": {
|
|
85
|
+
"steps": [
|
|
86
|
+
"format",
|
|
87
|
+
"lint",
|
|
88
|
+
"typecheck",
|
|
89
|
+
"build",
|
|
90
|
+
"test",
|
|
91
|
+
"knip"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
83
95
|
}
|