@turbobase/cli 1.1.9 → 1.1.11
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.
|
@@ -1600,7 +1600,7 @@ jobs:
|
|
|
1600
1600
|
--project . \\
|
|
1601
1601
|
--follow
|
|
1602
1602
|
`;function Dg(){return[{path:ku,content:_T}]}import Ii from"node:fs/promises";import Jo from"node:path";var Su={esbuild:"0.28.1","isolated-vm":"6.1.2"},Tu="@turbobase/isolated-vm-prebuilds";function Eu(t,e){return`${t}-${e}`}function Ru(t=process.versions){return`abi${t.modules}`}var Zg=["darwin-x64","win32-arm64"];async function Ug(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=Eu(e,r),o=Jo.join(t.home,"node_modules"),s=Jo.join(o,...Tu.split("/"),"prebuilds",n),i=Jo.join(o,"isolated-vm","prebuilds",n),a;try{a=await Ii.readdir(s)}catch{return[]}let c=[];await Ii.mkdir(i,{recursive:!0});for(let u of a){if(!u.endsWith(".node"))continue;let d=Jo.join(i,u);try{await Ii.copyFile(Jo.join(s,u),d,Ii.constants?.COPYFILE_EXCL??1),c.push(u)}catch{}}return c}import{homedir as wT}from"node:os";import{join as qg}from"node:path";import{promises as Pu}from"node:fs";function Bg(){let t=process.env.TURBOBASE_HOME??qg(wT(),".turbobase");return{dir:t,file:qg(t,"sandboxes.json")}}var vT={s:1e3,m:6e4,h:36e5,d:864e5};function xT(t){let e=/^\s*(\d+)\s*([smhd])\s*$/.exec(t);if(!e)throw new Error(`invalid --ttl '${t}': expected <number><unit> where unit is s|m|h|d (e.g. 72h, 30m, 7d).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<=0)throw new Error(`invalid --ttl '${t}': duration must be a positive integer.`);return r*vT[e[2]]}function $T(t,e=Date.now()){return new Date(e+xT(t)).toISOString()}function kT(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function ST(t){let e=/^\s*(\d+(?:\.\d+)?)\s*%?\s*$/.exec(t);if(!e)throw new Error(`invalid --sample '${t}': expected a percent like 2% (or a bare number).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<0||r>100)throw new Error(`invalid --sample '${t}': percent must be between 0 and 100.`);return r/100}function TT(t,e){if(e>=1)return[...t];if(e<=0||t.length===0)return[];let r=Math.max(1,Math.round(1/e)),n=[];for(let o=0;o<t.length;o+=r)n.push(t[o]);return n}var ET="[REDACTED]",Yo=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Au=class extends Error{table;column;constructor(e,r,n){super(`Refusing to copy ${e}.${r} into a sandbox: ${n}. A sandbox must never hold un-redacted PII \u2014 classify the column 'safe' (non-PII) or let redaction mask it, then retry.`),this.name="UnredactedPiiError",this.table=e,this.column=r}};function RT(t,e,r){let n={};for(let[o,s]of Object.entries(e)){let i=r[o];if(i===void 0)throw new Au(t,o,"column has no PII classification (unknown = unsafe)");i==="pii"?n[o]=s==null?s:ET:n[o]=s}return n}function PT(t,e,r){return e.map(n=>RT(t,n,r))}var Fg=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),AT=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function IT(t){let e=t.trim();try{let o=new URL(e);if(o.hostname)return o.hostname.toLowerCase()}catch{}let r=e.replace(/^[a-z]+:\/\//i,""),n=(r.split("/")[0]??r).split("@").pop()??r;return(n.split(":")[0]??n).toLowerCase()}function Iu(t){if(!t||!t.trim())throw new Error("local-only guard: empty database target \u2014 refusing to proceed.");let e=t.toLowerCase();for(let n of AT)if(e.includes(n))throw new Error(`local-only guard: target '${t}' looks like a cloud/prod context (matched '${n}'). \`turbobase sandbox create\` is LOCAL-ONLY and will never touch a cloud database.`);let r=IT(t);if(!Fg.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...Fg].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function OT(t,e=CT){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function CT(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function zT(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function NT(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Ou(){let{file:t}=Bg();try{let e=await Pu.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function Vg(t){let{dir:e,file:r}=Bg();await Pu.mkdir(e,{recursive:!0}),await Pu.writeFile(r,`${JSON.stringify(t,null,2)}
|
|
1603
|
-
`,{mode:384})}async function jT(t){let e=await Ou();e.push(t),await Vg(e)}async function Hg(t){let e=await Ou(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Vg(e),n??null}async function Oi(){return Ou()}function Cu(t,e=Date.now()){let r=[],n=[];for(let o of t)kT(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Kg(t,e){Iu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new Yo(r);let n=ST(e.sample),o=e.now??Date.now(),s=(e.nameGen??(u=>OT(u)))(e.fromTenant),i=zT(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let d=TT(u.rows,n),l=PT(u.table,d,u.classification);l.length>0&&await t.copyRows(i,u.table,l)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:$T(e.ttl,o),sampleFraction:n,url:NT(s,e.baseUrl)};return await jT(c),c}async function Wg(t,e){Iu(t.target());let n=(await Oi()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Hg(e),n):null}async function zu(t,e=Date.now()){Iu(t.target());let r=await Oi(),{expired:n}=Cu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Hg(s.name),o.push(s)}return o}var Xo={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},mD=Object.keys(Xo).filter(t=>Xo[t].mintable);var bD=Xo.pat.prefix,MT=Object.entries(Xo).map(([t,e])=>[e.prefix,t]),LT=/^[A-Za-z0-9_-]{20,}$/;function DT(t){if(typeof t!="string")return null;for(let[e,r]of MT)if(t.startsWith(e)&<.test(t.slice(e.length)))return r;return null}function Gg(t){return DT(t)==="pat"}var Qo="bundle.json",Ci="turbobase";function Yg(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let o=c=>Array.isArray(c)?c:[],s=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:o(r.hooks),conduits:o(r.conduits),sync:{fields:o(s.fields),tables:o(s.tables)},guardrails:{validationHooks:o(i.validationHooks),fieldRules:o(i.fieldRules),conduits:o(i.conduits)},schedules:o(r.schedules)};return ZT(a)}function ZT(t){let e=r=>(n,o)=>r(n)<r(o)?-1:r(n)>r(o)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(Jg)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(Jg)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function Jg(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function Xg(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function cj(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function uj(t){let e=await Ar();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),gn()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await yn(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function lj(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(o){return{ok:!1,kind:"unreachable",detail:o instanceof Error?o.message:String(o)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function dj(t,e,r){let n=await Gm({baseUrl:t,token:e}),o=(await Ar()).length-1;if(R(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),!r)return;let s=await lj(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(qe(`warning: ${n.baseUrl} did not respond to the TurboBase API (/v1) \u2014 got ${s.detail}.`),qe(" In local dev the TurboBase API is on :4100, not the app dev-server port (e.g. :5173)."),qe(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?qe(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):qe(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function pj(t){let e=await Vo(t),r=c=>{let u=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),o=r("id");if(n){let c=r("on"),u=r("recordType"),d=r("source");return(!o||!u||!c||!d)&&k(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:o,recordType:u,event:c,source:d,sourceCode:e,extensionKind:"conduit"}}let s=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!o||!s||!i)&&k(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:o,recordType:s,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Po(t,e,r,n){try{return await _e.access(t),"skipped"}catch{}return await _e.mkdir(ni(t),{recursive:!0}),await _e.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await _e.chmod(t,n),"wrote"}async function V$(t,e){if(H$(t)!==".gitignore")return Po(t,e.content,e.executable,e.mode);let r=await _e.readFile(t,"utf8").catch(()=>null);if(r===null)return Po(t,e.content,e.executable,e.mode);let n=Tg(r);return n===null?"skipped":(await _e.writeFile(t,n,"utf8"),"merged")}function Jh(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function fj(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",o=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:o,type:n}})}}function hj(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function mj(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function rm({includeInternal:t=!0}={}){let e=new bm;e.name("turbobase").description("Build apps, author extensions, and drive your TurboBase instance.").version("1.1.
|
|
1603
|
+
`,{mode:384})}async function jT(t){let e=await Ou();e.push(t),await Vg(e)}async function Hg(t){let e=await Ou(),r=e.findIndex(o=>o.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Vg(e),n??null}async function Oi(){return Ou()}function Cu(t,e=Date.now()){let r=[],n=[];for(let o of t)kT(o.expiresAt,e)?n.push(o):r.push(o);return{active:r,expired:n}}async function Kg(t,e){Iu(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new Yo(r);let n=ST(e.sample),o=e.now??Date.now(),s=(e.nameGen??(u=>OT(u)))(e.fromTenant),i=zT(s);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let d=TT(u.rows,n),l=PT(u.table,d,u.classification);l.length>0&&await t.copyRows(i,u.table,l)}let c={name:s,fromTenant:e.fromTenant,database:i,createdAt:new Date(o).toISOString(),expiresAt:$T(e.ttl,o),sampleFraction:n,url:NT(s,e.baseUrl)};return await jT(c),c}async function Wg(t,e){Iu(t.target());let n=(await Oi()).find(o=>o.name===e);return n?(await t.dropDatabase(n.database),await Hg(e),n):null}async function zu(t,e=Date.now()){Iu(t.target());let r=await Oi(),{expired:n}=Cu(r,e),o=[];for(let s of n){try{await t.dropDatabase(s.database)}catch{continue}await Hg(s.name),o.push(s)}return o}var Xo={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},mD=Object.keys(Xo).filter(t=>Xo[t].mintable);var bD=Xo.pat.prefix,MT=Object.entries(Xo).map(([t,e])=>[e.prefix,t]),LT=/^[A-Za-z0-9_-]{20,}$/;function DT(t){if(typeof t!="string")return null;for(let[e,r]of MT)if(t.startsWith(e)&<.test(t.slice(e.length)))return r;return null}function Gg(t){return DT(t)==="pat"}var Qo="bundle.json",Ci="turbobase";function Yg(t){let e;try{e=JSON.parse(t)}catch(c){throw new Error(`bundle.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let o=c=>Array.isArray(c)?c:[],s=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:o(r.hooks),conduits:o(r.conduits),sync:{fields:o(s.fields),tables:o(s.tables)},guardrails:{validationHooks:o(i.validationHooks),fieldRules:o(i.fieldRules),conduits:o(i.conduits)},schedules:o(r.schedules)};return ZT(a)}function ZT(t){let e=r=>(n,o)=>r(n)<r(o)?-1:r(n)>r(o)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(Jg)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(Jg)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function Jg(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function Xg(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function cj(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function uj(t){let e=await Ar();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),gn()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await yn(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function lj(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(o){return{ok:!1,kind:"unreachable",detail:o instanceof Error?o.message:String(o)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function dj(t,e,r){let n=await Gm({baseUrl:t,token:e}),o=(await Ar()).length-1;if(R(`signed in to ${n.baseUrl} \u2014 now the active instance`+(o>0?` (${o} other${o===1?"":"s"} stored; commands default here).`:".")),!r)return;let s=await lj(n.baseUrl,e);s.ok||(s.kind==="wrong-host"?(qe(`warning: ${n.baseUrl} did not respond to the TurboBase API (/v1) \u2014 got ${s.detail}.`),qe(" In local dev the TurboBase API is on :4100, not the app dev-server port (e.g. :5173)."),qe(" The token is stored, but commands will fail against this base-url until it points at the API.")):s.kind==="bad-token"?qe(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):qe(`warning: could not reach ${n.baseUrl} to verify the token (${s.detail}). Stored anyway.`))}async function pj(t){let e=await Vo(t),r=c=>{let u=new RegExp(`${c}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),o=r("id");if(n){let c=r("on"),u=r("recordType"),d=r("source");return(!o||!u||!c||!d)&&k(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:o,recordType:u,event:c,source:d,sourceCode:e,extensionKind:"conduit"}}let s=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!o||!s||!i)&&k(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:o,recordType:s,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Po(t,e,r,n){try{return await _e.access(t),"skipped"}catch{}return await _e.mkdir(ni(t),{recursive:!0}),await _e.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await _e.chmod(t,n),"wrote"}async function V$(t,e){if(H$(t)!==".gitignore")return Po(t,e.content,e.executable,e.mode);let r=await _e.readFile(t,"utf8").catch(()=>null);if(r===null)return Po(t,e.content,e.executable,e.mode);let n=Tg(r);return n===null?"skipped":(await _e.writeFile(t,n,"utf8"),"merged")}function Jh(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function fj(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",o=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:o,type:n}})}}function hj(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function mj(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function rm({includeInternal:t=!0}={}){let e=new bm;e.name("turbobase").description("Build apps, author extensions, and drive your TurboBase instance.").version("1.1.11").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",m=>m.command.parent?"":`${yu()}
|
|
1604
1604
|
`),e.hook("preAction",(m,y)=>{Rg(e.opts(),y.opts())});let r=e.command("auth").description("Sign in to an instance and manage stored credentials."),n=e.command("project").description("Set up the local project and the files an agent session reads."),o=e.command("ext").description("Author, test, ship, and observe extensions \u2014 hooks and conduits."),s=e.command("kb").description("Write into the instance's knowledge base.");r.command("login").description("Sign in to an instance by pasting a personal access token (PAT), stored in ~/.turbobase/credentials.").requiredOption("--base-url <url>","Instance base URL, e.g. https://acme.turbobase.app").option("--token <tb_pat_\u2026>","The PAT to store. Mint one in your app under Settings \u203A Access tokens.").option("--no-verify","Skip the post-login connectivity/token probe.").action(async m=>{let y=At(m.baseUrl);m.token||k(`auth login needs --token. Mint a PAT in your TurboBase app (Settings \u203A Access tokens), then:
|
|
1605
1605
|
${Wo} auth login --base-url ${y} --token tb_pat_\u2026`),Gg(m.token)||k("--token does not look like a TurboBase PAT (tb_pat_\u2026)."),await dj(y,m.token,m.verify)}),r.command("status").description("Show the active instance (the no-`--base-url` default) and every stored sign-in.").action(async()=>{let m=await Ar();if(m.length===0){R("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`.");return}let y=await rr(),_=w=>!!y&&w.baseUrl===y.baseUrl;if(bt())R(Ko(m.map(w=>[_(w)?I.green("*"):" ",I.bold(w.baseUrl),I.dim(w.updatedAt)]),{head:["","instance","updated"]}));else for(let w of m)R(`${_(w)?"* ":" "}${w.baseUrl} (updated ${w.updatedAt})`);y||qe("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}),r.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async m=>{let y=m??await uj("Pick the active instance:"),_=await xi(y);if(!_){let w=await Ar();k(`no stored sign-in for ${At(y)}.`+(w.length?` Stored: ${w.map(x=>x.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${At(y)} --token tb_pat_\u2026\`.`)}R(`active instance \u2192 ${_.baseUrl}.`)}),r.command("logout").description("Remove a stored sign-in (defaults to the active instance).").option("--base-url <url>","Which instance to remove (defaults to the active/single one).").option("--all","Remove every stored sign-in.").action(async m=>{if(m.all){let w=await Ar();for(let x of w)await iu(x.baseUrl);R(`removed ${w.length} stored sign-in${w.length===1?"":"s"}.`);return}let y=await iu(m.baseUrl);y||k(m.baseUrl?`no stored sign-in for ${At(m.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let _=await rr();R(`signed out of ${y}.`+(_?` Active instance \u2192 ${_.baseUrl}.`:""))}),n.command("types").description("Generate types.ts from the signed-in instance's catalog.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("-o, --out <file>","Output file","types.ts").option("--records-only","Emit only record-shape interfaces (skip the OpenAPI half).").action(async m=>{let{baseUrl:y,token:_}=await ke(m.baseUrl),w;try{w=await Pr({baseUrl:y,token:_,recordsOnly:m.recordsOnly})}catch(v){let $=v instanceof Error?v.message:String(v);if(!m.recordsOnly&&/openapi\.json failed \(401/.test($)){let O=await cj(y,_);if(O)try{w=await Pr({baseUrl:y,token:O,recordsOnly:!1});let M=$e(process.cwd(),m.out);await _e.writeFile(M,w,"utf8"),R(`wrote ${M} (${w.split(`
|
|
1606
1606
|
`).length} lines).`);return}catch{}qe("types-gen: /v1/catalog/openapi.json needs a session \u2014 emitting record shapes only. To get the OpenAPI half headlessly, mint a PAT with the `data:read` scope (Settings \u203A Tokens); the CLI then exchanges it for a short-lived session automatically.");try{w=await Pr({baseUrl:y,token:_,recordsOnly:!0})}catch(M){k(`types generation failed: ${M instanceof Error?M.message:String(M)}`)}}else k(`types generation failed: ${$}`)}let x=$e(process.cwd(),m.out);await _e.writeFile(x,w,"utf8"),R(`wrote ${x} (${w.split(`
|
|
@@ -1630,7 +1630,7 @@ Next: mint a releases:write PAT in your TurboBase app (Settings \u203A Access to
|
|
|
1630
1630
|
samples init: ${O} written, ${M} skipped.`)});let Ke=e.command("sandbox").description("Ephemeral local authoring sandboxes.");Ke.command("create").description("Stand up a fresh local sandbox seeded with a sampled+redacted slice of an instance.").requiredOption("--from <instance>","Source instance to clone the data slice from.").option("--ttl <duration>","Time-to-live before reaping (e.g. 72h, 30m, 7d).","72h").option("--sample <pct>","Percent of each table to copy (e.g. 2%).","2%").option("--base-url <url>","Local base URL the sandbox is reachable at.").action(async m=>{process.env.TURBOBASE_SANDBOX="1";let y=Xh(),_;try{_=await Kg(y,{fromTenant:m.from,ttl:m.ttl,sample:m.sample,baseUrl:m.baseUrl})}catch(w){w instanceof Yo&&k(w.message),k(`sandbox create failed: ${w instanceof Error?w.message:String(w)}`)}R(`sandbox ${_.name} ready.`),R(` source: ${_.fromTenant} (sampled ${Math.round(_.sampleFraction*100)}%, redacted)`),R(` url: ${_.url}`),R(` expires: ${_.expiresAt}`)}),Ke.command("destroy").argument("<name>","The sandbox name to tear down.").description("Drop a sandbox database and remove it from the registry.").action(async m=>{let y=Xh();await zu(y).catch(()=>[]);let _;try{_=await Wg(y,m)}catch(w){k(`sandbox destroy failed: ${w instanceof Error?w.message:String(w)}`)}_||k(`no sandbox named '${m}'.`),R(`destroyed sandbox ${_.name} (dropped ${_.database}).`)}),Ke.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let m=Xh(),y=await zu(m).catch(()=>[]),_=await Oi(),{active:w}=Cu(_);if(w.length===0)R("no active sandboxes.");else{for(let x of w)R(` ${x.name} from=${x.fromTenant} expires=${x.expiresAt} ${x.url}`);R(`
|
|
1631
1631
|
${w.length} active sandbox(es).`)}y.length>0&&R(`(reaped ${y.length} expired sandbox(es).)`)}),o.command("export").description("Snapshot the live instance into turbobase/bundle.json.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",Ci).action(async m=>{let{baseUrl:y,token:_}=await ke(m.baseUrl),w=new xe({baseUrl:y,token:_}),x;try{x=await je("exporting instance config",()=>w.configExport())}catch(O){O instanceof Re&&(O.status===401||O.status===403)&&k("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),k(`ext export failed: ${O instanceof Error?O.message:String(O)}`)}let v=typeof x.serialized=="string"?x.serialized:`${JSON.stringify(x.manifest??x,null,2)}
|
|
1632
1632
|
`,$=$e(process.cwd(),m.out,Qo);await _e.mkdir(ni($),{recursive:!0}),await _e.writeFile($,v,"utf8"),R(`wrote ${m.out}/${Qo} (${v.split(`
|
|
1633
|
-
`).length} lines).`)});let dr=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Ao=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],$r=()=>process.env.TURBOBASE_HOME?.trim()||Jr(aj(),".turbobase"),Io=()=>"1.1.
|
|
1633
|
+
`).length} lines).`)});let dr=o.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Ao=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],$r=()=>process.env.TURBOBASE_HOME?.trim()||Jr(aj(),".turbobase"),Io=()=>"1.1.11",Yr=async()=>Promise.all(Ao.map(async m=>{try{return await import(m.name),{...m,present:!0}}catch{return{...m,present:!1}}}));dr.command("status").description("Report which optional native packages are installed.").action(async()=>{let m=await Yr();for(let y of m)R(`${y.present?ct.success("installed"):ct.warn("missing")} ${I.bold(y.name)} \u2014 ${y.needs}`);m.some(y=>!y.present)&&R(`
|
|
1634
1634
|
run \`${Wo} ext extras install\` to fetch them into ${$r()}.`)}),dr.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let m=await Yr();if(m.every(M=>M.present)){R(ct.success("extras already installed \u2014 nothing to do."));return}let y=$r();await _e.mkdir(y,{recursive:!0});let _=m.filter(M=>!M.present).map(M=>M.name),w=Eu(process.platform,process.arch),x=async(M,L)=>{let{code:D,stderr:ie}=await oi("npm",["install","--prefix",y,"--no-audit","--no-fund",...M]);D!==0&&k(`extras install failed while installing ${L} (npm exited ${D}).
|
|
1635
1635
|
${ie.trim()}
|
|
1636
1636
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},v=_.filter(M=>M!=="isolated-vm");if(v.length>0){let M=v.map(L=>`${L}@${Su[L]??"latest"}`);R(`installing ${M.join(", ")} \u2192 ${y}/node_modules \u2026`),await x(M,v.join(", "))}if(_.includes("isolated-vm")){let M=`isolated-vm@${Su["isolated-vm"]}`;if(R(`installing ${M} \u2192 ${y}/node_modules \u2026`),await x(["--ignore-scripts",M],"isolated-vm"),Zg.includes(w)){let D=`${Tu}@${Io()}`;R(`fetching ${D} (no upstream binary for ${w}) \u2026`);let{code:ie}=await oi("npm",["install","--prefix",y,"--no-audit","--no-fund","--ignore-scripts",D]);ie!==0&&R(ct.warn(`could not fetch ${D} \u2014 falling back to a source build.`))}let L=await Ug({home:y});L.length>0&&R(` ${ct.success("prebuilt")} ${L.join(", ")} \u2192 isolated-vm/prebuilds/${w}`)}let $=await Yr(),O=$.filter(M=>!M.present).map(M=>M.name);if(O.includes("isolated-vm")){R(ct.warn(`no prebuilt isolated-vm for ${w} / Node ${process.versions.node} (${Ru()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:M}=await oi("npm",["rebuild","--prefix",y,"isolated-vm"]);M===0&&($=await Yr(),O=$.filter(L=>!L.present).map(L=>L.name))}O.length>0&&k(`npm succeeded but ${O.join(", ")} still will not load \u2014 no prebuilt binary for ${w} / Node ${process.versions.node} (${Ru()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),R(ct.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let Xr=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return Xr.command("deploy").description("Enqueue a server-side release deploy. UPLOADS the SDF project src/ from the checkout (the producer swap) when present; else a solution-only live-fetch deploy. An approved target requires --approved-by.").requiredOption("--target <mode>","Deploy posture: auto (apply now) | approved (needs a recorded approval).").option("--solution <id>","The solution id the release delivers (omit for an upload-only deploy).").option("--project <dir>","SDF project dir whose src/ is uploaded (default: cwd).").option("--git-repo <repo>","Git provenance LINK: source repo (default $GITHUB_REPOSITORY or `git remote get-url origin`).").option("--git-ref <ref>","Git provenance LINK: ref/branch/tag (default $GITHUB_REF).").option("--ref <committish>","Git provenance: the ref/branch/tag/sha the upload was built from; resolved to a commit sha via `git rev-parse` (default $GITHUB_SHA or HEAD).").option("--pr-url <url>","Git provenance LINK: the PR this deploy came from (default $PR_URL).").option("--approved-by <email>","Record the approval (REQUIRED for --target approved).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async m=>{let y=m.target==="approved"?"approved":m.target==="auto"?"auto":null;y||k(`--target must be 'auto' or 'approved' (got '${m.target}').`);let _=await _j($e(process.cwd(),m.project??".")),w=Object.keys(_).length;!m.solution&&w===0&&k("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let v=(m.ref?await Qh(["rev-parse",m.ref])??m.ref:null)??process.env.GITHUB_SHA??await Qh(["rev-parse","HEAD"]),$=m.gitRepo??process.env.GITHUB_REPOSITORY??await Qh(["remote","get-url","origin"]),O=m.gitRef??process.env.GITHUB_REF??null,M=m.prUrl??process.env.PR_URL??null,{baseUrl:L,token:D}=await ke(m.baseUrl),ie=new xe({baseUrl:L,token:D}),Q;try{Q=await ie.releaseDeploy({solutionId:m.solution??null,target:y,approvedBy:m.approvedBy??null,files:w>0?_:null,gitRepo:$,gitRef:O,gitSha:v,prUrl:M})}catch(we){Bt(we,"releases:write","release deploy"),we instanceof Re&&we.status===404&&k("solution_not_found \u2014 no solution with that id."),k(`release deploy failed: ${we instanceof Error?we.message:String(we)}`)}R(`release ${Q.releaseId} enqueued \u2192 taskId=${Q.taskId} (target=${y}`+(w>0?`, uploaded ${w} bundle file(s)`:", live-fetch")+")."),m.follow&&await xu(()=>ie.releaseStatus(Q.releaseId),`release ${Q.releaseId}`,m.intervalMs)}),Xr.command("status").argument("<id>","The release id").description("Show a release deploy job's status; with --follow, poll to completion.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async(m,y)=>{let{baseUrl:_,token:w}=await ke(y.baseUrl),x=new xe({baseUrl:_,token:w});if(y.follow){await xu(()=>x.releaseStatus(m),`release ${m}`,y.intervalMs);return}let v;try{v=await x.releaseStatus(m)}catch($){Bt($,"releases:write","release status"),$ instanceof Re&&$.status===404&&k(`release ${m} not found.`),k(`release status failed: ${$ instanceof Error?$.message:String($)}`)}R(`release ${m}: status=${v.releaseStatus??"?"} stage=${v.stage} ${v.percent}%`+(v.runStatus?` (${v.runStatus})`:"")),v.outputTail&&R(v.outputTail.replace(/\n+$/,""))}),Xr.command("rollback").argument("<id>","The release id to roll back").description("Roll back a live release (LIFO-guarded; server-side reverse deploy).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(m,y)=>{let{baseUrl:_,token:w}=await ke(y.baseUrl),x=new xe({baseUrl:_,token:w}),v;try{v=await x.releaseRollback(m)}catch($){Bt($,"releases:write","release rollback"),$ instanceof Re&&$.status===404&&k(`release ${m} not found.`),$ instanceof Re&&$.status===409&&k(`release ${m} cannot be rolled back (not live, or a newer release advanced past it).`),k(`release rollback failed: ${$ instanceof Error?$.message:String($)}`)}R(`release ${v.releaseId} rolled back \u2192 status=${v.release?.status??"rolled_back"}.`)}),Xr.command("list").description("List releases, newest-first, with delivered titles + latest status.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async m=>{let{baseUrl:y,token:_}=await ke(m.baseUrl),w=new xe({baseUrl:y,token:_}),x;try{x=await w.releaseList()}catch(v){Bt(v,"releases:write","release list"),k(`release list failed: ${v instanceof Error?v.message:String(v)}`)}if(x.length===0){R("no releases.");return}if(bt())R(Ko(x.map(v=>[v.id,Gn(v.status),v.target,v.backend,v.solutionTitles?.length?v.solutionTitles.map($=>`"${$}"`).join(", "):I.dim("\u2014")]),{head:["id","status","target","backend","delivered"]}));else for(let v of x){let $=v.solutionTitles?.length?` "${v.solutionTitles.join('", "')}"`:"";R(` ${v.id} ${v.status.padEnd(11)} target=${v.target} backend=${v.backend}${$}`)}R(`
|
package/dist/turbobase.js
CHANGED
|
@@ -1553,7 +1553,7 @@ jobs:
|
|
|
1553
1553
|
--project . \\
|
|
1554
1554
|
--follow
|
|
1555
1555
|
`;function Bd(){return[{path:vi,content:Qy}]}import ms from"node:fs/promises";import sn from"node:path";var _i={esbuild:"0.28.1","isolated-vm":"6.1.2"},Si="@turbobase/isolated-vm-prebuilds";function $i(t,e){return`${t}-${e}`}function ki(t=process.versions){return`abi${t.modules}`}var Fd=["darwin-x64","win32-arm64"];async function Hd(t){let e=t.platform??process.platform,r=t.arch??process.arch,n=$i(e,r),s=sn.join(t.home,"node_modules"),o=sn.join(s,...Si.split("/"),"prebuilds",n),i=sn.join(s,"isolated-vm","prebuilds",n),a;try{a=await ms.readdir(o)}catch{return[]}let l=[];await ms.mkdir(i,{recursive:!0});for(let u of a){if(!u.endsWith(".node"))continue;let p=sn.join(i,u);try{await ms.copyFile(sn.join(o,u),p,ms.constants?.COPYFILE_EXCL??1),l.push(u)}catch{}}return l}import{homedir as Xy}from"node:os";import{join as Kd}from"node:path";import{promises as xi}from"node:fs";function Gd(){let t=process.env.TURBOBASE_HOME??Kd(Xy(),".turbobase");return{dir:t,file:Kd(t,"sandboxes.json")}}var eb={s:1e3,m:6e4,h:36e5,d:864e5};function tb(t){let e=/^\s*(\d+)\s*([smhd])\s*$/.exec(t);if(!e)throw new Error(`invalid --ttl '${t}': expected <number><unit> where unit is s|m|h|d (e.g. 72h, 30m, 7d).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<=0)throw new Error(`invalid --ttl '${t}': duration must be a positive integer.`);return r*eb[e[2]]}function rb(t,e=Date.now()){return new Date(e+tb(t)).toISOString()}function nb(t,e=Date.now()){let r=Date.parse(t);return Number.isNaN(r)?!0:r<=e}function sb(t){let e=/^\s*(\d+(?:\.\d+)?)\s*%?\s*$/.exec(t);if(!e)throw new Error(`invalid --sample '${t}': expected a percent like 2% (or a bare number).`);let r=Number(e[1]);if(!Number.isFinite(r)||r<0||r>100)throw new Error(`invalid --sample '${t}': percent must be between 0 and 100.`);return r/100}function ob(t,e){if(e>=1)return[...t];if(e<=0||t.length===0)return[];let r=Math.max(1,Math.round(1/e)),n=[];for(let s=0;s<t.length;s+=r)n.push(t[s]);return n}var ib="[REDACTED]",on=class extends Error{constructor(e){super(e),this.name="SandboxDataCopyUnavailableError"}},Ti=class extends Error{table;column;constructor(e,r,n){super(`Refusing to copy ${e}.${r} into a sandbox: ${n}. A sandbox must never hold un-redacted PII \u2014 classify the column 'safe' (non-PII) or let redaction mask it, then retry.`),this.name="UnredactedPiiError",this.table=e,this.column=r}};function ab(t,e,r){let n={};for(let[s,o]of Object.entries(e)){let i=r[s];if(i===void 0)throw new Ti(t,s,"column has no PII classification (unknown = unsafe)");i==="pii"?n[s]=o==null?o:ib:n[s]=o}return n}function cb(t,e,r){return e.map(n=>ab(t,n,r))}var Vd=new Set(["localhost","127.0.0.1","::1","0.0.0.0"]),lb=["oraclecloud.com","us-ashburn-1","amazonaws.com","rds.","supabase.co","turbobase.app","turbobase.io"];function db(t){let e=t.trim();try{let s=new URL(e);if(s.hostname)return s.hostname.toLowerCase()}catch{}let r=e.replace(/^[a-z]+:\/\//i,""),n=(r.split("/")[0]??r).split("@").pop()??r;return(n.split(":")[0]??n).toLowerCase()}function Ri(t){if(!t||!t.trim())throw new Error("local-only guard: empty database target \u2014 refusing to proceed.");let e=t.toLowerCase();for(let n of lb)if(e.includes(n))throw new Error(`local-only guard: target '${t}' looks like a cloud/prod context (matched '${n}'). \`turbobase sandbox create\` is LOCAL-ONLY and will never touch a cloud database.`);let r=db(t);if(!Vd.has(r))throw new Error(`local-only guard: target host '${r}' is not a recognised local Postgres (${[...Vd].join(", ")}). \`turbobase sandbox create\` is LOCAL-ONLY.`)}function ub(t,e=pb){return`sbx-${t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"tenant"}-${e()}`}function pb(){return Math.floor(Math.random()*16777215).toString(16).padStart(6,"0")}function fb(t){return`turbobase_${t.replace(/[^a-z0-9]+/gi,"_").toLowerCase()}`}function hb(t,e){return`${(e??process.env.TURBOBASE_SANDBOX_BASE_URL??"http://localhost:5173").replace(/\/+$/,"")}/?sandbox=${encodeURIComponent(t)}`}async function Ei(){let{file:t}=Gd();try{let e=await xi.readFile(t,"utf8"),r=JSON.parse(e);return Array.isArray(r)?r:[]}catch(e){if(e.code==="ENOENT")return[];throw e}}async function Wd(t){let{dir:e,file:r}=Gd();await xi.mkdir(e,{recursive:!0}),await xi.writeFile(r,`${JSON.stringify(t,null,2)}
|
|
1556
|
-
`,{mode:384})}async function mb(t){let e=await Ei();e.push(t),await Wd(e)}async function Jd(t){let e=await Ei(),r=e.findIndex(s=>s.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Wd(e),n??null}async function gs(){return Ei()}function Pi(t,e=Date.now()){let r=[],n=[];for(let s of t)nb(s.expiresAt,e)?n.push(s):r.push(s);return{active:r,expired:n}}async function Zd(t,e){Ri(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new on(r);let n=sb(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>ub(u)))(e.fromTenant),i=fb(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=ob(u.rows,n),d=cb(u.table,p,u.classification);d.length>0&&await t.copyRows(i,u.table,d)}let l={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:rb(e.ttl,s),sampleFraction:n,url:hb(o,e.baseUrl)};return await mb(l),l}async function Yd(t,e){Ri(t.target());let n=(await gs()).find(s=>s.name===e);return n?(await t.dropDatabase(n.database),await Jd(e),n):null}async function Ai(t,e=Date.now()){Ri(t.target());let r=await gs(),{expired:n}=Pi(r,e),s=[];for(let o of n){try{await t.dropDatabase(o.database)}catch{continue}await Jd(o.name),s.push(o)}return s}var an={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},XR=Object.keys(an).filter(t=>an[t].mintable);var rE=an.pat.prefix,gb=Object.entries(an).map(([t,e])=>[e.prefix,t]),yb=/^[A-Za-z0-9_-]{20,}$/;function bb(t){if(typeof t!="string")return null;for(let[e,r]of gb)if(t.startsWith(e)&&yb.test(t.slice(e.length)))return r;return null}function Qd(t){return bb(t)==="pat"}var cn="bundle.json",ys="turbobase";function eu(t){let e;try{e=JSON.parse(t)}catch(l){throw new Error(`bundle.json is not valid JSON: ${l instanceof Error?l.message:String(l)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let s=l=>Array.isArray(l)?l:[],o=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:s(r.hooks),conduits:s(r.conduits),sync:{fields:s(o.fields),tables:s(o.tables)},guardrails:{validationHooks:s(i.validationHooks),fieldRules:s(i.fieldRules),conduits:s(i.conduits)},schedules:s(r.schedules)};return wb(a)}function wb(t){let e=r=>(n,s)=>r(n)<r(s)?-1:r(n)>r(s)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(Xd)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(Xd)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function Xd(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function tu(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function j0(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function z0(t){let e=await Rt();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Jt()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Zt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function L0(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(s){return{ok:!1,kind:"unreachable",detail:s instanceof Error?s.message:String(s)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function D0(t,e,r){let n=await Ql({baseUrl:t,token:e}),s=(await Rt()).length-1;if(x(`signed in to ${n.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),!r)return;let o=await L0(n.baseUrl,e);o.ok||(o.kind==="wrong-host"?(ye(`warning: ${n.baseUrl} did not respond to the TurboBase API (/v1) \u2014 got ${o.detail}.`),ye(" In local dev the TurboBase API is on :4100, not the app dev-server port (e.g. :5173)."),ye(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?ye(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):ye(`warning: could not reach ${n.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function M0(t){let e=await Xr(t),r=l=>{let u=new RegExp(`${l}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),s=r("id");if(n){let l=r("on"),u=r("recordType"),p=r("source");return(!s||!u||!l||!p)&&k(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:s,recordType:u,event:l,source:p,sourceCode:e,extensionKind:"conduit"}}let o=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!s||!o||!i)&&k(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:s,recordType:o,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Hr(t,e,r,n){try{return await oe.access(t),"skipped"}catch{}return await oe.mkdir(Qn(t),{recursive:!0}),await oe.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await oe.chmod(t,n),"wrote"}async function Zm(t,e){if(Ym(t)!==".gitignore")return Hr(t,e.content,e.executable,e.mode);let r=await oe.readFile(t,"utf8").catch(()=>null);if(r===null)return Hr(t,e.content,e.executable,e.mode);let n=Pd(r);return n===null?"skipped":(await oe.writeFile(t,n,"utf8"),"merged")}function Rl(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function q0(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",s=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:s,type:n}})}}function U0(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function B0(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function Ol({includeInternal:t=!0}={}){let e=new N0;e.name("turbobase").description("Build apps, author extensions, and drive your TurboBase instance.").version("1.1.
|
|
1556
|
+
`,{mode:384})}async function mb(t){let e=await Ei();e.push(t),await Wd(e)}async function Jd(t){let e=await Ei(),r=e.findIndex(s=>s.name===t);if(r===-1)return null;let[n]=e.splice(r,1);return await Wd(e),n??null}async function gs(){return Ei()}function Pi(t,e=Date.now()){let r=[],n=[];for(let s of t)nb(s.expiresAt,e)?n.push(s):r.push(s);return{active:r,expired:n}}async function Zd(t,e){Ri(t.target());let r=t.dataCopyUnavailable?.();if(r)throw new on(r);let n=sb(e.sample),s=e.now??Date.now(),o=(e.nameGen??(u=>ub(u)))(e.fromTenant),i=fb(o);await t.createDatabase(i),await t.replayMigrations(i);let a=await t.readSourceTables(e.fromTenant);for(let u of a){let p=ob(u.rows,n),d=cb(u.table,p,u.classification);d.length>0&&await t.copyRows(i,u.table,d)}let l={name:o,fromTenant:e.fromTenant,database:i,createdAt:new Date(s).toISOString(),expiresAt:rb(e.ttl,s),sampleFraction:n,url:hb(o,e.baseUrl)};return await mb(l),l}async function Yd(t,e){Ri(t.target());let n=(await gs()).find(s=>s.name===e);return n?(await t.dropDatabase(n.database),await Jd(e),n):null}async function Ai(t,e=Date.now()){Ri(t.target());let r=await gs(),{expired:n}=Pi(r,e),s=[];for(let o of n){try{await t.dropDatabase(o.database)}catch{continue}await Jd(o.name),s.push(o)}return s}var an={pat:{prefix:"tb_pat_",defaultScopes:["extensions:deploy"],allowedScopes:["extensions:deploy","catalog:read","mcp:authoring","analyzer:run","releases:write","solutions:write","data:read"],defaultTtlDays:90,mintable:!0,label:"Personal access token"},catalog:{prefix:"tb_cat_",defaultScopes:["catalog:read"],allowedScopes:["catalog:read"],defaultTtlDays:null,mintable:!0,label:"AI / catalog token"}},XR=Object.keys(an).filter(t=>an[t].mintable);var rE=an.pat.prefix,gb=Object.entries(an).map(([t,e])=>[e.prefix,t]),yb=/^[A-Za-z0-9_-]{20,}$/;function bb(t){if(typeof t!="string")return null;for(let[e,r]of gb)if(t.startsWith(e)&&yb.test(t.slice(e.length)))return r;return null}function Qd(t){return bb(t)==="pat"}var cn="bundle.json",ys="turbobase";function eu(t){let e;try{e=JSON.parse(t)}catch(l){throw new Error(`bundle.json is not valid JSON: ${l instanceof Error?l.message:String(l)}`)}if(typeof e!="object"||e===null||Array.isArray(e))throw new Error("bundle.json must be a JSON object");let r=e,n=typeof r.version=="number"?r.version:0;if(n!==1)throw new Error(`bundle.json version ${n} is unsupported (expected 1)`);let s=l=>Array.isArray(l)?l:[],o=r.sync??{},i=r.guardrails??{},a={version:1,...typeof r.tenant=="string"?{tenant:r.tenant}:{},hooks:s(r.hooks),conduits:s(r.conduits),sync:{fields:s(o.fields),tables:s(o.tables)},guardrails:{validationHooks:s(i.validationHooks),fieldRules:s(i.fieldRules),conduits:s(i.conduits)},schedules:s(r.schedules)};return wb(a)}function wb(t){let e=r=>(n,s)=>r(n)<r(s)?-1:r(n)>r(s)?1:0;return{version:1,...t.tenant?{tenant:t.tenant}:{},hooks:[...t.hooks].sort(e(r=>r.id)),conduits:[...t.conduits].sort(e(r=>r.id)),sync:{fields:[...t.sync.fields].sort(e(Xd)),tables:[...t.sync.tables].sort(e(r=>r.sourceRecordType))},guardrails:{validationHooks:[...t.guardrails.validationHooks].sort(),fieldRules:[...t.guardrails.fieldRules].sort(e(Xd)),conduits:[...t.guardrails.conduits].sort(e(r=>r.conduitId))},schedules:[...t.schedules].sort(e(r=>r.name))}}function Xd(t){return`${t.sourceSystem}::${t.recordType}::${t.fieldName}`}function tu(t,e){let r=(e??"").trim();if(!r)return{ok:!1,reason:"target instance slug is unknown \u2014 cannot confirm a promote"};let n=(t??"").trim();return n?n!==r?{ok:!1,reason:`--confirm "${n}" does not match the target instance "${r}" \u2014 refusing to apply`}:{ok:!0}:{ok:!1,reason:`--confirm <slug> is required for promote (expected "${r}")`}}async function j0(t,e){try{let r=await fetch(`${t.replace(/\/+$/,"")}/auth/pat-exchange`,{method:"POST",headers:{Authorization:`Bearer ${e}`}});if(!r.ok)return null;let n=await r.json();return typeof n.token=="string"&&n.token.length>0?n.token:null}catch{return null}}async function z0(t){let e=await Rt();e.length===0&&k("no stored sign-ins \u2014 run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`."),Jt()||k("this command needs an explicit <base-url> in a non-interactive shell.");try{return await Zt(t,e.map(r=>({value:r.baseUrl,label:r.baseUrl,description:`updated ${r.updatedAt}`})))}catch{k("cancelled.")}}async function L0(t,e){let r;try{r=await fetch(`${t}/v1/catalog/records`,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(8e3)})}catch(s){return{ok:!1,kind:"unreachable",detail:s instanceof Error?s.message:String(s)}}let n=(r.headers.get("content-type")??"").includes("application/json");return r.status===200||r.status===403?{ok:!0}:r.status===401?{ok:!1,kind:"bad-token",detail:`HTTP ${r.status}`}:n?{ok:!0}:{ok:!1,kind:"wrong-host",detail:`HTTP ${r.status}`}}async function D0(t,e,r){let n=await Ql({baseUrl:t,token:e}),s=(await Rt()).length-1;if(x(`signed in to ${n.baseUrl} \u2014 now the active instance`+(s>0?` (${s} other${s===1?"":"s"} stored; commands default here).`:".")),!r)return;let o=await L0(n.baseUrl,e);o.ok||(o.kind==="wrong-host"?(ye(`warning: ${n.baseUrl} did not respond to the TurboBase API (/v1) \u2014 got ${o.detail}.`),ye(" In local dev the TurboBase API is on :4100, not the app dev-server port (e.g. :5173)."),ye(" The token is stored, but commands will fail against this base-url until it points at the API.")):o.kind==="bad-token"?ye(`warning: ${n.baseUrl} rejected the token (401) \u2014 the PAT may be stale, revoked, or for a different instance.`):ye(`warning: could not reach ${n.baseUrl} to verify the token (${o.detail}). Stored anyway.`))}async function M0(t){let e=await Xr(t),r=l=>{let u=new RegExp(`${l}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(e);return u?u[2]:void 0},n=/\bdefineConduit\s*[<(]/.test(e),s=r("id");if(n){let l=r("on"),u=r("recordType"),p=r("source");return(!s||!u||!l||!p)&&k(`could not parse id/on/from.source/from.recordType from ${t} \u2014 is it a defineConduit({...}) file?`),{id:s,recordType:u,event:l,source:p,sourceCode:e,extensionKind:"conduit"}}let o=r("recordType"),i=r("event"),a=r("source")??"standalone";return(!s||!o||!i)&&k(`could not parse id/recordType/event from ${t} \u2014 is it a defineHook({...}) file?`),{id:s,recordType:o,event:i,source:a,sourceCode:e,extensionKind:"hook"}}async function Hr(t,e,r,n){try{return await oe.access(t),"skipped"}catch{}return await oe.mkdir(Qn(t),{recursive:!0}),await oe.writeFile(t,e,{mode:n??(r?493:420)}),n!==void 0&&await oe.chmod(t,n),"wrote"}async function Zm(t,e){if(Ym(t)!==".gitignore")return Hr(t,e.content,e.executable,e.mode);let r=await oe.readFile(t,"utf8").catch(()=>null);if(r===null)return Hr(t,e.content,e.executable,e.mode);let n=Pd(r);return n===null?"skipped":(await oe.writeFile(t,n,"utf8"),"merged")}function Rl(t){if(t!==void 0){if(t==="junit"||t==="json")return t;k(`unknown --reporter '${t}' (expected 'junit' or 'json').`)}}function q0(t){return{name:"turbobase ext test",cases:t.map(r=>{if(r.pass)return{name:r.name,status:"passed"};let n=r.error?"error":r.rejection?.code??"mismatch",s=r.error?`error: ${r.error}`:r.observed!==r.expected?`expected ${r.expected}, observed ${r.observed}`+(r.rejection?` (rejected: ${r.rejection.code??"rejected"}${r.rejection.message?` \u2014 ${r.rejection.message}`:""})`:""):`rejected: ${r.rejection?.code??"rejected"}${r.rejection?.message?` \u2014 ${r.rejection.message}`:""}`;return{name:r.name,status:"failed",detail:s,type:n}})}}function U0(t,e){let r=e.warnings.length===0?[{name:`${t} (${e.kind})`,status:"passed"}]:e.warnings.map(n=>({name:`${t}: ${n.rule}`,status:"failed",type:n.rule,detail:`${n.message} \u2192 fix: ${n.fix}`}));return{name:`turbobase ext lint ${t}`,cases:r}}function B0(t){let e=[];if(t.error)return e.push({name:`replay ${t.fixtureName}`,status:"failed",type:"error",detail:t.error}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e};for(let r of t.guards)e.push(r.status==="failed"?{name:`guard: ${r.name}`,status:"failed",type:r.name,detail:r.detail}:{name:`guard: ${r.name}`,status:"passed",detail:r.status==="skipped-offline"?`skipped-offline: ${r.detail}`:void 0});return t.whenPassed&&t.applyBranch&&e.push({name:`apply-branch: ${t.applyBranch}`,status:"passed",detail:`${t.masterAction??""} (${t.wouldWriteBindings.length} binding(s) would be written)`.trim()}),{name:`turbobase ext replay ${t.fixtureName}`,cases:e}}function Ol({includeInternal:t=!0}={}){let e=new N0;e.name("turbobase").description("Build apps, author extensions, and drive your TurboBase instance.").version("1.1.11").option("--no-color","Disable ANSI colour + animations (also honoured via NO_COLOR / non-TTY)."),e.addHelpText("beforeAll",f=>f.command.parent?"":`${fi()}
|
|
1557
1557
|
`),e.hook("preAction",(f,m)=>{Id(e.opts(),m.opts())});let r=e.command("auth").description("Sign in to an instance and manage stored credentials."),n=e.command("project").description("Set up the local project and the files an agent session reads."),s=e.command("ext").description("Author, test, ship, and observe extensions \u2014 hooks and conduits."),o=e.command("kb").description("Write into the instance's knowledge base.");r.command("login").description("Sign in to an instance by pasting a personal access token (PAT), stored in ~/.turbobase/credentials.").requiredOption("--base-url <url>","Instance base URL, e.g. https://acme.turbobase.app").option("--token <tb_pat_\u2026>","The PAT to store. Mint one in your app under Settings \u203A Access tokens.").option("--no-verify","Skip the post-login connectivity/token probe.").action(async f=>{let m=Ve(f.baseUrl);f.token||k(`auth login needs --token. Mint a PAT in your TurboBase app (Settings \u203A Access tokens), then:
|
|
1558
1558
|
${rn} auth login --base-url ${m} --token tb_pat_\u2026`),Qd(f.token)||k("--token does not look like a TurboBase PAT (tb_pat_\u2026)."),await D0(m,f.token,f.verify)}),r.command("status").description("Show the active instance (the no-`--base-url` default) and every stored sign-in.").action(async()=>{let f=await Rt();if(f.length===0){x("not signed in. Run `turbobase auth login --base-url <instance> --token tb_pat_\u2026`.");return}let m=await pt(),y=b=>!!m&&b.baseUrl===m.baseUrl;if(Me())x(tn(f.map(b=>[y(b)?R.green("*"):" ",R.bold(b.baseUrl),R.dim(b.updatedAt)]),{head:["","instance","updated"]}));else for(let b of f)x(`${y(b)?"* ":" "}${b.baseUrl} (updated ${b.updatedAt})`);m||ye("no active default \u2014 run `turbobase auth use <base-url>` or pass --base-url.")}),r.command("use [base-url]").description("Make an already-stored instance the active default (omit the arg to pick interactively).").action(async f=>{let m=f??await z0("Pick the active instance:"),y=await is(m);if(!y){let b=await Rt();k(`no stored sign-in for ${Ve(m)}.`+(b.length?` Stored: ${b.map(S=>S.baseUrl).join(", ")}.`:"")+` Run \`turbobase auth login --base-url ${Ve(m)} --token tb_pat_\u2026\`.`)}x(`active instance \u2192 ${y.baseUrl}.`)}),r.command("logout").description("Remove a stored sign-in (defaults to the active instance).").option("--base-url <url>","Which instance to remove (defaults to the active/single one).").option("--all","Remove every stored sign-in.").action(async f=>{if(f.all){let b=await Rt();for(let S of b)await ri(S.baseUrl);x(`removed ${b.length} stored sign-in${b.length===1?"":"s"}.`);return}let m=await ri(f.baseUrl);m||k(f.baseUrl?`no stored sign-in for ${Ve(f.baseUrl)}.`:"nothing to sign out of (or ambiguous \u2014 pass --base-url / --all).");let y=await pt();x(`signed out of ${m}.`+(y?` Active instance \u2192 ${y.baseUrl}.`:""))}),n.command("types").description("Generate types.ts from the signed-in instance's catalog.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("-o, --out <file>","Output file","types.ts").option("--records-only","Emit only record-shape interfaces (skip the OpenAPI half).").action(async f=>{let{baseUrl:m,token:y}=await le(f.baseUrl),b;try{b=await Tt({baseUrl:m,token:y,recordsOnly:f.recordsOnly})}catch(w){let $=w instanceof Error?w.message:String(w);if(!f.recordsOnly&&/openapi\.json failed \(401/.test($)){let E=await j0(m,y);if(E)try{b=await Tt({baseUrl:m,token:E,recordsOnly:!1});let A=ce(process.cwd(),f.out);await oe.writeFile(A,b,"utf8"),x(`wrote ${A} (${b.split(`
|
|
1559
1559
|
`).length} lines).`);return}catch{}ye("types-gen: /v1/catalog/openapi.json needs a session \u2014 emitting record shapes only. To get the OpenAPI half headlessly, mint a PAT with the `data:read` scope (Settings \u203A Tokens); the CLI then exchanges it for a short-lived session automatically.");try{b=await Tt({baseUrl:m,token:y,recordsOnly:!0})}catch(A){k(`types generation failed: ${A instanceof Error?A.message:String(A)}`)}}else k(`types generation failed: ${$}`)}let S=ce(process.cwd(),f.out);await oe.writeFile(S,b,"utf8"),x(`wrote ${S} (${b.split(`
|
|
@@ -1583,7 +1583,7 @@ Next: mint a releases:write PAT in your TurboBase app (Settings \u203A Access to
|
|
|
1583
1583
|
samples init: ${E} written, ${A} skipped.`)});let Ee=e.command("sandbox").description("Ephemeral local authoring sandboxes.");Ee.command("create").description("Stand up a fresh local sandbox seeded with a sampled+redacted slice of an instance.").requiredOption("--from <instance>","Source instance to clone the data slice from.").option("--ttl <duration>","Time-to-live before reaping (e.g. 72h, 30m, 7d).","72h").option("--sample <pct>","Percent of each table to copy (e.g. 2%).","2%").option("--base-url <url>","Local base URL the sandbox is reachable at.").action(async f=>{process.env.TURBOBASE_SANDBOX="1";let m=Pl(),y;try{y=await Zd(m,{fromTenant:f.from,ttl:f.ttl,sample:f.sample,baseUrl:f.baseUrl})}catch(b){b instanceof on&&k(b.message),k(`sandbox create failed: ${b instanceof Error?b.message:String(b)}`)}x(`sandbox ${y.name} ready.`),x(` source: ${y.fromTenant} (sampled ${Math.round(y.sampleFraction*100)}%, redacted)`),x(` url: ${y.url}`),x(` expires: ${y.expiresAt}`)}),Ee.command("destroy").argument("<name>","The sandbox name to tear down.").description("Drop a sandbox database and remove it from the registry.").action(async f=>{let m=Pl();await Ai(m).catch(()=>[]);let y;try{y=await Yd(m,f)}catch(b){k(`sandbox destroy failed: ${b instanceof Error?b.message:String(b)}`)}y||k(`no sandbox named '${f}'.`),x(`destroyed sandbox ${y.name} (dropped ${y.database}).`)}),Ee.command("list").description("List active sandboxes with TTL/expiry; reaps any past their TTL.").action(async()=>{let f=Pl(),m=await Ai(f).catch(()=>[]),y=await gs(),{active:b}=Pi(y);if(b.length===0)x("no active sandboxes.");else{for(let S of b)x(` ${S.name} from=${S.fromTenant} expires=${S.expiresAt} ${S.url}`);x(`
|
|
1584
1584
|
${b.length} active sandbox(es).`)}m.length>0&&x(`(reaped ${m.length} expired sandbox(es).)`)}),s.command("export").description("Snapshot the live instance into turbobase/bundle.json.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--out <dir>","Directory to write the bundle into.",ys).action(async f=>{let{baseUrl:m,token:y}=await le(f.baseUrl),b=new ae({baseUrl:m,token:y}),S;try{S=await me("exporting instance config",()=>b.configExport())}catch(E){E instanceof pe&&(E.status===401||E.status===403)&&k("ext export forbidden \u2014 the stored PAT lacks extensions:deploy. Re-run `turbobase auth login`."),k(`ext export failed: ${E instanceof Error?E.message:String(E)}`)}let w=typeof S.serialized=="string"?S.serialized:`${JSON.stringify(S.manifest??S,null,2)}
|
|
1585
1585
|
`,$=ce(process.cwd(),f.out,cn);await oe.mkdir(Qn($),{recursive:!0}),await oe.writeFile($,w,"utf8"),x(`wrote ${f.out}/${cn} (${w.split(`
|
|
1586
|
-
`).length} lines).`)});let ur=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Kr=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],kt=()=>process.env.TURBOBASE_HOME?.trim()||Kt(O0(),".turbobase"),Vr=()=>"1.1.
|
|
1586
|
+
`).length} lines).`)});let ur=s.command("extras").description("Manage the optional native packages `ext test` and `ext replay` need."),Kr=[{name:"esbuild",needs:"compiling an extension (`test`, `replay`)"},{name:"isolated-vm",needs:"executing an extension in the sandbox (`test`, `replay`)"}],kt=()=>process.env.TURBOBASE_HOME?.trim()||Kt(O0(),".turbobase"),Vr=()=>"1.1.11",Vt=async()=>Promise.all(Kr.map(async f=>{try{return await import(f.name),{...f,present:!0}}catch{return{...f,present:!1}}}));ur.command("status").description("Report which optional native packages are installed.").action(async()=>{let f=await Vt();for(let m of f)x(`${m.present?Oe.success("installed"):Oe.warn("missing")} ${R.bold(m.name)} \u2014 ${m.needs}`);f.some(m=>!m.present)&&x(`
|
|
1587
1587
|
run \`${rn} ext extras install\` to fetch them into ${kt()}.`)}),ur.command("install").description("Install the optional native packages into $TURBOBASE_HOME (needs npm).").action(async()=>{let f=await Vt();if(f.every(A=>A.present)){x(Oe.success("extras already installed \u2014 nothing to do."));return}let m=kt();await oe.mkdir(m,{recursive:!0});let y=f.filter(A=>!A.present).map(A=>A.name),b=$i(process.platform,process.arch),S=async(A,I)=>{let{code:C,stderr:G}=await Xn("npm",["install","--prefix",m,"--no-audit","--no-fund",...A]);C!==0&&k(`extras install failed while installing ${I} (npm exited ${C}).
|
|
1588
1588
|
${G.trim()}
|
|
1589
1589
|
These are native packages \u2014 they need npm and a working toolchain. Everything except \`test\` and \`replay\` works without them.`)},w=y.filter(A=>A!=="isolated-vm");if(w.length>0){let A=w.map(I=>`${I}@${_i[I]??"latest"}`);x(`installing ${A.join(", ")} \u2192 ${m}/node_modules \u2026`),await S(A,w.join(", "))}if(y.includes("isolated-vm")){let A=`isolated-vm@${_i["isolated-vm"]}`;if(x(`installing ${A} \u2192 ${m}/node_modules \u2026`),await S(["--ignore-scripts",A],"isolated-vm"),Fd.includes(b)){let C=`${Si}@${Vr()}`;x(`fetching ${C} (no upstream binary for ${b}) \u2026`);let{code:G}=await Xn("npm",["install","--prefix",m,"--no-audit","--no-fund","--ignore-scripts",C]);G!==0&&x(Oe.warn(`could not fetch ${C} \u2014 falling back to a source build.`))}let I=await Hd({home:m});I.length>0&&x(` ${Oe.success("prebuilt")} ${I.join(", ")} \u2192 isolated-vm/prebuilds/${b}`)}let $=await Vt(),E=$.filter(A=>!A.present).map(A=>A.name);if(E.includes("isolated-vm")){x(Oe.warn(`no prebuilt isolated-vm for ${b} / Node ${process.versions.node} (${ki()}) \u2014 building from source. This needs Python and a C++ toolchain, and takes a few minutes.`));let{code:A}=await Xn("npm",["rebuild","--prefix",m,"isolated-vm"]);A===0&&($=await Vt(),E=$.filter(I=>!I.present).map(I=>I.name))}E.length>0&&k(`npm succeeded but ${E.join(", ")} still will not load \u2014 no prebuilt binary for ${b} / Node ${process.versions.node} (${ki()}), and the source build did not produce one either. Everything except \`test\` and \`replay\` works without them.`),x(Oe.success("extras installed \u2014 `turbobase ext test` and `turbobase ext replay` are ready."))});let Gt=e.command("release").description("Ship a release to your instance: deploy | status | rollback | list.");return Gt.command("deploy").description("Enqueue a server-side release deploy. UPLOADS the SDF project src/ from the checkout (the producer swap) when present; else a solution-only live-fetch deploy. An approved target requires --approved-by.").requiredOption("--target <mode>","Deploy posture: auto (apply now) | approved (needs a recorded approval).").option("--solution <id>","The solution id the release delivers (omit for an upload-only deploy).").option("--project <dir>","SDF project dir whose src/ is uploaded (default: cwd).").option("--git-repo <repo>","Git provenance LINK: source repo (default $GITHUB_REPOSITORY or `git remote get-url origin`).").option("--git-ref <ref>","Git provenance LINK: ref/branch/tag (default $GITHUB_REF).").option("--ref <committish>","Git provenance: the ref/branch/tag/sha the upload was built from; resolved to a commit sha via `git rev-parse` (default $GITHUB_SHA or HEAD).").option("--pr-url <url>","Git provenance LINK: the PR this deploy came from (default $PR_URL).").option("--approved-by <email>","Record the approval (REQUIRED for --target approved).").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async f=>{let m=f.target==="approved"?"approved":f.target==="auto"?"auto":null;m||k(`--target must be 'auto' or 'approved' (got '${f.target}').`);let y=await V0(ce(process.cwd(),f.project??".")),b=Object.keys(y).length;!f.solution&&b===0&&k("release deploy needs --solution <id> (live-fetch) or an SDF project (a src/ dir under --project, default cwd) to upload.");let w=(f.ref?await Al(["rev-parse",f.ref])??f.ref:null)??process.env.GITHUB_SHA??await Al(["rev-parse","HEAD"]),$=f.gitRepo??process.env.GITHUB_REPOSITORY??await Al(["remote","get-url","origin"]),E=f.gitRef??process.env.GITHUB_REF??null,A=f.prUrl??process.env.PR_URL??null,{baseUrl:I,token:C}=await le(f.baseUrl),G=new ae({baseUrl:I,token:C}),q;try{q=await G.releaseDeploy({solutionId:f.solution??null,target:m,approvedBy:f.approvedBy??null,files:b>0?y:null,gitRepo:$,gitRef:E,gitSha:w,prUrl:A})}catch(ie){st(ie,"releases:write","release deploy"),ie instanceof pe&&ie.status===404&&k("solution_not_found \u2014 no solution with that id."),k(`release deploy failed: ${ie instanceof Error?ie.message:String(ie)}`)}x(`release ${q.releaseId} enqueued \u2192 taskId=${q.taskId} (target=${m}`+(b>0?`, uploaded ${b} bundle file(s)`:", live-fetch")+")."),f.follow&&await bi(()=>G.releaseStatus(q.releaseId),`release ${q.releaseId}`,f.intervalMs)}),Gt.command("status").argument("<id>","The release id").description("Show a release deploy job's status; with --follow, poll to completion.").option("--base-url <url>","Instance base URL (defaults to the active instance).").option("--follow","Poll the release deploy to completion (exit non-zero on failure).").option("--interval-ms <ms>","Poll interval for --follow (default 2000).").action(async(f,m)=>{let{baseUrl:y,token:b}=await le(m.baseUrl),S=new ae({baseUrl:y,token:b});if(m.follow){await bi(()=>S.releaseStatus(f),`release ${f}`,m.intervalMs);return}let w;try{w=await S.releaseStatus(f)}catch($){st($,"releases:write","release status"),$ instanceof pe&&$.status===404&&k(`release ${f} not found.`),k(`release status failed: ${$ instanceof Error?$.message:String($)}`)}x(`release ${f}: status=${w.releaseStatus??"?"} stage=${w.stage} ${w.percent}%`+(w.runStatus?` (${w.runStatus})`:"")),w.outputTail&&x(w.outputTail.replace(/\n+$/,""))}),Gt.command("rollback").argument("<id>","The release id to roll back").description("Roll back a live release (LIFO-guarded; server-side reverse deploy).").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async(f,m)=>{let{baseUrl:y,token:b}=await le(m.baseUrl),S=new ae({baseUrl:y,token:b}),w;try{w=await S.releaseRollback(f)}catch($){st($,"releases:write","release rollback"),$ instanceof pe&&$.status===404&&k(`release ${f} not found.`),$ instanceof pe&&$.status===409&&k(`release ${f} cannot be rolled back (not live, or a newer release advanced past it).`),k(`release rollback failed: ${$ instanceof Error?$.message:String($)}`)}x(`release ${w.releaseId} rolled back \u2192 status=${w.release?.status??"rolled_back"}.`)}),Gt.command("list").description("List releases, newest-first, with delivered titles + latest status.").option("--base-url <url>","Instance base URL (defaults to the active instance).").action(async f=>{let{baseUrl:m,token:y}=await le(f.baseUrl),b=new ae({baseUrl:m,token:y}),S;try{S=await b.releaseList()}catch(w){st(w,"releases:write","release list"),k(`release list failed: ${w instanceof Error?w.message:String(w)}`)}if(S.length===0){x("no releases.");return}if(Me())x(tn(S.map(w=>[w.id,mr(w.status),w.target,w.backend,w.solutionTitles?.length?w.solutionTitles.map($=>`"${$}"`).join(", "):R.dim("\u2014")]),{head:["id","status","target","backend","delivered"]}));else for(let w of S){let $=w.solutionTitles?.length?` "${w.solutionTitles.join('", "')}"`:"";x(` ${w.id} ${w.status.padEnd(11)} target=${w.target} backend=${w.backend}${$}`)}x(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turbobase/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "Author, test, and deploy TurboBase extensions (hooks + conduits) against a tenant over HTTP + a personal access token.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|