@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.
Files changed (68) hide show
  1. package/README.md +65 -1
  2. package/bin/cli.js +105 -33
  3. package/bin/cli.js.map +1 -1
  4. package/lib/command-spec.d.ts +8 -0
  5. package/lib/command-spec.js +8 -0
  6. package/lib/command-spec.js.map +1 -1
  7. package/lib/env.d.ts +29 -0
  8. package/lib/env.js +41 -0
  9. package/lib/env.js.map +1 -0
  10. package/lib/get-credentials.d.ts +15 -0
  11. package/lib/get-credentials.js +35 -0
  12. package/lib/get-credentials.js.map +1 -0
  13. package/lib/get-current-workspace-id.js +3 -4
  14. package/lib/get-current-workspace-id.js.map +1 -1
  15. package/lib/get-seam.js +21 -9
  16. package/lib/get-seam.js.map +1 -1
  17. package/lib/get-server.d.ts +5 -0
  18. package/lib/get-server.js +11 -1
  19. package/lib/get-server.js.map +1 -1
  20. package/lib/interact-for-access-code.d.ts +1 -1
  21. package/lib/interact-for-access-code.js +7 -4
  22. package/lib/interact-for-access-code.js.map +1 -1
  23. package/lib/interact-for-acs-user.js +2 -2
  24. package/lib/interact-for-acs-user.js.map +1 -1
  25. package/lib/interact-for-action-attempt-poll.d.ts +1 -1
  26. package/lib/interact-for-action-attempt-poll.js +8 -8
  27. package/lib/interact-for-action-attempt-poll.js.map +1 -1
  28. package/lib/interact-for-blueprint-object.js +6 -8
  29. package/lib/interact-for-blueprint-object.js.map +1 -1
  30. package/lib/interact-for-custom-metadata.d.ts +1 -1
  31. package/lib/interact-for-custom-metadata.js +15 -15
  32. package/lib/interact-for-custom-metadata.js.map +1 -1
  33. package/lib/interact-for-login.js +2 -0
  34. package/lib/interact-for-login.js.map +1 -1
  35. package/lib/interact-for-server-selection.js +3 -0
  36. package/lib/interact-for-server-selection.js.map +1 -1
  37. package/lib/interact-for-use-remote-api-defs.js +4 -4
  38. package/lib/interact-for-use-remote-api-defs.js.map +1 -1
  39. package/lib/interact-for-workspace-id.js +2 -0
  40. package/lib/interact-for-workspace-id.js.map +1 -1
  41. package/lib/render-help.js +4 -0
  42. package/lib/render-help.js.map +1 -1
  43. package/lib/util/cli-args.d.ts +21 -0
  44. package/lib/util/cli-args.js +26 -1
  45. package/lib/util/cli-args.js.map +1 -1
  46. package/lib/version.d.ts +1 -1
  47. package/lib/version.js +1 -1
  48. package/package.json +1 -1
  49. package/src/bin/cli.ts +148 -32
  50. package/src/lib/command-spec.ts +12 -0
  51. package/src/lib/env.d.ts +12 -0
  52. package/src/lib/env.ts +63 -0
  53. package/src/lib/get-credentials.ts +38 -0
  54. package/src/lib/get-current-workspace-id.ts +3 -5
  55. package/src/lib/get-seam.ts +39 -11
  56. package/src/lib/get-server.ts +12 -1
  57. package/src/lib/interact-for-access-code.ts +6 -4
  58. package/src/lib/interact-for-acs-user.ts +2 -2
  59. package/src/lib/interact-for-action-attempt-poll.ts +9 -9
  60. package/src/lib/interact-for-blueprint-object.ts +6 -8
  61. package/src/lib/interact-for-custom-metadata.ts +18 -20
  62. package/src/lib/interact-for-login.ts +3 -0
  63. package/src/lib/interact-for-server-selection.ts +10 -0
  64. package/src/lib/interact-for-use-remote-api-defs.ts +4 -4
  65. package/src/lib/interact-for-workspace-id.ts +12 -0
  66. package/src/lib/render-help.ts +4 -0
  67. package/src/lib/util/cli-args.ts +33 -1
  68. 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 should_auto_submit =
54
+ const shouldAutoSubmit =
55
55
  ctx.interactivity !== 'interactive' &&
56
56
  haveAllRequiredParams &&
57
57
  !args.isSubProperty
58
- if (should_auto_submit) {
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 connected_account_id = await interactForConnectedAccount()
164
- args.params[paramToEdit] = connected_account_id
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 user_identity_id = await interactForUserIdentity()
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
- custom_metadata: CustomMetadata,
13
+ customMetadata: CustomMetadata,
14
14
  ) => {
15
- const updated_custom_metadata: UpdatedCustomMetadata = { ...custom_metadata }
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(updated_custom_metadata).length > 0) {
21
- Object.keys(updated_custom_metadata).forEach((key, index) => {
22
- output.info(`${index + 1}: ${key}: ${updated_custom_metadata[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
- updated_custom_metadata[newKey] = null
64
+ updatedCustomMetadata[newKey] = null
65
65
  } else {
66
- updated_custom_metadata[newKey] = newValue
66
+ updatedCustomMetadata[newKey] = newValue
67
67
  }
68
68
  }
69
69
  } else if (action === 'remove') {
70
- const { custom_key_to_remove } = await prompt({
70
+ const { customKeyToRemove } = await prompt({
71
71
  type: 'select',
72
- name: 'custom_key_to_remove',
72
+ name: 'customKeyToRemove',
73
73
  message: 'Choose a key-value pair to remove from params:',
74
- choices: Object.keys(updated_custom_metadata).map(
75
- (custom_metadata_key) => {
76
- return {
77
- title: `${custom_metadata_key}: ${updated_custom_metadata[custom_metadata_key]}`,
78
- value: custom_metadata_key,
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 (custom_key_to_remove) {
85
- delete custom_metadata[custom_key_to_remove]
82
+ if (customKeyToRemove) {
83
+ delete customMetadata[customKeyToRemove]
86
84
  }
87
85
  }
88
86
  } while (action !== 'done')
89
87
 
90
- return updated_custom_metadata
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 { use_remote_api_defs } = await prompt([
6
+ const { useRemoteApiDefs } = await prompt([
7
7
  {
8
8
  type: 'select',
9
- name: 'use_remote_api_defs',
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', use_remote_api_defs)
26
- getOutput().info(`Use remote API Definitions: ${use_remote_api_defs}`)
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(),
@@ -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.',
@@ -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
- string: ['code'],
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)
@@ -1,5 +1,5 @@
1
1
  // Versions are replaced with generated values when the package is packed.
2
- const seamapiCliVersion = '0.13.1'
2
+ const seamapiCliVersion = '0.15.0'
3
3
  const seamapiBlueprintVersion = '1.2.0'
4
4
 
5
5
  export { seamapiBlueprintVersion }