@portalsdk/cli 0.4.1 → 0.5.0
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 +9 -9
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createRequire as
|
|
3
|
-
`,{mode:B}),await k.chmod(r,B),r}async function j(e=b.homedir()){try{await k.unlink(
|
|
4
|
-
`).trimEnd();if(r)return r}return`Failed to compile ${
|
|
2
|
+
import{createRequire as gt}from"module";import{Command as mt}from"commander";import c from"picocolors";import{spawn as Ee}from"child_process";import Pe from"os";var d=class extends Error{constructor(t,r,o){super(t);this.status=r;this.code=o;this.name="ApiError"}},v=class{constructor(n,t){this.secret=t;this.baseUrl=n.replace(/\/+$/,"")}baseUrl;async createDeploy(n){let{status:t,data:r}=await this.send("POST","/v1/deploys",n);if(t>=200&&t<300)return{versionId:F(r),alreadyDeployed:!1};if(t===409)return{versionId:F(r),alreadyDeployed:!0};throw this.fail(t,r)}async activate(n){let{status:t,data:r}=await this.send("POST",`/v1/deploys/${encodeURIComponent(n)}/activate`);if(t<200||t>=300)throw this.fail(t,r)}async setSecret(n,t){let{status:r,data:o}=await this.send("PUT",`/v1/secrets/${encodeURIComponent(n)}`,{value:t});if(r<200||r>=300)throw this.fail(r,o)}async send(n,t,r){let o;try{o=await fetch(`${this.baseUrl}${t}`,{method:n,headers:{authorization:`Bearer ${this.secret}`,"content-type":"application/json"},body:r===void 0?void 0:JSON.stringify(r)})}catch(a){throw new d(`Could not reach the Portal API at ${this.baseUrl}: ${a instanceof Error?a.message:String(a)}`,0)}let s=await o.text(),i={};if(s)try{i=JSON.parse(s)}catch{i={reason:s}}return{status:o.status,data:i}}fail(n,t){let r=t.code;return n===401||r==="unauthorized"?new d("Your PORTAL_SECRET was not accepted. It must be a valid project secret key (sk_...).",n,r):new d(t.reason||`The Portal API rejected the request (${r??n}).`,n,r)}};function F(e){if(!e.versionId)throw new d("The Portal API did not return a version id for this deploy.",502);return e.versionId}var u=class{baseUrl;constructor(n){this.baseUrl=n.replace(/\/+$/,"")}async startDeviceLogin(n){let{status:t,data:r}=await this.send("POST","/v1/device/start",{...n?{name:n}:{}});if(t<200||t>=300)throw this.fail(t,r);let o=r;if(!o.deviceCode||!o.userCode||!o.verificationUri)throw new d("The Portal API did not return a usable login code.",502);return{deviceCode:o.deviceCode,userCode:o.userCode,verificationUri:o.verificationUri,interval:typeof o.interval=="number"?o.interval:5,expiresIn:typeof o.expiresIn=="number"?o.expiresIn:600}}async pollDeviceLogin(n){let{status:t,data:r}=await this.send("POST","/v1/device/poll",{deviceCode:n});if(t===429)return{status:"slow_down"};if(t===404)return{status:"expired"};if(t<200||t>=300)throw this.fail(t,r);let o=r;switch(o.status){case"approved":return{status:"approved",token:o.token,expiresAt:o.expiresAt};case"denied":return{status:"denied"};case"expired":return{status:"expired"};default:return{status:"pending"}}}async fetchIdentity(n){let{status:t,data:r}=await this.send("GET","/v1/me",void 0,n);if(t<200||t>=300)throw this.fail(t,r);return r}async revokeCurrentToken(n){let{status:t,data:r}=await this.send("DELETE","/v1/cli-tokens/current",void 0,n);if(t<200||t>=300)throw this.fail(t,r)}async revokeAllTokens(n){let{status:t,data:r}=await this.send("DELETE","/v1/cli-tokens",void 0,n);if(t<200||t>=300)throw this.fail(t,r);let o=r;return typeof o.revoked=="number"?o.revoked:0}async createProject(n,t,r){let{status:o,data:s}=await this.send("POST","/v1/projects",{organizationId:t,name:r},n);if(o<200||o>=300)throw this.fail(o,s);let i=s;if(!i.project?.id||!Array.isArray(i.environments))throw new d("The Portal API did not return a usable project.",502);return{project:i.project,environments:i.environments}}async createApiKey(n,t,r){let{status:o,data:s}=await this.send("POST",`/v1/environments/${encodeURIComponent(t)}/api-keys`,{keyType:r},n);if(o<200||o>=300)throw this.fail(o,s);let i=s;if(!i.apiKey?.value)throw new d("The Portal API did not return a usable API key.",502);return i.apiKey}async createAllowedOrigin(n,t,r){let{status:o,data:s}=await this.send("POST",`/v1/environments/${encodeURIComponent(t)}/allowed-origins`,{origin:r},n);if(o<200||o>=300)throw this.fail(o,s);let i=s;if(!i.origin?.id)throw new d("The Portal API did not return a usable origin.",502);return i.origin}async listAllowedOrigins(n,t){let{status:r,data:o}=await this.send("GET",`/v1/environments/${encodeURIComponent(t)}/allowed-origins`,void 0,n);if(r<200||r>=300)throw this.fail(r,o);let s=o;return Array.isArray(s.origins)?s.origins:[]}async deleteAllowedOrigin(n,t){let{status:r,data:o}=await this.send("DELETE",`/v1/allowed-origins/${encodeURIComponent(t)}`,void 0,n);if(r<200||r>=300)throw this.fail(r,o)}async send(n,t,r,o){let s;try{s=await fetch(`${this.baseUrl}${t}`,{method:n,headers:{"content-type":"application/json",...o?{authorization:`Bearer ${o}`}:{}},body:r===void 0?void 0:JSON.stringify(r)})}catch(l){throw new d(`Could not reach the Portal API at ${this.baseUrl}: ${l instanceof Error?l.message:String(l)}`,0)}let i=await s.text(),a={};if(i)try{a=JSON.parse(i)}catch{a={reason:i}}return{status:s.status,data:a}}fail(n,t){let r=typeof t.code=="string"?t.code:void 0,o=typeof t.reason=="string"?t.reason:void 0;return n===401?new d("Your login is no longer valid.",n,r):new d(o||`The Portal API rejected the request (${r??n}).`,n,r)}};import k from"fs/promises";import b from"os";import J from"path";var be=448,B=384;function V(e=b.homedir()){return J.join(e,".portal")}function A(e=b.homedir()){return J.join(V(e),"credentials.json")}async function S(e=b.homedir()){let n;try{n=await k.readFile(A(e),"utf8")}catch{return null}try{let t=JSON.parse(n);return typeof t.token!="string"||!t.token?null:{token:t.token,expiresAt:typeof t.expiresAt=="string"?t.expiresAt:void 0}}catch{return null}}async function G(e,n=b.homedir()){let t=V(n),r=A(n);return await k.mkdir(t,{recursive:!0,mode:be}),await k.writeFile(r,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,{mode:B}),await k.chmod(r,B),r}async function j(e=b.homedir()){try{await k.unlink(A(e))}catch{}}var Se=e=>new Promise(n=>setTimeout(n,e));function Ie(e){return e.length===8?`${e.slice(0,4)}-${e.slice(4)}`:e}function Oe(){try{return Pe.hostname()||void 0}catch{return}}function Ce(e){let n=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",t=process.platform==="win32"?["/c","start","",e]:[e];try{let r=Ee(n,t,{stdio:"ignore",detached:!0});r.on("error",()=>{}),r.unref()}catch{}}async function Y(e){let n=e.sleep??Se,t=e.openBrowser??Ce,r=e.log??(p=>console.log(p)),o=new u(e.apiUrl),s=e.name??Oe(),i=await o.startDeviceLogin(s),a=`${i.verificationUri}?code=${encodeURIComponent(i.userCode)}`;r(""),r(` Your code: ${Ie(i.userCode)}`),r(` Approve at: ${a}`),r(""),r(" Waiting for approval\u2026"),t(a);let l=Date.now()+i.expiresIn*1e3,g=i.interval*1e3;for(;Date.now()<l;){await n(g);let p=await o.pollDeviceLogin(i.deviceCode);if(p.status==="slow_down"){g+=5e3;continue}if(p.status==="pending")continue;if(p.status==="denied")throw new Error("The login request was denied.");if(p.status==="expired")throw new Error("The login request expired. Run `portal login` to try again.");if(!p.token)throw new Error("This login code was already used. Run `portal login` to start a new one.");let w=await G({token:p.token,expiresAt:p.expiresAt}),$=null;try{$=await o.fetchIdentity(p.token)}catch{$=null}return{identity:$,credentialsPath:w}}throw new Error("The login request expired. Run `portal login` to try again.")}async function X(e){let n=await S();if(!n)return{loggedIn:!1};try{return{loggedIn:!0,identity:await new u(e.apiUrl).fetchIdentity(n.token)}}catch(t){if(t instanceof d&&t.status===401)return await j(),{loggedIn:!1};throw t}}async function Q(e){let n=await S();if(!n)return{wasLoggedIn:!1,revoked:!1};let t=!1,r;try{let o=new u(e.apiUrl);e.all?r=await o.revokeAllTokens(n.token):await o.revokeCurrentToken(n.token),t=!0}catch{}return await j(),{wasLoggedIn:!0,revoked:t,...r!==void 0?{revokedCount:r}:{}}}async function $e(){return(await S())?.token??null}async function x(){let e=await $e();if(!e)throw new Error("Not signed in. Run: portal login");return e}import et from"path";import{existsSync as ee}from"fs";import R from"path";import{mkdtemp as Ae,rm as je,writeFile as Ue}from"fs/promises";import{tmpdir as Re}from"os";import I from"path";import{pathToFileURL as Te}from"url";import Z from"esbuild";var U=class extends Error{};async function O(e){let n;try{n=(await Z.build({entryPoints:[e],bundle:!0,write:!1,format:"esm",platform:"node",target:"node18",logLevel:"silent",absWorkingDir:I.dirname(e)})).outputFiles[0].text}catch(o){throw new U(Le(o,e))}let t=await Ae(I.join(Re(),"portal-cli-")),r=I.join(t,"module.mjs");try{return await Ue(r,n,"utf8"),await import(Te(r).href)}finally{await je(t,{recursive:!0,force:!0})}}function Le(e,n){if(e!=null&&typeof e=="object"&&"errors"in e&&Array.isArray(e.errors)){let r=Z.formatMessagesSync(e.errors,{kind:"error",color:!1}).join(`
|
|
4
|
+
`).trimEnd();if(r)return r}return`Failed to compile ${I.basename(n)}: ${e instanceof Error?e.message:String(e)}`}var E=class extends Error{},Ne=["portal.config.ts","portal.config.mts","portal.config.js","portal.config.mjs"];function te(e,n){if(n){let t=R.resolve(e,n);if(!ee(t))throw new E(`No config file found at ${n}`);return t}for(let t of Ne){let r=R.join(e,t);if(ee(r))return r}throw new E(`No portal.config.ts found in ${e}. Create one, or point at it with --config <path>.`)}async function ne(e){let t=(await O(e)).default;if(t==null||typeof t!="object")throw new E(`${R.basename(e)} must \`export default defineConfig({ ... })\`.`);return t}function T(e,n){let t={version:"",project:{webhooks:e.webhooks&&typeof e.webhooks=="object"&&e.webhooks.url?{url:e.webhooks.url}:null},channels:{}};e.auth&&(t.project.auth={issuer:e.auth.issuer,jwksUrl:e.auth.jwksUrl,claimMap:{...e.auth.claimMap}});for(let[r,o]of Object.entries(e.channels??{}))t.channels[r]=De(o,n.get(r)??[]);return t}function De(e,n){let t={};e.authz&&(t.authz=!0),e.onPublish&&e.onPublish.length>0&&(t.onPublish=e.onPublish.length),e.onDisconnect&&e.onDisconnect.length>0&&(t.onDisconnect=e.onDisconnect.length),e.notify&&(t.notify=!0);let r={};for(let o of n)r[o.handle]={namespace:o.namespace,transport:o.transport};return{mode:e.mode??"standard",anonymous:e.anonymous??!0,hooks:t,extensions:r}}function qe(e){let n="";for(let t of e)n+=t.toString(16).padStart(2,"0");return n}async function Me(e){let n=new TextEncoder().encode(e),t=await crypto.subtle.digest("SHA-256",n);return qe(new Uint8Array(t))}function C(e){if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(C).join(",")}]`;let n=e;return`{${Object.keys(n).sort().map(r=>`${JSON.stringify(r)}:${C(n[r])}`).join(",")}}`}async function L(e,n,t){let{version:r,...o}=e;return`cfg_${await Me(C({extract:o,hookScript:n??null,extensionScripts:t}))}`}import oe from"path";import Fe from"esbuild";var f=class extends Error{},N=5;function D(e,n){e.webhooks!=null&&ze(e.webhooks);for(let[t,r]of Object.entries(e.channels??{})){if(r&&"onMiddlewareFailure"in r)throw new f(`Channel "${t}" sets \`onMiddlewareFailure\`, which is no longer supported \u2014 message handling always fails closed. Remove it.`);_e(t),We(t,n.get(t)??[]),He(t,n.get(t)??[])}}function _e(e){if(e.includes("*")&&!/^[^*]+\*$/.test(e))throw new f(`Unsupported channel pattern "${e}": a template must end with a single "*" over a fixed prefix (e.g. "room-*").`)}function We(e,n){if(n.length<=N)return;let t=n.map(r=>`"${r.handle}"`).join(", ");throw new f(`Channel "${e}" attaches ${n.length} extensions (${t}), but a channel may carry at most ${N}. Remove ${n.length-N} of them, or split the channel.`)}function He(e,n){let t=new Map;for(let r of n){let o=t.get(r.namespace);if(o)throw new f(`Channel "${e}" has two extensions using the namespace "${r.namespace}" ("${o}" and "${r.handle}"). Each namespace must be unique within a channel.`);t.set(r.namespace,r.handle)}}function ze(e){let{url:n}=e;if(typeof n!="string"||!n)throw new f("`webhooks.url` must be a non-empty string.");let t;try{t=new URL(n)}catch{throw new f(`\`webhooks.url\` is not a valid URL: "${n}".`)}let r=t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="[::1]";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&r))throw new f(`\`webhooks.url\` must use https (http is only allowed for localhost): "${n}".`);let o=Ke(t.hostname);if(o)throw new f(`\`webhooks.url\` must not point to a private or internal address (${o}): "${n}".`)}function Ke(e){let n=e.replace(/^\[|\]$/g,"").toLowerCase();if(n.endsWith(".internal")||n.endsWith(".localhost"))return"internal TLD";let t=n.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);if(t){let[r,o]=[Number(t[1]),Number(t[2])];if(r===10)return"RFC1918 10.0.0.0/8";if(r===172&&o>=16&&o<=31)return"RFC1918 172.16.0.0/12";if(r===192&&o===168)return"RFC1918 192.168.0.0/16";if(r===169&&o===254)return"link-local 169.254.0.0/16";if(r===127)return"loopback 127.0.0.0/8";if(r===0)return"unspecified 0.0.0.0"}return n==="::1"||n==="0:0:0:0:0:0:0:1"?"IPv6 loopback":n.startsWith("fc")||n.startsWith("fd")?"IPv6 unique-local fc00::/7":n.startsWith("fe80:")?"IPv6 link-local fe80::/10":n.startsWith("fec0:")?"IPv6 site-local fec0::/10":null}var Be="hooks",q="PortalExtension";function ie(e){return`import Extension from ${JSON.stringify(e)};
|
|
5
5
|
|
|
6
|
-
export class ${
|
|
6
|
+
export class ${q} {
|
|
7
7
|
constructor(ctx, env) {
|
|
8
8
|
this.ctx = ctx;
|
|
9
9
|
this.instance = new Extension(ctx, env);
|
|
@@ -83,7 +83,7 @@ export default {
|
|
|
83
83
|
);
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
|
-
`}function
|
|
86
|
+
`}function se(e){return`import config from ${JSON.stringify(e)};
|
|
87
87
|
|
|
88
88
|
const channels = config.channels || {};
|
|
89
89
|
|
|
@@ -132,10 +132,10 @@ export async function run(request) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export default { run };
|
|
135
|
-
`}function
|
|
136
|
-
`),n(r);return}a===
|
|
137
|
-
`),process.exit(1)),a===
|
|
135
|
+
`}function ae(e){return Object.values(e.channels??{}).some(n=>n.authz!=null||(n.onPublish?.length??0)>0||(n.onDisconnect?.length??0)>0||n.notify!=null)}async function M(e,n,t){let r=oe.dirname(n),o;if(ae(e)){let i=await Ve(n);o={name:Be,content:i}}let s=[];for(let[i,a]of t)for(let l of a){let g=await Ge(l.path,r);s.push({key:i,handle:l.handle,name:Ye(i,l.handle),durableObjectClass:q,content:g})}return Je(s),{hookScript:o,extensionScripts:s}}function Je(e){let n=new Map;for(let t of e){let r=n.get(t.name);if(r)throw new f(`Extensions "${r.handle}" (channel "${r.key}") and "${t.handle}" (channel "${t.key}") both map to the upload name "${t.name}". Rename a handle or channel so they differ by more than punctuation.`);n.set(t.name,t)}}function _(e,n){for(let t of n){let r=e.channels[t.key]?.extensions[t.handle];r&&(r.script=t.name)}}async function Ve(e){return ce("hooks.mjs",se(e),oe.dirname(e))}async function Ge(e,n){return ce("extension.mjs",ie(e),n)}async function ce(e,n,t){return(await Fe.build({stdin:{contents:n,sourcefile:e,resolveDir:t},bundle:!0,write:!1,format:"esm",target:"es2022",platform:"neutral",logLevel:"silent",absWorkingDir:t})).outputFiles[0].text}function Ye(e,n){return`ext-${re(e)}-${re(n)}`}function re(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}import{existsSync as Xe}from"fs";import Qe from"path";var P=class extends Error{};async function W(e,n){let t=new Map;for(let[r,o]of Object.entries(e.channels??{})){let s=o.extensions;if(!s||Object.keys(s).length===0)continue;let i=[];for(let[a,l]of Object.entries(s)){let g=Qe.resolve(n,l);if(!Xe(g))throw new P(`Channel "${r}" attaches extension "${a}" from "${l}", but that file does not exist.`);let p=await O(g),w=Ze(p.default);if(!w)throw new P(`Extension "${a}" (${l}) must \`export default defineExtension(...)\` with a static manifest.`);i.push({handle:a,source:l,namespace:w.namespace,transport:w.transport,path:g})}t.set(r,i)}return t}function Ze(e){if(typeof e!="function"&&typeof e!="object")return null;let n=e?.manifest;if(n==null||typeof n!="object")return null;let{namespace:t,transport:r}=n;return typeof t!="string"||!t.endsWith(".")||r!=="ws"&&r!=="http"?null:{namespace:t,transport:r}}async function le(e){let n=te(e.cwd,e.configPath),t=await ne(n),r=et.dirname(n),o=await W(t,r);D(t,o);let s=T(t,o),i=await M(t,n,o);_(s,i.extensionScripts),s.version=await L(s,i.hookScript,i.extensionScripts);let a=new v(e.apiUrl,e.secret),{versionId:l,alreadyDeployed:g}=await a.createDeploy({extract:s,hookScript:i.hookScript,extensionScripts:i.extensionScripts.length>0?i.extensionScripts:void 0});return await a.activate(l),{configPath:n,versionId:l,alreadyDeployed:g,channels:Object.keys(s.channels),hasHookScript:!!i.hookScript,extensionCount:i.extensionScripts.length}}async function pe(e){await new v(e.apiUrl,e.secret).setSecret(e.name,e.value)}import de from"path";var tt="https://api.useportal.co";function ue(e,n){let t=de.relative(n,e);return t&&!t.startsWith("..")&&!de.isAbsolute(t)?t:e}function m(){return(process.env.PORTAL_API_URL||tt).replace(/\/+$/,"")}function H(){let e=process.env.PORTAL_SECRET;if(!e)throw new Error("Set PORTAL_SECRET to your project's secret key (sk_...) before running this command.");return e}async function fe(e){if(e!=null&&e!=="")return e;if(process.stdin.isTTY){let t=await lt("Value: ");if(!t)throw new Error("No value entered.");return t}let n=await nt();if(!n)throw new Error("No value provided. Pass --value <value> or pipe the value on stdin.");return n}async function nt(){let e=[];for await(let n of process.stdin)e.push(n);return Buffer.concat(e).toString("utf8").trim()}var rt=10,ot=13,it=4,st=3,at=8,ct=127;function lt(e){return new Promise(n=>{let t=process.stdin;process.stdout.write(e),t.setRawMode?.(!0),t.resume(),t.setEncoding("utf8");let r="",o=s=>{for(let i of s){let a=i.charCodeAt(0);if(a===rt||a===ot||a===it){t.setRawMode?.(!1),t.pause(),t.removeListener("data",o),process.stdout.write(`
|
|
136
|
+
`),n(r);return}a===st&&(t.setRawMode?.(!1),t.pause(),t.removeListener("data",o),process.stdout.write(`
|
|
137
|
+
`),process.exit(1)),a===at||a===ct?r=r.slice(0,-1):r+=i}};t.on("data",o)})}async function ge(e){let n=await x();return new u(e.apiUrl).createApiKey(n,e.environmentId,e.keyType)}import{Portal as pt}from"@portalsdk/core";import{WebSocket as dt}from"ws";typeof globalThis.WebSocket>"u"&&(globalThis.WebSocket=dt);function ut(e){let n=new Date(e.timestamp).toISOString(),t=e.sender.anon?`anon:${e.sender.id}`:e.sender.username??e.sender.id,r=typeof e.content=="string"?e.content:JSON.stringify(e.content);return`[${n}] ${t}: ${r}`}function me(e){let n=e.log??(s=>console.log(s)),t=e.info??(s=>console.error(s)),o=new pt({apiKey:e.apiKey,...e.apiUrl?{apiUrl:e.apiUrl}:{},...e.realtimeUrl?{realtimeUrl:e.realtimeUrl}:{}}).channel(e.channelId,{history:"none"});return new Promise(s=>{let i=!1,a=()=>{i||(i=!0,process.off("SIGINT",a),l(),g(),o.release(),s())},l=o.on("message",p=>{n(ut(p))}),g=o.on("status",(p,w)=>{p==="ready"&&t(`Listening on ${e.channelId} (Ctrl-C to exit)\u2026`),p==="blocked"&&(t(`Connection blocked${w?`: ${w.message}`:""}.`),a())});process.on("SIGINT",a),o.acquire()})}var z='Origin must be a bare "scheme://host[:port]" \u2014 e.g. https://app.example.com (no path, no trailing slash).';function ft(e){let n=e.trim().replace(/\/+$/,""),t;try{t=new URL(n)}catch{return{ok:!1,message:z}}return t.protocol!=="http:"&&t.protocol!=="https:"?{ok:!1,message:z}:t.origin!==n?{ok:!1,message:z}:{ok:!0,origin:n}}async function he(e){let n=ft(e.origin);if(!n.ok)throw new Error(n.message);let t=await x();return new u(e.apiUrl).createAllowedOrigin(t,e.environmentId,n.origin)}async function ye(e){let n=await x();return new u(e.apiUrl).listAllowedOrigins(n,e.environmentId)}async function we(e){let n=await x();await new u(e.apiUrl).deleteAllowedOrigin(n,e.originId)}async function xe(e){let n=await x(),t=new u(e.apiUrl),r=await t.fetchIdentity(n),{organizations:o}=r;if(o.length===0)throw new Error("Your account has no organization yet \u2014 create one in the dashboard first.");if(o.length>1){let s=o.map(i=>i.name).join(", ");throw new Error(`Your account belongs to multiple organizations (${s}). portal projects create only supports a single organization right now.`)}return t.createProject(n,o[0].id,e.name)}var ht=gt(import.meta.url),yt=ht("../package.json").version,h=new mt;h.name("portal").description("Sign in, deploy your Portal configuration, and manage secrets.").addHelpText("after",`
|
|
138
138
|
Authentication:
|
|
139
139
|
login signs you in as a user (for account-level commands)
|
|
140
140
|
PORTAL_SECRET your project's secret key (sk_...), used by deploy and secrets
|
|
141
|
-
`).version(
|
|
141
|
+
`).version(yt);h.command("login").description("Sign in to Portal.").option("--name <name>","Label for this machine (default: its hostname)").action(async e=>{await y(async()=>{let n=await Y({apiUrl:m(),name:e.name}),t=n.identity?ke(n.identity):null;console.log(`${c.green("\u2713")} Signed in${t?` as ${c.bold(t)}`:""}.`)})});h.command("whoami").description("Show who you are signed in as.").action(async()=>{await y(async()=>{let e=await X({apiUrl:m()});if(!e.loggedIn){console.log("Not signed in. Run: portal login");return}console.log(ke(e.identity));let{organizations:n}=e.identity;n.length>0&&console.log(c.dim(` ${n.map(t=>t.name).join(", ")}`))})});h.command("logout").description("Sign out of Portal on this machine.").option("--all","Revoke every CLI session for your account, not just this machine").action(async e=>{await y(async()=>{let n=await Q({apiUrl:m(),all:e.all});if(!n.wasLoggedIn){console.log("You were not signed in.");return}if(n.revokedCount!==void 0){let t=n.revokedCount;console.log(`${c.green("\u2713")} Signed out. Revoked ${t} CLI session${t===1?"":"s"}.`);return}console.log(`${c.green("\u2713")} Signed out.`)})});h.command("deploy").description("Deploy your portal.config.ts.").option("-c, --config <path>","Path to your config file (default: portal.config.ts)").action(async e=>{await y(async()=>{let n=H(),t=await le({cwd:process.cwd(),configPath:e.config,apiUrl:m(),secret:n});kt(t)})});var wt=h.command("secrets").description("Manage your project secrets.");wt.command("set").argument("<name>",'Secret name, referenced in your config with env("NAME")').description("Set a project secret.").option("--value <value>","The secret value (otherwise read from stdin)").action(async(e,n)=>{await y(async()=>{let t=H(),r=await fe(n.value);await pe({name:e,value:r,apiUrl:m(),secret:t}),console.log(`${c.green("\u2713")} Secret ${c.bold(e)} set.`)})});var xt=h.command("projects").description("Manage your Portal projects.");xt.command("create").argument("<name>","Project name").description("Create a project (auto-picks your organization) and its two environments.").action(async e=>{await y(async()=>{let n=await xe({apiUrl:m(),name:e});console.log(`${c.green("\u2713")} Created project ${c.bold(n.project.name)}`),console.log(` Project: ${n.project.id}`);for(let t of n.environments)console.log(` ${bt(t.type)}: ${t.id}`)})});var vt=h.command("keys").description("Manage environment API keys.");vt.command("create").description("Create an API key for an environment. The value is printed once.").requiredOption("--env <envId>","Environment id").requiredOption("--type <type>","public|secret").action(async e=>{await y(async()=>{if(e.type!=="public"&&e.type!=="secret")throw new Error("--type must be public or secret.");let n=await ge({apiUrl:m(),environmentId:e.env,keyType:e.type});console.log(`${c.green("\u2713")} Created ${n.keyType} key.`),console.log(` ${n.value}`),console.log(c.dim(" This value is shown once \u2014 store it now."))})});var K=h.command("origins").description("Manage an environment's allowed browser origins.");K.command("add").argument("<origin>","Browser origin, e.g. https://app.example.com").description("Register an origin so browsers on it can use a pk_ from that environment.").requiredOption("--env <envId>","Environment id").action(async(e,n)=>{await y(async()=>{let t=await he({apiUrl:m(),environmentId:n.env,origin:e});console.log(`${c.green("\u2713")} Registered ${c.bold(t.origin)}`),console.log(c.dim(" localhost / 127.0.0.1 never need registering \u2014 loopback is always allowed."))})});K.command("list").description("List an environment's registered origins.").requiredOption("--env <envId>","Environment id").action(async e=>{await y(async()=>{let n=await ye({apiUrl:m(),environmentId:e.env});if(n.length===0){console.log("No origins registered (loopback is always allowed regardless).");return}for(let t of n)console.log(`${t.id} ${t.origin}`)})});K.command("remove").argument("<id>","Origin id, from `portal origins list`").description("Remove a registered origin.").action(async e=>{await y(async()=>{await we({apiUrl:m(),originId:e}),console.log(`${c.green("\u2713")} Removed.`)})});h.command("listen").argument("<channelId>","Channel id to listen on").description("Connect anonymously and print every incoming message as one line.").requiredOption("--key <key>","Publishable key (pk_...)").action(async(e,n)=>{await y(async()=>{await me({apiKey:n.key,channelId:e})})});h.parseAsync(process.argv);async function y(e){try{await e()}catch(n){console.error(`${c.red("Error:")} ${n instanceof Error?n.message:String(n)}`),process.exitCode=1}}function kt(e){e.alreadyDeployed?console.log(`${c.green("\u2713")} Already up to date \u2014 version ${c.bold(e.versionId)}`):(console.log(`${c.green("\u2713")} Deployed ${c.bold(ue(e.configPath,process.cwd()))}`),console.log(` Version ${e.versionId}`));let{channels:n}=e;console.log(` ${n.length} channel ${ve(n.length,"override","overrides")}: ${n.join(", ")}`);let t=[];e.hasHookScript&&t.push("hooks"),e.extensionCount>0&&t.push(`${e.extensionCount} ${ve(e.extensionCount,"extension","extensions")}`),t.length>0&&console.log(` Uploaded: ${t.join(", ")}`),console.log(c.dim(" Channels with active connections keep their current configuration until they restart; new connections use this version right away."))}function ve(e,n,t){return e===1?n:t}function bt(e){return e.charAt(0).toUpperCase()+e.slice(1)}function ke(e){let{firstName:n,lastName:t,email:r}=e.user,o=[n,t].filter(Boolean).join(" ").trim();return o?`${o} (${r})`:r}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portalsdk/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Portal command line: deploy your configuration and manage secrets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"tsx": "^4.7.1",
|
|
37
37
|
"typescript": "^5.8.3",
|
|
38
38
|
"vitest": "^3.2.4",
|
|
39
|
-
"@portalsdk/
|
|
40
|
-
"@portalsdk/
|
|
39
|
+
"@portalsdk/config": "0.2.0",
|
|
40
|
+
"@portalsdk/extension-protocol": "^0.1.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsup",
|