@seamapi/cli 0.8.0 → 0.10.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 +25 -6
- package/bin/cli.js +61 -15
- package/bin/cli.js.map +1 -1
- package/lib/blueprint.d.ts +2 -1
- package/lib/blueprint.js +171 -26
- package/lib/blueprint.js.map +1 -1
- package/lib/get-api-blueprint.d.ts +4 -1
- package/lib/get-api-blueprint.js +3 -4
- package/lib/get-api-blueprint.js.map +1 -1
- package/lib/interact-for-blueprint-object.js +12 -2
- package/lib/interact-for-blueprint-object.js.map +1 -1
- package/lib/interact-for-command-selection.js +15 -0
- package/lib/interact-for-command-selection.js.map +1 -1
- package/lib/types.d.ts +2 -1
- package/lib/util/cli-args.d.ts +30 -0
- package/lib/util/cli-args.js +42 -0
- package/lib/util/cli-args.js.map +1 -0
- package/lib/version.d.ts +3 -1
- package/lib/version.js +4 -1
- package/lib/version.js.map +1 -1
- package/package.json +4 -3
- package/src/bin/cli.ts +88 -16
- package/src/lib/blueprint.ts +228 -28
- package/src/lib/get-api-blueprint.ts +7 -3
- package/src/lib/interact-for-blueprint-object.ts +18 -2
- package/src/lib/interact-for-command-selection.ts +21 -0
- package/src/lib/types.ts +2 -1
- package/src/lib/util/cli-args.ts +62 -0
- package/src/lib/version.ts +4 -1
|
@@ -5,15 +5,19 @@ import { getServer } from './get-server.js'
|
|
|
5
5
|
|
|
6
6
|
export type ApiBlueprint = Blueprint
|
|
7
7
|
|
|
8
|
+
export interface GetApiBlueprintOptions {
|
|
9
|
+
update?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
export const getApiBlueprint = async (
|
|
9
13
|
useRemoteDefinitions: boolean,
|
|
14
|
+
options: GetApiBlueprintOptions = {},
|
|
10
15
|
): Promise<ApiBlueprint> => {
|
|
11
16
|
// Remote definitions describe whatever the server is currently running, so
|
|
12
|
-
// build them directly from the server's OpenAPI document.
|
|
13
|
-
// does not load @seamapi/types.
|
|
17
|
+
// build them directly from the server's OpenAPI document.
|
|
14
18
|
if (useRemoteDefinitions) return await createRemoteBlueprint()
|
|
15
19
|
|
|
16
|
-
return await getBlueprint()
|
|
20
|
+
return await getBlueprint(options)
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
const createRemoteBlueprint = async (): Promise<Blueprint> => {
|
|
@@ -12,6 +12,7 @@ import { interactForDevice } from './interact-for-device.js'
|
|
|
12
12
|
import { interactForTimestamp } from './interact-for-timestamp.js'
|
|
13
13
|
import { interactForUserIdentity } from './interact-for-user-identity.js'
|
|
14
14
|
import type { ContextHelpers } from './types.js'
|
|
15
|
+
import { NonInteractiveError, toArgName } from './util/cli-args.js'
|
|
15
16
|
import { ellipsis } from './util/ellipsis.js'
|
|
16
17
|
|
|
17
18
|
const ergonomicPropOrder = [
|
|
@@ -47,12 +48,28 @@ export const interactForBlueprintObject = async (
|
|
|
47
48
|
|
|
48
49
|
const haveAllRequiredParams = required.every((k) => args.params[k])
|
|
49
50
|
|
|
51
|
+
const cmdPath = `/${args.command.join('/').replace(/-/g, '_')}`
|
|
52
|
+
|
|
50
53
|
const should_auto_submit =
|
|
51
|
-
|
|
54
|
+
ctx.interactivity !== 'interactive' &&
|
|
55
|
+
haveAllRequiredParams &&
|
|
56
|
+
!args.isSubProperty
|
|
52
57
|
if (should_auto_submit) {
|
|
53
58
|
return args.params
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
if (ctx.interactivity === 'non-interactive') {
|
|
62
|
+
const missing = required.filter((k) => !args.params[k])
|
|
63
|
+
const target = args.isSubProperty ? `"${args.subPropertyPath}"` : cmdPath
|
|
64
|
+
throw new NonInteractiveError(
|
|
65
|
+
missing.length > 0
|
|
66
|
+
? `Missing required ${
|
|
67
|
+
missing.length === 1 ? 'parameter' : 'parameters'
|
|
68
|
+
} for ${target}: ${missing.map(toArgName).join(' ')}`
|
|
69
|
+
: `Cannot prompt for ${target} in non-interactive mode`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
const propSortScore = (prop: string) => {
|
|
57
74
|
if (required.includes(prop)) return 100 - ergonomicPropOrder.indexOf(prop)
|
|
58
75
|
if (args.params[prop] !== undefined) {
|
|
@@ -61,7 +78,6 @@ export const interactForBlueprintObject = async (
|
|
|
61
78
|
return ergonomicPropOrder.indexOf(prop)
|
|
62
79
|
}
|
|
63
80
|
|
|
64
|
-
const cmdPath = `/${args.command.join('/').replace(/-/g, '_')}`
|
|
65
81
|
const parameterSelectionMessage = args.isSubProperty
|
|
66
82
|
? `Editing "${args.subPropertyPath}"`
|
|
67
83
|
: `[${cmdPath}] Parameters`
|
|
@@ -3,6 +3,7 @@ import { isDeepStrictEqual as isEqual } from 'node:util'
|
|
|
3
3
|
import prompts from 'prompts'
|
|
4
4
|
|
|
5
5
|
import type { ContextHelpers } from './types.js'
|
|
6
|
+
import { NonInteractiveError } from './util/cli-args.js'
|
|
6
7
|
|
|
7
8
|
const uniqBy = <T>(items: T[], keyOf: (item: T) => unknown): T[] => {
|
|
8
9
|
const seen = new Set<unknown>()
|
|
@@ -64,6 +65,26 @@ export async function interactForCommandSelection(
|
|
|
64
65
|
return commandPath
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
if (helpers.interactivity === 'non-interactive') {
|
|
69
|
+
// The command path is itself a command, so call it directly rather than
|
|
70
|
+
// prompting to select one of its sub-commands.
|
|
71
|
+
if (possibleCommands.some((cmd) => cmd.length === commandPath.length)) {
|
|
72
|
+
return commandPath
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const subcommands = possibleCommands
|
|
76
|
+
.map((cmd) => cmd[commandPath.length])
|
|
77
|
+
.filter((subcommand) => subcommand != null)
|
|
78
|
+
.sort(ergonomicSort)
|
|
79
|
+
throw new NonInteractiveError(
|
|
80
|
+
`${
|
|
81
|
+
commandPath.length === 0
|
|
82
|
+
? 'Missing command'
|
|
83
|
+
: `Incomplete command "seam ${commandPath.join(' ')}"`
|
|
84
|
+
}: expected one of ${subcommands.join(', ')}`,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
// Add dynamic 'back' command for sub-commands to allow returning
|
|
68
89
|
// to previous level.
|
|
69
90
|
if (commandPath.length > 0) {
|
package/src/lib/types.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import parseArgs, { type ParsedArgs } from 'minimist'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How the CLI should behave when properties are not given as arguments.
|
|
5
|
+
*
|
|
6
|
+
* - `auto`: make the request as soon as every required property is given,
|
|
7
|
+
* otherwise prompt for what is missing. This is the default.
|
|
8
|
+
* - `interactive`: always prompt to review and edit properties, prefilled
|
|
9
|
+
* with the given arguments. Selected with `--interactive` or `-i`.
|
|
10
|
+
* - `non-interactive`: never prompt: anything missing is an error.
|
|
11
|
+
* Selected with `--non-interactive` or `-y`.
|
|
12
|
+
*/
|
|
13
|
+
export type Interactivity = 'auto' | 'interactive' | 'non-interactive'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Argument keys that select the interactivity
|
|
17
|
+
* and are therefore not command parameters.
|
|
18
|
+
*/
|
|
19
|
+
export const interactivityFlags: string[] = [
|
|
20
|
+
'non_interactive',
|
|
21
|
+
'y',
|
|
22
|
+
'interactive',
|
|
23
|
+
'i',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Thrown when the CLI needs input it cannot prompt for.
|
|
28
|
+
*/
|
|
29
|
+
export class NonInteractiveError extends Error {
|
|
30
|
+
override name = 'NonInteractiveError'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const parseCliArgs = (argv: string[]): ParsedArgs =>
|
|
34
|
+
parseArgs(argv, {
|
|
35
|
+
string: ['code'],
|
|
36
|
+
boolean: ['non-interactive', 'interactive'],
|
|
37
|
+
// Deliberately not aliased to -n, which is reserved for a future
|
|
38
|
+
// --dry-run flag.
|
|
39
|
+
alias: { 'non-interactive': 'y', interactive: 'i' },
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export const getInteractivity = (args: ParsedArgs): Interactivity => {
|
|
43
|
+
const isNonInteractive =
|
|
44
|
+
args['non_interactive'] === true || args['y'] === true
|
|
45
|
+
const isInteractive = args['interactive'] === true || args['i'] === true
|
|
46
|
+
|
|
47
|
+
if (isNonInteractive && isInteractive) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'The --interactive and --non-interactive flags cannot be used together',
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
if (isNonInteractive) return 'non-interactive'
|
|
53
|
+
if (isInteractive) return 'interactive'
|
|
54
|
+
return 'auto'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Render a parameter name as the argument used to set it,
|
|
59
|
+
* e.g., `device_id` as `--device-id`.
|
|
60
|
+
*/
|
|
61
|
+
export const toArgName = (parameterName: string): string =>
|
|
62
|
+
`--${parameterName.replace(/_/g, '-')}`
|
package/src/lib/version.ts
CHANGED