@settlemint/btp-sdk-cli
Advanced tools
Comparing version 0.3.2-main-a438d15 to 0.3.2-main1a9ceb3
#!/usr/bin/env node | ||
import { program, Command } from '@commander-js/extra-typings'; | ||
import 'reflect-metadata'; | ||
import w from 'boxen'; | ||
import { blue, green, yellow, red } from 'yoctocolors'; | ||
import { writeFileSync, existsSync } from 'node:fs'; | ||
import c from 'node:path'; | ||
import { cancel, intro, outro, confirm, spinner, note, isCancel, password, text, select } from '@clack/prompts'; | ||
import { Command } from '@commander-js/extra-typings'; | ||
import dotenv from 'dotenv'; | ||
import { greenBright, magentaBright, inverse, redBright } from 'yoctocolors'; | ||
import { cosmiconfig } from 'cosmiconfig'; | ||
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'node:fs'; | ||
import path, { join, parse, dirname } from 'node:path'; | ||
import { merge } from 'ts-deepmerge'; | ||
import { z } from 'zod'; | ||
import { input, password } from '@inquirer/prompts'; | ||
import { generate } from '@graphql-codegen/cli'; | ||
import { createConfig as createConfig$1 } from '@redocly/openapi-core'; | ||
import openapiTS, { astToString } from 'openapi-typescript'; | ||
function s(e,r="info",t=!0){let n={info:blue,success:green,warning:yellow,error:red},i={info:"blue",success:"green",warning:"yellow",error:"red"},l={info:"\u2139\uFE0F",success:"\u2705",warning:"\u26A0\uFE0F",error:"\u274C"},a=n[r](`${l[r]} ${e.replace(/\s+/g," ").trim()}`),o=w(a,{padding:1,margin:1,borderStyle:"round",borderColor:i[r]});return t&&(r==="error"?console.error(o):console.log(o)),o}var p=z.object({pat:z.string(),instance:z.string()});async function E(){let r=await cosmiconfig("btp").search();if(r)return p.parse(r.config)}function S(e){let r=e;for(;r!==c.parse(r).root;){if(existsSync(c.join(r,"package.json")))return r;r=c.dirname(r);}throw new Error("Unable to find project root")}async function u(e){let t=merge({pat:"sm_pat_xxxxxxxxxxxxxxxx",instance:"https://console.settlemint.com"},e),n=p.parse(t),i=await E(),l=i?merge(n,i):t,a=p.parse(l),o=S(process.cwd()),v=c.join(o,".btprc.json");return writeFileSync(v,JSON.stringify(a,null,2)),a}async function m(e){let r=process.env.BTP_INSTANCE_URL||e;return d(r)||(r=await input({message:"Enter the URL of your BTP instance",default:"https://console.settlemint.com",validate:t=>L(t)})),r}function d(e){try{return new URL(e??"").protocol==="https:"}catch{return !1}}function L(e){return d(e)?!0:"Invalid BTP instance URL. Please enter a valid HTTPS URL."}async function x(e){let r=process.env.BTP_PAT_TOKEN||e;return h(r)||(r=await password({message:"Enter a Personal Access Token for authentication",validate:t=>j(t)})),r}function h(e){return /^sm_pat_[a-f0-9]{16}$/.test(e?.trim()??"")}function j(e){return h(e)?!0:"Invalid Personal Access Token"}function P(){return new Command("init").option("-p, --pat <key>","Personal Access Token for authentication (BTP_PAT_TOKEN environment variable)").option("-i, --instance <url>","The url to your BTP instance, defaults to https://console.settlemint.com (BTP_INSTANCE_URL environment variable)").description("Initializes the setup of the BTP SDK").action(async({pat:e,instance:r})=>{s("Setting up the BTP SDK in your project","info");try{let t=await x(e),n=await m(r);await u({pat:t,instance:n}),s("Config file created at .btprc.json","success");}catch(t){s(t.message,"error"),console.error(t.stack),process.exit(1);}})}var T=program.description("CLI for the SettleMint Blockchain Transformation Platform SDK");T.addCommand(P());T.parse(process.argv); | ||
// package.json | ||
var package_default = { | ||
name: "@settlemint/btp-sdk-cli", | ||
version: "0.3.2", | ||
main: "./dist/index.js", | ||
module: "./dist/index.js", | ||
types: "./dist/index.d.ts", | ||
type: "module", | ||
private: false, | ||
license: "MIT", | ||
author: { | ||
name: "SettleMint", | ||
email: "support@settlemint.com", | ||
url: "https://settlemint.com" | ||
}, | ||
homepage: "https://github.com/settlemint/btp-sdk/blob/main/packages/cli/README.md", | ||
repository: { | ||
type: "git", | ||
url: "git+https://github.com/settlemint/btp-sdk.git" | ||
}, | ||
bugs: { | ||
url: "https://github.com/settlemint/btp-sdk/issues", | ||
email: "support@settlemint.com" | ||
}, | ||
files: [ | ||
"dist" | ||
], | ||
exports: { | ||
"./package.json": "./package.json", | ||
".": { | ||
types: "./dist/index.d.ts", | ||
import: "./dist/index.js" | ||
} | ||
}, | ||
bin: { | ||
"btp-sdk-cli": "dist/index.js" | ||
}, | ||
scripts: { | ||
build: "tsup-node src/index.ts --format esm --dts", | ||
dev: "tsup-node src/index.ts --format esm --dts --watch" | ||
}, | ||
devDependencies: { | ||
"@clack/prompts": "0.7.0", | ||
"@commander-js/extra-typings": "12.1.0", | ||
"@graphql-codegen/cli": "5.0.2", | ||
"@tsconfig/node20": "20.1.4", | ||
"@tsconfig/strictest": "2.0.5", | ||
"@types/node": "20.14.13", | ||
commander: "12.1.0", | ||
cosmiconfig: "9.0.0", | ||
dotenv: "16.4.5", | ||
"openapi-typescript": "7.3.0", | ||
"ts-deepmerge": "7.0.1", | ||
tsup: "8.2.3", | ||
yoctocolors: "2.1.1", | ||
zod: "3.23.8" | ||
}, | ||
dependencies: { | ||
"graphql-request": "7.1.0", | ||
"openapi-fetch": "0.10.4" | ||
}, | ||
peerDependencies: { | ||
graphql: "16.9.0" | ||
} | ||
}; | ||
var printAsciiArt = () => console.log( | ||
magentaBright(` | ||
_________ __ __ .__ _____ .__ __ | ||
/ _____/ _____/ |__/ |_| | ____ / \\ |__| _____/ |_ | ||
\\_____ \\_/ __ \\ __\\ __\\ | _/ __ \\ / \\ / \\| |/ \\ __\\ | ||
/ \\ ___/| | | | | |_\\ ___// Y \\ | | \\ | | ||
/_________/\\_____>__| |__| |____/\\_____>____|____/__|___|__/__| | ||
`) | ||
); | ||
var printIntro = (msg) => intro(inverse(magentaBright(msg))); | ||
var printOutro = (msg) => outro(inverse(greenBright(msg))); | ||
var printCancel = (msg) => cancel(inverse(redBright(msg))); | ||
var handleCancellation = (result) => { | ||
if (isCancel(result)) { | ||
printCancel("Cancelled"); | ||
process.exit(0); | ||
} | ||
}; | ||
var promptConfirm = async (options) => { | ||
const result = await confirm(options); | ||
handleCancellation(result); | ||
return result; | ||
}; | ||
var promptPassword = async (options) => { | ||
const result = await password(options); | ||
handleCancellation(result); | ||
return result; | ||
}; | ||
var promptText = async (options) => { | ||
const result = await text(options); | ||
handleCancellation(result); | ||
return result; | ||
}; | ||
var printSpinner = async (options) => { | ||
const s = spinner(); | ||
s.start(options.startMessage); | ||
const result = await options.task(); | ||
s.stop(options.stopMessage); | ||
return result; | ||
}; | ||
var promptSelect = async (options) => { | ||
if (options.options.length === 0) { | ||
return void 0; | ||
} | ||
if (options.noneOption) { | ||
options.options.unshift(options.noneOption); | ||
} | ||
const result = await select(options); | ||
handleCancellation(result); | ||
return result; | ||
}; | ||
var printNote = (message, title) => note(message, title); | ||
function findProjectRoot(startDir) { | ||
let currentDir = startDir; | ||
while (currentDir !== parse(currentDir).root) { | ||
if (existsSync(join(currentDir, "package.json"))) { | ||
return currentDir; | ||
} | ||
currentDir = dirname(currentDir); | ||
} | ||
throw new Error("Unable to find project root"); | ||
} | ||
// src/lib/config.ts | ||
var EnvironmentConfigSchema = z.object({ | ||
workspace: z.string(), | ||
childWorkspace: z.string().optional(), | ||
application: z.string(), | ||
portal: z.string().url().optional(), | ||
portalRest: z.string().url().optional(), | ||
graph: z.string().url().optional(), | ||
hasura: z.string().url().optional(), | ||
node: z.string().url().optional() | ||
}); | ||
var ConfigSchema = z.object({ | ||
framework: z.string(), | ||
instance: z.string().url(), | ||
defaultEnvironment: z.string().default("development"), | ||
environments: z.record(z.string(), EnvironmentConfigSchema).optional() | ||
}); | ||
var EnvSchema = z.object({ | ||
BTP_PAT_TOKEN: z.string(), | ||
NEXT_PUBLIC_BTP_APP_URL: z.string().url().optional() | ||
}); | ||
var ConfigEnvSchema = ConfigSchema.extend({ | ||
pat: z.string(), | ||
appUrl: z.string().url().optional() | ||
}); | ||
async function config() { | ||
const config2 = await parseConfig(); | ||
if (!config2) { | ||
return void 0; | ||
} | ||
return ConfigEnvSchema.parse({ | ||
...config2, | ||
pat: process.env.BTP_PAT_TOKEN, | ||
appUrl: process.env.NEXT_PUBLIC_BTP_APP_URL | ||
}); | ||
} | ||
async function parseConfig() { | ||
const explorer = cosmiconfig("btp"); | ||
const result = await explorer.search(); | ||
if (!result) { | ||
return void 0; | ||
} | ||
try { | ||
return ConfigSchema.parse(result.config); | ||
} catch (e) { | ||
printCancel("Configuration file does not match the expected format. Starting fresh!"); | ||
return void 0; | ||
} | ||
} | ||
async function createConfig(config2) { | ||
const defaultConfig = {}; | ||
const preConfiguredConfig = merge(defaultConfig, config2); | ||
const existingConfig = await parseConfig(); | ||
const mergedConfig = existingConfig ? merge(existingConfig, preConfiguredConfig) : preConfiguredConfig; | ||
const validatedMergedConfig = ConfigSchema.parse(mergedConfig); | ||
const projectRoot = findProjectRoot(process.cwd()); | ||
const configPath = path.join(projectRoot, ".btprc.json"); | ||
writeFileSync(configPath, JSON.stringify(validatedMergedConfig, null, 2)); | ||
} | ||
var escapeNewlines = (str) => str.replace(/\n/g, "\\n"); | ||
var format = (key, value) => `${key}=${escapeNewlines(value)}`; | ||
async function createEnv(env) { | ||
const projectRoot = findProjectRoot(process.cwd()); | ||
const envPath = path.join(projectRoot, ".env.local"); | ||
let dotEnv = {}; | ||
try { | ||
dotEnv = dotenv.parse(readFileSync(envPath, "utf-8")); | ||
} catch (e) { | ||
} | ||
const mergedEnv = merge(dotEnv, env); | ||
const validatedMergedEnv = EnvSchema.parse(mergedEnv); | ||
const contents = Object.entries(validatedMergedEnv).filter(([_, value]) => value !== void 0).map(([key, value]) => format(key, value)).join("\n"); | ||
writeFileSync(envPath, contents); | ||
dotenv.config({ | ||
path: [".env.local", ".env"], | ||
override: true | ||
}); | ||
} | ||
async function createGqlClient(options) { | ||
const { framework, type, gqlUrl, personalAccessToken } = options; | ||
const btpDir = join(findProjectRoot(process.cwd()), ".btp"); | ||
const typeDir = join(btpDir, type); | ||
const typeGqlDir = join(typeDir, "gql"); | ||
const typeCodegenDir = join(typeGqlDir, "codegen"); | ||
const typeQueriesDir = join(findProjectRoot(process.cwd()), "graphql", type); | ||
mkdirSync(typeCodegenDir, { recursive: true }); | ||
mkdirSync(typeQueriesDir, { recursive: true }); | ||
if (framework === "nextjs") { | ||
writeFileSync( | ||
`${typeGqlDir}/index.ts`, | ||
`import { GraphQLClient } from "graphql-request"; | ||
export const ${type} = new GraphQLClient(\`\${process.env.NEXT_PUBLIC_BTP_APP_URL}/proxy/${type}\`);` | ||
); | ||
} else { | ||
writeFileSync( | ||
`${typeGqlDir}/index.ts`, | ||
`import { GraphQLClient } from "graphql-request"; | ||
export const ${type} = new GraphQLClient('${gqlUrl}', { | ||
headers: { | ||
"x-auth-token": process.env.BTP_PAT_TOKEN, | ||
}, | ||
});` | ||
); | ||
} | ||
writeFileSync( | ||
`${typeQueriesDir}/apollo.config.ts`, | ||
`module.exports = { | ||
client: { | ||
includes: ["./*.graphql", "./**/*.graphql"], | ||
service: { | ||
name: "${type}", | ||
url: "${gqlUrl}", | ||
headers: { | ||
"x-auth-token": "${personalAccessToken}", | ||
}, | ||
}, | ||
}, | ||
};` | ||
); | ||
await generate( | ||
{ | ||
errorsOnly: true, | ||
silent: true, | ||
ignoreNoDocuments: true, | ||
generates: { | ||
[`${typeCodegenDir}/`]: { | ||
preset: "client", | ||
schema: [ | ||
{ | ||
[gqlUrl]: { | ||
headers: { | ||
"x-auth-token": personalAccessToken | ||
} | ||
} | ||
} | ||
], | ||
documents: [`./${typeQueriesDir}/*.graphql`, `./${typeQueriesDir}/**/*.graphql`], | ||
presetConfig: { | ||
useTypeImports: true, | ||
nonOptionalTypename: true, | ||
dedupeFragments: true, | ||
avoidOptionals: true, | ||
fragmentMasking: false | ||
}, | ||
config: { | ||
scalars: { | ||
BigInt: "string", | ||
BigDecimal: "string", | ||
date: "Date | string", | ||
Bytes: "string", | ||
Int8: "number", | ||
Upload: "Blob", | ||
Timestamp: "number" | ||
}, | ||
strictScalars: false | ||
} | ||
} | ||
} | ||
}, | ||
true | ||
); | ||
} | ||
async function createRestClient(options) { | ||
const { framework, restURL, personalAccessToken } = options; | ||
if (restURL) { | ||
const redocly = await createConfig$1( | ||
{ | ||
rules: { | ||
"operation-operationId-unique": { severity: "error" } | ||
}, | ||
resolve: { | ||
http: { | ||
headers: [ | ||
{ | ||
matches: `${new URL(restURL).protocol}://${new URL(restURL).host}/**`, | ||
name: "x-auth-token", | ||
value: personalAccessToken | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
{ extends: ["recommended"] } | ||
); | ||
const ast = await openapiTS(new URL(`${personalAccessToken}/docs/json`, restURL), { | ||
redocly | ||
}); | ||
const contents = astToString(ast); | ||
const btpDir = join(findProjectRoot(process.cwd()), ".btp"); | ||
const portalDir = join(btpDir, "portal"); | ||
const restDir = join(portalDir, "rest"); | ||
const restCodegenDir = join(restDir, "codegen"); | ||
mkdirSync(restCodegenDir, { recursive: true }); | ||
const portalRestTypesPath = join(restCodegenDir, "portal-schema.d.ts"); | ||
writeFileSync(portalRestTypesPath, contents); | ||
const portalRestClientPath = join(restDir, "index.ts"); | ||
if (framework === "nextjs") { | ||
writeFileSync( | ||
portalRestClientPath, | ||
` | ||
import createClient from "openapi-fetch"; | ||
import type { paths } from "./codegen/portal-schema"; | ||
export const portal = createClient<paths>({ baseUrl: \`\${process.env.NEXT_PUBLIC_BTP_APP_URL}/proxy/portal/rest\` }); | ||
` | ||
); | ||
} else { | ||
writeFileSync( | ||
portalRestClientPath, | ||
` | ||
import createClient from "openapi-fetch"; | ||
import type { paths } from "./codegen/portal-schema"; | ||
if(!process.env.BTP_PAT_TOKEN){ | ||
throw new Error("BTP_PAT_TOKEN environment variable is required"); | ||
} | ||
export const portal = createClient<paths>({ baseUrl: '${restURL}', headers: { "x-auth-token": process.env.BTP_PAT_TOKEN } }); | ||
` | ||
); | ||
} | ||
} | ||
} | ||
// src/commands/codegen.ts | ||
function codegenCommand() { | ||
return new Command("codegen").description("Generates the code for using the BTP services").option("-e, --environment <id>", "The name of the environment to use (BTP_ENVIRONMENT environment variable)").action(async ({ environment }) => { | ||
printAsciiArt(); | ||
printIntro("Code generating"); | ||
try { | ||
const cfg = await config(); | ||
if (!cfg) { | ||
printCancel("No configuration found"); | ||
process.exit(0); | ||
} | ||
const { pat, defaultEnvironment, environments } = cfg; | ||
if (!environments) { | ||
printCancel("No environments found in configuration"); | ||
process.exit(0); | ||
} | ||
const environmentConfig = environments[process.env.BTP_ENVIRONMENT ?? environment ?? defaultEnvironment]; | ||
if (!environmentConfig) { | ||
printCancel("No environment found"); | ||
process.exit(0); | ||
} | ||
const { portalRest, portal } = environmentConfig; | ||
let usageMessage = ""; | ||
if (portalRest) { | ||
await printSpinner({ | ||
startMessage: "Generating the Portal REST client", | ||
task: async () => { | ||
await createRestClient({ framework: cfg.framework, restURL: portalRest, personalAccessToken: pat }); | ||
}, | ||
stopMessage: "Portal REST client generated" | ||
}); | ||
usageMessage += ` | ||
To use the Portal REST client: | ||
${greenBright("import { portal } from './.btp/portal/rest'")} | ||
${greenBright("// leverage typescript autocomplete for other methods and params")} | ||
${greenBright("const pendingTransactions = await portal.get('/transactions/pending')")} | ||
`; | ||
} | ||
if (portal) { | ||
await printSpinner({ | ||
startMessage: "Generating the Portal GQL client", | ||
task: async () => { | ||
await createGqlClient({ | ||
framework: cfg.framework, | ||
type: "portal", | ||
gqlUrl: portal, | ||
personalAccessToken: pat | ||
}); | ||
}, | ||
stopMessage: "Portal GQL client generated" | ||
}); | ||
usageMessage += ` | ||
To use the Portal GQL client: | ||
${greenBright("import { portal } from './.btp/portal/gql/portal'")} | ||
${greenBright("// leverage typescript autocomplete for other methods and params")} | ||
${greenBright("const pendingTransactions = await portal.get('/transactions/pending')")} | ||
`; | ||
} | ||
printNote(usageMessage, "Usage hints"); | ||
printOutro("Code generation complete"); | ||
process.exit(0); | ||
} catch (error) { | ||
printCancel(`Error: ${error.message}`); | ||
console.error(error.stack); | ||
process.exit(1); | ||
} | ||
}); | ||
} | ||
// src/lib/cluster-manager.ts | ||
async function getServices({ instance, pat }) { | ||
const result = await fetch(`${instance}/cm/sdk/services`, { | ||
headers: { | ||
"Content-Type": "application/json", | ||
"x-auth-token": pat | ||
} | ||
}); | ||
if (!result.ok) { | ||
console.warn(`Failed to fetch services. Status: ${result.status}`); | ||
return []; | ||
} | ||
const services = await result.json(); | ||
return services; | ||
} | ||
// src/lib/coerce.ts | ||
async function coerceText(options) { | ||
const { | ||
configValue, | ||
defaultValue, | ||
invalidMessage, | ||
existingMessage, | ||
type, | ||
promptMessage, | ||
envValue, | ||
cliParamValue, | ||
validate | ||
} = options; | ||
let value = envValue || cliParamValue || configValue || defaultValue; | ||
try { | ||
if (validate(value)) { | ||
const change = await promptConfirm({ | ||
message: type === "password" ? existingMessage : `${existingMessage} (${value})`, | ||
initialValue: false | ||
}); | ||
if (!change) { | ||
return value; | ||
} | ||
} | ||
} catch { | ||
} | ||
if (type === "password") { | ||
value = await promptPassword({ | ||
message: promptMessage, | ||
validate(value2) { | ||
try { | ||
if (!validate(value2)) { | ||
return invalidMessage; | ||
} | ||
return; | ||
} catch { | ||
return invalidMessage; | ||
} | ||
} | ||
}); | ||
} else { | ||
value = await promptText({ | ||
message: promptMessage, | ||
defaultValue, | ||
initialValue: value ?? defaultValue, | ||
placeholder: value ?? defaultValue, | ||
validate(value2) { | ||
try { | ||
if (!validate(value2)) { | ||
return invalidMessage; | ||
} | ||
return; | ||
} catch { | ||
return invalidMessage; | ||
} | ||
} | ||
}); | ||
} | ||
return value; | ||
} | ||
async function coerceSelect(params) { | ||
const { | ||
skipCoerce, | ||
configValue, | ||
options, | ||
noneOption, | ||
existingMessage, | ||
promptMessage, | ||
envValue, | ||
cliParamValue, | ||
validate | ||
} = params; | ||
let value = envValue || cliParamValue || configValue; | ||
if (!skipCoerce) { | ||
try { | ||
if (validate(value)) { | ||
const change = await promptConfirm({ | ||
message: `${existingMessage} (${// biome-ignore lint/suspicious/noExplicitAny: <explanation> | ||
typeof value === "string" ? value : value ? value.name : ""})`, | ||
initialValue: false | ||
}); | ||
if (!change) { | ||
return value; | ||
} | ||
} | ||
} catch { | ||
} | ||
} | ||
value = await promptSelect({ | ||
options, | ||
message: promptMessage, | ||
noneOption | ||
}); | ||
if (noneOption && value === noneOption.value) { | ||
return void 0; | ||
} | ||
return value; | ||
} | ||
// src/commands/init.ts | ||
function initCommand() { | ||
return new Command("init").option("-p, --pat <key>", "Personal Access Token for authentication (BTP_PAT_TOKEN environment variable)").option( | ||
"-i, --instance <url>", | ||
"The url to your BTP instance, defaults to https://console.settlemint.com (BTP_INSTANCE_URL environment variable)" | ||
).option( | ||
"-au, --appUrl <url>", | ||
"The development url to your application, defaults to http://localhost:3000 (NEXT_PUBLIC_BTP_APP_URL environment variable)" | ||
).option("-w, --workspace <id>", "The id of the workspace to use (BTP_WORKSPACE environment variable)").option( | ||
"-cw, --childWorkspace <id>", | ||
"The id of the child workspace to use (BTP_CHILD_WORKSPACE environment variable)" | ||
).option("-e, --environment <id>", "The name of the environment to use (BTP_ENVIRONMENT environment variable)").option("-a, --application <id>", "The id of the application to use (BTP_APPLICATION environment variable)").option("-pr, --portalRest <url>", "The url to the portal rest api (BTP_PORTAL_REST_URL environment variable)").option("-pg, --portalGql <url>", "The url to the portal gql api (BTP_PORTAL_GQL_URL environment variable)").option("-tg, --theGrapqh <url>", "The url to the graph gql api (BTP_THE_GRAPH_GQL_URL environment variable)").option("-h, --hasura <url>", "The url to the hasura gql api (BTP_HASURA_GQL_URL environment variable)").option("-n, --node <url>", "The url to the node rpc api (BTP_NODE_URL environment variable)").option("-f, --framework <framework>", "The framework to use (BTP_FRAMEWORK environment variable)").option("-c, --create", "Create a new environment if it does not exist (BTP_CREATE environment variable)").description("Initializes the setup of the BTP SDK").action( | ||
async ({ | ||
pat, | ||
instance, | ||
appUrl, | ||
portalRest, | ||
portalGql, | ||
theGrapqh, | ||
hasura, | ||
node, | ||
workspace, | ||
application, | ||
childWorkspace, | ||
framework, | ||
environment, | ||
create | ||
}) => { | ||
printAsciiArt(); | ||
printIntro("Setting up the BTP SDK in your project"); | ||
try { | ||
let cfg; | ||
try { | ||
cfg = await config(); | ||
} catch { | ||
} | ||
const frameworks = [ | ||
{ value: "nodejs", label: "NodeJS" }, | ||
{ value: "nextjs", label: "Next.js" } | ||
]; | ||
const selectedFramework = await coerceSelect({ | ||
options: frameworks, | ||
envValue: process.env.BTP_FRAMEWORK, | ||
cliParamValue: framework, | ||
configValue: cfg?.framework, | ||
validate: (value) => frameworks.map((fr) => fr.value).includes(value ?? ""), | ||
promptMessage: "Which framework do you want to use?", | ||
existingMessage: "A valid framework is already provided. Do you want to change it?" | ||
}); | ||
if (!selectedFramework) { | ||
printCancel("No framework selected"); | ||
process.exit(0); | ||
} | ||
const personalAccessToken = await coerceText({ | ||
type: "password", | ||
envValue: process.env.BTP_PAT_TOKEN, | ||
cliParamValue: pat, | ||
configValue: cfg?.pat, | ||
validate: (value) => /^sm_pat_[a-f0-9]{16}$/.test(value?.trim() ?? ""), | ||
promptMessage: "Enter a Personal Access Token for authentication", | ||
existingMessage: "A valid Personal Access Token is already provided. Do you want to change it?", | ||
invalidMessage: "Invalid Personal Access Token" | ||
}); | ||
const instanceUrl = await coerceText({ | ||
type: "text", | ||
envValue: process.env.BTP_INSTANCE_URL, | ||
cliParamValue: instance, | ||
defaultValue: "https://console.settlemint.com", | ||
configValue: cfg?.instance, | ||
validate: (value) => new URL(value ?? "").protocol === "https:", | ||
promptMessage: "Enter the URL of your BTP instance", | ||
existingMessage: "A valid BTP instance URL is already provided. Do you want to change it?", | ||
invalidMessage: "Invalid BTP instance URL. Please enter a valid HTTPS URL." | ||
}); | ||
const possibleEnvironments = Object.keys(cfg?.environments ?? {}); | ||
if (possibleEnvironments.length === 0) { | ||
possibleEnvironments.push("development"); | ||
} | ||
let createEnvironment = !!process.env.BTP_CREATE || !!create; | ||
if (!createEnvironment) { | ||
createEnvironment = await promptConfirm({ | ||
message: `Do you want to create a new environment? (${possibleEnvironments.join(", ")})`, | ||
initialValue: false | ||
}); | ||
} | ||
let currentEnvironment; | ||
if (!createEnvironment) { | ||
currentEnvironment = await coerceSelect({ | ||
options: possibleEnvironments.map((penv) => ({ | ||
value: penv, | ||
label: penv | ||
})), | ||
envValue: process.env.BTP_ENVIRONMENT, | ||
cliParamValue: environment, | ||
configValue: cfg?.defaultEnvironment, | ||
validate: (value) => !!value?.trim(), | ||
promptMessage: "Select an environment to configure", | ||
existingMessage: "A valid default environment is already provided. Do you want to add one?", | ||
skipCoerce: true | ||
}); | ||
} else { | ||
currentEnvironment = await coerceText({ | ||
type: "text", | ||
envValue: process.env.BTP_ENVIRONMENT, | ||
cliParamValue: void 0, | ||
configValue: void 0, | ||
validate: (value) => !!value?.trim() && !possibleEnvironments.includes(value), | ||
promptMessage: "Enter a new environment name", | ||
existingMessage: "A valid environment name is already provided. Do you want to change it?", | ||
invalidMessage: "Invalid environment name or it already exists. Please enter a valid name." | ||
}); | ||
possibleEnvironments.push(currentEnvironment); | ||
} | ||
if (!currentEnvironment) { | ||
printCancel("No environment selected"); | ||
process.exit(0); | ||
} | ||
let selectedAppUrl; | ||
if (selectedFramework === "nextjs") { | ||
selectedAppUrl = await coerceText({ | ||
type: "text", | ||
envValue: process.env.NEXT_PUBLIC_BTP_APP_URL, | ||
cliParamValue: appUrl, | ||
configValue: cfg?.appUrl, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Enter the development URL of your application instance", | ||
existingMessage: "A valid application URL is already provided. Do you want to change it?", | ||
invalidMessage: "Invalid application instance URL. Please enter a valid URL." | ||
}); | ||
} | ||
const services = await printSpinner({ | ||
startMessage: "Fetching services", | ||
task: async () => { | ||
return getServices({ instance: instanceUrl, pat: personalAccessToken }); | ||
}, | ||
stopMessage: "Services fetched" | ||
}); | ||
if (services.length === 0) { | ||
printCancel("No workspaces found using the provided personal access token"); | ||
process.exit(0); | ||
} | ||
const selectedWorkspace = await coerceSelect({ | ||
options: services.map((service) => ({ | ||
value: service, | ||
label: service.name | ||
})), | ||
envValue: services.find((svc) => svc.id === process.env.BTP_WORKSPACE), | ||
cliParamValue: services.find((svc) => svc.id === workspace), | ||
configValue: services.find((svc) => svc.id === cfg?.environments?.[currentEnvironment]?.workspace), | ||
validate: (value) => !!value?.id, | ||
promptMessage: "Select a top level workspace", | ||
existingMessage: "A valid top level workspace is already provided. Do you want to change it?" | ||
}); | ||
if (!selectedWorkspace) { | ||
printCancel("No workspace selected"); | ||
process.exit(0); | ||
} | ||
let lowestWorkspace = selectedWorkspace; | ||
let selectedChildWorkspace; | ||
if (selectedWorkspace.childWorkspaces.length > 0) { | ||
const list = [ | ||
{ ...selectedWorkspace, name: `Top level: ${selectedWorkspace.name}` }, | ||
...selectedWorkspace.childWorkspaces | ||
]; | ||
const options = list.map((childWorkspace2) => ({ | ||
value: childWorkspace2, | ||
label: childWorkspace2.name | ||
})); | ||
selectedChildWorkspace = await coerceSelect({ | ||
options, | ||
envValue: list.find((svc) => svc.id === process.env.BTP_CHILD_WORKSPACE), | ||
cliParamValue: list.find((svc) => svc.id === childWorkspace), | ||
configValue: list.find((svc) => svc.id === cfg?.environments?.[currentEnvironment]?.childWorkspace), | ||
validate: (value) => !!value?.id, | ||
promptMessage: "Select a child workspace", | ||
existingMessage: "A valid child workspace is already provided. Do you want to change it?" | ||
}); | ||
if (!selectedChildWorkspace) { | ||
printCancel("No sub-workspace selected"); | ||
process.exit(0); | ||
} | ||
lowestWorkspace = selectedChildWorkspace; | ||
} | ||
if (lowestWorkspace.applications.length === 0) { | ||
printCancel("No applications found using the provided personal access token and workspace"); | ||
process.exit(0); | ||
} | ||
const selectedApplication = await coerceSelect({ | ||
options: lowestWorkspace.applications.map((app) => ({ value: app, label: app.name })), | ||
envValue: lowestWorkspace.applications.find((svc) => svc.id === process.env.BTP_APPLICATION), | ||
cliParamValue: lowestWorkspace.applications.find((svc) => svc.id === application), | ||
configValue: lowestWorkspace.applications.find( | ||
(svc) => svc.id === cfg?.environments?.[currentEnvironment]?.application | ||
), | ||
validate: (value) => !!value?.id, | ||
promptMessage: "Select an application", | ||
existingMessage: "A valid application is already provided. Do you want to change it?" | ||
}); | ||
if (!selectedApplication) { | ||
printCancel("No application selected"); | ||
process.exit(0); | ||
} | ||
const portalRestUrl = await coerceSelect({ | ||
options: selectedApplication.portals.map((portal) => ({ | ||
value: portal.restUrl, | ||
label: `${portal.name} (${portal.uniqueName})` | ||
})), | ||
noneOption: { value: void 0, label: "None" }, | ||
envValue: process.env.BTP_PORTAL_REST_URL, | ||
cliParamValue: portalRest, | ||
configValue: cfg?.environments?.[currentEnvironment]?.portalRest, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Select your Smart Contract Set Portal instance (REST API)", | ||
existingMessage: "A valid Smart Contract Set Portal instance URL for REST is already provided. Do you want to change it?" | ||
}); | ||
const portalGqlUrl = await coerceSelect({ | ||
options: selectedApplication.portals.map((portal) => ({ | ||
value: portal.gqlUrl, | ||
label: `${portal.name} (${portal.uniqueName})` | ||
})), | ||
noneOption: { value: void 0, label: "None" }, | ||
envValue: process.env.BTP_PORTAL_GQL_URL, | ||
cliParamValue: portalGql, | ||
configValue: cfg?.environments?.[currentEnvironment]?.portal, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Select your Smart Contract Set Portal instance (GraphQL API)", | ||
existingMessage: "A valid Smart Contract Set Portal instance URL for GraphQL is already provided. Do you want to change it?" | ||
}); | ||
const thegraphGqlUrl = await coerceSelect({ | ||
options: selectedApplication.graphs.map((graph) => ({ | ||
value: graph.gqlUrl, | ||
label: `${graph.name} (${graph.uniqueName})` | ||
})), | ||
noneOption: { value: void 0, label: "None" }, | ||
envValue: process.env.BTP_THE_GRAPH_GQL_URL, | ||
cliParamValue: theGrapqh, | ||
configValue: cfg?.environments?.[currentEnvironment]?.graph, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Select your The Graph instance", | ||
existingMessage: "A valid The Graph URL is already provided. Do you want to change it?" | ||
}); | ||
const hasuraUrl = await coerceSelect({ | ||
options: selectedApplication.hasuras.map((hasura2) => ({ | ||
value: hasura2.gqlUrl, | ||
label: `${hasura2.name} (${hasura2.uniqueName})` | ||
})), | ||
noneOption: { value: void 0, label: "None" }, | ||
envValue: process.env.BTP_HASURA_GQL_URL, | ||
cliParamValue: hasura, | ||
configValue: cfg?.environments?.[currentEnvironment]?.hasura, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Select your Hasura instance", | ||
existingMessage: "A valid Hasura URL is already provided. Do you want to change it?" | ||
}); | ||
const nodeUrl = await coerceSelect({ | ||
options: selectedApplication.nodes.map((node2) => ({ | ||
value: node2.rpcUrl, | ||
label: `${node2.name} (${node2.uniqueName})` | ||
})), | ||
noneOption: { value: void 0, label: "None" }, | ||
envValue: process.env.BTP_NODE_URL, | ||
cliParamValue: node, | ||
configValue: cfg?.environments?.[currentEnvironment]?.node, | ||
validate: (value) => !!new URL(value ?? "").toString(), | ||
promptMessage: "Select a blockchain node or loadbalancer", | ||
existingMessage: "A valid blockchain node URL is already provided. Do you want to change it?" | ||
}); | ||
await printSpinner({ | ||
startMessage: "Creating or updating the .env.local file", | ||
task: async () => { | ||
await createEnv({ BTP_PAT_TOKEN: personalAccessToken, NEXT_PUBLIC_BTP_APP_URL: selectedAppUrl }); | ||
}, | ||
stopMessage: ".env.local file created or updated" | ||
}); | ||
const defaultEnvironment = await coerceSelect({ | ||
options: possibleEnvironments.map((penv) => ({ | ||
value: penv, | ||
label: penv | ||
})), | ||
configValue: cfg?.defaultEnvironment, | ||
validate: (value) => !!value?.trim(), | ||
promptMessage: "Select a default environment (used when no environment is specified)", | ||
existingMessage: "A valid default environment is already provided. Do you want to select a different one?" | ||
}); | ||
if (!defaultEnvironment) { | ||
printCancel("No default environment selected"); | ||
process.exit(0); | ||
} | ||
await printSpinner({ | ||
startMessage: "Creating or updating the .btprc.json config file", | ||
task: async () => { | ||
await createConfig({ | ||
defaultEnvironment, | ||
framework: selectedFramework, | ||
instance: instanceUrl, | ||
environments: { | ||
[currentEnvironment]: { | ||
workspace: selectedWorkspace.id, | ||
childWorkspace: selectedChildWorkspace?.id, | ||
application: selectedApplication.id, | ||
portal: portalGqlUrl, | ||
portalRest: portalRestUrl, | ||
graph: thegraphGqlUrl, | ||
hasura: hasuraUrl, | ||
node: nodeUrl | ||
} | ||
} | ||
}); | ||
}, | ||
stopMessage: ".btprc.json config file created or updated" | ||
}); | ||
printNote( | ||
`To generate the code for using the BTP services, run the following command: | ||
${greenBright("btp-sdk-cli codegen")} | ||
or for another environment: | ||
${greenBright(`btp-sdk-cli codegen -e <${possibleEnvironments.join(" | ")}>`)}`, | ||
"Next steps" | ||
); | ||
printOutro("You're all set!"); | ||
process.exit(0); | ||
} catch (error) { | ||
printCancel(`Error: ${error.message}`); | ||
console.error(error.stack); | ||
process.exit(1); | ||
} | ||
} | ||
); | ||
} | ||
// src/index.ts | ||
dotenv.config({ | ||
path: [".env.local", ".env"], | ||
override: true | ||
}); | ||
var sdkcli = new Command(); | ||
sdkcli.name("btp-sdk-cli").usage("[command]").description(`CLI for the SettleMint Blockchain Transformation Platform SDK (v${package_default.version})`).version(package_default.version, "-v, --version", "Output the current version").helpOption("-h, --help", "Display help for command").allowUnknownOption().showSuggestionAfterError(true).showHelpAfterError(); | ||
sdkcli.addCommand(initCommand()); | ||
sdkcli.addCommand(codegenCommand()); | ||
sdkcli.parseAsync(process.argv).catch(async (reason) => { | ||
cancel("An unexpected error occurred. Please report it as a bug:"); | ||
console.error(reason); | ||
process.exit(1); | ||
}); | ||
//# sourceMappingURL=index.js.map | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@settlemint/btp-sdk-cli", | ||
"version": "0.3.2-main-a438d15", | ||
"version": "0.3.2-main1a9ceb3", | ||
"main": "./dist/index.js", | ||
@@ -38,21 +38,28 @@ "module": "./dist/index.js", | ||
"scripts": { | ||
"build": "tsup src/index.ts --format esm --dts", | ||
"dev": "tsup src/index.ts --format esm --dts --watch" | ||
"build": "tsup-node src/index.ts --format esm --dts", | ||
"dev": "tsup-node src/index.ts --format esm --dts --watch" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "20.14.12", | ||
"tsup": "8.2.3", | ||
"type-fest": "4.23.0" | ||
}, | ||
"dependencies": { | ||
"@clack/prompts": "0.7.0", | ||
"@commander-js/extra-typings": "12.1.0", | ||
"@inquirer/prompts": "5.3.2", | ||
"boxen": "8.0.0", | ||
"@graphql-codegen/cli": "5.0.2", | ||
"@tsconfig/node20": "20.1.4", | ||
"@tsconfig/strictest": "2.0.5", | ||
"@types/node": "20.14.13", | ||
"commander": "12.1.0", | ||
"cosmiconfig": "9.0.0", | ||
"reflect-metadata": "0.2.2", | ||
"dotenv": "16.4.5", | ||
"openapi-typescript": "7.3.0", | ||
"ts-deepmerge": "7.0.1", | ||
"tsup": "8.2.3", | ||
"yoctocolors": "2.1.1", | ||
"zod": "3.23.8" | ||
}, | ||
"peerDependencies": {} | ||
"dependencies": { | ||
"graphql-request": "7.1.0", | ||
"openapi-fetch": "0.10.4" | ||
}, | ||
"peerDependencies": { | ||
"graphql": "16.9.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 12 instances in 1 package
118520
3
889
14
18
2
+ Addedgraphql-request@7.1.0
+ Addedopenapi-fetch@0.10.4
+ Added@graphql-typed-document-node/core@3.2.0(transitive)
+ Added@molt/command@0.9.0(transitive)
+ Added@molt/types@0.2.0(transitive)
+ Addedalge@0.8.1(transitive)
+ Addedgraphql@16.9.0(transitive)
+ Addedgraphql-request@7.1.0(transitive)
+ Addedlodash.camelcase@4.3.0(transitive)
+ Addedlodash.ismatch@4.4.0(transitive)
+ Addedlodash.snakecase@4.1.1(transitive)
+ Addedopenapi-fetch@0.10.4(transitive)
+ Addedopenapi-typescript-helpers@0.0.11(transitive)
+ Addedreadline-sync@1.4.10(transitive)
+ Addedremeda@1.61.0(transitive)
+ Addedstring-length@6.0.0(transitive)
+ Addedts-toolbelt@9.6.0(transitive)
+ Addedzod@3.24.2(transitive)
- Removed@commander-js/extra-typings@12.1.0
- Removed@inquirer/prompts@5.3.2
- Removedboxen@8.0.0
- Removedcosmiconfig@9.0.0
- Removedreflect-metadata@0.2.2
- Removedts-deepmerge@7.0.1
- Removedyoctocolors@2.1.1
- Removedzod@3.23.8
- Removed@babel/code-frame@7.26.2(transitive)
- Removed@babel/helper-validator-identifier@7.25.9(transitive)
- Removed@commander-js/extra-typings@12.1.0(transitive)
- Removed@inquirer/checkbox@2.5.0(transitive)
- Removed@inquirer/confirm@3.2.0(transitive)
- Removed@inquirer/core@9.2.1(transitive)
- Removed@inquirer/editor@2.2.0(transitive)
- Removed@inquirer/expand@2.3.0(transitive)
- Removed@inquirer/figures@1.0.10(transitive)
- Removed@inquirer/input@2.3.0(transitive)
- Removed@inquirer/number@1.1.0(transitive)
- Removed@inquirer/password@2.2.0(transitive)
- Removed@inquirer/prompts@5.3.2(transitive)
- Removed@inquirer/rawlist@2.3.0(transitive)
- Removed@inquirer/search@1.1.0(transitive)
- Removed@inquirer/select@2.5.0(transitive)
- Removed@inquirer/type@1.5.52.0.0(transitive)
- Removed@types/mute-stream@0.0.4(transitive)
- Removed@types/node@22.13.8(transitive)
- Removed@types/wrap-ansi@3.0.0(transitive)
- Removedansi-align@3.0.1(transitive)
- Removedansi-escapes@4.3.2(transitive)
- Removedansi-regex@5.0.1(transitive)
- Removedansi-styles@4.3.06.2.1(transitive)
- Removedargparse@2.0.1(transitive)
- Removedboxen@8.0.0(transitive)
- Removedcallsites@3.1.0(transitive)
- Removedcamelcase@8.0.0(transitive)
- Removedchardet@0.7.0(transitive)
- Removedcli-boxes@4.0.1(transitive)
- Removedcli-width@4.1.0(transitive)
- Removedcolor-convert@2.0.1(transitive)
- Removedcolor-name@1.1.4(transitive)
- Removedcommander@12.1.0(transitive)
- Removedcosmiconfig@9.0.0(transitive)
- Removedemoji-regex@10.4.08.0.0(transitive)
- Removedenv-paths@2.2.1(transitive)
- Removederror-ex@1.3.2(transitive)
- Removedexternal-editor@3.1.0(transitive)
- Removedget-east-asian-width@1.3.0(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedimport-fresh@3.3.1(transitive)
- Removedis-arrayish@0.2.1(transitive)
- Removedis-fullwidth-code-point@3.0.0(transitive)
- Removedjs-tokens@4.0.0(transitive)
- Removedjs-yaml@4.1.0(transitive)
- Removedjson-parse-even-better-errors@2.3.1(transitive)
- Removedlines-and-columns@1.2.4(transitive)
- Removedmute-stream@1.0.0(transitive)
- Removedos-tmpdir@1.0.2(transitive)
- Removedparent-module@1.0.1(transitive)
- Removedparse-json@5.2.0(transitive)
- Removedpicocolors@1.1.1(transitive)
- Removedreflect-metadata@0.2.2(transitive)
- Removedresolve-from@4.0.0(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsignal-exit@4.1.0(transitive)
- Removedstring-width@4.2.37.2.0(transitive)
- Removedstrip-ansi@6.0.1(transitive)
- Removedtmp@0.0.33(transitive)
- Removedts-deepmerge@7.0.1(transitive)
- Removedtype-fest@0.21.3(transitive)
- Removedundici-types@6.20.0(transitive)
- Removedwidest-line@5.0.0(transitive)
- Removedwrap-ansi@6.2.09.0.0(transitive)
- Removedyoctocolors@2.1.1(transitive)
- Removedyoctocolors-cjs@2.1.2(transitive)
- Removedzod@3.23.8(transitive)