Skip to content

Commit

Permalink
Merge branch 'main' of github.com:seamapi/seam-cli into semantic-release
Browse files Browse the repository at this point in the history
  • Loading branch information
andrii-balitskyi committed Dec 29, 2023
2 parents 7b273fc + 1d9ceb0 commit 3a3ce01
Show file tree
Hide file tree
Showing 10 changed files with 443 additions and 321 deletions.
47 changes: 36 additions & 11 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env node
import isEqual from "lodash/isEqual"
import { getCommandOpenApiDef } from "./lib/get-command-open-api-def"
import { getConfigStore } from "./lib/get-config-store"
import { interactForCommandParams } from "./lib/interact-for-command-params"
import { interactForCommandSelection } from "./lib/interact-for-command-selection"
Expand All @@ -12,12 +11,12 @@ import chalk from "chalk"
import { interactForServerSelection } from "./lib/interact-for-server-selection"
import { getServer } from "./lib/get-server"
import prompts from "prompts"
import { pollActionAttemptUntilReady } from "./lib/util/poll-action-attempt-until-ready"
import logResponse from "./lib/util/log-response"
import { getApiDefinitions } from "./lib/get-api-definitions"
import commandLineUsage from "command-line-usage"
import { ContextHelpers } from "./lib/types"
import { version } from './package.json';
import { version } from "./package.json"
import open from "open"

const sections = [
{
Expand Down Expand Up @@ -66,6 +65,12 @@ const sections = [
async function cli(args: ParsedArgs) {
const config = getConfigStore()

if (args.help || args.h) {
const usage = commandLineUsage(sections)
console.log(usage)
return
}

if (
!config.get(`${getServer()}.pat`) &&
args._[0] !== "login" &&
Expand All @@ -75,12 +80,6 @@ async function cli(args: ParsedArgs) {
process.exit(1)
}

if (args.help || args.h) {
const usage = commandLineUsage(sections)
console.log(usage)
return
}

if (args.version) {
console.log(version)
process.exit(0)
Expand Down Expand Up @@ -170,6 +169,30 @@ async function cli(args: ParsedArgs) {

logResponse(response)

if (response.data.connect_webview) {
if (
response.data &&
response.data.connect_webview &&
response.data.connect_webview.url
) {
const url = response.data.connect_webview.url

if (process.env.INSIDE_WEB_BROWSER !== "1") {
const { action } = await prompts({
type: "confirm",
name: "action",
message: "Would you like to open the webview in your browser?",
})

if (action) {
await open(url)
}
} else {
//TODO: Figure out how to open the webview in the browser
}
}
}

if ("action_attempt" in response.data) {
const { poll_for_action_attempt } = await prompts({
name: "poll_for_action_attempt",
Expand All @@ -181,8 +204,10 @@ async function cli(args: ParsedArgs) {
})

if (poll_for_action_attempt) {
await pollActionAttemptUntilReady(
response.data.action_attempt.action_attempt_id
const { action_attempt_id } = response.data.action_attempt
await seam.actionAttempts.get(
{ action_attempt_id },
{ waitForActionAttempt: { pollingInterval: 240, timeout: 10_000 } }
)
}
}
Expand Down
4 changes: 3 additions & 1 deletion lib/get-api-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export const getApiDefinitions = async (
return SwaggerParser.dereference(schema as unknown as OpenAPI.Document)
}

const getSchema = async (useRemoteDefinitions: boolean): typeof openapi => {
const getSchema = async (
useRemoteDefinitions: boolean
): Promise<typeof openapi> => {
if (!useRemoteDefinitions) return openapi
const endpoint = getServer()
return getOpenapiSchema(endpoint)
Expand Down
22 changes: 14 additions & 8 deletions lib/get-seam.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { SeamHttp, SeamHttpMultiWorkspace } from "@seamapi/http/connect"
import {
SeamHttp,
SeamHttpMultiWorkspace,
isApiKey,
} from "@seamapi/http/connect"
import { getConfigStore } from "./get-config-store"
import { getServer } from "./get-server"

Expand All @@ -22,10 +26,12 @@ export const getSeam = async (): Promise<SeamHttp> => {
return SeamHttp.fromApiKey(token, options)
}

export const getSeamMultiWorkspace =
async (): Promise<SeamHttpMultiWorkspace> => {
const config = getConfigStore()
const token = config.get(`${getServer()}.pat`)
const options = { endpoint: getServer() }
return SeamHttpMultiWorkspace.fromPersonalAccessToken(token, options)
}
export const getSeamMultiWorkspace = async (): Promise<
SeamHttpMultiWorkspace | SeamHttp
> => {
const config = getConfigStore()
const token = config.get(`${getServer()}.pat`)
const options = { endpoint: getServer() }
if (isApiKey(token)) return SeamHttp.fromApiKey(token, options)
return SeamHttpMultiWorkspace.fromPersonalAccessToken(token, options)
}
252 changes: 9 additions & 243 deletions lib/interact-for-command-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { interactForAcsUser } from "./interact-for-acs-user"
import { interactForCredentialPool } from "./interact-for-credential-pool"
import { ContextHelpers } from "./types"
import { interactForAcsEntrance } from "./interact-for-acs-entrance"
import { interactForOpenApiObject } from "./interact-for-open-api-object"

const ergonomicPropOrder = [
"name",
Expand Down Expand Up @@ -42,249 +43,14 @@ export const interactForCommandParams = async (

if (!("content" in requestBody)) throw new Error("No content in requestBody")

const schema: OpenApiSchema = flattenObjSchema(
(requestBody as any).content["application/json"].schema
)

const { properties = {}, required = [] } = schema

const haveAllRequiredParams = required.every((k) => currentParams[k])

const propSortScore = (prop: string) => {
if (required.includes(prop)) return 100
if (currentParams[prop] !== undefined)
return 50 - Object.keys(currentParams).indexOf(prop)
return ergonomicPropOrder.indexOf(prop)
}

const cmdPath = `/${command.join("/").replace(/-/g, "_")}`
console.log("")
const { paramToEdit } = await prompts({
name: "paramToEdit",
message: `[${cmdPath}] Parameters`,
type: "autocomplete",
choices: [
...(haveAllRequiredParams
? [
{
value: "done",
title: `[Make API Call] ${cmdPath}`,
},
]
: []),
...Object.keys(properties)
.map((k) => {
let propDesc = (properties[k] as any)?.description ?? ""
return {
title: k + (required.includes(k) ? "*" : ""),
value: k,
description:
currentParams[k] !== undefined
? `[${currentParams[k]}] ${propDesc}`
: propDesc,
}
})
.sort((a, b) => propSortScore(b.value) - propSortScore(a.value)),
],
})

if (paramToEdit === "done") {
// TODO check for required
return currentParams
}

const prop = properties[paramToEdit]

if (paramToEdit === "device_id") {
const device_id = await interactForDevice()
return interactForCommandParams(
{ command, params: { ...currentParams, device_id } },
ctx
)
} else if (paramToEdit === "access_code_id") {
const access_code_id = await interactForAccessCode(currentParams as any)
return interactForCommandParams(
{
command,
params: {
...currentParams,
access_code_id,
},
},
ctx
)
} else if (paramToEdit === "connected_account_id") {
const connected_account_id = await interactForConnectedAccount()
return interactForCommandParams(
{
command,
params: {
...currentParams,
connected_account_id,
},
},
ctx
)
} else if (paramToEdit === "user_identity_id") {
const user_identity_id = await interactForUserIdentity()
return interactForCommandParams(
{
command,
params: {
...currentParams,
user_identity_id,
},
},
ctx
)
} else if (paramToEdit.endsWith("acs_system_id")) {
const acs_system_id = await interactForAcsSystem()
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: acs_system_id,
},
},
ctx
)
} else if (paramToEdit.endsWith("credential_pool_id")) {
const credential_pool_id = await interactForCredentialPool()
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: credential_pool_id,
},
},
ctx
)
} else if (paramToEdit.endsWith("acs_user_id")) {
const acs_user_id = await interactForAcsUser()
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: acs_user_id,
},
},
ctx
)
} else if (paramToEdit.endsWith("acs_entrance_id")) {
const acs_entrance_id = await interactForAcsEntrance()
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: acs_entrance_id,
},
},
ctx
)
} else if (
// TODO replace when openapi returns if a field is a timestamp
paramToEdit.endsWith("_at") ||
paramToEdit === "since" ||
paramToEdit.endsWith("_before") ||
paramToEdit.endsWith("_after")
) {
const tsval = await interactForTimestamp()
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: tsval,
},
},
ctx
)
}

if ("type" in prop) {
if (prop.type === "string") {
let value
if (prop.enum) {
value = (
await prompts({
name: "value",
message: `${paramToEdit}:`,
type: "select",
choices: prop.enum.map((v) => ({
title: v.toString(),
value: v.toString(),
})),
})
).value
} else {
value = (
await prompts({
name: "value",
message: `${paramToEdit}:`,
type: "text",
})
).value
}
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: value,
},
},
ctx
)
} else if (prop.type === "boolean") {
const { value } = await prompts({
name: "value",
message: `${paramToEdit}:`,
type: "toggle",
initial: true,
active: "true",
inactive: "false",
})

return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: value,
},
},
ctx
)
} else if (prop.type === "array") {
const value = (
await prompts({
name: "value",
message: `${paramToEdit}:`,
type: "autocompleteMultiselect",
choices: (prop as any).items.enum.map((v: string) => ({
title: v.toString(),
value: v.toString(),
})),
})
).value
return interactForCommandParams(
{
command,
params: {
...currentParams,
[paramToEdit]: value,
},
},
ctx
)
}
}
const requestSchema = (requestBody as any).content["application/json"].schema

throw new Error(
`Didn't know how to handle OpenAPI schema for property: "${paramToEdit}"`
return interactForOpenApiObject(
{
command: args.command,
params: args.params,
schema: requestSchema,
},
ctx
)
}
Loading

0 comments on commit 3a3ce01

Please sign in to comment.