@seamapi/cli 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -1
- package/bin/cli.js +105 -33
- package/bin/cli.js.map +1 -1
- package/lib/command-spec.d.ts +8 -0
- package/lib/command-spec.js +8 -0
- package/lib/command-spec.js.map +1 -1
- package/lib/env.d.ts +29 -0
- package/lib/env.js +41 -0
- package/lib/env.js.map +1 -0
- package/lib/get-credentials.d.ts +15 -0
- package/lib/get-credentials.js +35 -0
- package/lib/get-credentials.js.map +1 -0
- package/lib/get-current-workspace-id.js +3 -4
- package/lib/get-current-workspace-id.js.map +1 -1
- package/lib/get-seam.js +21 -9
- package/lib/get-seam.js.map +1 -1
- package/lib/get-server.d.ts +5 -0
- package/lib/get-server.js +11 -1
- package/lib/get-server.js.map +1 -1
- package/lib/interact-for-access-code.d.ts +1 -1
- package/lib/interact-for-access-code.js +7 -4
- package/lib/interact-for-access-code.js.map +1 -1
- package/lib/interact-for-acs-user.js +2 -2
- package/lib/interact-for-acs-user.js.map +1 -1
- package/lib/interact-for-action-attempt-poll.d.ts +1 -1
- package/lib/interact-for-action-attempt-poll.js +8 -8
- package/lib/interact-for-action-attempt-poll.js.map +1 -1
- package/lib/interact-for-blueprint-object.js +6 -8
- package/lib/interact-for-blueprint-object.js.map +1 -1
- package/lib/interact-for-custom-metadata.d.ts +1 -1
- package/lib/interact-for-custom-metadata.js +15 -15
- package/lib/interact-for-custom-metadata.js.map +1 -1
- package/lib/interact-for-login.js +2 -0
- package/lib/interact-for-login.js.map +1 -1
- package/lib/interact-for-server-selection.js +3 -0
- package/lib/interact-for-server-selection.js.map +1 -1
- package/lib/interact-for-use-remote-api-defs.js +4 -4
- package/lib/interact-for-use-remote-api-defs.js.map +1 -1
- package/lib/interact-for-workspace-id.js +2 -0
- package/lib/interact-for-workspace-id.js.map +1 -1
- package/lib/render-help.js +4 -0
- package/lib/render-help.js.map +1 -1
- package/lib/util/cli-args.d.ts +21 -0
- package/lib/util/cli-args.js +26 -1
- package/lib/util/cli-args.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/bin/cli.ts +148 -32
- package/src/lib/command-spec.ts +12 -0
- package/src/lib/env.d.ts +12 -0
- package/src/lib/env.ts +63 -0
- package/src/lib/get-credentials.ts +38 -0
- package/src/lib/get-current-workspace-id.ts +3 -5
- package/src/lib/get-seam.ts +39 -11
- package/src/lib/get-server.ts +12 -1
- package/src/lib/interact-for-access-code.ts +6 -4
- package/src/lib/interact-for-acs-user.ts +2 -2
- package/src/lib/interact-for-action-attempt-poll.ts +9 -9
- package/src/lib/interact-for-blueprint-object.ts +6 -8
- package/src/lib/interact-for-custom-metadata.ts +18 -20
- package/src/lib/interact-for-login.ts +3 -0
- package/src/lib/interact-for-server-selection.ts +10 -0
- package/src/lib/interact-for-use-remote-api-defs.ts +4 -4
- package/src/lib/interact-for-workspace-id.ts +12 -0
- package/src/lib/render-help.ts +4 -0
- package/src/lib/util/cli-args.ts +33 -1
- package/src/lib/version.ts +1 -1
|
@@ -51,11 +51,11 @@ export const interactForBlueprintObject = async (
|
|
|
51
51
|
|
|
52
52
|
const cmdPath = `/${args.command.join('/').replace(/-/g, '_')}`
|
|
53
53
|
|
|
54
|
-
const
|
|
54
|
+
const shouldAutoSubmit =
|
|
55
55
|
ctx.interactivity !== 'interactive' &&
|
|
56
56
|
haveAllRequiredParams &&
|
|
57
57
|
!args.isSubProperty
|
|
58
|
-
if (
|
|
58
|
+
if (shouldAutoSubmit) {
|
|
59
59
|
return args.params
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -160,18 +160,16 @@ export const interactForBlueprintObject = async (
|
|
|
160
160
|
args.params[paramToEdit] = await interactForAccessCode(args.params as any)
|
|
161
161
|
return interactForBlueprintObject(args, ctx)
|
|
162
162
|
} else if (paramToEdit === 'connected_account_id') {
|
|
163
|
-
const
|
|
164
|
-
args.params[paramToEdit] =
|
|
163
|
+
const connectedAccountId = await interactForConnectedAccount()
|
|
164
|
+
args.params[paramToEdit] = connectedAccountId
|
|
165
165
|
return interactForBlueprintObject(args, ctx)
|
|
166
166
|
} else if (
|
|
167
167
|
paramToEdit === 'user_identity_id' ||
|
|
168
168
|
paramToEdit === 'user_identity_ids'
|
|
169
169
|
) {
|
|
170
|
-
const
|
|
170
|
+
const userIdentityId = await interactForUserIdentity()
|
|
171
171
|
args.params[paramToEdit] =
|
|
172
|
-
paramToEdit === 'user_identity_ids'
|
|
173
|
-
? [user_identity_id]
|
|
174
|
-
: user_identity_id
|
|
172
|
+
paramToEdit === 'user_identity_ids' ? [userIdentityId] : userIdentityId
|
|
175
173
|
return interactForBlueprintObject(args, ctx)
|
|
176
174
|
} else if (paramToEdit.endsWith('acs_system_id')) {
|
|
177
175
|
args.params[paramToEdit] = await interactForAcsSystem()
|
|
@@ -10,16 +10,16 @@ type UpdatedCustomMetadata = {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export const interactForCustomMetadata = async (
|
|
13
|
-
|
|
13
|
+
customMetadata: CustomMetadata,
|
|
14
14
|
) => {
|
|
15
|
-
const
|
|
15
|
+
const updatedCustomMetadata: UpdatedCustomMetadata = { ...customMetadata }
|
|
16
16
|
const output = getOutput()
|
|
17
17
|
|
|
18
18
|
const displayCurrentCustomMetadata = () => {
|
|
19
19
|
output.info('custom_metadata:')
|
|
20
|
-
if (Object.keys(
|
|
21
|
-
Object.keys(
|
|
22
|
-
output.info(`${index + 1}: ${key}: ${
|
|
20
|
+
if (Object.keys(updatedCustomMetadata).length > 0) {
|
|
21
|
+
Object.keys(updatedCustomMetadata).forEach((key, index) => {
|
|
22
|
+
output.info(`${index + 1}: ${key}: ${updatedCustomMetadata[key]}`)
|
|
23
23
|
})
|
|
24
24
|
} else {
|
|
25
25
|
output.info('The custom metadata param is empty.')
|
|
@@ -61,31 +61,29 @@ export const interactForCustomMetadata = async (
|
|
|
61
61
|
newValue = Boolean(newValue)
|
|
62
62
|
}
|
|
63
63
|
if (newValue === 'null') {
|
|
64
|
-
|
|
64
|
+
updatedCustomMetadata[newKey] = null
|
|
65
65
|
} else {
|
|
66
|
-
|
|
66
|
+
updatedCustomMetadata[newKey] = newValue
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
} else if (action === 'remove') {
|
|
70
|
-
const {
|
|
70
|
+
const { customKeyToRemove } = await prompt({
|
|
71
71
|
type: 'select',
|
|
72
|
-
name: '
|
|
72
|
+
name: 'customKeyToRemove',
|
|
73
73
|
message: 'Choose a key-value pair to remove from params:',
|
|
74
|
-
choices: Object.keys(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
),
|
|
74
|
+
choices: Object.keys(updatedCustomMetadata).map((customMetadataKey) => {
|
|
75
|
+
return {
|
|
76
|
+
title: `${customMetadataKey}: ${updatedCustomMetadata[customMetadataKey]}`,
|
|
77
|
+
value: customMetadataKey,
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
82
80
|
})
|
|
83
81
|
|
|
84
|
-
if (
|
|
85
|
-
delete
|
|
82
|
+
if (customKeyToRemove) {
|
|
83
|
+
delete customMetadata[customKeyToRemove]
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
} while (action !== 'done')
|
|
89
87
|
|
|
90
|
-
return
|
|
88
|
+
return updatedCustomMetadata
|
|
91
89
|
}
|
|
@@ -2,6 +2,7 @@ import { isApiKey, isPersonalAccessToken } from '@seamapi/http/connect'
|
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
|
|
4
4
|
import { getConfigStore } from './config/index.js'
|
|
5
|
+
import { assertEnvVarUnset, getTokenFromEnv, tokenEnvVar } from './env.js'
|
|
5
6
|
import { getServer } from './get-server.js'
|
|
6
7
|
import { interactForWorkspaceId } from './interact-for-workspace-id.js'
|
|
7
8
|
import { getOutput } from './output/get-output.js'
|
|
@@ -13,6 +14,8 @@ export const interactForLogin = async () => {
|
|
|
13
14
|
const config = await getConfigStore()
|
|
14
15
|
const output = getOutput()
|
|
15
16
|
|
|
17
|
+
assertEnvVarUnset(tokenEnvVar, getTokenFromEnv(), 'log in')
|
|
18
|
+
|
|
16
19
|
if (getServer().includes('localhost')) {
|
|
17
20
|
output.info(
|
|
18
21
|
`You're using a local Seam Connect instance, you can enter the API Key to your local user, you can create a new user from:\n\n${getServer()}/admin/create_user_with_api_key`,
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
2
|
|
|
3
3
|
import { getConfigStore } from './config/index.js'
|
|
4
|
+
import {
|
|
5
|
+
assertEnvVarUnset,
|
|
6
|
+
endpointEnvVar,
|
|
7
|
+
getEndpointFromEnv,
|
|
8
|
+
getTokenFromEnv,
|
|
9
|
+
tokenEnvVar,
|
|
10
|
+
} from './env.js'
|
|
4
11
|
import { getServer } from './get-server.js'
|
|
5
12
|
import { getOutput } from './output/get-output.js'
|
|
6
13
|
import { prompt } from './util/prompt.js'
|
|
7
14
|
|
|
8
15
|
export async function interactForServerSelection() {
|
|
16
|
+
assertEnvVarUnset(endpointEnvVar, getEndpointFromEnv(), 'select a server')
|
|
17
|
+
|
|
9
18
|
const servers = [
|
|
10
19
|
'http://localhost:3020',
|
|
11
20
|
'https://connect.getseam.com',
|
|
@@ -36,6 +45,7 @@ export async function interactForServerSelection() {
|
|
|
36
45
|
if (userUrlSeed.trim().length === 0) {
|
|
37
46
|
userUrlSeed = randomBytes(5).toString('hex')
|
|
38
47
|
}
|
|
48
|
+
assertEnvVarUnset(tokenEnvVar, getTokenFromEnv(), 'log in')
|
|
39
49
|
config.set('server', `https://${userUrlSeed}.fakeseamconnect.seam.vc`)
|
|
40
50
|
config.set(`${getServer()}.pat`, `seam_apikey1_token`)
|
|
41
51
|
output.info(`PAT set to use fakeseamconnect with "seam_apikey1_token"`)
|
|
@@ -3,10 +3,10 @@ import { getOutput } from './output/get-output.js'
|
|
|
3
3
|
import { prompt } from './util/prompt.js'
|
|
4
4
|
|
|
5
5
|
export async function interactForUseRemoteApiDefs() {
|
|
6
|
-
const {
|
|
6
|
+
const { useRemoteApiDefs } = await prompt([
|
|
7
7
|
{
|
|
8
8
|
type: 'select',
|
|
9
|
-
name: '
|
|
9
|
+
name: 'useRemoteApiDefs',
|
|
10
10
|
message: 'Always use remote API Definitions?',
|
|
11
11
|
choices: [
|
|
12
12
|
{
|
|
@@ -22,6 +22,6 @@ export async function interactForUseRemoteApiDefs() {
|
|
|
22
22
|
])
|
|
23
23
|
|
|
24
24
|
const config = getConfigStore()
|
|
25
|
-
config.set('use_remote_api_defs',
|
|
26
|
-
getOutput().info(`Use remote API Definitions: ${
|
|
25
|
+
config.set('use_remote_api_defs', useRemoteApiDefs)
|
|
26
|
+
getOutput().info(`Use remote API Definitions: ${useRemoteApiDefs}`)
|
|
27
27
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { SeamHttpWithoutWorkspace } from '@seamapi/http/connect'
|
|
2
2
|
|
|
3
3
|
import { getConfigStore } from './config/index.js'
|
|
4
|
+
import {
|
|
5
|
+
assertEnvVarUnset,
|
|
6
|
+
getWorkspaceIdFromEnv,
|
|
7
|
+
workspaceIdEnvVar,
|
|
8
|
+
} from './env.js'
|
|
4
9
|
import { getSeamMultiWorkspace } from './get-seam.js'
|
|
5
10
|
import { getServer } from './get-server.js'
|
|
6
11
|
import { prompt } from './util/prompt.js'
|
|
@@ -8,6 +13,13 @@ import { withLoading } from './util/with-loading.js'
|
|
|
8
13
|
|
|
9
14
|
export const interactForWorkspaceId = async (personalAccessToken?: string) => {
|
|
10
15
|
const config = getConfigStore()
|
|
16
|
+
|
|
17
|
+
assertEnvVarUnset(
|
|
18
|
+
workspaceIdEnvVar,
|
|
19
|
+
getWorkspaceIdFromEnv(),
|
|
20
|
+
'select a workspace',
|
|
21
|
+
)
|
|
22
|
+
|
|
11
23
|
const seam = personalAccessToken
|
|
12
24
|
? SeamHttpWithoutWorkspace.fromPersonalAccessToken(personalAccessToken, {
|
|
13
25
|
endpoint: getServer(),
|
package/src/lib/render-help.ts
CHANGED
|
@@ -66,6 +66,10 @@ const examples = [
|
|
|
66
66
|
name: "seam access-codes create {bold --code} '1234' {bold --name} 'My Code'",
|
|
67
67
|
summary: 'Create an access code.',
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: 'seam devices list {bold --page-cursor} $NEXT_PAGE_CURSOR',
|
|
71
|
+
summary: 'List the next page of devices.',
|
|
72
|
+
},
|
|
69
73
|
{
|
|
70
74
|
name: 'seam devices list > devices.json',
|
|
71
75
|
summary: 'Write the response to a file as JSON.',
|
package/src/lib/util/cli-args.ts
CHANGED
|
@@ -44,9 +44,27 @@ export class NonInteractiveError extends Error {
|
|
|
44
44
|
override name = 'NonInteractiveError'
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when the arguments do not name something the CLI can run.
|
|
49
|
+
*/
|
|
50
|
+
export class UsageError extends Error {
|
|
51
|
+
override name = 'UsageError'
|
|
52
|
+
|
|
53
|
+
/** What to run instead, reported after the message. */
|
|
54
|
+
readonly hint: string
|
|
55
|
+
|
|
56
|
+
constructor(message: string, { hint = '' }: { hint?: string } = {}) {
|
|
57
|
+
super(message)
|
|
58
|
+
this.hint = hint
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
export const parseCliArgs = (argv: string[]): ParsedArgs =>
|
|
48
63
|
parseArgs(argv, {
|
|
49
|
-
|
|
64
|
+
// A page cursor is opaque, so keep it exactly as given: read as a number
|
|
65
|
+
// it would lose leading zeroes and turn exponent notation into a digit
|
|
66
|
+
// string, naming a page the API never issued.
|
|
67
|
+
string: ['code', 'page-cursor', 'page_cursor'],
|
|
50
68
|
boolean: ['non-interactive', 'interactive', 'json'],
|
|
51
69
|
// Deliberately not aliased to -n, which is reserved for a future
|
|
52
70
|
// --dry-run flag.
|
|
@@ -90,3 +108,17 @@ export const getInteractivity = (
|
|
|
90
108
|
*/
|
|
91
109
|
export const toArgName = (parameterName: string): string =>
|
|
92
110
|
`--${parameterName.replace(/_/g, '-')}`
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Read an argument key as the parameter it names, e.g., `--page-cursor`,
|
|
114
|
+
* `--page_cursor`, and `--PAGE-CURSOR` all name `page_cursor`.
|
|
115
|
+
*/
|
|
116
|
+
export const toParameterName = (argKey: string): string =>
|
|
117
|
+
argKey.toLowerCase().replace(/-/g, '_')
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render an argument key as the argument it was given as, naming a one letter
|
|
121
|
+
* key as the short form it can only have been written as, e.g., `-n`.
|
|
122
|
+
*/
|
|
123
|
+
export const toGivenArgName = (argKey: string): string =>
|
|
124
|
+
argKey.length === 1 ? `-${argKey}` : toArgName(argKey)
|
package/src/lib/version.ts
CHANGED