@medplum/cli 2.0.26 → 2.0.28
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/cjs/index.cjs +4 -4
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/esm/index.mjs +4 -4
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/profiles.d.ts +2 -0
- package/dist/types/storage.d.ts +3 -1
- package/dist/types/util/client.d.ts +1 -1
- package/dist/types/utils.d.ts +1 -0
- package/package.json +9 -9
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var vt=Object.create;var I=Object.defineProperty;var bt=Object.getOwnPropertyDescriptor;var Et=Object.getOwnPropertyNames;var Mt=Object.getPrototypeOf,kt=Object.prototype.hasOwnProperty;var Pt=(e,t)=>{for(var o in t)I(e,o,{get:t[o],enumerable:!0})},ye=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of Et(t))!kt.call(e,a)&&a!==o&&I(e,a,{get:()=>t[a],enumerable:!(n=bt(t,a))||n.enumerable});return e};var P=(e,t,o)=>(o=e!=null?vt(Mt(e)):{},ye(t||!e||!e.__esModule?I(o,"default",{value:e,enumerable:!0}):o,e)),xt=e=>ye(I({},"__esModule",{value:!0}),e);var ao={};Pt(ao,{main:()=>wt,run:()=>Ct});module.exports=xt(ao);var q=require("@medplum/core"),ht=require("commander"),St=P(require("dotenv"));var T=require("@medplum/core"),ve=require("os"),be=require("child_process"),Ee=require("http");var he=require("commander");function m(e){return new he.Command(e).option("--client-id <clientId>","FHIR server client id").option("--client-secret <clientSecret>","FHIR server client secret").option("--base-url <baseUrl>","FHIR server base url").option("--token-url <tokenUrl>","FHIR server token url").option("--authorize-url <authorizeUrl>","FHIR server authorize url").option("--fhir-url-path <fhirUrlPath>","FHIR server url path")}var Ce=require("@medplum/core");var Se=require("@medplum/core"),h=require("fs"),we=require("os"),G=require("path"),R=class extends Se.ClientStorage{constructor(){super();this.dirName=(0,G.resolve)((0,we.homedir)(),".medplum"),this.fileName=(0,G.resolve)(this.dirName,"credentials")}clear(){this.writeFile({})}getString(o){return this.readFile()?.[o]}setString(o,n){let a=this.readFile()??{};n?a[o]=n:delete a[o],this.writeFile(a)}readFile(){if((0,h.existsSync)(this.fileName))return JSON.parse((0,h.readFileSync)(this.fileName,"utf8"))}writeFile(o){(0,h.existsSync)(this.dirName)||(0,h.mkdirSync)(this.dirName),(0,h.writeFileSync)(this.fileName,JSON.stringify(o,null,2),"utf8")}};async function l(e){let t=e.baseUrl??process.env.MEDPLUM_BASE_URL??"https://api.medplum.com/",o=e.fhirUrlPath??process.env.MEDPLUM_FHIR_URL_PATH??"",n=e.accessToken??process.env.MEDPLUM_CLIENT_ACCESS_TOKEN??"",a=e.tokenUrl??process.env.MEDPLUM_TOKEN_URL??"",i=e.authorizeUrl??process.env.MEDPLUM_AUTHORIZE_URL??"",s=e.fetch??fetch,c=new Ce.MedplumClient({fetch:s,baseUrl:t,tokenUrl:a,fhirUrlPath:o,authorizeUrl:i,storage:new R,onUnauthenticated:At});n&&c.setAccessToken(n);let u=e.clientId||process.env.MEDPLUM_CLIENT_ID,y=e.clientSecret||process.env.MEDPLUM_CLIENT_SECRET;return u&&y&&(c.setBasicAuth(u,y),await c.startClientLogin(u,y)),c}function At(){console.log("Unauthenticated: run `npx medplum login` to sign in")}var Me="medplum-cli",ke="http://localhost:9615",H=m("login"),V=m("whoami");H.action(async e=>{let t=await l(e);await Dt(t)});V.action(async e=>{let t=await l(e);Rt(t)});async function Dt(e){await Nt(e);let t=new URL(e.getAuthorizeUrl());t.searchParams.set("client_id",Me),t.searchParams.set("redirect_uri",ke),t.searchParams.set("scope","openid"),t.searchParams.set("response_type","code"),t.searchParams.set("prompt","login"),await It(t.toString())}async function Nt(e){let t=(0,Ee.createServer)(async(o,n)=>{let a=new URL(o.url,"http://localhost:9615"),i=a.searchParams.get("code");if(a.pathname==="/"&&i)try{let s=await e.processCode(i,{clientId:Me,redirectUri:ke});n.writeHead(200,{"Content-Type":"text/plain"}),n.end(`Signed in as ${(0,T.getDisplayString)(s)}. You may close this window.`)}catch(s){n.writeHead(400,{"Content-Type":"text/plain"}),n.end(`Error: ${(0,T.normalizeErrorString)(s)}`)}finally{t.close()}else n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not found")}).listen(9615)}async function It(e){let t=(0,ve.platform)(),o;switch(t){case"openbsd":case"linux":o=`xdg-open '${e}'`;break;case"darwin":o=`open '${e}'`;break;case"win32":o=`cmd /c start "" "${e}"`;break;default:throw new Error("Unsupported platform: "+t)}(0,be.exec)(o)}function Rt(e){let t=e.getActiveLogin();t?(console.log(`Server: ${e.getBaseUrl()}`),console.log(`Profile: ${t.profile.display} (${t.profile.reference})`),console.log(`Project: ${t.project.display} (${t.project.reference})`)):console.log("Not logged in")}var Qe=require("commander");var w=require("@aws-sdk/client-cloudformation"),Pe=require("@aws-sdk/client-cloudfront"),xe=require("@aws-sdk/client-ecs"),Ae=require("@aws-sdk/client-s3"),Y=new w.CloudFormationClient({}),De=new Pe.CloudFrontClient({}),Ne=new xe.ECSClient({}),Ie=new Ae.S3Client({}),Tt="medplum:environment";async function J(){return(await Y.send(new w.ListStacksCommand({}))).StackSummaries?.filter(t=>t.StackName&&t.StackStatus!=="DELETE_COMPLETE")||[]}async function E(e){let t=await J();for(let o of t){let n=o.StackName,a=await X(n);if(a?.tag===e)return a}}async function X(e){let t=new w.DescribeStacksCommand({StackName:e}),n=(await Y.send(t))?.Stacks?.[0],a=n?.Tags?.find(c=>c.Key===Tt);if(!a)return;let i=await Y.send(new w.DescribeStackResourcesCommand({StackName:e}));if(!i.StackResources)return;let s={stack:n,tag:a.Value};for(let c of i.StackResources)c.ResourceType==="AWS::ECS::Cluster"?s.ecsCluster=c:c.ResourceType==="AWS::ECS::Service"?s.ecsService=c:c.ResourceType==="AWS::S3::Bucket"&&c.LogicalResourceId?.startsWith("FrontEndAppBucket")?s.appBucket=c:c.ResourceType==="AWS::S3::Bucket"&&c.LogicalResourceId?.startsWith("StorageStorageBucket")?s.storageBucket=c:c.ResourceType==="AWS::CloudFront::Distribution"&&c.LogicalResourceId?.startsWith("FrontEndAppDistribution")&&(s.appDistribution=c);return s}function B(e){console.log(`Medplum Tag: ${e.tag}`),console.log(`Stack Name: ${e.stack.StackName}`),console.log(`Stack ID: ${e.stack.StackId}`),console.log(`Status: ${e.stack.StackStatus}`),console.log(`ECS Cluster: ${e.ecsCluster?.PhysicalResourceId}`),console.log(`ECS Service: ${Z(e.ecsService)}`),console.log(`App Bucket: ${e.appBucket?.PhysicalResourceId}`),console.log(`Storage Bucket: ${e.storageBucket?.PhysicalResourceId}`)}function Z(e){return e?.PhysicalResourceId?.split("/")?.pop()||""}async function Re(e){let t=await E(e);if(!t){console.log("Stack not found");return}B(t)}var C=require("@aws-sdk/client-acm"),M=require("@aws-sdk/client-ssm"),j=require("@aws-sdk/client-sts"),$=require("crypto"),L=require("fs"),Be=require("path"),Ue=P(require("readline")),Bt=e=>`${e}DomainName`,Fe=e=>`${e}SslCertArn`,F;async function je(){let e={apiPort:8103,region:"us-east-1"};F=Ue.default.createInterface({input:process.stdin,output:process.stdout}),d("MEDPLUM"),r("This tool prepares the necessary prerequisites for deploying Medplum in your AWS account."),r(""),r("Most Medplum infrastructure is deployed using the AWS CDK."),r("However, some AWS resources must be created manually, such as email addresses and SSL certificates."),r("This tool will help you create those resources."),r(""),r("Upon completion, this tool will:"),r(" 1. Generate a Medplum CDK config file (i.e., medplum.demo.config.json)"),r(" 2. Optionally generate an AWS CloudFront signing key"),r(" 3. Optionally request SSL certificates from AWS Certificate Manager"),r(" 4. Optionally write server config settings to AWS Parameter Store"),r(""),r("The Medplum infra config file is an input to the Medplum CDK."),r("The Medplum CDK will create and manage the necessary AWS resources."),r(""),r("We will ask a series of questions to generate your infra config file."),r("Some questions have predefined options in [square brackets]."),r("Some questions have default values in (parentheses), which you can accept by pressing Enter."),r("Press Ctrl+C at any time to exit.");let t=await Ut(e.region);t||(r("It appears that you do not have AWS credentials configured."),r("AWS credentials are not strictly required, but will enable some additional features."),r("If you intend to use AWS credentials, please configure them now."),await U("Do you want to continue without AWS credentials?")),d("ENVIRONMENT NAME"),r('Medplum deployments have a short environment name such as "prod", "staging", "alice", or "demo".'),r("The environment name is used in multiple places:"),r(" 1. As part of config file names (i.e., medplum.demo.config.json)"),r(" 2. As the base of CloudFormation stack names (i.e., MedplumDemo)"),r(" 3. AWS Parameter Store keys (i.e., /medplum/demo/...)"),e.name=await f("What is your environment name?","demo"),r('Using environment name "'+e.name+'"...'),d("CONFIG FILE"),r("Medplum Infrastructure will create a config file in the current directory.");let o=await f("What is the config file name?",`medplum.${e.name}.config.json`);(0,L.existsSync)(o)&&(r("Config file already exists."),await U("Do you want to overwrite the config file?")),r('Using config file "'+o+'"...'),p(o,e),d("AWS REGION"),r("Most Medplum resources will be created in a single AWS region."),e.region=await f("Enter your AWS region:","us-east-1"),p(o,e),d("AWS ACCOUNT NUMBER"),r("Medplum Infrastructure will use your AWS account number to create AWS resources."),t&&r("Using the AWS CLI, your current account ID is: "+t),e.accountNumber=await f("What is your AWS account number?",t),p(o,e),d("STACK NAME"),r("Medplum will create a CloudFormation stack to manage AWS resources."),r("AWS CloudFormation stack names ");let n="Medplum"+e.name.charAt(0).toUpperCase()+e.name.slice(1);for(e.stackName=await f("Enter your CloudFormation stack name?",n),p(o,e),d("BASE DOMAIN NAME"),r("Please enter the base domain name for your Medplum deployment."),r(""),r("Medplum deploys multiple subdomains for various services."),r(""),r('For example, "api." for the REST API and "app." for the web application.'),r("The base domain name is the common suffix for all subdomains."),r(""),r('For example, if your base domain name is "example.com",'),r('then the REST API will be "api.example.com".'),r(""),r('The base domain should include the TLD (i.e., ".com", ".org", ".net").'),r(""),r("Note that you must own the base domain, and it must use Route53 DNS.");!e.domainName;)e.domainName=await f("Enter your base domain name:");p(o,e),d("SUPPORT EMAIL"),r("Medplum sends transactional emails to users."),r("For example, emails to new users or for password reset."),r("Medplum will use the support email address to send these emails."),r("Note that you must verify the support email address in SES.");let a=await f("Enter your support email address:");d("API DOMAIN NAME"),r("Medplum deploys a REST API for the backend services."),e.apiDomainName=await f("Enter your REST API domain name:","api."+e.domainName),p(o,e),d("APP DOMAIN NAME"),r("Medplum deploys a web application for the user interface."),e.appDomainName=await f("Enter your web application domain name:","app."+e.domainName),p(o,e),d("STORAGE DOMAIN NAME"),r("Medplum deploys a storage service for file uploads."),e.storageDomainName=await f("Enter your storage domain name:","storage."+e.domainName),p(o,e),d("STORAGE BUCKET"),r("Medplum uses an S3 bucket to store binary content such as file uploads."),r("Medplum will create a the S3 bucket as part of the CloudFormation stack."),e.storageBucketName=await f("Enter your storage bucket name:","medplum-"+e.name+"-storage"),p(o,e),d("MAX AVAILABILITY ZONES"),r("Medplum API servers can be deployed in multiple availability zones."),r("This provides redundancy and high availability."),r("However, it also increases the cost of the deployment."),r("If you want to use all availability zones, choose a large number such as 99."),r("If you want to restrict the number, for example to manage EIP limits,"),r("then choose a small number such as 1 or 2."),e.maxAzs=await x("Enter the maximum number of availability zones:",[1,2,3,99],2),d("DATABASE INSTANCES"),r("Medplum uses a relational database to store data."),r("You can set up your own database,"),r("or Medplum can create a new RDS database as part of the CloudFormation stack."),await ee("Do you want to create a new RDS database as part of the CloudFormation stack?")?(r("Medplum will create a new RDS database as part of the CloudFormation stack."),r(""),r("If you need high availability, you can choose multiple instances."),r("Use 1 for a single instance, or 2 for a primary and a standby."),e.rdsInstances=await x("Enter the number of database instances:",[1,2],1)):(r("Medplum will not create a new RDS database."),r("Please create a new RDS database and enter the database name, username, and password."),r('Set the AWS Secrets Manager secret ARN in the config file in the "rdsSecretsArn" setting.'),e.rdsSecretsArn="TODO"),p(o,e),d("SERVER INSTANCES"),r("Medplum uses AWS Fargate to run the API servers."),r("Medplum will create a new Fargate cluster as part of the CloudFormation stack."),r("Fargate will automatically scale the number of servers up and down."),r("If you need high availability, you can choose multiple instances."),e.desiredServerCount=await x("Enter the number of server instances:",[1,2,3,4,6,8],1),p(o,e),d("SERVER MEMORY"),r("You can choose the amount of memory for each server instance."),r("The default is 512 MB, which is sufficient for getting started."),r("Note that only certain CPU units are compatible with memory units."),r('Consult AWS Fargate "Task Definition Parameters" for more information.'),e.serverMemory=await x("Enter the server memory (MB):",[512,1024,2048,4096,8192,16384],512),p(o,e),d("SERVER CPU"),r("You can choose the amount of CPU for each server instance."),r("CPU is expressed as an integer using AWS CPU units"),r("The default is 256, which is sufficient for getting started."),r("Note that only certain CPU units are compatible with memory units."),r('Consult AWS Fargate "Task Definition Parameters" for more information.'),e.serverCpu=await x("Enter the server CPU:",[256,512,1024,2048,4096,8192,16384],256),p(o,e),d("SERVER IMAGE"),r("Medplum uses Docker images for the API servers."),r("You can choose the image to use for the servers."),r("Docker images can be loaded from either Docker Hub or AWS ECR."),r("The default is the latest Medplum release."),e.serverImage=await f("Enter the server image:","medplum/medplum-server:latest"),p(o,e),d("SIGNING KEY"),r("Medplum uses AWS CloudFront Presigned URLs for binary content such as file uploads.");let{privateKey:i,publicKey:s,passphrase:c}=Lt();e.storagePublicKey=s,p(o,e),d("SSL CERTIFICATES"),r("Medplum will now check for existing SSL certificates for the subdomains.");let u=await Ft(e.region);r("Found "+u.length+" certificate(s).");for(let{region:z,certName:D}of[{region:e.region,certName:"api"},{region:"us-east-1",certName:"app"},{region:"us-east-1",certName:"storage"}]){r("");let N=await jt(e,u,z,D);e[Fe(D)]=N,p(o,e)}d("AWS PARAMETER STORE"),r("Medplum uses AWS Parameter Store to store sensitive configuration values."),r("These values will be encrypted at rest."),r(`The values will be stored in the "/medplum/${e.name}" path.`);let y={port:e.apiPort,baseUrl:`https://${e.apiDomainName}/`,appBaseUrl:`https://${e.appDomainName}/`,storageBaseUrl:`https://${e.storageDomainName}/binary/`,binaryStorage:`s3:${e.storageBucketName}`,signingKey:i,signingKeyPassphrase:c,supportEmail:a};r(JSON.stringify({...y,signingKey:"****",signingKeyPassphrase:"****"},null,2)),await U("Do you want to store these values in AWS Parameter Store?"),await _t(e.region,`/medplum/${e.name}/`,y),d("DONE!"),r("Medplum configuration complete."),r("You can now proceed to deploying the Medplum infrastructure with CDK."),r("Run:"),r(""),r(` npx cdk bootstrap -c config=${o}`),r(` npx cdk synth -c config=${o}`),e.region==="us-east-1"?r(` npx cdk deploy -c config=${o}`):r(` npx cdk deploy -c config=${o} --all`),r(""),r("See Medplum documentation for more information:"),r(""),r(" https://www.medplum.com/docs/self-hosting/install-on-aws"),r(""),F.close()}function r(e){F.write(e+`
|
|
2
|
+
"use strict";var Dt=Object.create;var U=Object.defineProperty;var It=Object.getOwnPropertyDescriptor;var Rt=Object.getOwnPropertyNames;var Tt=Object.getPrototypeOf,Ut=Object.prototype.hasOwnProperty;var Bt=(e,t)=>{for(var o in t)U(e,o,{get:t[o],enumerable:!0})},Se=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of Rt(t))!Ut.call(e,a)&&a!==o&&U(e,a,{get:()=>t[a],enumerable:!(n=It(t,a))||n.enumerable});return e};var I=(e,t,o)=>(o=e!=null?Dt(Tt(e)):{},Se(t||!e||!e.__esModule?U(o,"default",{value:e,enumerable:!0}):o,e)),jt=e=>Se(U({},"__esModule",{value:!0}),e);var ho={};Bt(ho,{main:()=>Nt,run:()=>At});module.exports=jt(ho);var H=require("@medplum/core"),Pt=require("commander"),xt=I(require("dotenv"));var j=require("@medplum/core"),ve=require("os"),Ee=require("child_process"),Me=require("http");var B=require("commander");function m(e){return new B.Command(e).option("--client-id <clientId>","FHIR server client id").option("--client-secret <clientSecret>","FHIR server client secret").option("--base-url <baseUrl>","FHIR server base url").option("--token-url <tokenUrl>","FHIR server token url").option("--authorize-url <authorizeUrl>","FHIR server authorize url").option("--fhir-url-path <fhirUrlPath>","FHIR server url path").addOption(new B.Option("--auth-type <authType>","Type of authentication").choices(["basic","client_credentials","authorization_code","jwt-bearer","token-exchange"]))}var be=require("@medplum/core");var we=require("@medplum/core"),h=require("fs"),Ce=require("os"),V=require("path"),S=class extends we.ClientStorage{constructor(o){super();this.dirName=(0,V.resolve)((0,Ce.homedir)(),".medplum"),this.fileName=(0,V.resolve)(this.dirName,o+".json")}clear(){this.writeFile({})}getString(o){return this.readFile()?.[o]}setString(o,n){let a=this.readFile()??{};n?a[o]=n:delete a[o],this.writeFile(a)}getObject(o){let n=this.getString(o);return n?JSON.parse(n):void 0}setObject(o,n){this.setString(o,n?JSON.stringify(n):void 0)}readFile(){if((0,h.existsSync)(this.fileName))return JSON.parse((0,h.readFileSync)(this.fileName,"utf8"))}writeFile(o){(0,h.existsSync)(this.dirName)||(0,h.mkdirSync)(this.dirName),(0,h.writeFileSync)(this.fileName,JSON.stringify(o,null,2),"utf8")}};async function l(e,t){let o=t??"default",n=new S(o),{baseUrl:a,fhirUrlPath:i,accessToken:s,tokenUrl:c,authorizeUrl:u,fetchApi:y}=Ft(e,n),C=new be.MedplumClient({fetch:y,baseUrl:a,tokenUrl:c,fhirUrlPath:i,authorizeUrl:u,storage:n,onUnauthenticated:Ot});s&&C.setAccessToken(s);let b=e.clientId||process.env.MEDPLUM_CLIENT_ID,v=e.clientSecret||process.env.MEDPLUM_CLIENT_SECRET;return b&&v&&(C.setBasicAuth(b,v),await C.startClientLogin(b,v)),C}function Ft(e,t){let o=e.baseUrl??t.getString("baseUrl")??process.env.MEDPLUM_BASE_URL??"https://api.medplum.com/",n=e.fhirUrlPath??t.getString("fhirUrlPath")??process.env.MEDPLUM_FHIR_URL_PATH??"",a=e.accessToken??t.getString("accessToken")??process.env.MEDPLUM_CLIENT_ACCESS_TOKEN??"",i=e.tokenUrl??t.getString("tokenUrl")??process.env.MEDPLUM_TOKEN_URL??"",s=e.authorizeUrl??t.getString("authorizeUrl")??process.env.MEDPLUM_AUTHORIZE_URL??"",c=e.fetch??fetch;return{baseUrl:o,fhirUrlPath:n,accessToken:a,tokenUrl:i,authorizeUrl:s,fetchApi:c}}function Ot(){console.log("Unauthenticated: run `npx medplum login` to sign in")}var ke="medplum-cli",Pe="http://localhost:9615",J=m("login"),Y=m("whoami");J.action(async e=>{let t=await l(e);await $t(t)});Y.action(async e=>{let t=await l(e);_t(t)});async function $t(e){await Lt(e);let t=new URL(e.getAuthorizeUrl());t.searchParams.set("client_id",ke),t.searchParams.set("redirect_uri",Pe),t.searchParams.set("scope","openid"),t.searchParams.set("response_type","code"),t.searchParams.set("prompt","login"),await Wt(t.toString())}async function Lt(e){let t=(0,Me.createServer)(async(o,n)=>{let a=new URL(o.url,"http://localhost:9615"),i=a.searchParams.get("code");if(a.pathname==="/"&&i)try{let s=await e.processCode(i,{clientId:ke,redirectUri:Pe});n.writeHead(200,{"Content-Type":"text/plain"}),n.end(`Signed in as ${(0,j.getDisplayString)(s)}. You may close this window.`)}catch(s){n.writeHead(400,{"Content-Type":"text/plain"}),n.end(`Error: ${(0,j.normalizeErrorString)(s)}`)}finally{t.close()}else n.writeHead(404,{"Content-Type":"text/plain"}),n.end("Not found")}).listen(9615)}async function Wt(e){let t=(0,ve.platform)(),o;switch(t){case"openbsd":case"linux":o=`xdg-open '${e}'`;break;case"darwin":o=`open '${e}'`;break;case"win32":o=`cmd /c start "" "${e}"`;break;default:throw new Error("Unsupported platform: "+t)}(0,Ee.exec)(o)}function _t(e){let t=e.getActiveLogin();t?(console.log(`Server: ${e.getBaseUrl()}`),console.log(`Profile: ${t.profile.display} (${t.profile.reference})`),console.log(`Project: ${t.project.display} (${t.project.reference})`)):console.log("Not logged in")}var Qe=require("commander");var E=require("@aws-sdk/client-cloudformation"),xe=require("@aws-sdk/client-cloudfront"),Ne=require("@aws-sdk/client-ecs"),Ae=require("@aws-sdk/client-s3"),X=new E.CloudFormationClient({}),De=new xe.CloudFrontClient({}),Ie=new Ne.ECSClient({}),Re=new Ae.S3Client({}),Kt="medplum:environment";async function Z(){return(await X.send(new E.ListStacksCommand({}))).StackSummaries?.filter(t=>t.StackName&&t.StackStatus!=="DELETE_COMPLETE")||[]}async function x(e){let t=await Z();for(let o of t){let n=o.StackName,a=await Q(n);if(a?.tag===e)return a}}async function Q(e){let t=new E.DescribeStacksCommand({StackName:e}),n=(await X.send(t))?.Stacks?.[0],a=n?.Tags?.find(c=>c.Key===Kt);if(!a)return;let i=await X.send(new E.DescribeStackResourcesCommand({StackName:e}));if(!i.StackResources)return;let s={stack:n,tag:a.Value};for(let c of i.StackResources)c.ResourceType==="AWS::ECS::Cluster"?s.ecsCluster=c:c.ResourceType==="AWS::ECS::Service"?s.ecsService=c:c.ResourceType==="AWS::S3::Bucket"&&c.LogicalResourceId?.startsWith("FrontEndAppBucket")?s.appBucket=c:c.ResourceType==="AWS::S3::Bucket"&&c.LogicalResourceId?.startsWith("StorageStorageBucket")?s.storageBucket=c:c.ResourceType==="AWS::CloudFront::Distribution"&&c.LogicalResourceId?.startsWith("FrontEndAppDistribution")&&(s.appDistribution=c);return s}function F(e){console.log(`Medplum Tag: ${e.tag}`),console.log(`Stack Name: ${e.stack.StackName}`),console.log(`Stack ID: ${e.stack.StackId}`),console.log(`Status: ${e.stack.StackStatus}`),console.log(`ECS Cluster: ${e.ecsCluster?.PhysicalResourceId}`),console.log(`ECS Service: ${ee(e.ecsService)}`),console.log(`App Bucket: ${e.appBucket?.PhysicalResourceId}`),console.log(`Storage Bucket: ${e.storageBucket?.PhysicalResourceId}`)}function ee(e){return e?.PhysicalResourceId?.split("/")?.pop()||""}async function Te(e){let t=await x(e);if(!t){console.log("Stack not found");return}F(t)}var M=require("@aws-sdk/client-acm"),N=require("@aws-sdk/client-ssm"),L=require("@aws-sdk/client-sts"),W=require("crypto"),_=require("fs"),Be=require("path"),je=I(require("readline")),qt=e=>`${e}DomainName`,Fe=e=>`${e}SslCertArn`,$;async function Oe(){let e={apiPort:8103,region:"us-east-1"};$=je.default.createInterface({input:process.stdin,output:process.stdout}),d("MEDPLUM"),r("This tool prepares the necessary prerequisites for deploying Medplum in your AWS account."),r(""),r("Most Medplum infrastructure is deployed using the AWS CDK."),r("However, some AWS resources must be created manually, such as email addresses and SSL certificates."),r("This tool will help you create those resources."),r(""),r("Upon completion, this tool will:"),r(" 1. Generate a Medplum CDK config file (i.e., medplum.demo.config.json)"),r(" 2. Optionally generate an AWS CloudFront signing key"),r(" 3. Optionally request SSL certificates from AWS Certificate Manager"),r(" 4. Optionally write server config settings to AWS Parameter Store"),r(""),r("The Medplum infra config file is an input to the Medplum CDK."),r("The Medplum CDK will create and manage the necessary AWS resources."),r(""),r("We will ask a series of questions to generate your infra config file."),r("Some questions have predefined options in [square brackets]."),r("Some questions have default values in (parentheses), which you can accept by pressing Enter."),r("Press Ctrl+C at any time to exit.");let t=await zt(e.region);t||(r("It appears that you do not have AWS credentials configured."),r("AWS credentials are not strictly required, but will enable some additional features."),r("If you intend to use AWS credentials, please configure them now."),await O("Do you want to continue without AWS credentials?")),d("ENVIRONMENT NAME"),r('Medplum deployments have a short environment name such as "prod", "staging", "alice", or "demo".'),r("The environment name is used in multiple places:"),r(" 1. As part of config file names (i.e., medplum.demo.config.json)"),r(" 2. As the base of CloudFormation stack names (i.e., MedplumDemo)"),r(" 3. AWS Parameter Store keys (i.e., /medplum/demo/...)"),e.name=await f("What is your environment name?","demo"),r('Using environment name "'+e.name+'"...'),d("CONFIG FILE"),r("Medplum Infrastructure will create a config file in the current directory.");let o=await f("What is the config file name?",`medplum.${e.name}.config.json`);(0,_.existsSync)(o)&&(r("Config file already exists."),await O("Do you want to overwrite the config file?")),r('Using config file "'+o+'"...'),p(o,e),d("AWS REGION"),r("Most Medplum resources will be created in a single AWS region."),e.region=await f("Enter your AWS region:","us-east-1"),p(o,e),d("AWS ACCOUNT NUMBER"),r("Medplum Infrastructure will use your AWS account number to create AWS resources."),t&&r("Using the AWS CLI, your current account ID is: "+t),e.accountNumber=await f("What is your AWS account number?",t),p(o,e),d("STACK NAME"),r("Medplum will create a CloudFormation stack to manage AWS resources."),r("AWS CloudFormation stack names ");let n="Medplum"+e.name.charAt(0).toUpperCase()+e.name.slice(1);for(e.stackName=await f("Enter your CloudFormation stack name?",n),p(o,e),d("BASE DOMAIN NAME"),r("Please enter the base domain name for your Medplum deployment."),r(""),r("Medplum deploys multiple subdomains for various services."),r(""),r('For example, "api." for the REST API and "app." for the web application.'),r("The base domain name is the common suffix for all subdomains."),r(""),r('For example, if your base domain name is "example.com",'),r('then the REST API will be "api.example.com".'),r(""),r('The base domain should include the TLD (i.e., ".com", ".org", ".net").'),r(""),r("Note that you must own the base domain, and it must use Route53 DNS.");!e.domainName;)e.domainName=await f("Enter your base domain name:");p(o,e),d("SUPPORT EMAIL"),r("Medplum sends transactional emails to users."),r("For example, emails to new users or for password reset."),r("Medplum will use the support email address to send these emails."),r("Note that you must verify the support email address in SES.");let a=await f("Enter your support email address:");d("API DOMAIN NAME"),r("Medplum deploys a REST API for the backend services."),e.apiDomainName=await f("Enter your REST API domain name:","api."+e.domainName),e.baseUrl=`https://${e.apiDomainName}/`,p(o,e),d("APP DOMAIN NAME"),r("Medplum deploys a web application for the user interface."),e.appDomainName=await f("Enter your web application domain name:","app."+e.domainName),p(o,e),d("STORAGE DOMAIN NAME"),r("Medplum deploys a storage service for file uploads."),e.storageDomainName=await f("Enter your storage domain name:","storage."+e.domainName),p(o,e),d("STORAGE BUCKET"),r("Medplum uses an S3 bucket to store binary content such as file uploads."),r("Medplum will create a the S3 bucket as part of the CloudFormation stack."),e.storageBucketName=await f("Enter your storage bucket name:","medplum-"+e.name+"-storage"),p(o,e),d("MAX AVAILABILITY ZONES"),r("Medplum API servers can be deployed in multiple availability zones."),r("This provides redundancy and high availability."),r("However, it also increases the cost of the deployment."),r("If you want to use all availability zones, choose a large number such as 99."),r("If you want to restrict the number, for example to manage EIP limits,"),r("then choose a small number such as 1 or 2."),e.maxAzs=await R("Enter the maximum number of availability zones:",[1,2,3,99],2),d("DATABASE INSTANCES"),r("Medplum uses a relational database to store data."),r("You can set up your own database,"),r("or Medplum can create a new RDS database as part of the CloudFormation stack."),await oe("Do you want to create a new RDS database as part of the CloudFormation stack?")?(r("Medplum will create a new RDS database as part of the CloudFormation stack."),r(""),r("If you need high availability, you can choose multiple instances."),r("Use 1 for a single instance, or 2 for a primary and a standby."),e.rdsInstances=await R("Enter the number of database instances:",[1,2],1)):(r("Medplum will not create a new RDS database."),r("Please create a new RDS database and enter the database name, username, and password."),r('Set the AWS Secrets Manager secret ARN in the config file in the "rdsSecretsArn" setting.'),e.rdsSecretsArn="TODO"),p(o,e),d("SERVER INSTANCES"),r("Medplum uses AWS Fargate to run the API servers."),r("Medplum will create a new Fargate cluster as part of the CloudFormation stack."),r("Fargate will automatically scale the number of servers up and down."),r("If you need high availability, you can choose multiple instances."),e.desiredServerCount=await R("Enter the number of server instances:",[1,2,3,4,6,8],1),p(o,e),d("SERVER MEMORY"),r("You can choose the amount of memory for each server instance."),r("The default is 512 MB, which is sufficient for getting started."),r("Note that only certain CPU units are compatible with memory units."),r('Consult AWS Fargate "Task Definition Parameters" for more information.'),e.serverMemory=await R("Enter the server memory (MB):",[512,1024,2048,4096,8192,16384],512),p(o,e),d("SERVER CPU"),r("You can choose the amount of CPU for each server instance."),r("CPU is expressed as an integer using AWS CPU units"),r("The default is 256, which is sufficient for getting started."),r("Note that only certain CPU units are compatible with memory units."),r('Consult AWS Fargate "Task Definition Parameters" for more information.'),e.serverCpu=await R("Enter the server CPU:",[256,512,1024,2048,4096,8192,16384],256),p(o,e),d("SERVER IMAGE"),r("Medplum uses Docker images for the API servers."),r("You can choose the image to use for the servers."),r("Docker images can be loaded from either Docker Hub or AWS ECR."),r("The default is the latest Medplum release."),e.serverImage=await f("Enter the server image:","medplum/medplum-server:latest"),p(o,e),d("SIGNING KEY"),r("Medplum uses AWS CloudFront Presigned URLs for binary content such as file uploads.");let{privateKey:i,publicKey:s,passphrase:c}=Jt();e.storagePublicKey=s,p(o,e),d("SSL CERTIFICATES"),r("Medplum will now check for existing SSL certificates for the subdomains.");let u=await Gt(e.region);r("Found "+u.length+" certificate(s).");for(let{region:C,certName:b}of[{region:e.region,certName:"api"},{region:"us-east-1",certName:"app"},{region:"us-east-1",certName:"storage"}]){r("");let v=await Ht(e,u,C,b);e[Fe(b)]=v,p(o,e)}d("AWS PARAMETER STORE"),r("Medplum uses AWS Parameter Store to store sensitive configuration values."),r("These values will be encrypted at rest."),r(`The values will be stored in the "/medplum/${e.name}" path.`);let y={port:e.apiPort,baseUrl:e.baseUrl,appBaseUrl:`https://${e.appDomainName}/`,storageBaseUrl:`https://${e.storageDomainName}/binary/`,binaryStorage:`s3:${e.storageBucketName}`,signingKey:i,signingKeyPassphrase:c,supportEmail:a};r(JSON.stringify({...y,signingKey:"****",signingKeyPassphrase:"****"},null,2)),await O("Do you want to store these values in AWS Parameter Store?"),await Zt(e.region,`/medplum/${e.name}/`,y),d("DONE!"),r("Medplum configuration complete."),r("You can now proceed to deploying the Medplum infrastructure with CDK."),r("Run:"),r(""),r(` npx cdk bootstrap -c config=${o}`),r(` npx cdk synth -c config=${o}`),e.region==="us-east-1"?r(` npx cdk deploy -c config=${o}`):r(` npx cdk deploy -c config=${o} --all`),r(""),r("See Medplum documentation for more information:"),r(""),r(" https://www.medplum.com/docs/self-hosting/install-on-aws"),r(""),$.close()}function r(e){$.write(e+`
|
|
3
3
|
`)}function d(e){r(`
|
|
4
4
|
`+e+`
|
|
5
|
-
`)}function f(e,t=""){return new Promise(o=>{F.question(e+(t?" ("+t+")":"")+" ",n=>{o(n||t.toString())})})}async function Q(e,t,o=""){let n=e+" ["+t.map(a=>a===o?"("+a+")":a).join("|")+"]";for(;;){let a=await f(n)||o;if(t.includes(a))return a;r("Please choose one of the following options: "+t.join(", "))}}async function x(e,t,o){return parseInt(await Q(e,t.map(n=>n.toString()),o.toString()),10)}async function ee(e){return(await Q(e,["y","n"])).toLowerCase()==="y"}async function U(e){if(!await ee(e))throw r("Exiting..."),new Error("User cancelled")}function p(e,t){(0,L.writeFileSync)((0,Be.resolve)(e),JSON.stringify(t,void 0,2),"utf-8")}async function Ut(e){try{let t=new j.STSClient({region:e}),o=new j.GetCallerIdentityCommand({});return(await t.send(o)).Account}catch(t){console.log("Warning: Unable to get AWS account ID",t.message);return}}async function Ft(e){let t=await Te(e);if(e!=="us-east-1"){let o=await Te("us-east-1");t.push(...o)}return t}async function Te(e){try{let t=new C.ACMClient({region:e}),o=new C.ListCertificatesCommand({MaxItems:1e3});return(await t.send(o)).CertificateSummaryList}catch(t){return console.log("Warning: Unable to list certificates",t.message),[]}}async function jt(e,t,o,n){let a=e[Bt(n)],i=t.find(c=>c.CertificateArn?.includes(o)&&c.DomainName===a);if(i)return r(`Found existing certificate for "${a}" in "${o}.`),i.CertificateArn;if(r(`No existing certificate found for "${a}" in "${o}.`),!await ee("Do you want to request a new certificate?"))return r(`Please add your certificate ARN to the config file in the "${Fe(n)}" setting.`),"TODO";let s=await $t(o,a);return r("Certificate ARN: "+s),s}async function $t(e,t){try{let o=await Q("Validate certificate using DNS or email validation?",["dns","email"],"dns"),n=new C.ACMClient({region:e}),a=new C.RequestCertificateCommand({DomainName:t,ValidationMethod:o.toUpperCase()});return(await n.send(a)).CertificateArn}catch(o){return console.log("Error: Unable to request certificate",o.message),"TODO"}}function Lt(){let e=(0,$.randomUUID)(),t=(0,$.generateKeyPairSync)("rsa",{modulusLength:2048,publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs1",format:"pem",cipher:"aes-256-cbc",passphrase:e}});return{publicKey:t.publicKey,privateKey:t.privateKey,passphrase:e}}async function Ot(e,t){let o=new M.GetParameterCommand({Name:t,WithDecryption:!0});try{return(await e.send(o)).Parameter?.Value}catch(n){if(n.name==="ParameterNotFound")return;throw n}}async function Wt(e,t,o){let n=new M.PutParameterCommand({Name:t,Value:o,Type:"SecureString",Overwrite:!0});await e.send(n)}async function _t(e,t,o){let n=new M.SSMClient({region:e});for(let[a,i]of Object.entries(o)){let s=t+a,c=i.toString(),u=await Ot(n,s);u!==void 0&&u!==c&&(r(`Parameter "${s}" exists with different value.`),await U(`Do you want to overwrite "${s}"?`)),await Wt(n,s,c)}}async function $e(){let e=await J();for(let t of e){let o=t.StackName,n=await X(o);n&&(B(n),console.log(""))}}var qe=require("@aws-sdk/client-cloudfront"),ze=require("@aws-sdk/client-s3"),Ge=P(require("fast-glob")),g=require("fs"),ae=P(require("node-fetch")),He=require("os"),v=require("path"),Ve=require("stream/promises");var k=require("fs"),Le=require("path"),Oe=P(require("tar"));function S(e){console.log(JSON.stringify(e,null,2))}async function te(e,t,o){let n=ne(t.source);if(n)try{console.log("Update bot code.....");let a=await e.updateResource({...o,code:n});console.log(a?"Success! New bot version: "+a.meta?.versionId:"Bot not modified")}catch(a){console.log("Update error: ",a)}}async function We(e,t,o){let n=ne(t.dist??t.source);if(n)try{console.log("Deploying bot...");let a=await e.post(e.fhirUrl("Bot",o.id,"$deploy"),{code:n});console.log("Deploy result: "+a.issue?.[0]?.details?.text)}catch(a){console.log("Deploy error: ",a)}}async function oe(e,t){if(t.length<4){console.log("Error: command needs to be npx medplum <new-bot-name> <project-id> <source-file> <dist-file>");return}let o=t[0],n=t[1],a=t[2],i=t[3];try{let s={name:o,description:""},c=await e.post("admin/projects/"+n+"/bot",s),u=await e.readResource("Bot",c.id),y={name:o,id:c.id,source:a,dist:i};await te(e,y,u),console.log(`Success! Bot created: ${u.id}`),Kt(y)}catch(s){console.log("Error while creating new bot: "+s)}}function _e(e){let t=new RegExp("^"+qt(e).replace(/\\\*/g,".*")+"$"),o=O()?.bots?.filter(n=>t.test(n.name));return o||[]}function O(e){let t=e?`medplum.${e}.config.json`:"medplum.config.json",o=ne(t);if(o)return JSON.parse(o)}function ne(e){let t=(0,Le.resolve)(process.cwd(),e);return(0,k.existsSync)(t)?(0,k.readFileSync)(t,"utf8"):(console.log("Error: File does not exist: "+t),"")}function Kt(e){let t=O();t?.bots?.push(e),(0,k.writeFile)("medplum.config.json",JSON.stringify(t),()=>{console.log(`Bot added to config: ${e.id}`)})}function qt(e){return e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")}function Ke(e){let n=0,a=0;return Oe.default.x({cwd:e,filter:(i,s)=>{if(n++,n>100)throw new Error("Tar extractor reached max number of files");if(a+=s.size,a>10485760)throw new Error("Tar extractor reached max size");return!0}})}function re(){return{extension:[{url:"http://hl7.org/fhir/StructureDefinition/data-absent-reason",valueCode:"unsupported"}]}}async function Ye(e){let t=O(e);if(!t){console.log("Config not found");return}let o=await E(e);if(!o){console.log("Stack not found");return}let n=o.appBucket;if(!n){console.log("App bucket not found");return}let a=await Gt("@medplum/app","latest");Je(a,{MEDPLUM_BASE_URL:t.baseUrl,MEDPLUM_CLIENT_ID:t.clientId??"",GOOGLE_CLIENT_ID:t.googleClientId??"",RECAPTCHA_SITE_KEY:t.recaptchaSiteKey??"",MEDPLUM_REGISTER_ENABLED:t.registerEnabled?"true":"false"}),await Vt(a,n.PhysicalResourceId),o.appDistribution?.PhysicalResourceId&&await Xt(o.appDistribution.PhysicalResourceId),console.log("Done")}async function zt(e,t){let o=`https://registry.npmjs.org/${e}/${t}`;return(await(0,ae.default)(o)).json()}async function Gt(e,t){let n=(await zt(e,t)).dist.tarball,a=(0,g.mkdtempSync)((0,v.join)((0,He.tmpdir)(),"tarball-"));try{let i=await(0,ae.default)(n),s=Ke(a);return await(0,Ve.pipeline)(i.body,s),(0,v.join)(a,"package","dist")}catch(i){throw(0,g.rmSync)(a,{recursive:!0,force:!0}),i}}function Je(e,t){for(let o of(0,g.readdirSync)(e,{withFileTypes:!0})){let n=(0,v.join)(e,o.name);o.isDirectory()?Je(n,t):o.isFile()&&n.endsWith(".js")&&Ht(n,t)}}function Ht(e,t){let o=(0,g.readFileSync)(e,"utf-8");for(let[n,a]of Object.entries(t))o=o.replaceAll(`__${n}__`,a);(0,g.writeFileSync)(e,o)}async function Vt(e,t){let o=[["css/**/*.css","text/css",!0],["css/**/*.css.map","application/json",!0],["img/**/*.png","image/png",!0],["img/**/*.svg","image/svg+xml",!0],["js/**/*.js","application/javascript",!0],["js/**/*.js.map","application/json",!0],["js/**/*.txt","text/plain",!0],["favicon.ico","image/vnd.microsoft.icon",!0],["robots.txt","text/plain",!0],["workbox-*.js","application/javascript",!0],["workbox-*.js.map","application/json",!0],["manifest.webmanifest","application/manifest+json",!1],["service-worker.js","application/javascript",!1],["service-worker.js.map","application/json",!1],["index.html","text/html",!1]];for(let n of o)await Yt({rootDir:e,bucketName:t,fileNamePattern:n[0],contentType:n[1],cached:n[2]})}async function Yt(e){let t=Ge.default.sync(e.fileNamePattern,{cwd:e.rootDir});for(let o of t)await Jt((0,v.join)(e.rootDir,o),e)}async function Jt(e,t){let o=(0,g.createReadStream)(e),n=e.substring(t.rootDir.length+1).split(v.sep).join("/"),a={Bucket:t.bucketName,Key:n,Body:o,ContentType:t.contentType,CacheControl:t.cached?"public, max-age=31536000":"no-cache, no-store, must-revalidate"};console.log(`Uploading ${n} to ${t.bucketName}...`),await Ie.send(new ze.PutObjectCommand(a))}async function Xt(e){let t=await De.send(new qe.CreateInvalidationCommand({DistributionId:e,InvalidationBatch:{CallerReference:`invalidate-all-${Date.now()}`,Paths:{Quantity:1,Items:["/*"]}}}));console.log(`Created invalidation with ID: ${t.Invalidation?.Id}`)}var Xe=require("@aws-sdk/client-ecs");async function Ze(e){let t=await E(e);if(!t){console.log("Stack not found");return}let o=t.ecsCluster?.PhysicalResourceId;if(!o){console.log("ECS Cluster not found");return}let n=Z(t.ecsService);if(!n){console.log("ECS Service not found");return}await Ne.send(new Xe.UpdateServiceCommand({cluster:o,service:n,forceNewDeployment:!0})),console.log(`Service "${n}" updated successfully.`)}var b=new Qe.Command("aws").description("Commands to manage AWS resources");b.command("init").description("Initialize a new Medplum AWS CloudFormation stacks").action(je);b.command("list").description("List Medplum AWS CloudFormation stacks").action($e);b.command("describe").description("Describe a Medplum AWS CloudFormation stack by tag").argument("<tag>").action(Re);b.command("update-server").alias("deploy-server").description("Update the server image").argument("<tag>").action(Ze);b.command("update-app").alias("deploy-app").description("Update the app site").argument("<tag>").action(Ye);var et=require("commander");var tt=m("save"),ot=m("deploy"),nt=m("create"),rt=new et.Command("bot").addCommand(tt).addCommand(ot).addCommand(nt),ie=m("save-bot"),se=m("deploy-bot"),ce=m("create-bot");tt.description("Saving the bot").argument("<botName>").action(async(e,t)=>{let o=await l(t);await W(o,e)});ot.description("Deploy the app to AWS").argument("<botName>").action(async(e,t)=>{let o=await l(t);await W(o,e,!0)});nt.arguments("<botName> <projectId> <sourceFile> <distFile>").description("Creating a bot").action(async(e,t,o,n,a)=>{let i=await l(a);await oe(i,[e,t,o,n])});async function W(e,t,o=!1){let n=_e(t);for(let a of n){let i=await e.readResource("Bot",a.id);await te(e,a,i),o&&await We(e,a,i)}console.log(`Number of bots deployed: ${n.length}`)}ie.description("Saves the bot").argument("<botName>").action(async(e,t)=>{let o=await l(t);await W(o,e)});se.description("Deploy the bot to AWS").argument("<botName>").action(async(e,t)=>{let o=await l(t);await W(o,e,!0)});ce.arguments("<botName> <projectId> <sourceFile> <distFile>").description("Creates and saves the bot").action(async(e,t,o,n,a)=>{let i=await l(a);await oe(i,[e,t,o,n])});var it=require("commander"),_=require("fs"),me=require("path"),st=require("readline");var ct=m("export"),mt=m("import"),lt=new it.Command("bulk").addCommand(ct).addCommand(mt);ct.option("-e, --export-level <exportLevel>",'Optional export level. Defaults to system level export. "Group/:id" - Group of Patients, "Patient" - All Patients.').option("-t, --types <types>","optional resource types to export").option("-s, --since <since>","optional Resources will be included in the response if their state has changed after the supplied time (e.g. if Resource.meta.lastUpdated is later than the supplied _since time).").option("-d, --target-directory <targetDirectory>","optional target directory to save files from the bulk export operations.").action(async e=>{let{exportLevel:t,types:o,since:n,targetDirectory:a}=e,i=await l(e);(await i.bulkExport(t,o,n)).output?.forEach(async({type:c,url:u})=>{let y=new URL(u),z=await i.download(u),D=`${c}_${y.pathname}`.replace(/[^a-zA-Z0-9]+/g,"_")+".ndjson",N=(0,me.resolve)(a??"",D);(0,_.writeFile)(`${N}`,await z.text(),()=>{console.log(`${N} is created`)})})});mt.argument("<filename>","File Name").option("--num-resources-per-request <numResourcesPerRequest>","optional number of resources to import per batch request. Defaults to 25.","25").option("--add-extensions-for-missing-values","optional flag to add extensions for missing values in a resource",!1).option("-d, --target-directory <targetDirectory>","optional target directory of file to be imported").action(async(e,t)=>{let{numResourcesPerRequest:o,addExtensionsForMissingValues:n,targetDirectory:a}=t,i=(0,me.resolve)(a??process.cwd(),e),s=await l(t);await Zt(i,parseInt(o,10),s,n)});async function Zt(e,t,o,n){let a=[],i=(0,_.createReadStream)(e),s=(0,st.createInterface)({input:i});for await(let c of s){let u=Qt(c,n);a.push({resource:u,request:{method:"POST",url:u.resourceType}}),a.length%t===0&&(await at(a,o),a=[])}a.length>0&&await at(a,o)}async function at(e,t){(await t.executeBatch({resourceType:"Bundle",type:"transaction",entry:e})).entry?.forEach(n=>{S(n.response)})}function Qt(e,t){let o=JSON.parse(e);return t?eo(o):o}function eo(e){return e.resourceType==="ExplanationOfBenefit"?to(e):e}function to(e){return e.provider||(e.provider=re()),e.item?.forEach(t=>{t?.productOrService||(t.productOrService=re())}),e}var K=require("commander");var dt=m("list"),ut=m("current"),pt=m("switch"),ft=m("invite"),gt=new K.Command("project").addCommand(dt).addCommand(ut).addCommand(pt).addCommand(ft);dt.description("List of current projects").action(async e=>{let t=await l(e);oo(t)});function oo(e){let o=e.getLogins().map(n=>`${n.project.display} (${n.project.reference})`).join(`
|
|
5
|
+
`)}function f(e,t=""){return new Promise(o=>{$.question(e+(t?" ("+t+")":"")+" ",n=>{o(n||t.toString())})})}async function te(e,t,o=""){let n=e+" ["+t.map(a=>a===o?"("+a+")":a).join("|")+"]";for(;;){let a=await f(n)||o;if(t.includes(a))return a;r("Please choose one of the following options: "+t.join(", "))}}async function R(e,t,o){return parseInt(await te(e,t.map(n=>n.toString()),o.toString()),10)}async function oe(e){return(await te(e,["y","n"])).toLowerCase()==="y"}async function O(e){if(!await oe(e))throw r("Exiting..."),new Error("User cancelled")}function p(e,t){(0,_.writeFileSync)((0,Be.resolve)(e),JSON.stringify(t,void 0,2),"utf-8")}async function zt(e){try{let t=new L.STSClient({region:e}),o=new L.GetCallerIdentityCommand({});return(await t.send(o)).Account}catch(t){console.log("Warning: Unable to get AWS account ID",t.message);return}}async function Gt(e){let t=await Ue(e);if(e!=="us-east-1"){let o=await Ue("us-east-1");t.push(...o)}return t}async function Ue(e){try{let t=new M.ACMClient({region:e}),o=new M.ListCertificatesCommand({MaxItems:1e3});return(await t.send(o)).CertificateSummaryList}catch(t){return console.log("Warning: Unable to list certificates",t.message),[]}}async function Ht(e,t,o,n){let a=e[qt(n)],i=t.find(c=>c.CertificateArn?.includes(o)&&c.DomainName===a);if(i)return r(`Found existing certificate for "${a}" in "${o}.`),i.CertificateArn;if(r(`No existing certificate found for "${a}" in "${o}.`),!await oe("Do you want to request a new certificate?"))return r(`Please add your certificate ARN to the config file in the "${Fe(n)}" setting.`),"TODO";let s=await Vt(o,a);return r("Certificate ARN: "+s),s}async function Vt(e,t){try{let o=await te("Validate certificate using DNS or email validation?",["dns","email"],"dns"),n=new M.ACMClient({region:e}),a=new M.RequestCertificateCommand({DomainName:t,ValidationMethod:o.toUpperCase()});return(await n.send(a)).CertificateArn}catch(o){return console.log("Error: Unable to request certificate",o.message),"TODO"}}function Jt(){let e=(0,W.randomUUID)(),t=(0,W.generateKeyPairSync)("rsa",{modulusLength:2048,publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs1",format:"pem",cipher:"aes-256-cbc",passphrase:e}});return{publicKey:t.publicKey,privateKey:t.privateKey,passphrase:e}}async function Yt(e,t){let o=new N.GetParameterCommand({Name:t,WithDecryption:!0});try{return(await e.send(o)).Parameter?.Value}catch(n){if(n.name==="ParameterNotFound")return;throw n}}async function Xt(e,t,o){let n=new N.PutParameterCommand({Name:t,Value:o,Type:"SecureString",Overwrite:!0});await e.send(n)}async function Zt(e,t,o){let n=new N.SSMClient({region:e});for(let[a,i]of Object.entries(o)){let s=t+a,c=i.toString(),u=await Yt(n,s);u!==void 0&&u!==c&&(r(`Parameter "${s}" exists with different value.`),await O(`Do you want to overwrite "${s}"?`)),await Xt(n,s,c)}}async function $e(){let e=await Z();for(let t of e){let o=t.StackName,n=await Q(o);n&&(F(n),console.log(""))}}var qe=require("@aws-sdk/client-cloudfront"),ze=require("@aws-sdk/client-s3"),Ge=I(require("fast-glob")),g=require("fs"),se=I(require("node-fetch")),He=require("os"),k=require("path"),Ve=require("stream/promises");var A=require("fs"),D=require("path"),Le=I(require("tar"));function w(e){console.log(JSON.stringify(e,null,2))}async function ne(e,t,o){let n=t.source,a=ae(n);if(a)try{console.log("Saving source code...");let i=await e.createAttachment(a,(0,D.basename)(n),to(n));console.log("Updating bot.....");let s=await e.updateResource({...o,sourceCode:i});console.log("Success! New bot version: "+s.meta?.versionId)}catch(i){console.log("Update error: ",i)}}async function We(e,t,o){let n=t.dist??t.source,a=ae(n);if(a)try{console.log("Deploying bot...");let i=await e.post(e.fhirUrl("Bot",o.id,"$deploy"),{code:a});console.log("Deploy result: "+i.issue?.[0]?.details?.text)}catch(i){console.log("Deploy error: ",i)}}async function re(e,t){if(t.length<4){console.log("Error: command needs to be npx medplum <new-bot-name> <project-id> <source-file> <dist-file>");return}let o=t[0],n=t[1],a=t[2],i=t[3];try{let s={name:o,description:""},c=await e.post("admin/projects/"+n+"/bot",s),u=await e.readResource("Bot",c.id),y={name:o,id:c.id,source:a,dist:i};await ne(e,y,u),console.log(`Success! Bot created: ${u.id}`),Qt(y)}catch(s){console.log("Error while creating new bot: "+s)}}function _e(e){let t=new RegExp("^"+eo(e).replace(/\\\*/g,".*")+"$"),o=K()?.bots?.filter(n=>t.test(n.name));return o||[]}function K(e){let t=e?`medplum.${e}.config.json`:"medplum.config.json",o=ae(t);if(o)return JSON.parse(o)}function ae(e){let t=(0,D.resolve)(process.cwd(),e);return(0,A.existsSync)(t)?(0,A.readFileSync)(t,"utf8"):(console.log("Error: File does not exist: "+t),"")}function Qt(e){let t=K();t?.bots?.push(e),(0,A.writeFile)("medplum.config.json",JSON.stringify(t),()=>{console.log(`Bot added to config: ${e.id}`)})}function eo(e){return e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")}function Ke(e){let n=0,a=0;return Le.default.x({cwd:e,filter:(i,s)=>{if(n++,n>100)throw new Error("Tar extractor reached max number of files");if(a+=s.size,a>10485760)throw new Error("Tar extractor reached max size");return!0}})}function ie(){return{extension:[{url:"http://hl7.org/fhir/StructureDefinition/data-absent-reason",valueCode:"unsupported"}]}}function to(e){let t=(0,D.extname)(e).toLowerCase();return[".cjs",".mjs",".js"].includes(t)?"text/javascript":[".cts",".mts",".ts"].includes(t)?"text/typescript":"text/plain"}async function Je(e){let t=K(e);if(!t){console.log("Config not found");return}let o=await x(e);if(!o){console.log("Stack not found");return}let n=o.appBucket;if(!n){console.log("App bucket not found");return}let a=await no("@medplum/app","latest");Ye(a,{MEDPLUM_BASE_URL:t.baseUrl,MEDPLUM_CLIENT_ID:t.clientId??"",GOOGLE_CLIENT_ID:t.googleClientId??"",RECAPTCHA_SITE_KEY:t.recaptchaSiteKey??"",MEDPLUM_REGISTER_ENABLED:t.registerEnabled?"true":"false"}),await ao(a,n.PhysicalResourceId),o.appDistribution?.PhysicalResourceId&&await co(o.appDistribution.PhysicalResourceId),console.log("Done")}async function oo(e,t){let o=`https://registry.npmjs.org/${e}/${t}`;return(await(0,se.default)(o)).json()}async function no(e,t){let n=(await oo(e,t)).dist.tarball,a=(0,g.mkdtempSync)((0,k.join)((0,He.tmpdir)(),"tarball-"));try{let i=await(0,se.default)(n),s=Ke(a);return await(0,Ve.pipeline)(i.body,s),(0,k.join)(a,"package","dist")}catch(i){throw(0,g.rmSync)(a,{recursive:!0,force:!0}),i}}function Ye(e,t){for(let o of(0,g.readdirSync)(e,{withFileTypes:!0})){let n=(0,k.join)(e,o.name);o.isDirectory()?Ye(n,t):o.isFile()&&n.endsWith(".js")&&ro(n,t)}}function ro(e,t){let o=(0,g.readFileSync)(e,"utf-8");for(let[n,a]of Object.entries(t))o=o.replaceAll(`__${n}__`,a);(0,g.writeFileSync)(e,o)}async function ao(e,t){let o=[["css/**/*.css","text/css",!0],["css/**/*.css.map","application/json",!0],["img/**/*.png","image/png",!0],["img/**/*.svg","image/svg+xml",!0],["js/**/*.js","text/javascript",!0],["js/**/*.js.map","application/json",!0],["js/**/*.txt","text/plain",!0],["favicon.ico","image/vnd.microsoft.icon",!0],["robots.txt","text/plain",!0],["workbox-*.js","text/javascript",!0],["workbox-*.js.map","application/json",!0],["manifest.webmanifest","application/manifest+json",!1],["service-worker.js","text/javascript",!1],["service-worker.js.map","application/json",!1],["index.html","text/html",!1]];for(let n of o)await io({rootDir:e,bucketName:t,fileNamePattern:n[0],contentType:n[1],cached:n[2]})}async function io(e){let t=Ge.default.sync(e.fileNamePattern,{cwd:e.rootDir});for(let o of t)await so((0,k.join)(e.rootDir,o),e)}async function so(e,t){let o=(0,g.createReadStream)(e),n=e.substring(t.rootDir.length+1).split(k.sep).join("/"),a={Bucket:t.bucketName,Key:n,Body:o,ContentType:t.contentType,CacheControl:t.cached?"public, max-age=31536000":"no-cache, no-store, must-revalidate"};console.log(`Uploading ${n} to ${t.bucketName}...`),await Re.send(new ze.PutObjectCommand(a))}async function co(e){let t=await De.send(new qe.CreateInvalidationCommand({DistributionId:e,InvalidationBatch:{CallerReference:`invalidate-all-${Date.now()}`,Paths:{Quantity:1,Items:["/*"]}}}));console.log(`Created invalidation with ID: ${t.Invalidation?.Id}`)}var Xe=require("@aws-sdk/client-ecs");async function Ze(e){let t=await x(e);if(!t){console.log("Stack not found");return}let o=t.ecsCluster?.PhysicalResourceId;if(!o){console.log("ECS Cluster not found");return}let n=ee(t.ecsService);if(!n){console.log("ECS Service not found");return}await Ie.send(new Xe.UpdateServiceCommand({cluster:o,service:n,forceNewDeployment:!0})),console.log(`Service "${n}" updated successfully.`)}var P=new Qe.Command("aws").description("Commands to manage AWS resources");P.command("init").description("Initialize a new Medplum AWS CloudFormation stacks").action(Oe);P.command("list").description("List Medplum AWS CloudFormation stacks").action($e);P.command("describe").description("Describe a Medplum AWS CloudFormation stack by tag").argument("<tag>").action(Te);P.command("update-server").alias("deploy-server").description("Update the server image").argument("<tag>").action(Ze);P.command("update-app").alias("deploy-app").description("Update the app site").argument("<tag>").action(Je);var et=require("commander");var tt=m("save"),ot=m("deploy"),nt=m("create"),rt=new et.Command("bot").addCommand(tt).addCommand(ot).addCommand(nt),ce=m("save-bot"),me=m("deploy-bot"),le=m("create-bot");tt.description("Saving the bot").argument("<botName>").action(async(e,t)=>{let o=await l(t);await q(o,e)});ot.description("Deploy the app to AWS").argument("<botName>").action(async(e,t)=>{let o=await l(t);await q(o,e,!0)});nt.arguments("<botName> <projectId> <sourceFile> <distFile>").description("Creating a bot").action(async(e,t,o,n,a)=>{let i=await l(a);await re(i,[e,t,o,n])});async function q(e,t,o=!1){let n=_e(t);for(let a of n){let i=await e.readResource("Bot",a.id);await ne(e,a,i),o&&await We(e,a,i)}console.log(`Number of bots deployed: ${n.length}`)}ce.description("Saves the bot").argument("<botName>").action(async(e,t)=>{let o=await l(t);await q(o,e)});me.description("Deploy the bot to AWS").argument("<botName>").action(async(e,t)=>{let o=await l(t);await q(o,e,!0)});le.arguments("<botName> <projectId> <sourceFile> <distFile>").description("Creates and saves the bot").action(async(e,t,o,n,a)=>{let i=await l(a);await re(i,[e,t,o,n])});var it=require("commander"),z=require("fs"),de=require("path"),st=require("readline");var ct=m("export"),mt=m("import"),lt=new it.Command("bulk").addCommand(ct).addCommand(mt);ct.option("-e, --export-level <exportLevel>",'Optional export level. Defaults to system level export. "Group/:id" - Group of Patients, "Patient" - All Patients.').option("-t, --types <types>","optional resource types to export").option("-s, --since <since>","optional Resources will be included in the response if their state has changed after the supplied time (e.g. if Resource.meta.lastUpdated is later than the supplied _since time).").option("-d, --target-directory <targetDirectory>","optional target directory to save files from the bulk export operations.").action(async e=>{let{exportLevel:t,types:o,since:n,targetDirectory:a}=e,i=await l(e);(await i.bulkExport(t,o,n)).output?.forEach(async({type:c,url:u})=>{let y=new URL(u),C=await i.download(u),b=`${c}_${y.pathname}`.replace(/[^a-zA-Z0-9]+/g,"_")+".ndjson",v=(0,de.resolve)(a??"",b);(0,z.writeFile)(`${v}`,await C.text(),()=>{console.log(`${v} is created`)})})});mt.argument("<filename>","File Name").option("--num-resources-per-request <numResourcesPerRequest>","optional number of resources to import per batch request. Defaults to 25.","25").option("--add-extensions-for-missing-values","optional flag to add extensions for missing values in a resource",!1).option("-d, --target-directory <targetDirectory>","optional target directory of file to be imported").action(async(e,t)=>{let{numResourcesPerRequest:o,addExtensionsForMissingValues:n,targetDirectory:a}=t,i=(0,de.resolve)(a??process.cwd(),e),s=await l(t);await mo(i,parseInt(o,10),s,n)});async function mo(e,t,o,n){let a=[],i=(0,z.createReadStream)(e),s=(0,st.createInterface)({input:i});for await(let c of s){let u=lo(c,n);a.push({resource:u,request:{method:"POST",url:u.resourceType}}),a.length%t===0&&(await at(a,o),a=[])}a.length>0&&await at(a,o)}async function at(e,t){(await t.executeBatch({resourceType:"Bundle",type:"transaction",entry:e})).entry?.forEach(n=>{w(n.response)})}function lo(e,t){let o=JSON.parse(e);return t?uo(o):o}function uo(e){return e.resourceType==="ExplanationOfBenefit"?po(e):e}function po(e){return e.provider||(e.provider=ie()),e.item?.forEach(t=>{t?.productOrService||(t.productOrService=ie())}),e}var G=require("commander");var dt=m("list"),ut=m("current"),pt=m("switch"),ft=m("invite"),gt=new G.Command("project").addCommand(dt).addCommand(ut).addCommand(pt).addCommand(ft);dt.description("List of current projects").action(async e=>{let t=await l(e);fo(t)});function fo(e){let o=e.getLogins().map(n=>`${n.project.display} (${n.project.reference})`).join(`
|
|
6
6
|
|
|
7
|
-
`);console.log(o)}ut.description("Project you are currently on").action(async e=>{let o=(await l(e)).getActiveLogin();if(!o)throw new Error("Unauthenticated: run `npx medplum login` to login");console.log(`${o.project.display} (${o.project.reference})`)});pt.description("Switching to another project from the current one").argument("<projectId>").action(async(e,t)=>{let o=await l(t);await
|
|
8
|
-
`)):console.log(`Error: project ${t} not found. Make sure you are added as a user to this project`)}async function
|
|
7
|
+
`);console.log(o)}ut.description("Project you are currently on").action(async e=>{let o=(await l(e)).getActiveLogin();if(!o)throw new Error("Unauthenticated: run `npx medplum login` to login");console.log(`${o.project.display} (${o.project.reference})`)});pt.description("Switching to another project from the current one").argument("<projectId>").action(async(e,t)=>{let o=await l(t);await go(o,e)});ft.description("Invite a member to your current project (run npx medplum project current to confirm)").arguments("<firstName> <lastName> <email>").option("--send-email","If you want to send the email when inviting the user").option("--admin","If the user you are inviting is an admin").addOption(new G.Option("-r, --role <role>","Role of user").choices(["Practitioner","Patient","RelatedPerson"]).default("Practitioner")).action(async(e,t,o,n)=>{let a=await l(n),i=a.getActiveLogin();if(!i)throw new Error("Unauthenticated: run `npx medplum login` to login");if(!i.project.reference)throw new Error("No current project to invite user to");let s=i.project.reference.split("/")[1],c={resourceType:n.role,firstName:e,lastName:t,email:o,sendEmail:!!n.sendEmail,admin:!!n.admin};await yo(s,c,a)});async function go(e,t){let n=e.getLogins().find(a=>a.project.reference?.includes(t));n?(await e.setActiveLogin(n),console.log(`Switched to project ${t}
|
|
8
|
+
`)):console.log(`Error: project ${t} not found. Make sure you are added as a user to this project`)}async function yo(e,t,o){try{await o.invite(e,t),t.sendEmail&&console.log("Email sent"),console.log("See your users at https://app.medplum.com/admin/users")}catch(n){console.log("Error while sending invite "+n)}}var yt=require("@medplum/core");var ue=m("delete"),pe=m("get"),fe=m("patch"),ge=m("post"),ye=m("put");ue.argument("<url>","Resource/$id").action(async(e,t)=>{let o=await l(t);w(await o.delete(T(e,t)))});pe.argument("<url>","Resource/$id").option("--as-transaction","Print out the bundle as a transaction type").action(async(e,t)=>{let n=await(await l(t)).get(T(e,t));t.asTransaction?w((0,yt.convertToTransactionBundle)(n)):w(n)});fe.arguments("<url> <body>").action(async(e,t,o)=>{let n=await l(o);w(await n.patch(T(e,o),he(t)))});ge.arguments("<url> <body>").action(async(e,t,o)=>{let n=await l(o);w(await n.post(T(e,o),he(t)))});ye.arguments("<url> <body>").action(async(e,t,o)=>{let n=await l(o);w(await n.put(T(e,o),he(t)))});function he(e){if(e)try{return JSON.parse(e)}catch{return e}}function T(e,t){let o=["admin/","auth/","fhir/R4"],{fhirUrlPath:n}=t;return o.some(a=>e.startsWith(a))?e:n?`${n}/${e}`:"fhir/R4/"+e}var ht=require("commander");var St=require("path"),wt=require("fs"),Ct=require("os"),bt=m("set"),vt=m("remove"),Et=m("list"),Mt=m("describe"),kt=new ht.Command("profile").addCommand(bt).addCommand(vt).addCommand(Et).addCommand(Mt);bt.argument("<profileName>","Name of the profile").description("Create a new profile or replace it with the given name and its associated properties").action(async(e,t)=>{new S(e).setObject("options",t),console.log(`${e} profile created`)});vt.argument("<profileName>","Name of the profile").description("Remove a profile by name").action(async e=>{new S(e).setObject("options",void 0),console.log(`${e} profile removed`)});Et.description("List all profiles saved").action(async()=>{let e=(0,St.resolve)((0,Ct.homedir)(),".medplum"),t=(0,wt.readdirSync)(e),o=[];t.forEach(n=>{let a=n.split(".")[0],s=new S(a).getObject("options");s&&o.push({profileName:a,profile:s})}),console.log(o)});Mt.argument("<profileName>","Name of the profile").description("Describes a profile").action(async e=>{let o=new S(e).getObject("options");console.log(o)});async function Nt(e){try{let t=new Pt.Command("medplum").description("Command to access Medplum CLI");t.version(H.MEDPLUM_VERSION),t.addCommand(J),t.addCommand(Y),t.addCommand(pe),t.addCommand(ge),t.addCommand(fe),t.addCommand(ye),t.addCommand(ue),t.addCommand(gt),t.addCommand(lt),t.addCommand(rt),t.addCommand(ce),t.addCommand(me),t.addCommand(le),t.addCommand(kt),t.addCommand(P),await t.parseAsync(e)}catch(t){console.error("Error: "+(0,H.normalizeErrorString)(t))}}async function At(){xt.default.config(),await Nt(process.argv)}require.main===module&&At().catch(e=>console.error("Unhandled error:",e));0&&(module.exports={main,run});
|
|
9
9
|
//# sourceMappingURL=index.cjs.map
|