@supacontrol/cli 0.1.3 → 0.1.4

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 CHANGED
@@ -1,25 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import{Command as dt}from"commander";import Ee from"picocolors";import{createRequire as gt}from"module";import{Command as mn}from"commander";import y from"picocolors";import{readFile as Fo}from"fs/promises";import{resolve as qo,dirname as Ct}from"path";import{parse as Uo}from"smol-toml";import ve from"picocolors";import{z as x}from"zod";var Do=x.enum(["push","reset","pull","seed","link","unlink"]),Ve=x.object({strict_mode:x.boolean().default(!1),require_clean_git:x.boolean().default(!0),show_migration_diff:x.boolean().default(!0)}),ze=x.object({project_ref:x.string().optional(),git_branches:x.array(x.string()).default([]),protected_operations:x.array(Do).default([]),confirm_word:x.string().optional(),locked:x.boolean().optional()}),Ye=x.object({settings:Ve.default({strict_mode:!1,require_clean_git:!0,show_migration_diff:!0}),environments:x.record(x.string(),ze).default({})}),Ke=x.object({settings:Ve.optional(),environments:x.record(x.string(),ze).optional()});function G(e,o){return o.locked!==void 0?o.locked:e==="production"||o.git_branches.includes("main")||o.git_branches.includes("master")}var Ho=["supacontrol.toml","config/supacontrol.toml"],oe=class extends Error{filePath;constructor(o,n,t){super(o,{cause:t}),this.name="ConfigError",this.filePath=n}};async function Wo(e){for(let o of Ho){let n=qo(e,o);try{return{content:await Fo(n,"utf-8"),path:n}}catch(t){if(t instanceof Error&&"code"in t&&t.code==="ENOENT")continue;throw new oe(`Failed to read config file: ${n}`,n,t instanceof Error?t:void 0)}}return null}function Ze(e){return e.issues.map(n=>{let t=n.path.join(".");return` ${ve.dim(t?`${t}: `:"")}${n.message}`}).join(`
2
+ import{Command as dt}from"commander";import Ee from"picocolors";import{createRequire as gt}from"module";import{Command as mn}from"commander";import w from"picocolors";import{readFile as Fo}from"fs/promises";import{resolve as qo,dirname as Ct}from"path";import{parse as Uo}from"smol-toml";import ve from"picocolors";import{z as x}from"zod";var Do=x.enum(["push","reset","pull","seed","link","unlink"]),Ve=x.object({strict_mode:x.boolean().default(!1),require_clean_git:x.boolean().default(!0),show_migration_diff:x.boolean().default(!0)}),ze=x.object({project_ref:x.string().optional(),git_branches:x.array(x.string()).default([]),protected_operations:x.array(Do).default([]),confirm_word:x.string().optional(),locked:x.boolean().optional()}),Ye=x.object({settings:Ve.default({strict_mode:!1,require_clean_git:!0,show_migration_diff:!0}),environments:x.record(x.string(),ze).default({})}),Ke=x.object({settings:Ve.optional(),environments:x.record(x.string(),ze).optional()});function G(e,o){return o.locked!==void 0?o.locked:e==="production"||o.git_branches.includes("main")||o.git_branches.includes("master")}var Ho=["supacontrol.toml","config/supacontrol.toml"],oe=class extends Error{filePath;constructor(o,n,t){super(o,{cause:t}),this.name="ConfigError",this.filePath=n}};async function Wo(e){for(let o of Ho){let n=qo(e,o);try{return{content:await Fo(n,"utf-8"),path:n}}catch(t){if(t instanceof Error&&"code"in t&&t.code==="ENOENT")continue;throw new oe(`Failed to read config file: ${n}`,n,t instanceof Error?t:void 0)}}return null}function Ze(e){return e.issues.map(n=>{let t=n.path.join(".");return` ${ve.dim(t?`${t}: `:"")}${n.message}`}).join(`
3
3
  `)}async function ce(e){let o=e??process.cwd(),n=await Wo(o);if(!n)return null;let t;try{t=Uo(n.content)}catch(s){throw new oe(`Invalid TOML syntax in ${n.path}:
4
4
  ${s instanceof Error?s.message:String(s)}`,n.path,s instanceof Error?s:void 0)}let r=Ke.safeParse(t);if(!r.success)throw new oe(`Invalid config in ${n.path}:
5
5
  ${Ze(r.error)}`,n.path);let i=Ye.safeParse(t);if(!i.success)throw new oe(`Invalid config in ${n.path}:
6
6
  ${Ze(i.error)}`,n.path);return i.data}async function F(e){try{let o=await ce(e);return o||(console.error(ve.red("\u2717"),"No supacontrol.toml found"),console.error(ve.dim(" Run `supacontrol init` to create one")),process.exit(1)),o}catch(o){throw o instanceof oe&&(console.error(ve.red("\u2717"),o.message),process.exit(1)),o}}function H(e,o){let n=o.environments[e];return n?{name:e,config:n,projectRef:n.project_ref,matchType:"exact"}:null}function ne(e){return Object.keys(e.environments)}function W(e,o){if(!e)return null;let n=Object.entries(o.environments);for(let[t,r]of n)if(r&&r.project_ref===e)return{name:t,config:r,projectRef:r.project_ref,matchType:"exact"};return null}import{execa as Je}from"execa";var le=null,te=null,de=null;function Y(){le=null,te=null,de=null}async function Le(e,o){let n=o?await Je("git",e,{cwd:o}):await Je("git",e);return typeof n.stdout=="string"?n.stdout:""}async function Ce(e){if(de!==null)return de;try{return await Le(["rev-parse","--is-inside-work-tree"],e),de=!0,!0}catch{return de=!1,!1}}async function V(e){if(le!==null)return le;try{return await Ce(e)?(le=(await Le(["symbolic-ref","--short","HEAD"],e)).trim(),le):null}catch{return le=null,null}}async function J(e){if(te!==null)return te;try{return await Ce(e)?(te=(await Le(["status","--porcelain"],e)).trim().length>0,te):(te=!1,!1)}catch{return te=!0,!0}}import{readFile as Vo}from"fs/promises";import{resolve as zo}from"path";var Pe=["push","reset","seed","pull","migrate"];function M(e){return{allowed:!0,...e}}function ue(e,o){return{allowed:!1,reason:e,...o}}function Te(e,o){return{allowed:!0,requiresConfirmation:!0,confirmWord:e,...o}}function Qe(e){for(let a of e)if(!a.allowed)return a;let o=["low","medium","high","critical"],n="low",t=!1,r,i=[];for(let a of e){if(a.riskLevel){let l=o.indexOf(n);o.indexOf(a.riskLevel)>l&&(n=a.riskLevel)}a.requiresConfirmation&&(t=!0,a.confirmWord&&(r=a.confirmWord)),a.suggestions&&i.push(...a.suggestions)}let s={allowed:!0,riskLevel:n,requiresConfirmation:t};return r!==void 0&&(s.confirmWord=r),i.length>0&&(s.suggestions=[...new Set(i)]),s}var Yo="supabase/.temp/project-ref",re;function q(){re=void 0}async function _(e){if(re!==void 0)return re;let o=e??process.cwd(),n=zo(o,Yo);try{return re=(await Vo(n,"utf-8")).trim(),re}catch(t){return t instanceof Error&&"code"in t&&t.code==="ENOENT",re=null,null}}async function Xe(e){let{environment:o,environmentName:n}=e;if(!o.project_ref)return M({suggestions:[`Consider adding 'project_ref' to [environments.${n}] for extra safety`]});let t=await _();return t===null?M({suggestions:["No Supabase project is currently linked",`Run 'supabase link --project-ref ${o.project_ref}' to link`]}):t!==o.project_ref?ue(`Project mismatch: linked to '${t}' but '${n}' expects '${o.project_ref}'`,{suggestions:[`Run 'supabase link --project-ref ${o.project_ref}' to switch`,`Or update [environments.${n}].project_ref in supacontrol.toml`],riskLevel:"high"}):M()}import{execa as Oe}from"execa";import Q from"picocolors";var ke;async function me(){if(ke!==void 0)return ke;try{return await Oe("supabase",["--version"]),ke=!0,!0}catch{return ke=!1,!1}}async function je(){try{let e=await Oe("supabase",["--version"]),o=typeof e.stdout=="string"?e.stdout:"",n=o.match(/(\d+\.\d+\.\d+)/);return n&&n[1]?n[1]:o.trim()||null}catch{return null}}async function E(e,o={}){let{cwd:n,stream:t=!0,env:r}=o;if(!await me())return console.error(Q.red("\u2717"),"Supabase CLI is not installed"),console.error(Q.dim(" Install it with: npm install -g supabase")),console.error(Q.dim(" Or: brew install supabase/tap/supabase")),{exitCode:1,stdout:"",stderr:"Supabase CLI not installed",success:!1};try{let s={reject:!1,...n?{cwd:n}:{},...r?{env:r}:{},...t?{stdio:"inherit"}:{},...o.input?{input:o.input}:{}},a=await Oe("supabase",e,s);return{exitCode:a.exitCode??0,stdout:typeof a.stdout=="string"?a.stdout:"",stderr:typeof a.stderr=="string"?a.stderr:"",success:a.exitCode===0}}catch(s){return{exitCode:1,stdout:"",stderr:s instanceof Error?s.message:String(s),success:!1}}}async function X(){await me()||(console.error(Q.red("\u2717"),"Supabase CLI is not installed"),console.error(),console.error(Q.dim("Install it with one of:")),console.error(Q.dim(" npm install -g supabase")),console.error(Q.dim(" brew install supabase/tap/supabase")),console.error(Q.dim(" scoop install supabase")),console.error(),process.exit(1))}import{homedir as eo}from"os";import{join as oo}from"path";import{readFile as Ko,writeFile as Zo,mkdir as Jo,chmod as Qo}from"fs/promises";import*as K from"@clack/prompts";import no from"picocolors";var Xo="SUPABASE_ACCESS_TOKEN",to=".supacontrol",en="credentials",on="https://supabase.com/dashboard/account/tokens";function Ae(){return oo(eo(),to,en)}function nn(){return oo(eo(),to)}async function ge(){let e=process.env[Xo];if(e)return e;try{let o=Ae(),t=(await Ko(o,"utf-8")).trim();if(t)return t}catch{}return null}async function tn(e){let o=nn(),n=Ae();await Jo(o,{recursive:!0}),await Zo(n,e,"utf-8");try{await Qo(n,384)}catch{}}async function rn(){console.log(),K.note(["To fetch your Supabase projects, we need an access token.","",`Generate one at: ${no.cyan(on)}`,"",'Select "Generate new token" and copy the token.'].join(`
7
- `),"Authentication Required");let e=await K.password({message:"Paste your Supabase access token:",validate(o){if(!o||o.length<10)return"Please enter a valid access token"}});return K.isCancel(e)?null:e}async function ro(e){let{skipPrompt:o=!1,saveToken:n=!0}=e??{},t=await ge();if(t)return t;if(o)return null;let r=await rn();if(!r)return null;if(n){let i=await K.confirm({message:"Save token for future use?",initialValue:!0});!K.isCancel(i)&&i&&(await tn(r),console.log(no.dim(` Saved to ${Ae()}`)))}return r}import{z as v}from"zod";var sn="https://api.supabase.com/v1",an=120,cn=v.enum(["ACTIVE_HEALTHY","ACTIVE_UNHEALTHY","COMING_UP","GOING_DOWN","INACTIVE","INIT_FAILED","REMOVED","RESTORING","UNKNOWN","UPGRADING","PAUSING","PAUSED"]),ln=v.enum(["free","pro","team","enterprise","platform"]),un=v.object({id:v.string(),name:v.string(),plan:ln.optional(),allowed_release_channels:v.array(v.string()).optional(),opt_in_tags:v.array(v.string()).optional()}),so=v.object({id:v.string(),organization_id:v.string(),organization_slug:v.string().optional(),name:v.string(),region:v.string(),created_at:v.string(),database:v.object({host:v.string(),version:v.string()}).optional(),status:cn,is_branch_enabled:v.boolean().optional(),preview_branch_refs:v.array(v.string()).optional()}),io=v.object({id:v.string(),name:v.string(),project_ref:v.string(),parent_project_ref:v.string(),is_default:v.boolean(),status:v.string(),created_at:v.string().optional()}),z=class extends Error{constructor(n,t,r){super(n);this.statusCode=t;this.response=r;this.name="SupabaseAPIError"}},Ie=class{accessToken;requestCount=0;requestWindowStart=Date.now();constructor(o){this.accessToken=o}async request(o,n={}){await this.checkRateLimit();let t=`${sn}${o}`,r=await fetch(t,{...n,headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/json",...n.headers}});if(this.requestCount++,!r.ok){let i=`API request failed: ${r.status} ${r.statusText}`,s;try{s=await r.json(),typeof s=="object"&&s!==null&&"message"in s&&(i=String(s.message))}catch{}throw r.status===401?new z("Invalid or expired access token. Run `supabase login` to re-authenticate.",r.status,s):r.status===429?new z("Rate limit exceeded. Please wait a moment and try again.",r.status,s):new z(i,r.status,s)}return r.json()}async checkRateLimit(){let o=Date.now(),n=60*1e3;if(o-this.requestWindowStart>n&&(this.requestCount=0,this.requestWindowStart=o),this.requestCount>=an){let t=n-(o-this.requestWindowStart);t>0&&(await new Promise(r=>setTimeout(r,t)),this.requestCount=0,this.requestWindowStart=Date.now())}}async authenticate(){try{return await this.getProjects(),!0}catch(o){if(o instanceof z&&o.statusCode===401)return!1;throw o}}async getProjects(){let o=await this.request("/projects"),n=[];for(let t of o){let r=so.safeParse(t);r.success&&n.push(r.data)}return n}async getProject(o){try{let n=await this.request(`/projects/${o}`),t=so.safeParse(n);return t.success?t.data:null}catch(n){if(n instanceof z&&n.statusCode===404)return null;throw n}}async getProjectByName(o){return(await this.getProjects()).find(t=>t.name.toLowerCase()===o.toLowerCase())??null}async getOrganization(o){try{let n=await this.request(`/organizations/${o}`),t=un.safeParse(n);return t.success?t.data:null}catch(n){if(n instanceof z&&n.statusCode===404)return null;throw n}}async getOrganizationForProject(o){let n=o.organization_slug||o.organization_id;return this.getOrganization(n)}async getBranches(o){try{let n=await this.request(`/projects/${o}/branches`),t=[];for(let r of n){let i=io.safeParse(r);i.success&&t.push(i.data)}return t}catch(n){if(n instanceof z&&(n.statusCode===403||n.statusCode===400))return[];throw n}}async createBranch(o,n){let t=await this.request(`/projects/${o}/branches`,{method:"POST",body:JSON.stringify({branch_name:n})}),r=io.safeParse(t);return r.success?r.data:null}async deleteBranch(o,n){try{return await this.request(`/projects/${o}/branches/${n}`,{method:"DELETE"}),!0}catch(t){return t instanceof z&&t.statusCode===404}}async checkBranchingCapability(o){let n="unknown";try{let l=await this.getOrganizationForProject(o);l?.plan&&(n=l.plan)}catch{}let r=n!=="unknown"&&["pro","team","enterprise","platform"].includes(n),i=n==="free",s=[];try{s=await this.getBranches(o.id)}catch{}return i?{available:!1,plan:n,branches:s,reason:"free_plan"}:r?{available:!0,plan:n,branches:s,reason:"paid_plan"}:s.filter(l=>!l.is_default).length>0?{available:!0,plan:n,branches:s,reason:"has_non_default_branches"}:{available:!1,plan:n,branches:s,reason:"unknown_capability"}}async isBranchingAvailable(o){return(await this.checkBranchingCapability(o)).available}};function $e(e){return new Ie(e)}function ao(){return new mn("status").description("Show current environment and project status").action(async()=>{await dn()})}async function pn(e,o,n){let t=o.find(r=>r.id===e);if(t)return{ref:e,type:"project",name:t.name};for(let r of o)if(r.status==="ACTIVE_HEALTHY")try{let s=(await n.getBranches(r.id)).find(a=>a.project_ref===e);if(s)return{ref:e,type:"branch",name:s.name,parentProjectName:r.name,parentProjectRef:r.id}}catch{}return{ref:e,type:"unknown"}}async function dn(){console.log(),console.log(y.bold("SupaControl Status")),console.log(y.dim("\u2500".repeat(50)));let e=await ce();if(!e){console.log(),console.log(y.yellow("\u26A0"),"No supacontrol.toml found"),console.log(y.dim(" Run: supacontrol init")),console.log();return}let o=await _(),n=null,t=await ge(),r=!1;if(o&&t)try{let g=$e(t),d=await g.getProjects();n=await pn(o,d,g)}catch{r=!0}let i=W(o,e);if(console.log(),i){let g=G(i.name,i.config),d=g?y.red("\u{1F512}"):y.green("\u{1F513}"),h=g?y.red("LOCKED"):y.green("unlocked");console.log(y.bold(`Active Environment: ${y.cyan(i.name)} ${d}`)),n?.type==="project"?console.log(` Project: ${n.name}`):n?.type==="branch"?console.log(` Project: ${n.parentProjectName} ${y.dim(`(${n.name} branch)`)}`):o&&console.log(` Project: ${o}`),console.log(` Status: ${h}`),i.config.protected_operations.length>0&&console.log(` Protected: ${y.yellow(i.config.protected_operations.join(", "))}`)}else o?(console.log(y.bold(`Active Environment: ${y.yellow("unknown")}`)),n?.type==="project"?console.log(` Linked to: ${n.name} ${y.dim(`(${o})`)}`):n?.type==="branch"?(console.log(` Linked to: ${n.name} branch ${y.dim(`(${o})`)}`),console.log(` Parent: ${n.parentProjectName}`)):console.log(` Linked to: ${o}`),console.log(),console.log(y.yellow(" \u26A0 This project is not configured in supacontrol.toml")),console.log(y.dim(" Add it to an environment or run: supacontrol init"))):(console.log(y.bold(`Active Environment: ${y.dim("none")}`)),console.log(y.dim(" No Supabase project linked")),console.log(y.dim(" Run: supacontrol switch <environment>")));console.log();let s=await V(),a=await J();if(s){let g=a?y.yellow(" *"):"";console.log(`Git: ${y.cyan(s)}${g}`)}else console.log(`Git: ${y.dim("not a git repository")}`);let l=Object.keys(e.environments);if(l.length>0){console.log(),console.log(y.bold("Environments"));for(let g of l){let d=e.environments[g];if(!d)continue;let m=G(g,d)?y.red("\u{1F512}"):y.green("\u{1F513}"),b=i?.name===g,P=b?y.cyan("\u2192"):" ",R=b?y.cyan(" \u2190 active"):"";console.log(` ${P} ${g} ${m}${R}`)}}if(console.log(),await me()){let g=await je();console.log(y.dim(`Supabase CLI v${g}`))}else console.log(y.red("Supabase CLI not installed"));r&&(console.log(),console.log(y.dim("Note: Could not fetch project details from API")),console.log(y.dim(" Your access token may have expired")),console.log(y.dim(" Generate a new one: https://supabase.com/dashboard/account/tokens"))),console.log()}import{Command as Sn}from"commander";import $ from"picocolors";import Ne from"picocolors";function co(e){let{environmentName:o,environment:n,operation:t}=e;return Pe.includes(t)?G(o,n)?ue(`Environment '${o}' is locked`,{suggestions:[`Set 'locked = false' in supacontrol.toml for [environments.${o}]`,"Or use --force flag to override (not recommended for production)"],riskLevel:"critical"}):M():M()}var lo={diff:"low",pull:"low",push:"medium",migrate:"medium",seed:"high",reset:"critical",link:"low",unlink:"medium"};function uo(e){let{operation:o,environment:n,environmentName:t,isCI:r}=e,i=lo[o]??"medium";if(!n.protected_operations.includes(o))return M({riskLevel:i});let a=n.confirm_word??t;return r?Te(a,{reason:`Operation '${o}' on '${t}' requires confirmation`,riskLevel:i}):Te(a,{reason:`This operation is protected. Type '${a}' to confirm.`,riskLevel:i})}function Me(e){return lo[e]??"medium"}function mo(e){let{config:o,hasUncommittedChanges:n,operation:t}=e;return o.settings.require_clean_git?Pe.includes(t)?n?ue("Git working directory has uncommitted changes",{suggestions:["Run `git stash` to temporarily store changes","Or run `git commit` to commit your changes","Or set `require_clean_git = false` in supacontrol.toml"],riskLevel:"medium"}):M():M():M()}import*as N from"@clack/prompts";import T from"picocolors";var po={low:{color:T.blue,label:"Low Risk"},medium:{color:T.yellow,label:"Medium Risk"},high:{color:T.red,label:"High Risk"},critical:{color:e=>T.bold(T.red(e)),label:"CRITICAL RISK"}},gn={push:"Push local migrations to the remote database",reset:"Reset the remote database to match local migrations",pull:"Pull remote schema changes to local migrations",seed:"Run seed data on the remote database",migrate:"Run database migrations",diff:"Show differences between local and remote schemas",link:"Link to a Supabase project",unlink:"Unlink from the current Supabase project"};async function Be(e){let{environmentName:o,operation:n,riskLevel:t,confirmWord:r,isCI:i,reason:s}=e,a=po[t],l=gn[n]??n;if(i)return console.error(T.red("\u2717"),"Cannot confirm interactively in CI mode"),console.error(T.dim(" Use --i-know-what-im-doing flag to bypass confirmation")),{confirmed:!1};if(N.note([a.color(`${a.label}`),"",`Operation: ${T.bold(n)}`,`Environment: ${T.bold(o)}`,`Description: ${l}`,s?`
7
+ `),"Authentication Required");let e=await K.password({message:"Paste your Supabase access token:",validate(o){if(!o||o.length<10)return"Please enter a valid access token"}});return K.isCancel(e)?null:e}async function ro(e){let{skipPrompt:o=!1,saveToken:n=!0}=e??{},t=await ge();if(t)return t;if(o)return null;let r=await rn();if(!r)return null;if(n){let i=await K.confirm({message:"Save token for future use?",initialValue:!0});!K.isCancel(i)&&i&&(await tn(r),console.log(no.dim(` Saved to ${Ae()}`)))}return r}import{z as v}from"zod";var sn="https://api.supabase.com/v1",an=120,cn=v.enum(["ACTIVE_HEALTHY","ACTIVE_UNHEALTHY","COMING_UP","GOING_DOWN","INACTIVE","INIT_FAILED","REMOVED","RESTORING","UNKNOWN","UPGRADING","PAUSING","PAUSED"]),ln=v.enum(["free","pro","team","enterprise","platform"]),un=v.object({id:v.string(),name:v.string(),plan:ln.optional(),allowed_release_channels:v.array(v.string()).optional(),opt_in_tags:v.array(v.string()).optional()}),so=v.object({id:v.string(),organization_id:v.string(),organization_slug:v.string().optional(),name:v.string(),region:v.string(),created_at:v.string(),database:v.object({host:v.string(),version:v.string()}).optional(),status:cn,is_branch_enabled:v.boolean().optional(),preview_branch_refs:v.array(v.string()).optional()}),io=v.object({id:v.string(),name:v.string(),project_ref:v.string(),parent_project_ref:v.string(),is_default:v.boolean(),status:v.string(),created_at:v.string().optional()}),z=class extends Error{constructor(n,t,r){super(n);this.statusCode=t;this.response=r;this.name="SupabaseAPIError"}},Ie=class{accessToken;requestCount=0;requestWindowStart=Date.now();constructor(o){this.accessToken=o}async request(o,n={}){await this.checkRateLimit();let t=`${sn}${o}`,r=await fetch(t,{...n,headers:{Authorization:`Bearer ${this.accessToken}`,"Content-Type":"application/json",...n.headers}});if(this.requestCount++,!r.ok){let i=`API request failed: ${r.status} ${r.statusText}`,s;try{s=await r.json(),typeof s=="object"&&s!==null&&"message"in s&&(i=String(s.message))}catch{}throw r.status===401?new z("Invalid or expired access token. Run `supabase login` to re-authenticate.",r.status,s):r.status===429?new z("Rate limit exceeded. Please wait a moment and try again.",r.status,s):new z(i,r.status,s)}return r.json()}async checkRateLimit(){let o=Date.now(),n=60*1e3;if(o-this.requestWindowStart>n&&(this.requestCount=0,this.requestWindowStart=o),this.requestCount>=an){let t=n-(o-this.requestWindowStart);t>0&&(await new Promise(r=>setTimeout(r,t)),this.requestCount=0,this.requestWindowStart=Date.now())}}async authenticate(){try{return await this.getProjects(),!0}catch(o){if(o instanceof z&&o.statusCode===401)return!1;throw o}}async getProjects(){let o=await this.request("/projects"),n=[];for(let t of o){let r=so.safeParse(t);r.success&&n.push(r.data)}return n}async getProject(o){try{let n=await this.request(`/projects/${o}`),t=so.safeParse(n);return t.success?t.data:null}catch(n){if(n instanceof z&&n.statusCode===404)return null;throw n}}async getProjectByName(o){return(await this.getProjects()).find(t=>t.name.toLowerCase()===o.toLowerCase())??null}async getOrganization(o){try{let n=await this.request(`/organizations/${o}`),t=un.safeParse(n);return t.success?t.data:null}catch(n){if(n instanceof z&&n.statusCode===404)return null;throw n}}async getOrganizationForProject(o){let n=o.organization_slug||o.organization_id;return this.getOrganization(n)}async getBranches(o){try{let n=await this.request(`/projects/${o}/branches`),t=[];for(let r of n){let i=io.safeParse(r);i.success&&t.push(i.data)}return t}catch(n){if(n instanceof z&&(n.statusCode===403||n.statusCode===400))return[];throw n}}async createBranch(o,n){let t=await this.request(`/projects/${o}/branches`,{method:"POST",body:JSON.stringify({branch_name:n})}),r=io.safeParse(t);return r.success?r.data:null}async deleteBranch(o,n){try{return await this.request(`/projects/${o}/branches/${n}`,{method:"DELETE"}),!0}catch(t){return t instanceof z&&t.statusCode===404}}async checkBranchingCapability(o){let n="unknown";try{let l=await this.getOrganizationForProject(o);l?.plan&&(n=l.plan)}catch{}let r=n!=="unknown"&&["pro","team","enterprise","platform"].includes(n),i=n==="free",s=[];try{s=await this.getBranches(o.id)}catch{}return i?{available:!1,plan:n,branches:s,reason:"free_plan"}:r?{available:!0,plan:n,branches:s,reason:"paid_plan"}:s.filter(l=>!l.is_default).length>0?{available:!0,plan:n,branches:s,reason:"has_non_default_branches"}:{available:!1,plan:n,branches:s,reason:"unknown_capability"}}async isBranchingAvailable(o){return(await this.checkBranchingCapability(o)).available}};function $e(e){return new Ie(e)}function ao(){return new mn("status").description("Show current environment and project status").action(async()=>{await dn()})}async function pn(e,o,n){let t=o.find(r=>r.id===e);if(t)return{ref:e,type:"project",name:t.name};for(let r of o)if(r.status==="ACTIVE_HEALTHY")try{let s=(await n.getBranches(r.id)).find(a=>a.project_ref===e);if(s)return{ref:e,type:"branch",name:s.name,parentProjectName:r.name,parentProjectRef:r.id}}catch{}return{ref:e,type:"unknown"}}async function dn(){console.log(),console.log(w.bold("SupaControl Status")),console.log(w.dim("\u2500".repeat(50)));let e=await ce();if(!e){console.log(),console.log(w.yellow("\u26A0"),"No supacontrol.toml found"),console.log(w.dim(" Run: supacontrol init")),console.log();return}let o=await _(),n=null,t=await ge(),r=!1;if(o&&t)try{let g=$e(t),d=await g.getProjects();n=await pn(o,d,g)}catch{r=!0}let i=W(o,e);if(console.log(),i){let g=G(i.name,i.config),d=g?w.red("\u{1F512}"):w.green("\u{1F513}"),h=g?w.red("LOCKED"):w.green("unlocked");console.log(w.bold(`Active Environment: ${w.cyan(i.name)} ${d}`)),n?.type==="project"?console.log(` Project: ${n.name}`):n?.type==="branch"?console.log(` Project: ${n.parentProjectName} ${w.dim(`(${n.name} branch)`)}`):o&&console.log(` Project: ${o}`),console.log(` Status: ${h}`),i.config.protected_operations.length>0&&console.log(` Protected: ${w.yellow(i.config.protected_operations.join(", "))}`)}else o?(console.log(w.bold(`Active Environment: ${w.yellow("unknown")}`)),n?.type==="project"?console.log(` Linked to: ${n.name} ${w.dim(`(${o})`)}`):n?.type==="branch"?(console.log(` Linked to: ${n.name} branch ${w.dim(`(${o})`)}`),console.log(` Parent: ${n.parentProjectName}`)):console.log(` Linked to: ${o}`),console.log(),console.log(w.yellow(" \u26A0 This project is not configured in supacontrol.toml")),console.log(w.dim(" Add it to an environment or run: supacontrol init"))):(console.log(w.bold(`Active Environment: ${w.dim("none")}`)),console.log(w.dim(" No Supabase project linked")),console.log(w.dim(" Run: supacontrol switch <environment>")));console.log();let s=await V(),a=await J();if(s){let g=a?w.yellow(" *"):"";console.log(`Git: ${w.cyan(s)}${g}`)}else console.log(`Git: ${w.dim("not a git repository")}`);let l=Object.keys(e.environments);if(l.length>0){console.log(),console.log(w.bold("Environments"));for(let g of l){let d=e.environments[g];if(!d)continue;let m=G(g,d)?w.red("\u{1F512}"):w.green("\u{1F513}"),b=i?.name===g,P=b?w.cyan("\u2192"):" ",R=b?w.cyan(" \u2190 active"):"";console.log(` ${P} ${g} ${m}${R}`)}}if(console.log(),await me()){let g=await je();console.log(w.dim(`Supabase CLI v${g}`))}else console.log(w.red("Supabase CLI not installed"));r&&(console.log(),console.log(w.dim("Note: Could not fetch project details from API")),console.log(w.dim(" Your access token may have expired")),console.log(w.dim(" Generate a new one: https://supabase.com/dashboard/account/tokens"))),console.log()}import{Command as Sn}from"commander";import $ from"picocolors";import Ne from"picocolors";function co(e){let{environmentName:o,environment:n,operation:t}=e;return Pe.includes(t)?G(o,n)?ue(`Environment '${o}' is locked`,{suggestions:[`Set 'locked = false' in supacontrol.toml for [environments.${o}]`,"Or use --force flag to override (not recommended for production)"],riskLevel:"critical"}):M():M()}var lo={diff:"low",pull:"low",push:"medium",migrate:"medium",seed:"high",reset:"critical",link:"low",unlink:"medium"};function uo(e){let{operation:o,environment:n,environmentName:t,isCI:r}=e,i=lo[o]??"medium";if(!n.protected_operations.includes(o))return M({riskLevel:i});let a=n.confirm_word??t;return r?Te(a,{reason:`Operation '${o}' on '${t}' requires confirmation`,riskLevel:i}):Te(a,{reason:`This operation is protected. Type '${a}' to confirm.`,riskLevel:i})}function Me(e){return lo[e]??"medium"}function mo(e){let{config:o,hasUncommittedChanges:n,operation:t}=e;return o.settings.require_clean_git?Pe.includes(t)?n?ue("Git working directory has uncommitted changes",{suggestions:["Run `git stash` to temporarily store changes","Or run `git commit` to commit your changes","Or set `require_clean_git = false` in supacontrol.toml"],riskLevel:"medium"}):M():M():M()}import*as N from"@clack/prompts";import T from"picocolors";var po={low:{color:T.blue,label:"Low Risk"},medium:{color:T.yellow,label:"Medium Risk"},high:{color:T.red,label:"High Risk"},critical:{color:e=>T.bold(T.red(e)),label:"CRITICAL RISK"}},gn={push:"Push local migrations to the remote database",reset:"Reset the remote database to match local migrations",pull:"Pull remote schema changes to local migrations",seed:"Run seed data on the remote database",migrate:"Run database migrations",diff:"Show differences between local and remote schemas",link:"Link to a Supabase project",unlink:"Unlink from the current Supabase project"};async function Be(e){let{environmentName:o,operation:n,riskLevel:t,confirmWord:r,isCI:i,reason:s}=e,a=po[t],l=gn[n]??n;if(i)return console.error(T.red("\u2717"),"Cannot confirm interactively in CI mode"),console.error(T.dim(" Use --i-know-what-im-doing flag to bypass confirmation")),{confirmed:!1};if(N.note([a.color(`${a.label}`),"",`Operation: ${T.bold(n)}`,`Environment: ${T.bold(o)}`,`Description: ${l}`,s?`
8
8
  Reason: ${s}`:""].filter(Boolean).join(`
9
- `),a.color("\u26A0 Confirmation Required")),t==="critical"||r){let g=r??o,d=await N.text({message:`Type '${T.bold(g)}' to confirm:`,placeholder:g,validate(h){if(h!==g)return`Please type exactly '${g}' to confirm`}});return N.isCancel(d)?(N.cancel("Operation cancelled"),{confirmed:!1,cancelled:!0}):{confirmed:d===g}}let f=await N.confirm({message:"Do you want to proceed?",initialValue:!1});return N.isCancel(f)?(N.cancel("Operation cancelled"),{confirmed:!1,cancelled:!0}):{confirmed:f}}function Ge(e,o,n,t){let r=po[t];console.log(),console.log(T.blue("\u2192"),T.bold(e),"on",r.color(o)),n&&console.log(T.dim(` Project: ${n}`)),console.log(T.dim(` Risk: ${r.label}`)),console.log()}async function pe(e){let o=[],n=co(e);if(!n.allowed)return Se(n),n;o.push(n);let t=uo(e);if(!t.allowed)return Se(t),t;o.push(t);let r=await Xe(e);if(!r.allowed)return Se(r),r;o.push(r);let i=mo(e);if(!i.allowed)return Se(i),i;o.push(i);let s=Qe(o);if(s.requiresConfirmation){let a=s.riskLevel??Me(e.operation),{confirmed:l,cancelled:f}=await Be({environmentName:e.environmentName,operation:e.operation,riskLevel:a,confirmWord:s.confirmWord,isCI:e.isCI,reason:s.reason});return f?{...s,allowed:!1,cancelled:!0}:l?{...s,allowed:!0,confirmed:!0}:{...s,allowed:!1,reason:"Confirmation declined"}}return Ge(e.operation,e.environmentName,e.environment.project_ref,s.riskLevel??"low"),s}function Se(e){if(console.error(),console.error(Ne.red("\u2717"),e.reason??"Operation blocked"),e.suggestions&&e.suggestions.length>0){console.error(),console.error(Ne.dim("Suggestions:"));for(let o of e.suggestions)console.error(Ne.dim(` \u2022 ${o}`))}console.error()}import{readdir as bo,readFile as yo,writeFile as se,mkdir as fn,rename as hn}from"fs/promises";import{resolve as ie,join as he,basename as go}from"path";import{createHash as bn}from"crypto";import p from"picocolors";import*as O from"@clack/prompts";function fo(e){return bn("sha256").update(e).digest("hex").slice(0,16)}async function yn(){let e=ie(process.cwd(),"supabase","migrations");try{return(await bo(e)).filter(n=>n.endsWith(".sql")&&!n.endsWith(".remote.sql")).map(n=>n.replace(".sql","")).sort()}catch{return[]}}async function fe(){let e=ie(process.cwd(),"supabase","migrations");try{return(await bo(e)).filter(n=>n.endsWith(".sql")&&!n.endsWith(".remote.sql")).map(n=>{let t=n.match(/^(\d{14})_?(.*)\.sql$/),r=t?.[1]??n.replace(".sql",""),i=t?.[2]??"";return{timestamp:r,name:i,filename:n,fullPath:he(e,n)}}).sort((n,t)=>n.timestamp.localeCompare(t.timestamp))}catch{return[]}}async function De(){let e=await E(["migration","list"],{stream:!1});if(!e.success||!e.stdout)return[];let o=e.stdout.split(`
10
- `),n=[];for(let t of o){if(t.includes("Local")||t.includes("---")||!t.trim())continue;let r=t.split("|");if(r.length>=2){let i=r[1]?.trim();if(i){let s=i.match(/^(\d{14})$/);s?.[1]&&n.push(s[1])}}}return n.sort()}async function Fe(){try{let[e,o]=await Promise.all([yn(),De()]),n=e.map(i=>i.match(/^(\d{14})/)?.[1]??i),t=o.filter(i=>!n.includes(i)),r=n.filter(i=>i!==void 0&&!o.includes(i));return{needsSync:t.length>0,remoteMissing:t,localMissing:r}}catch(e){return{needsSync:!1,remoteMissing:[],localMissing:[],error:e instanceof Error?e.message:"Unknown error"}}}async function wn(){let e=ie(process.cwd(),"supabase","migrations");try{await fn(e,{recursive:!0})}catch{}let o=await E(["migration","fetch","--linked"],{stream:!1,input:`y
11
- `});return o.success?{success:!0,fetched:await fe()}:{success:!1,error:o.stderr||"Failed to fetch migrations",fetched:[]}}async function vn(e){return await yo(e.fullPath,"utf-8")}function qe(e,o){let n=new Set(e.split(`
9
+ `),a.color("\u26A0 Confirmation Required")),t==="critical"||r){let g=r??o,d=await N.text({message:`Type '${T.bold(g)}' to confirm:`,placeholder:g,validate(h){if(h!==g)return`Please type exactly '${g}' to confirm`}});return N.isCancel(d)?(N.cancel("Operation cancelled"),{confirmed:!1,cancelled:!0}):{confirmed:d===g}}let f=await N.confirm({message:"Do you want to proceed?",initialValue:!1});return N.isCancel(f)?(N.cancel("Operation cancelled"),{confirmed:!1,cancelled:!0}):{confirmed:f}}function Ge(e,o,n,t){let r=po[t];console.log(),console.log(T.blue("\u2192"),T.bold(e),"on",r.color(o)),n&&console.log(T.dim(` Project: ${n}`)),console.log(T.dim(` Risk: ${r.label}`)),console.log()}async function pe(e){let o=[],n=co(e);if(!n.allowed)return Se(n),n;o.push(n);let t=uo(e);if(!t.allowed)return Se(t),t;o.push(t);let r=await Xe(e);if(!r.allowed)return Se(r),r;o.push(r);let i=mo(e);if(!i.allowed)return Se(i),i;o.push(i);let s=Qe(o);if(s.requiresConfirmation){let a=s.riskLevel??Me(e.operation),{confirmed:l,cancelled:f}=await Be({environmentName:e.environmentName,operation:e.operation,riskLevel:a,confirmWord:s.confirmWord,isCI:e.isCI,reason:s.reason});return f?{...s,allowed:!1,cancelled:!0}:l?{...s,allowed:!0,confirmed:!0}:{...s,allowed:!1,reason:"Confirmation declined"}}return Ge(e.operation,e.environmentName,e.environment.project_ref,s.riskLevel??"low"),s}function Se(e){if(console.error(),console.error(Ne.red("\u2717"),e.reason??"Operation blocked"),e.suggestions&&e.suggestions.length>0){console.error(),console.error(Ne.dim("Suggestions:"));for(let o of e.suggestions)console.error(Ne.dim(` \u2022 ${o}`))}console.error()}import{readdir as bo,readFile as wo,writeFile as se,mkdir as fn,rename as hn}from"fs/promises";import{resolve as ie,join as he,basename as go}from"path";import{createHash as bn}from"crypto";import p from"picocolors";import*as O from"@clack/prompts";function fo(e){return bn("sha256").update(e).digest("hex").slice(0,16)}async function wn(){let e=ie(process.cwd(),"supabase","migrations");try{return(await bo(e)).filter(n=>n.endsWith(".sql")&&!n.endsWith(".remote.sql")).map(n=>n.replace(".sql","")).sort()}catch{return[]}}async function fe(){let e=ie(process.cwd(),"supabase","migrations");try{return(await bo(e)).filter(n=>n.endsWith(".sql")&&!n.endsWith(".remote.sql")).map(n=>{let t=n.match(/^(\d{14})_?(.*)\.sql$/),r=t?.[1]??n.replace(".sql",""),i=t?.[2]??"";return{timestamp:r,name:i,filename:n,fullPath:he(e,n)}}).sort((n,t)=>n.timestamp.localeCompare(t.timestamp))}catch{return[]}}async function De(){let e=await E(["migration","list"],{stream:!1});if(!e.success||!e.stdout)return[];let o=e.stdout.split(`
10
+ `),n=[];for(let t of o){if(t.includes("Local")||t.includes("---")||!t.trim())continue;let r=t.split("|");if(r.length>=2){let i=r[1]?.trim();if(i){let s=i.match(/^(\d{14})$/);s?.[1]&&n.push(s[1])}}}return n.sort()}async function Fe(){try{let[e,o]=await Promise.all([wn(),De()]),n=e.map(i=>i.match(/^(\d{14})/)?.[1]??i),t=o.filter(i=>!n.includes(i)),r=n.filter(i=>i!==void 0&&!o.includes(i));return{needsSync:t.length>0,remoteMissing:t,localMissing:r}}catch(e){return{needsSync:!1,remoteMissing:[],localMissing:[],error:e instanceof Error?e.message:"Unknown error"}}}async function yn(){let e=ie(process.cwd(),"supabase","migrations");try{await fn(e,{recursive:!0})}catch{}let o=await E(["migration","fetch","--linked"],{stream:!1,input:`y
11
+ `});return o.success?{success:!0,fetched:await fe()}:{success:!1,error:o.stderr||"Failed to fetch migrations",fetched:[]}}async function vn(e){return await wo(e.fullPath,"utf-8")}function qe(e,o){let n=new Set(e.split(`
12
12
  `).map(s=>s.trim()).filter(Boolean)),t=new Set(o.split(`
13
13
  `).map(s=>s.trim()).filter(Boolean)),r=[],i=[];for(let s of n)t.has(s)||r.push(s);for(let s of t)n.has(s)||i.push(s);return{additions:r,removals:i}}function Cn(e){let{additions:o,removals:n}=qe(e.localContent,e.remoteContent);if(console.log(),console.log(p.bold(` File: ${e.filename}`)),console.log(),n.length>0){console.log(p.dim(" In database (will be kept):"));for(let t of n.slice(0,8))console.log(p.red(` - ${t.slice(0,70)}`));n.length>8&&console.log(p.dim(` ... and ${n.length-8} more lines`)),console.log()}if(o.length>0){console.log(p.dim(" In your local file (NOT in database):"));for(let t of o.slice(0,8))console.log(p.green(` + ${t.slice(0,70)}`));o.length>8&&console.log(p.dim(` ... and ${o.length-8} more lines`))}}async function Pn(e){console.log(),console.log(p.yellow("\u26A0"),p.bold("Local file differs from applied migration")),console.log(),Cn(e),console.log(),O.note([p.bold(p.yellow("Your local edits are NOT in the database.")),"","It looks like this migration file was edited after it was","already applied to your database. That's a common mistake!","",p.bold("How migrations work:"),' \u2022 Each migration runs ONCE, then is marked as "done"'," \u2022 Editing the file later has no effect - it won't re-run"," \u2022 To change your schema, create a NEW migration","",p.dim("Example: To add a column, don't edit the CREATE TABLE."),p.dim("Instead, create a new migration with ALTER TABLE ... ADD COLUMN.")].join(`
14
14
  `),"What happened?");let o=await O.select({message:"How do you want to handle this?",options:[{value:"create-migration",label:p.green("Create new migration from my edits")+" "+p.green("(Recommended)"),hint:"Applies your changes to the database properly"},{value:"keep-remote",label:"Restore file to match database",hint:"Discards your local edits"},{value:"save-both",label:"Keep both versions",hint:"Save applied version as .remote.sql for reference"},{value:"keep-local",label:p.dim("Keep local file as-is (not recommended)"),hint:"File won't match database - you'll need to fix manually"},{value:"cancel",label:p.dim("Cancel"),hint:"Abort the sync"}]});return O.isCancel(o)?"cancel":o}function kn(){let e=new Date;return[e.getUTCFullYear(),String(e.getUTCMonth()+1).padStart(2,"0"),String(e.getUTCDate()).padStart(2,"0"),String(e.getUTCHours()).padStart(2,"0"),String(e.getUTCMinutes()).padStart(2,"0"),String(e.getUTCSeconds()).padStart(2,"0")].join("")}async function jn(e,o){let n=ie(process.cwd(),"supabase","migrations"),t=kn();if(o&&t<=o){let h=parseInt(o,10)+1;t=String(h).padStart(14,"0")}let r=e.filename.match(/^\d{14}_(.+)\.sql$/),i=r?r[1]:"update",s=`${t}_${i}_changes.sql`,a=he(n,s),{additions:l}=qe(e.localContent,e.remoteContent);if(l.length===0)return{success:!1,error:"No differences found to migrate"};let f=["-- Migration generated by SupaControl",`-- Based on local edits to: ${e.filename}`,`-- Generated: ${new Date().toISOString()}`,"--","-- \u26A0\uFE0F REVIEW THIS MIGRATION BEFORE APPLYING","-- The following changes were detected in your local file:","--"],g=l.filter(h=>{let m=h.toLowerCase();return m.includes("create ")||m.includes("alter ")||m.includes("add ")||m.includes("drop ")||m.includes("index")||m.includes("column")||m.includes("constraint")||m.includes("default")||m.includes("comment on")}),d;return g.length>0?d=[...f,"","-- Detected changes (may need manual adjustment):",...g.map(h=>`-- ${h}`),"","-- TODO: Write the actual SQL to apply these changes","-- Example: ALTER TABLE ... ADD COLUMN ...;",""].join(`
15
15
  `):d=[...f,"","-- Could not automatically extract SQL statements.","-- Please review the diff and write the appropriate migration.","","-- Local file additions:",...l.slice(0,20).map(h=>`-- ${h}`),l.length>20?`-- ... and ${l.length-20} more lines`:"",""].filter(Boolean).join(`
16
- `),await se(a,d),await se(e.localPath,e.remoteContent),{success:!0,migrationPath:a}}async function $n(e,o,n){let t=ie(process.cwd(),"supabase","migrations");switch(o){case"create-migration":{let r=await jn(e,n);if(r.success&&r.migrationPath)console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),console.log(p.green("\u2713"),`Created new migration: ${go(r.migrationPath)}`),console.log(),console.log(p.yellow("\u26A0"),"Please review and edit the new migration before pushing"),console.log(p.dim(` ${r.migrationPath}`));else if(r.error==="No differences found to migrate")await se(e.localPath,e.remoteContent),console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),console.log(p.dim(" (No meaningful code differences found)"));else return console.log(p.red("\u2717"),"Failed to create migration:",r.error),!1;return!0}case"keep-local":return console.log(p.yellow("\u26A0"),`Keeping local version of ${e.filename}`),console.log(p.dim(" Note: This file does not match what's in your database")),!0;case"keep-remote":return await se(e.localPath,e.remoteContent),console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),!0;case"save-both":{let r=he(t,e.filename.replace(".sql",".remote.sql"));return await se(r,e.remoteContent),console.log(p.yellow("\u26A0"),`Keeping local ${e.filename} (doesn't match database)`),console.log(p.green("\u2713"),`Saved database version as ${go(r)}`),!0}case"cancel":return!1}}async function ho(e,o){let n=[],t=ie(process.cwd(),"supabase","migrations"),i=[...e].sort((a,l)=>a.timestamp.localeCompare(l.timestamp)).filter(a=>a.timestamp<=o);if(i.length===0)return n;console.log(),console.log(p.blue("\u2192"),"Reordering local migrations to come after remote...");let s=parseInt(o,10)+1;for(let a of i){let l=String(s).padStart(14,"0"),f=a.filename.replace(a.timestamp,l),g=he(t,f);await hn(a.fullPath,g),n.push({original:a.filename,renamed:f}),console.log(p.dim(` ${a.filename}`)),console.log(p.green(` \u2192 ${f}`)),s++}return n}async function wo(){return console.log(p.blue("\u2192"),"Pulling migrations from remote..."),(await E(["db","pull"],{stream:!0})).success}async function vo(){let e=await Fe();if(e.error)return console.log(p.yellow("\u26A0"),"Could not check migration sync status"),console.log(p.dim(` ${e.error}`)),{success:!0};if(e.remoteMissing.length===0&&e.localMissing.length===0)return{success:!0};if(e.remoteMissing.length===0&&e.localMissing.length>0){let m=await De(),b=m.length>0?m.sort()[m.length-1]:null;if(b&&e.localMissing.some(R=>R<=b)){console.log(p.blue("\u2192"),`${e.localMissing.length} new migration(s) need timestamp adjustment`);let I=(await fe()).filter(ae=>e.localMissing.includes(ae.timestamp)),Z=await ho(I,b);Z.length>0&&console.log(p.green("\u2713"),`Reordered ${Z.length} migration(s) to come after remote`)}else console.log(p.blue("\u2192"),`${e.localMissing.length} new migration(s) ready to push`);return{success:!0}}if(console.log(),console.log(p.yellow("\u26A0"),"Migration sync needed"),console.log(),e.remoteMissing.length>0){console.log(p.dim(" Remote has migrations not saved locally:"));for(let m of e.remoteMissing)console.log(p.blue(` + ${m}`))}if(e.localMissing.length>0){console.log(p.dim(" Local has migrations not on remote:"));for(let m of e.localMissing)console.log(p.green(` + ${m}`))}console.log(),O.note([p.bold("We will:"),"",`${p.blue("1.")} Download ${e.remoteMissing.length} migration file(s) from remote`,`${p.blue("2.")} Check for any content conflicts with your local files`,`${p.blue("3.")} Let you resolve any conflicts`,e.localMissing.length>0?`${p.blue("4.")} Reorder your local migrations if needed, then push`:"","",p.dim("This preserves both remote and local work.")].filter(Boolean).join(`
17
- `),"Migration Sync");let o=await O.confirm({message:"Proceed with sync?",initialValue:!0});if(O.isCancel(o)||!o)return{success:!1,cancelled:!0};let n=await fe(),t=new Map;for(let m of n)t.set(m.timestamp,m),m.content=await vn(m);let r=O.spinner();r.start("Fetching remote migrations...");let i=await wn();if(!i.success)return r.stop("Fetch failed"),console.log(p.red("\u2717"),"Failed to fetch remote migrations:",i.error),{success:!1};r.stop(`Fetched ${e.remoteMissing.length} migration(s) from remote`);let s=[];for(let[m,b]of t){if(e.remoteMissing.includes(m)||e.localMissing.includes(m))continue;let P=b.fullPath,R;try{R=await yo(P,"utf-8")}catch{continue}let I=b.content??"",Z=fo(I),ae=fo(R);if(Z!==ae){let{additions:ye,removals:we}=qe(I,R);if(ye.length===0&&we.length===0)continue;s.push({timestamp:m,filename:b.filename,localPath:P,localContent:I,remoteContent:R,localHash:Z,remoteHash:ae})}}let a=[...e.remoteMissing].sort(),l=a.length>0?a[a.length-1]:void 0;if(s.length>0){console.log(),console.log(p.yellow("\u26A0"),`Found ${s.length} file(s) with content differences`),console.log();for(let m=0;m<s.length;m++){let b=s[m];if(!b)continue;let P=m+1,R=s.length;console.log(p.blue("\u2500".repeat(60))),console.log(p.blue(` Conflict ${P} of ${R}`)),console.log(p.blue("\u2500".repeat(60)));let I=await Pn(b);if(I==="cancel"){let we=O.spinner();we.start("Restoring original files...");for(let[,_e]of t)_e.content&&await se(_e.fullPath,_e.content);return we.stop("Restored original files"),{success:!1,cancelled:!0}}let Z=O.spinner(),ae=I==="create-migration"?"Creating migration from your edits...":I==="keep-remote"?"Restoring file to match database...":I==="save-both"?"Saving both versions...":"Processing...";Z.start(ae);let ye=await $n(b,I,l);if(Z.stop(ye?"Complete":"Failed"),!ye)return{success:!1,cancelled:!0};m<s.length-1&&(console.log(),console.log(p.dim(" Moving to next conflict...")),console.log())}console.log(),console.log(p.green("\u2713"),`Resolved ${s.length} conflict(s)`)}let f=ie(process.cwd(),"supabase","migrations");for(let m of e.localMissing){let b=t.get(m);if(b&&b.content){let P=he(f,b.filename);await se(P,b.content)}}if(e.localMissing.length>0&&l){let b=(await fe()).filter(R=>e.localMissing.includes(R.timestamp)),P=await ho(b,l);P.length>0&&console.log(p.green("\u2713"),`Reordered ${P.length} local migration(s)`)}console.log(),console.log(p.green("\u2713"),"Migration sync complete!");let g=await fe(),d=await De(),h=g.filter(m=>!d.includes(m.timestamp)).map(m=>m.filename);if(h.length>0){console.log(),console.log(p.blue("\u2192"),`${h.length} migration(s) ready to push:`);for(let m of h)console.log(p.dim(` ${m}`))}return{success:!0}}function Po(){return new Sn("push").description("Push local migrations to the remote database").option("--force","Bypass all safety guards (use with caution)",!1).option("--dry-run","Show what would be pushed without executing",!1).option("--i-know-what-im-doing","Required flag for production operations in CI mode",!1).action(async function(){let e=this.optsWithGlobals();await Rn(e)})}async function Rn(e){Y(),q(),await X();let o=await F(),n=await V(),t=await J(),r;if(e.env)r=H(e.env,o),r||(console.error($.red("\u2717"),`Environment '${e.env}' not found in config`),console.error($.dim(" Available environments:"),Object.keys(o.environments).join(", ")),process.exit(1));else{let s=await _();s||(console.error($.red("\u2717"),"No Supabase project linked"),console.error($.dim(" Run: supacontrol switch <environment>")),process.exit(1)),r=W(s,o),r||(console.error($.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error($.dim(` Linked to: ${s}`)),console.error($.dim(" Run: supacontrol init or add this project to your config")),process.exit(1))}if(e.force)console.log($.yellow("\u26A0"),"Force mode: bypassing all safety guards"),console.log();else{let s={operation:"push",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},a=await pe(s);a.allowed||(a.cancelled&&process.exit(0),process.exit(1)),e.ci&&a.requiresConfirmation&&!e.iKnowWhatImDoing&&(console.error($.red("\u2717"),"CI mode requires --i-know-what-im-doing flag for this operation"),process.exit(1))}if(!e.force){let s=await vo();s.success||(s.cancelled&&process.exit(0),console.log(),console.log($.dim("Use --force to push anyway (not recommended)")),process.exit(1))}if(o.settings.show_migration_diff&&!e.dryRun){console.log($.blue("\u2192"),"Checking for pending migrations...");let s=await E(["db","diff"],{stream:!1});if(s.success&&s.stdout)if(s.stdout.trim().length>0){let l=s.stdout.split(`
16
+ `),await se(a,d),await se(e.localPath,e.remoteContent),{success:!0,migrationPath:a}}async function $n(e,o,n){let t=ie(process.cwd(),"supabase","migrations");switch(o){case"create-migration":{let r=await jn(e,n);if(r.success&&r.migrationPath)console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),console.log(p.green("\u2713"),`Created new migration: ${go(r.migrationPath)}`),console.log(),console.log(p.yellow("\u26A0"),"Please review and edit the new migration before pushing"),console.log(p.dim(` ${r.migrationPath}`));else if(r.error==="No differences found to migrate")await se(e.localPath,e.remoteContent),console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),console.log(p.dim(" (No meaningful code differences found)"));else return console.log(p.red("\u2717"),"Failed to create migration:",r.error),!1;return!0}case"keep-local":return console.log(p.yellow("\u26A0"),`Keeping local version of ${e.filename}`),console.log(p.dim(" Note: This file does not match what's in your database")),!0;case"keep-remote":return await se(e.localPath,e.remoteContent),console.log(p.green("\u2713"),`Restored ${e.filename} to match database`),!0;case"save-both":{let r=he(t,e.filename.replace(".sql",".remote.sql"));return await se(r,e.remoteContent),console.log(p.yellow("\u26A0"),`Keeping local ${e.filename} (doesn't match database)`),console.log(p.green("\u2713"),`Saved database version as ${go(r)}`),!0}case"cancel":return!1}}async function ho(e,o){let n=[],t=ie(process.cwd(),"supabase","migrations"),i=[...e].sort((a,l)=>a.timestamp.localeCompare(l.timestamp)).filter(a=>a.timestamp<=o);if(i.length===0)return n;console.log(),console.log(p.blue("\u2192"),"Reordering local migrations to come after remote...");let s=parseInt(o,10)+1;for(let a of i){let l=String(s).padStart(14,"0"),f=a.filename.replace(a.timestamp,l),g=he(t,f);await hn(a.fullPath,g),n.push({original:a.filename,renamed:f}),console.log(p.dim(` ${a.filename}`)),console.log(p.green(` \u2192 ${f}`)),s++}return n}async function yo(){return console.log(p.blue("\u2192"),"Pulling migrations from remote..."),(await E(["db","pull"],{stream:!0})).success}async function vo(){let e=await Fe();if(e.error)return console.log(p.yellow("\u26A0"),"Could not check migration sync status"),console.log(p.dim(` ${e.error}`)),{success:!0};if(e.remoteMissing.length===0&&e.localMissing.length===0)return{success:!0};if(e.remoteMissing.length===0&&e.localMissing.length>0){let m=await De(),b=m.length>0?m.sort()[m.length-1]:null;if(b&&e.localMissing.some(R=>R<=b)){console.log(p.blue("\u2192"),`${e.localMissing.length} new migration(s) need timestamp adjustment`);let I=(await fe()).filter(ae=>e.localMissing.includes(ae.timestamp)),Z=await ho(I,b);Z.length>0&&console.log(p.green("\u2713"),`Reordered ${Z.length} migration(s) to come after remote`)}else console.log(p.blue("\u2192"),`${e.localMissing.length} new migration(s) ready to push`);return{success:!0}}if(console.log(),console.log(p.yellow("\u26A0"),"Migration sync needed"),console.log(),e.remoteMissing.length>0){console.log(p.dim(" Remote has migrations not saved locally:"));for(let m of e.remoteMissing)console.log(p.blue(` + ${m}`))}if(e.localMissing.length>0){console.log(p.dim(" Local has migrations not on remote:"));for(let m of e.localMissing)console.log(p.green(` + ${m}`))}console.log(),O.note([p.bold("We will:"),"",`${p.blue("1.")} Download ${e.remoteMissing.length} migration file(s) from remote`,`${p.blue("2.")} Check for any content conflicts with your local files`,`${p.blue("3.")} Let you resolve any conflicts`,e.localMissing.length>0?`${p.blue("4.")} Reorder your local migrations if needed, then push`:"","",p.dim("This preserves both remote and local work.")].filter(Boolean).join(`
17
+ `),"Migration Sync");let o=await O.confirm({message:"Proceed with sync?",initialValue:!0});if(O.isCancel(o)||!o)return{success:!1,cancelled:!0};let n=await fe(),t=new Map;for(let m of n)t.set(m.timestamp,m),m.content=await vn(m);let r=O.spinner();r.start("Fetching remote migrations...");let i=await yn();if(!i.success)return r.stop("Fetch failed"),console.log(p.red("\u2717"),"Failed to fetch remote migrations:",i.error),{success:!1};r.stop(`Fetched ${e.remoteMissing.length} migration(s) from remote`);let s=[];for(let[m,b]of t){if(e.remoteMissing.includes(m)||e.localMissing.includes(m))continue;let P=b.fullPath,R;try{R=await wo(P,"utf-8")}catch{continue}let I=b.content??"",Z=fo(I),ae=fo(R);if(Z!==ae){let{additions:we,removals:ye}=qe(I,R);if(we.length===0&&ye.length===0)continue;s.push({timestamp:m,filename:b.filename,localPath:P,localContent:I,remoteContent:R,localHash:Z,remoteHash:ae})}}let a=[...e.remoteMissing].sort(),l=a.length>0?a[a.length-1]:void 0;if(s.length>0){console.log(),console.log(p.yellow("\u26A0"),`Found ${s.length} file(s) with content differences`),console.log();for(let m=0;m<s.length;m++){let b=s[m];if(!b)continue;let P=m+1,R=s.length;console.log(p.blue("\u2500".repeat(60))),console.log(p.blue(` Conflict ${P} of ${R}`)),console.log(p.blue("\u2500".repeat(60)));let I=await Pn(b);if(I==="cancel"){let ye=O.spinner();ye.start("Restoring original files...");for(let[,_e]of t)_e.content&&await se(_e.fullPath,_e.content);return ye.stop("Restored original files"),{success:!1,cancelled:!0}}let Z=O.spinner(),ae=I==="create-migration"?"Creating migration from your edits...":I==="keep-remote"?"Restoring file to match database...":I==="save-both"?"Saving both versions...":"Processing...";Z.start(ae);let we=await $n(b,I,l);if(Z.stop(we?"Complete":"Failed"),!we)return{success:!1,cancelled:!0};m<s.length-1&&(console.log(),console.log(p.dim(" Moving to next conflict...")),console.log())}console.log(),console.log(p.green("\u2713"),`Resolved ${s.length} conflict(s)`)}let f=ie(process.cwd(),"supabase","migrations");for(let m of e.localMissing){let b=t.get(m);if(b&&b.content){let P=he(f,b.filename);await se(P,b.content)}}if(e.localMissing.length>0&&l){let b=(await fe()).filter(R=>e.localMissing.includes(R.timestamp)),P=await ho(b,l);P.length>0&&console.log(p.green("\u2713"),`Reordered ${P.length} local migration(s)`)}console.log(),console.log(p.green("\u2713"),"Migration sync complete!");let g=await fe(),d=await De(),h=g.filter(m=>!d.includes(m.timestamp)).map(m=>m.filename);if(h.length>0){console.log(),console.log(p.blue("\u2192"),`${h.length} migration(s) ready to push:`);for(let m of h)console.log(p.dim(` ${m}`))}return{success:!0}}function Po(){return new Sn("push").description("Push local migrations to the remote database").option("--force","Bypass all safety guards (use with caution)",!1).option("--dry-run","Show what would be pushed without executing",!1).option("--i-know-what-im-doing","Required flag for production operations in CI mode",!1).action(async function(){let e=this.optsWithGlobals();await Rn(e)})}async function Rn(e){Y(),q(),await X();let o=await F(),n=await V(),t=await J(),r;if(e.env)r=H(e.env,o),r||(console.error($.red("\u2717"),`Environment '${e.env}' not found in config`),console.error($.dim(" Available environments:"),Object.keys(o.environments).join(", ")),process.exit(1));else{let s=await _();s||(console.error($.red("\u2717"),"No Supabase project linked"),console.error($.dim(" Run: supacontrol switch <environment>")),process.exit(1)),r=W(s,o),r||(console.error($.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error($.dim(` Linked to: ${s}`)),console.error($.dim(" Run: supacontrol init or add this project to your config")),process.exit(1))}if(e.force)console.log($.yellow("\u26A0"),"Force mode: bypassing all safety guards"),console.log();else{let s={operation:"push",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},a=await pe(s);a.allowed||(a.cancelled&&process.exit(0),process.exit(1)),e.ci&&a.requiresConfirmation&&!e.iKnowWhatImDoing&&(console.error($.red("\u2717"),"CI mode requires --i-know-what-im-doing flag for this operation"),process.exit(1))}if(!e.force){let s=await vo();s.success||(s.cancelled&&process.exit(0),console.log(),console.log($.dim("Use --force to push anyway (not recommended)")),process.exit(1))}if(o.settings.show_migration_diff&&!e.dryRun){console.log($.blue("\u2192"),"Checking for pending migrations...");let s=await E(["db","diff"],{stream:!1});if(s.success&&s.stdout)if(s.stdout.trim().length>0){let l=s.stdout.split(`
18
18
  `),f=l.filter(h=>h.trim().startsWith("create ")).length,g=l.filter(h=>h.trim().startsWith("alter ")).length,d=l.filter(h=>h.trim().startsWith("drop ")).length;f>0||g>0||d>0?console.log($.dim(` Found schema differences: ${f} create, ${g} alter, ${d} drop`)):console.log($.dim(" Schema is in sync"))}else console.log($.dim(" No pending schema changes"));else console.log($.dim(" Could not check for schema changes"));console.log()}if(e.dryRun){console.log($.yellow("\u26A0"),"Dry run mode: no changes will be made"),console.log(),console.log("Would execute:"),console.log($.dim(" supabase db push")),console.log();return}console.log($.blue("\u2192"),"Pushing migrations to",$.cyan(r.name)),console.log();let i=await E(["db","push","--yes"],{stream:!0});i.success?(console.log(),console.log($.green("\u2713"),"Push completed successfully")):(console.log(),console.error($.red("\u2717"),"Push failed"),process.exit(i.exitCode))}import{Command as xn}from"commander";import C from"picocolors";import*as D from"@clack/prompts";function ko(){return new xn("reset").description("Reset database to match local migrations (DESTRUCTIVE)").option("--force","Bypass all safety guards (DANGEROUS)",!1).option("--linked","Reset the linked remote database instead of local",!1).option("--i-know-what-im-doing","Required flag for this operation in CI mode",!1).action(async function(){let e=this.optsWithGlobals();await En(e)})}async function En(e){Y(),q(),await X();let o=await F(),n=await V(),t=await J(),r;if(e.env)r=H(e.env,o),r||(console.error(C.red("\u2717"),`Environment '${e.env}' not found in config`),console.error(C.dim(" Available environments:"),Object.keys(o.environments).join(", ")),process.exit(1));else{let l=await _();l||(console.error(C.red("\u2717"),"No Supabase project linked"),console.error(C.dim(" Run: supacontrol switch <environment>")),process.exit(1)),r=W(l,o),r||(console.error(C.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error(C.dim(` Linked to: ${l}`)),console.error(C.dim(" Run: supacontrol init or add this project to your config")),process.exit(1))}let i=G(r.name,r.config);if(e.ci&&(e.env||(console.error(C.red("\u2717"),"CI mode requires explicit --env flag for reset"),process.exit(1)),e.iKnowWhatImDoing||(console.error(C.red("\u2717"),"CI mode requires --i-know-what-im-doing flag for reset"),process.exit(1))),console.log(),D.note([C.bold(C.red("CRITICAL WARNING")),"",`This will ${C.red("DROP ALL TABLES")} and recreate the database`,"from your local migrations.","",`Environment: ${C.cyan(r.name)}`,e.linked?`Target: ${C.red("REMOTE DATABASE")}`:`Target: ${C.yellow("Local database")}`,i?`
19
19
  ${C.red("\u{1F512} Environment is LOCKED")}`:""].filter(Boolean).join(`
20
- `),C.red("\u26A0\uFE0F Database Reset")),e.force){if(console.log(),console.log(C.red("\u26A0"),C.bold("FORCE MODE ENABLED")),console.log(C.red(" All safety guards are being bypassed!")),console.log(),!e.ci){let l=await D.confirm({message:C.red("Are you absolutely sure you want to proceed?"),initialValue:!1});(D.isCancel(l)||!l)&&(D.cancel("Operation cancelled"),process.exit(0))}}else{let l={operation:"reset",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},f=await pe(l);f.allowed||(f.cancelled&&process.exit(0),process.exit(1))}if(!e.ci&&!e.force){let l=r.config.confirm_word??r.name;console.log();let f=await D.text({message:`Type '${C.bold(C.red(l))}' to confirm database reset:`,validate(g){if(g!==l)return`Please type exactly '${l}' to confirm`}});D.isCancel(f)&&(D.cancel("Operation cancelled"),process.exit(0)),f!==l&&(console.error(C.red("\u2717"),"Confirmation failed"),process.exit(1))}let s=["db","reset"];e.linked&&s.push("--linked"),console.log(),console.log(C.blue("\u2192"),"Resetting database for",C.cyan(r.name)),console.log();let a=await E(s,{stream:!0});a.success?(console.log(),console.log(C.green("\u2713"),"Database reset completed successfully")):(console.log(),console.error(C.red("\u2717"),"Database reset failed"),process.exit(a.exitCode))}import{Command as _n}from"commander";import B from"picocolors";function jo(){return new _n("pull").description("Pull remote schema changes to local migrations").option("--force","Bypass safety guards",!1).action(async function(){let e=this.optsWithGlobals();await Ln(e)})}async function Ln(e){Y(),q(),await X();let o=await F(),n=await V(),t=await J(),r;if(e.env)r=H(e.env,o),r||(console.error(B.red("\u2717"),`Environment '${e.env}' not found in config`),console.error(B.dim(" Available environments:"),Object.keys(o.environments).join(", ")),process.exit(1));else{let s=await _();s||(console.error(B.red("\u2717"),"No Supabase project linked"),console.error(B.dim(" Run: supacontrol switch <environment>")),process.exit(1)),r=W(s,o),r||(console.error(B.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error(B.dim(` Linked to: ${s}`)),console.error(B.dim(" Run: supacontrol init or add this project to your config")),process.exit(1))}if(e.force)console.log(B.yellow("\u26A0"),"Force mode: bypassing safety guards"),console.log();else{let s={operation:"pull",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},a=await pe(s);a.allowed||(a.cancelled&&process.exit(0),process.exit(1))}console.log(B.blue("\u2192"),"Pulling schema from",B.cyan(r.name)),console.log();let i=await E(["db","pull"],{stream:!0});i.success?(console.log(),console.log(B.green("\u2713"),"Pull completed successfully"),console.log(B.dim(" Review the generated migrations in supabase/migrations/"))):(console.log(),console.error(B.red("\u2717"),"Pull failed"),process.exit(i.exitCode))}import{Command as Tn}from"commander";import w from"picocolors";import*as Re from"@clack/prompts";function $o(){return new Tn("switch").description("Switch to a different environment (link to its project)").argument("<environment>","Target environment name").action(async e=>{await On(e)})}async function On(e){q(),await X();let o=await F(),n=H(e,o);if(!n){console.error(w.red("\u2717"),`Environment '${e}' not found in config`),console.error(),console.error(w.dim("Available environments:"));for(let i of ne(o)){let s=o.environments[i];s&&console.error(w.dim(` - ${i}${s.project_ref?` (${s.project_ref})`:""}`))}process.exit(1)}let t=await _();if(!n.projectRef){if(e==="local"){console.log(w.blue("\u2192"),"Switching to local development"),t?(console.log(w.dim(" Unlinking from remote project...")),(await E(["unlink"],{stream:!1})).success?(console.log(w.green("\u2713"),"Unlinked from remote project"),console.log(w.dim(" Now using local database"))):console.log(w.yellow("\u26A0"),"Could not unlink (may already be unlinked)")):console.log(w.green("\u2713"),"Already using local database");return}console.error(w.red("\u2717"),`Environment '${e}' has no project_ref configured`),console.error(w.dim(` Add 'project_ref' to [environments.${e}] in supacontrol.toml`)),process.exit(1)}if(t===n.projectRef){console.log(w.green("\u2713"),`Already linked to ${w.cyan(n.projectRef)}`),console.log(w.dim(` Environment: ${e}`));return}console.log(w.blue("\u2192"),`Switching to ${w.cyan(e)}`),t&&console.log(w.dim(` From: ${t}`)),console.log(w.dim(` To: ${n.projectRef}`)),console.log();let r=await E(["link","--project-ref",n.projectRef],{stream:!0});r.success?(console.log(),console.log(w.green("\u2713"),`Linked to ${w.cyan(n.projectRef)}`),console.log(w.dim(` Environment: ${e}`)),console.log(),await An()):(console.log(),console.error(w.red("\u2717"),"Failed to link to project"),console.error(w.dim(" Make sure you are logged in: supabase login")),process.exit(r.exitCode))}async function An(){let e=await Fe();if(e.needsSync&&e.remoteMissing.length>0){console.log(w.yellow("\u26A0"),`Remote has ${e.remoteMissing.length} migration(s) not in local`);for(let n of e.remoteMissing)console.log(w.dim(` - ${n}`));console.log();let o=await Re.confirm({message:"Pull remote migrations to sync local state?",initialValue:!0});if(Re.isCancel(o))return;o?await wo()?console.log(w.green("\u2713"),"Migrations synced"):console.log(w.yellow("\u26A0"),"Migration sync had issues - check output above"):(console.log(w.dim("Skipped migration sync")),console.log(w.dim(" Run `supabase db pull` manually to sync later")))}else e.localMissing.length>0?(console.log(w.blue("\u2192"),`You have ${e.localMissing.length} local migration(s) to push`),console.log(w.dim(" Run `spc push` when ready"))):console.log(w.green("\u2713"),"Migrations are in sync")}import{Command as Eo}from"commander";import S from"picocolors";import*as ee from"@clack/prompts";import{writeFile as In,access as Mn,constants as Bn}from"fs/promises";import{resolve as So}from"path";var Ro="supacontrol.toml";function Gn(e){return["[settings]","# Fail on any guard warning, not just errors",`strict_mode = ${e.strict_mode}`,"","# Require clean git working tree before destructive operations",`require_clean_git = ${e.require_clean_git}`,"","# Show migration diff before push",`show_migration_diff = ${e.show_migration_diff}`].join(`
20
+ `),C.red("\u26A0\uFE0F Database Reset")),e.force){if(console.log(),console.log(C.red("\u26A0"),C.bold("FORCE MODE ENABLED")),console.log(C.red(" All safety guards are being bypassed!")),console.log(),!e.ci){let l=await D.confirm({message:C.red("Are you absolutely sure you want to proceed?"),initialValue:!1});(D.isCancel(l)||!l)&&(D.cancel("Operation cancelled"),process.exit(0))}}else{let l={operation:"reset",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},f=await pe(l);f.allowed||(f.cancelled&&process.exit(0),process.exit(1))}if(!e.ci&&!e.force){let l=r.config.confirm_word??r.name;console.log();let f=await D.text({message:`Type '${C.bold(C.red(l))}' to confirm database reset:`,validate(g){if(g!==l)return`Please type exactly '${l}' to confirm`}});D.isCancel(f)&&(D.cancel("Operation cancelled"),process.exit(0)),f!==l&&(console.error(C.red("\u2717"),"Confirmation failed"),process.exit(1))}let s=["db","reset"];e.linked&&s.push("--linked"),console.log(),console.log(C.blue("\u2192"),"Resetting database for",C.cyan(r.name)),console.log();let a=await E(s,{stream:!0});a.success?(console.log(),console.log(C.green("\u2713"),"Database reset completed successfully")):(console.log(),console.error(C.red("\u2717"),"Database reset failed"),process.exit(a.exitCode))}import{Command as _n}from"commander";import B from"picocolors";function jo(){return new _n("pull").description("Pull remote schema changes to local migrations").option("--force","Bypass safety guards",!1).action(async function(){let e=this.optsWithGlobals();await Ln(e)})}async function Ln(e){Y(),q(),await X();let o=await F(),n=await V(),t=await J(),r;if(e.env)r=H(e.env,o),r||(console.error(B.red("\u2717"),`Environment '${e.env}' not found in config`),console.error(B.dim(" Available environments:"),Object.keys(o.environments).join(", ")),process.exit(1));else{let s=await _();s||(console.error(B.red("\u2717"),"No Supabase project linked"),console.error(B.dim(" Run: supacontrol switch <environment>")),process.exit(1)),r=W(s,o),r||(console.error(B.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error(B.dim(` Linked to: ${s}`)),console.error(B.dim(" Run: supacontrol init or add this project to your config")),process.exit(1))}if(e.force)console.log(B.yellow("\u26A0"),"Force mode: bypassing safety guards"),console.log();else{let s={operation:"pull",environmentName:r.name,environment:r.config,config:o,gitBranch:n,isCI:e.ci,hasUncommittedChanges:t},a=await pe(s);a.allowed||(a.cancelled&&process.exit(0),process.exit(1))}console.log(B.blue("\u2192"),"Pulling schema from",B.cyan(r.name)),console.log();let i=await E(["db","pull"],{stream:!0});i.success?(console.log(),console.log(B.green("\u2713"),"Pull completed successfully"),console.log(B.dim(" Review the generated migrations in supabase/migrations/"))):(console.log(),console.error(B.red("\u2717"),"Pull failed"),process.exit(i.exitCode))}import{Command as Tn}from"commander";import y from"picocolors";import*as Re from"@clack/prompts";function $o(){return new Tn("switch").description("Switch to a different environment (link to its project)").argument("<environment>","Target environment name").action(async e=>{await On(e)})}async function On(e){q(),await X();let o=await F(),n=H(e,o);if(!n){console.error(y.red("\u2717"),`Environment '${e}' not found in config`),console.error(),console.error(y.dim("Available environments:"));for(let i of ne(o)){let s=o.environments[i];s&&console.error(y.dim(` - ${i}${s.project_ref?` (${s.project_ref})`:""}`))}process.exit(1)}let t=await _();if(!n.projectRef){if(e==="local"){console.log(y.blue("\u2192"),"Switching to local development"),t?(console.log(y.dim(" Unlinking from remote project...")),(await E(["unlink"],{stream:!1})).success?(console.log(y.green("\u2713"),"Unlinked from remote project"),console.log(y.dim(" Now using local database"))):console.log(y.yellow("\u26A0"),"Could not unlink (may already be unlinked)")):console.log(y.green("\u2713"),"Already using local database");return}console.error(y.red("\u2717"),`Environment '${e}' has no project_ref configured`),console.error(y.dim(` Add 'project_ref' to [environments.${e}] in supacontrol.toml`)),process.exit(1)}if(t===n.projectRef){console.log(y.green("\u2713"),`Already linked to ${y.cyan(n.projectRef)}`),console.log(y.dim(` Environment: ${e}`));return}console.log(y.blue("\u2192"),`Switching to ${y.cyan(e)}`),t&&console.log(y.dim(` From: ${t}`)),console.log(y.dim(` To: ${n.projectRef}`)),console.log();let r=await E(["link","--project-ref",n.projectRef],{stream:!0});r.success?(console.log(),console.log(y.green("\u2713"),`Linked to ${y.cyan(n.projectRef)}`),console.log(y.dim(` Environment: ${e}`)),console.log(),await An()):(console.log(),console.error(y.red("\u2717"),"Failed to link to project"),console.error(y.dim(" Make sure you are logged in: supabase login")),process.exit(r.exitCode))}async function An(){let e=await Fe();if(e.needsSync&&e.remoteMissing.length>0){console.log(y.yellow("\u26A0"),`Remote has ${e.remoteMissing.length} migration(s) not in local`);for(let n of e.remoteMissing)console.log(y.dim(` - ${n}`));console.log();let o=await Re.confirm({message:"Pull remote migrations to sync local state?",initialValue:!0});if(Re.isCancel(o))return;o?await yo()?console.log(y.green("\u2713"),"Migrations synced"):console.log(y.yellow("\u26A0"),"Migration sync had issues - check output above"):(console.log(y.dim("Skipped migration sync")),console.log(y.dim(" Run `supabase db pull` manually to sync later")))}else e.localMissing.length>0?(console.log(y.blue("\u2192"),`You have ${e.localMissing.length} local migration(s) to push`),console.log(y.dim(" Run `spc push` when ready"))):console.log(y.green("\u2713"),"Migrations are in sync")}import{Command as Eo}from"commander";import S from"picocolors";import*as ee from"@clack/prompts";import{writeFile as In,access as Mn,constants as Bn}from"fs/promises";import{resolve as So}from"path";var Ro="supacontrol.toml";function Gn(e){return["[settings]","# Fail on any guard warning, not just errors",`strict_mode = ${e.strict_mode}`,"","# Require clean git working tree before destructive operations",`require_clean_git = ${e.require_clean_git}`,"","# Show migration diff before push",`show_migration_diff = ${e.show_migration_diff}`].join(`
21
21
  `)}function Nn(e,o){let n=[`[environments.${e}]`];return o.project_ref!==void 0&&(n.push("# Supabase project reference"),n.push(`project_ref = "${o.project_ref}"`)),o.git_branches.length>0&&(n.push("# Git branches that map to this environment"),n.push(`git_branches = [${o.git_branches.map(t=>`"${t}"`).join(", ")}]`)),o.protected_operations.length>0&&(n.push("# Operations that require confirmation"),n.push(`protected_operations = [${o.protected_operations.map(t=>`"${t}"`).join(", ")}]`)),o.confirm_word!==void 0&&(n.push("# Custom confirmation word (type this to confirm)"),n.push(`confirm_word = "${o.confirm_word}"`)),o.locked!==void 0&&(n.push("# Lock environment to prevent all destructive operations"),n.push(`locked = ${o.locked}`)),n.join(`
22
- `)}function Dn(e){let o=["# SupaControl Configuration","# https://github.com/your-org/supacontrol","",Gn(e.settings)],n=Object.keys(e.environments);if(n.length>0){o.push("");for(let t of n){let r=e.environments[t];r&&(o.push(Nn(t,r)),o.push(""))}}return o.join(`
22
+ `)}function Dn(e){let o=["# SupaControl Configuration","# https://github.com/haal-laah/supacontrol","",Gn(e.settings)],n=Object.keys(e.environments);if(n.length>0){o.push("");for(let t of n){let r=e.environments[t];r&&(o.push(Nn(t,r)),o.push(""))}}return o.join(`
23
23
  `).trimEnd()+`
24
24
  `}async function xo(e){let o=e??process.cwd(),n=So(o,Ro);try{return await Mn(n,Bn.F_OK),!0}catch{return!1}}async function be(e,o){let n=o??process.cwd(),t=So(n,Ro),r=Dn(e);return await In(t,r,"utf-8"),t}function _o(){return new Eo("lock").description("Lock an environment to prevent destructive operations").argument("[environment]","Environment to lock (defaults to current)").action(async function(e){let o=this.optsWithGlobals();await Fn(e,o)})}function Lo(){return new Eo("unlock").description("Unlock an environment to allow destructive operations").argument("[environment]","Environment to unlock (defaults to current)").action(async function(e){let o=this.optsWithGlobals();await qn(e,o)})}async function Fn(e,o){Y();let n=await F(),t=await To(e,n);t||process.exit(1);let r=n.environments[t];if(r||(console.error(S.red("\u2717"),`Environment '${t}' not found`),process.exit(1)),G(t,r)){console.log(S.green("\u2713"),`Environment '${t}' is already locked`);return}r.locked=!0,await be(n),console.log(S.green("\u2713"),`Locked environment '${S.cyan(t)}'`),console.log(S.dim(" Destructive operations are now blocked"))}async function qn(e,o){Y();let n=await F(),t=await To(e,n);t||process.exit(1);let r=n.environments[t];if(r||(console.error(S.red("\u2717"),`Environment '${t}' not found`),process.exit(1)),!G(t,r)){console.log(S.green("\u2713"),`Environment '${t}' is already unlocked`);return}if((t==="production"||r.git_branches.includes("main")||r.git_branches.includes("master"))&&!o.ci){console.log(),ee.note([S.yellow("Warning: Unlocking production environment"),"","This will allow destructive operations like:"," - db push"," - db reset"," - db seed"].join(`
25
25
  `),S.yellow("\u26A0 Production Unlock"));let a=await ee.confirm({message:`Are you sure you want to unlock '${t}'?`,initialValue:!1});(ee.isCancel(a)||!a)&&(ee.cancel("Operation cancelled"),process.exit(0))}r.locked=!1,await be(n),console.log(S.yellow("\u26A0"),`Unlocked environment '${S.cyan(t)}'`),console.log(S.dim(" Destructive operations are now allowed")),console.log(S.dim(` Run 'supacontrol lock ${t}' to re-lock`))}async function To(e,o){if(!o)return null;if(e){if(!H(e,o)){console.error(S.red("\u2717"),`Environment '${e}' not found in config`),console.error(),console.error(S.dim("Available environments:"));for(let i of ne(o))console.error(S.dim(` - ${i}`));return null}return e}q();let n=await _();if(!n)return console.error(S.red("\u2717"),"No Supabase project linked"),console.error(S.dim(" Specify environment: supacontrol lock <environment>")),console.error(S.dim(" Or run: supacontrol switch <environment>")),null;let t=W(n,o);return t?t.name:(console.error(S.red("\u2717"),"Linked project is not configured in supacontrol.toml"),console.error(S.dim(" Specify environment: supacontrol lock <environment>")),null)}import{Command as Un}from"commander";import L from"picocolors";import{access as Oo,constants as Ao}from"fs/promises";import{resolve as Io}from"path";function Mo(){return new Un("doctor").description("Check for common issues and misconfigurations").option("--verbose","Show detailed output",!1).option("--report","Show summary only",!1).action(async e=>{await Hn(e)})}async function Hn(e){console.log(),console.log(L.bold("SupaControl Doctor")),console.log(L.dim("Checking your project setup...")),console.log();let o=[];o.push(await Vn()),o.push(await zn()),o.push(await Yn()),o.push(await Kn()),o.push(await Zn()),o.push(await Jn()),o.push(await Qn());let n=o.filter(i=>i.status==="pass").length,t=o.filter(i=>i.status==="warn").length,r=o.filter(i=>i.status==="fail").length;if(!e.report){for(let i of o)Wn(i,e.verbose);console.log()}console.log(L.bold("Summary")),console.log(L.dim("\u2500".repeat(40))),console.log(` ${L.green("\u2713")} ${n} passed`),t>0&&console.log(` ${L.yellow("\u26A0")} ${t} warnings`),r>0&&console.log(` ${L.red("\u2717")} ${r} failed`),console.log(),(t>0||r>0)&&(console.log(L.dim("Fix the issues above to improve your setup.")),console.log()),r>0&&process.exit(1)}function Wn(e,o){let n=e.status==="pass"?L.green("\u2713"):e.status==="warn"?L.yellow("\u26A0"):e.status==="fail"?L.red("\u2717"):L.blue("\u2139");if(console.log(`${n} ${e.name}`),console.log(L.dim(` ${e.message}`)),o&&e.details)for(let t of e.details)console.log(L.dim(` - ${t}`));e.fix&&(e.status==="warn"||e.status==="fail")&&console.log(L.dim(` Fix: ${e.fix}`)),console.log()}async function Vn(){if(!await me())return{name:"Supabase CLI",status:"fail",message:"Supabase CLI is not installed",fix:"npm install -g supabase"};let o=await je();return{name:"Supabase CLI",status:"pass",message:`Installed${o?` (v${o})`:""}`}}async function zn(){if(!await Ce())return{name:"Git Repository",status:"warn",message:"Not a git repository",details:["Git branch detection will not work","Auto-environment switching disabled"],fix:"git init"};let o=await V();return{name:"Git Repository",status:"pass",message:o?`On branch '${o}'`:"Repository detected"}}async function Yn(){let e=await ce();if(!e)return{name:"SupaControl Config",status:"warn",message:"No supacontrol.toml found",details:["Create a config to enable environment protection"],fix:"supacontrol init"};let o=ne(e).length;return{name:"SupaControl Config",status:"pass",message:`Loaded with ${o} environment${o!==1?"s":""}`}}async function Kn(){let e=Io(process.cwd(),"supabase");try{return await Oo(e,Ao.F_OK),{name:"Supabase Project",status:"pass",message:"supabase/ directory found"}}catch{return{name:"Supabase Project",status:"warn",message:"No supabase/ directory found",details:["Initialize a Supabase project to use database features"],fix:"supabase init"}}}async function Zn(){let e=await _();return e?{name:"Linked Project",status:"pass",message:`Linked to ${e}`}:{name:"Linked Project",status:"info",message:"No project linked (using local database)",details:["Link a project to push migrations to remote"]}}async function Jn(){let e=await ce();if(!e)return{name:"Environment Safety",status:"info",message:"No config to check"};let o=ne(e),n=[],t=[];for(let r of o){let i=e.environments[r];if(!i)continue;G(r,i)?n.push(r):(r==="production"||i.git_branches.includes("main")||i.git_branches.includes("master"))&&t.push(r)}return t.length>0?{name:"Environment Safety",status:"warn",message:`Production environment(s) unlocked: ${t.join(", ")}`,details:["Consider locking production to prevent accidental changes"],fix:`supacontrol lock ${t[0]}`}:n.length>0?{name:"Environment Safety",status:"pass",message:`${n.length} environment${n.length!==1?"s":""} locked`,details:n.map(r=>`${r} is locked`)}:{name:"Environment Safety",status:"info",message:"No locked environments"}}async function Qn(){let e=Io(process.cwd(),"supabase/migrations");try{return await Oo(e,Ao.F_OK),{name:"Migrations",status:"pass",message:"Migrations directory found"}}catch{return{name:"Migrations",status:"info",message:"No migrations directory",details:["Create migrations with `supabase migration new <name>`"]}}}import{Command as et}from"commander";import{access as ot,constants as nt}from"fs/promises";import{resolve as tt}from"path";import*as u from"@clack/prompts";import c from"picocolors";import*as xe from"@clack/prompts";import U from"picocolors";function Xn(e){return new Date(e).toLocaleDateString("en-US",{year:"numeric",month:"short",day:"numeric"})}function Ue(e){let o=e.status==="ACTIVE_HEALTHY"?U.green("Active"):e.status==="PAUSED"?U.yellow("Paused"):U.red(e.status);console.log(),console.log(U.bold("Selected project:")),console.log(` ${U.cyan("Name:")} ${e.name}`),console.log(` ${U.cyan("Ref:")} ${e.id}`),console.log(` ${U.cyan("Region:")} ${e.region}`),console.log(` ${U.cyan("Status:")} ${o}`),console.log(` ${U.cyan("Created:")} ${Xn(e.created_at)}`),e.database&&console.log(` ${U.cyan("DB Host:")} ${U.dim(e.database.host)}`),console.log()}async function rt(){let e=tt(process.cwd(),"supabase","config.toml");try{return await ot(e,nt.F_OK),!0}catch{return!1}}var He={local:{label:"Local only",hint:"Just local development with Supabase CLI"},"local-staging":{label:"Local + Staging",hint:"Local dev + one remote staging environment"},"local-staging-production":{label:"Local + Staging + Production",hint:"Full setup with staging and production"}};function st(e,o){let n={};return(e==="local-staging"||e==="local-staging-production")&&(n.staging={project_ref:o.staging,git_branches:["develop","staging"],protected_operations:["reset"],confirm_word:void 0,locked:void 0}),e==="local-staging-production"&&(n.production={project_ref:o.production,git_branches:["main","master"],protected_operations:["push","reset","seed"],confirm_word:"production",locked:!0}),n}function it(){u.note([`${c.bold("Supabase Branching")} is now available!`,"","With branching, you can have isolated database environments","for each Git branch or PR, without managing multiple projects.","",`Learn more: ${c.cyan("https://supabase.com/docs/guides/platform/branching")}`].join(`