@seamapi/cli 0.22.0 → 0.24.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 +4 -4
- package/bin/cli.js +5 -5
- package/bin/cli.js.map +1 -1
- package/completions/seam.bash +1 -1
- package/completions/seam.fish +1 -1
- package/completions/seam.zsh +1 -1
- package/lib/args/parse.js +1 -1
- package/lib/args/parse.js.map +1 -1
- package/lib/auth/operations.d.ts +8 -8
- package/lib/auth/operations.js +16 -29
- package/lib/auth/operations.js.map +1 -1
- package/lib/blueprint/index.d.ts +3 -3
- package/lib/blueprint/index.js +4 -4
- package/lib/blueprint/index.js.map +1 -1
- package/lib/blueprint/source-npm.js +5 -5
- package/lib/blueprint/source-npm.js.map +1 -1
- package/lib/commands/api-command.js +1 -1
- package/lib/commands/local/completion.d.ts +3 -3
- package/lib/commands/local/completion.js +3 -3
- package/lib/commands/local/config-use-remote-schema.d.ts +2 -0
- package/lib/commands/local/config-use-remote-schema.js +20 -0
- package/lib/commands/local/config-use-remote-schema.js.map +1 -0
- package/lib/commands/local/wizard.d.ts +7 -0
- package/lib/commands/local/wizard.js +31 -0
- package/lib/commands/local/wizard.js.map +1 -1
- package/lib/commands/registry.js +2 -2
- package/lib/commands/registry.js.map +1 -1
- package/lib/commands/spec.d.ts +1 -1
- package/lib/commands/spec.js +6 -6
- package/lib/commands/spec.js.map +1 -1
- package/lib/config/cli-config.d.ts +19 -0
- package/lib/config/cli-config.js +54 -0
- package/lib/config/cli-config.js.map +1 -0
- package/lib/config/config-store.d.ts +8 -5
- package/lib/config/config-store.js +15 -13
- package/lib/config/config-store.js.map +1 -1
- package/lib/config/index.d.ts +3 -2
- package/lib/config/index.js +3 -2
- package/lib/config/index.js.map +1 -1
- package/lib/config/memory-config-store.d.ts +6 -0
- package/lib/config/memory-config-store.js +6 -0
- package/lib/config/memory-config-store.js.map +1 -1
- package/lib/context.d.ts +3 -3
- package/lib/context.js +6 -16
- package/lib/context.js.map +1 -1
- package/lib/interactions/endpoint-selection.js +2 -2
- package/lib/interactions/endpoint-selection.js.map +1 -1
- package/lib/interactions/index.d.ts +1 -1
- package/lib/interactions/index.js +1 -1
- package/lib/interactions/index.js.map +1 -1
- package/lib/interactions/login.js +2 -2
- package/lib/interactions/login.js.map +1 -1
- package/lib/interactions/use-remote-schema.d.ts +1 -0
- package/lib/interactions/use-remote-schema.js +21 -0
- package/lib/interactions/use-remote-schema.js.map +1 -0
- package/lib/interactions/workspace-id.js +2 -2
- package/lib/interactions/workspace-id.js.map +1 -1
- package/lib/render/completion/index.d.ts +1 -1
- package/lib/render/completion/index.js +2 -2
- package/lib/render/completion/render-bash.js +1 -1
- package/lib/render/completion/render-fish.js +1 -1
- package/lib/render/completion/render-zsh.js +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
- package/src/bin/cli.ts +5 -6
- package/src/lib/args/parse.ts +1 -1
- package/src/lib/auth/operations.ts +18 -32
- package/src/lib/blueprint/index.ts +6 -6
- package/src/lib/blueprint/source-npm.ts +5 -6
- package/src/lib/commands/api-command.ts +1 -1
- package/src/lib/commands/local/completion.ts +3 -3
- package/src/lib/commands/local/{config-use-remote-api-defs.ts → config-use-remote-schema.ts} +6 -6
- package/src/lib/commands/local/wizard.ts +48 -0
- package/src/lib/commands/registry.ts +2 -2
- package/src/lib/commands/spec.ts +6 -6
- package/src/lib/config/cli-config.ts +87 -0
- package/src/lib/config/config-store.ts +16 -13
- package/src/lib/config/index.ts +9 -4
- package/src/lib/config/memory-config-store.ts +9 -0
- package/src/lib/context.ts +7 -24
- package/src/lib/interactions/endpoint-selection.ts +2 -2
- package/src/lib/interactions/index.ts +1 -1
- package/src/lib/interactions/login.ts +2 -2
- package/src/lib/interactions/use-remote-schema.ts +22 -0
- package/src/lib/interactions/workspace-id.ts +2 -2
- package/src/lib/render/completion/index.ts +2 -2
- package/src/lib/render/completion/render-bash.ts +1 -1
- package/src/lib/render/completion/render-fish.ts +1 -1
- package/src/lib/render/completion/render-zsh.ts +1 -1
- package/src/lib/version.ts +1 -1
- package/lib/commands/local/config-use-remote-api-defs.d.ts +0 -2
- package/lib/commands/local/config-use-remote-api-defs.js +0 -20
- package/lib/commands/local/config-use-remote-api-defs.js.map +0 -1
- package/lib/interactions/use-remote-api-defs.d.ts +0 -1
- package/lib/interactions/use-remote-api-defs.js +0 -21
- package/lib/interactions/use-remote-api-defs.js.map +0 -1
- package/src/lib/interactions/use-remote-api-defs.ts +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "A command-line interface (CLI) for interacting with the Seam API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@clack/prompts": "^1.7.0",
|
|
96
96
|
"@seamapi/blueprint": "1.5.0",
|
|
97
97
|
"@seamapi/http": "2.2.0",
|
|
98
|
-
"@seamapi/wizard": "0.
|
|
98
|
+
"@seamapi/wizard": "0.7.0",
|
|
99
99
|
"chalk": "^6.0.0",
|
|
100
100
|
"command-line-usage": "^7.0.4",
|
|
101
101
|
"configstore": "^8.0.0",
|
package/src/bin/cli.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
findLocalCommand,
|
|
22
22
|
findLocalCommandTakingPositional,
|
|
23
23
|
} from 'lib/commands/registry.js'
|
|
24
|
-
import {
|
|
24
|
+
import { getConfig } from 'lib/config/index.js'
|
|
25
25
|
import { type CliContext, resolveAuth } from 'lib/context.js'
|
|
26
26
|
import { tokenEnvVar } from 'lib/env.js'
|
|
27
27
|
import { reportErrorAndExit } from 'lib/errors.js'
|
|
@@ -41,7 +41,7 @@ import { renderHelp } from 'lib/render/help.js'
|
|
|
41
41
|
import seamapiCliVersion from 'lib/version.js'
|
|
42
42
|
|
|
43
43
|
async function cli(args: ParsedArgs, argv: string[]) {
|
|
44
|
-
const config =
|
|
44
|
+
const config = getConfig()
|
|
45
45
|
const output = getOutput()
|
|
46
46
|
|
|
47
47
|
// Scoped to this one command, and read wherever auth resolves, so they are
|
|
@@ -63,7 +63,7 @@ async function cli(args: ParsedArgs, argv: string[]) {
|
|
|
63
63
|
|
|
64
64
|
const helpFlag = args['help'] ?? args['h']
|
|
65
65
|
if (helpFlag != null) {
|
|
66
|
-
// Help comes from the cached API
|
|
66
|
+
// Help comes from the cached API schema so that it works without
|
|
67
67
|
// logging in, and offline once the cache is warm.
|
|
68
68
|
const cachedBlueprint = await getApiBlueprint({ update })
|
|
69
69
|
const { spec } = buildRegistry(cachedBlueprint)
|
|
@@ -157,11 +157,10 @@ async function cli(args: ParsedArgs, argv: string[]) {
|
|
|
157
157
|
return
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
const
|
|
161
|
-
args['remote_api_defs'] ?? config.get('use_remote_api_defs')
|
|
160
|
+
const useRemoteSchema = args['remote_schema'] ?? config.getUseRemoteSchema()
|
|
162
161
|
|
|
163
162
|
const blueprint = await getApiBlueprint({
|
|
164
|
-
|
|
163
|
+
useRemoteSchema: useRemoteSchema ?? false,
|
|
165
164
|
update,
|
|
166
165
|
})
|
|
167
166
|
|
package/src/lib/args/parse.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CliConfig, getConfig } from 'lib/config/index.js'
|
|
2
2
|
import { type AuthContext, resolveAuth } from 'lib/context.js'
|
|
3
3
|
import {
|
|
4
4
|
assertEnvVarUnset,
|
|
@@ -57,7 +57,7 @@ export const assertMutable = (
|
|
|
57
57
|
*/
|
|
58
58
|
export const login = async (
|
|
59
59
|
token: string,
|
|
60
|
-
config:
|
|
60
|
+
config: CliConfig = getConfig(),
|
|
61
61
|
validate: typeof validateToken = validateToken,
|
|
62
62
|
): Promise<void> => {
|
|
63
63
|
const auth = resolveAuth(config)
|
|
@@ -68,30 +68,27 @@ export const login = async (
|
|
|
68
68
|
|
|
69
69
|
await validate(token, auth.workspaceId ?? undefined)
|
|
70
70
|
|
|
71
|
-
config.
|
|
71
|
+
config.setToken(auth.endpoint, token)
|
|
72
72
|
// The selection belongs to whoever was logged in before.
|
|
73
|
-
config.
|
|
73
|
+
config.unsetWorkspace()
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** Store the token for the current endpoint, e.g., one just prompted for. */
|
|
77
77
|
export const storeToken = (
|
|
78
78
|
token: string,
|
|
79
|
-
config:
|
|
79
|
+
config: CliConfig = getConfig(),
|
|
80
80
|
): void => {
|
|
81
81
|
const auth = resolveAuth(config)
|
|
82
82
|
assertMutable(auth, 'token', 'log in')
|
|
83
|
-
config.
|
|
83
|
+
config.setToken(auth.endpoint, token)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** Remove the stored token and workspace selection. */
|
|
87
|
-
export const logout = (config:
|
|
87
|
+
export const logout = (config: CliConfig = getConfig()): void => {
|
|
88
88
|
const auth = resolveAuth(config)
|
|
89
89
|
assertMutable(auth, 'token', 'log out')
|
|
90
|
-
config.
|
|
91
|
-
|
|
92
|
-
// un-namespaced token, so drop that too.
|
|
93
|
-
config.delete('pat')
|
|
94
|
-
config.delete('current_workspace_id')
|
|
90
|
+
config.unsetToken(auth.endpoint)
|
|
91
|
+
config.unsetWorkspace()
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
/**
|
|
@@ -101,36 +98,25 @@ export const logout = (config: ConfigStore = getConfigStore()): void => {
|
|
|
101
98
|
*/
|
|
102
99
|
export const selectEndpoint = (
|
|
103
100
|
endpoint: string,
|
|
104
|
-
config:
|
|
101
|
+
config: CliConfig = getConfig(),
|
|
105
102
|
): void => {
|
|
106
103
|
assertMutable(resolveAuth(config), 'endpoint', 'select an endpoint')
|
|
107
|
-
|
|
104
|
+
config.setEndpoint(endpoint)
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
/** Store the workspace requests are made against. */
|
|
111
108
|
export const selectWorkspace = (
|
|
112
109
|
workspaceId: string,
|
|
113
|
-
config:
|
|
110
|
+
config: CliConfig = getConfig(),
|
|
114
111
|
): void => {
|
|
115
112
|
assertMutable(resolveAuth(config), 'workspaceId', 'select a workspace')
|
|
116
|
-
config.
|
|
113
|
+
config.setWorkspace(workspaceId)
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
/** Store whether API
|
|
120
|
-
export const
|
|
121
|
-
|
|
122
|
-
config:
|
|
116
|
+
/** Store whether the API schema comes from the endpoint instead of npm. */
|
|
117
|
+
export const setUseRemoteSchema = (
|
|
118
|
+
useRemoteSchema: boolean,
|
|
119
|
+
config: CliConfig = getConfig(),
|
|
123
120
|
): void => {
|
|
124
|
-
config.
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Write the endpoint, dropping what belonged to the previous one: the
|
|
129
|
-
* workspace selection, and any value left under the legacy `server` key that
|
|
130
|
-
* {@link resolveAuth} would otherwise still fall back to.
|
|
131
|
-
*/
|
|
132
|
-
const storeEndpoint = (endpoint: string, config: ConfigStore): void => {
|
|
133
|
-
config.set('endpoint', endpoint)
|
|
134
|
-
config.delete('server')
|
|
135
|
-
config.delete('current_workspace_id')
|
|
121
|
+
config.setUseRemoteSchema(useRemoteSchema)
|
|
136
122
|
}
|
|
@@ -10,18 +10,18 @@ export interface GetApiBlueprintOptions {
|
|
|
10
10
|
* Build from the OpenAPI document the configured endpoint is currently
|
|
11
11
|
* running, instead of the published npm types.
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
/** Force an update of the cached Seam API
|
|
13
|
+
useRemoteSchema?: boolean
|
|
14
|
+
/** Force an update of the cached Seam API schema. */
|
|
15
15
|
update?: boolean
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const getApiBlueprint = async ({
|
|
19
|
-
|
|
19
|
+
useRemoteSchema = false,
|
|
20
20
|
update = false,
|
|
21
21
|
}: GetApiBlueprintOptions = {}): Promise<ApiBlueprint> => {
|
|
22
|
-
//
|
|
23
|
-
// build
|
|
24
|
-
if (
|
|
22
|
+
// The remote schema describes whatever the endpoint is currently running, so
|
|
23
|
+
// build it directly from that endpoint's OpenAPI document.
|
|
24
|
+
if (useRemoteSchema) return await createRemoteBlueprint()
|
|
25
25
|
|
|
26
26
|
return await getBlueprint({ update })
|
|
27
27
|
}
|
|
@@ -3,9 +3,9 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { pathToFileURL } from 'node:url'
|
|
4
4
|
|
|
5
5
|
import type { Blueprint, TypesModuleInput } from '@seamapi/blueprint'
|
|
6
|
-
import envPaths from 'env-paths'
|
|
7
6
|
import { extract } from 'tar'
|
|
8
7
|
|
|
8
|
+
import { rootPaths } from 'lib/config/index.js'
|
|
9
9
|
import { withLoading } from 'lib/output/with-loading.js'
|
|
10
10
|
|
|
11
11
|
import {
|
|
@@ -38,8 +38,7 @@ export const getBlueprint = async (
|
|
|
38
38
|
options: GetBlueprintOptions = {},
|
|
39
39
|
): Promise<Blueprint> => {
|
|
40
40
|
const update = options.update ?? false
|
|
41
|
-
const cacheDirectory =
|
|
42
|
-
options.cacheDirectory ?? envPaths('seam', { suffix: '' }).cache
|
|
41
|
+
const cacheDirectory = options.cacheDirectory ?? rootPaths.cache
|
|
43
42
|
const cacheFile = getCacheFile(cacheDirectory)
|
|
44
43
|
const blueprintVersion = await getBlueprintVersion()
|
|
45
44
|
|
|
@@ -59,7 +58,7 @@ export const getBlueprint = async (
|
|
|
59
58
|
// over failing, unless an update was explicitly requested.
|
|
60
59
|
if (cache != null && !update) return cache.blueprint
|
|
61
60
|
throw new Error(
|
|
62
|
-
`Could not check for Seam API
|
|
61
|
+
`Could not check for Seam API schema updates: ${toErrorMessage(error)}`,
|
|
63
62
|
)
|
|
64
63
|
}
|
|
65
64
|
|
|
@@ -74,13 +73,13 @@ export const getBlueprint = async (
|
|
|
74
73
|
let blueprint: Blueprint
|
|
75
74
|
try {
|
|
76
75
|
blueprint = await withLoading(
|
|
77
|
-
`Downloading Seam API
|
|
76
|
+
`Downloading Seam API schema (${typesPackageName}@${manifest.version})`,
|
|
78
77
|
async () => await generateBlueprint(manifest, cacheDirectory),
|
|
79
78
|
)
|
|
80
79
|
} catch (error) {
|
|
81
80
|
if (cache != null && !update) return cache.blueprint
|
|
82
81
|
throw new Error(
|
|
83
|
-
`Could not update Seam API
|
|
82
|
+
`Could not update Seam API schema: ${toErrorMessage(error)}`,
|
|
84
83
|
)
|
|
85
84
|
}
|
|
86
85
|
|
|
@@ -94,7 +94,7 @@ export const executeApiCommand = async (
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Per-endpoint request policy that is not derivable from the API
|
|
97
|
-
*
|
|
97
|
+
* schema. Keep this table small and explicit.
|
|
98
98
|
*/
|
|
99
99
|
const applyEndpointDefaults = (
|
|
100
100
|
path: string[],
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
/**
|
|
11
11
|
* Print the completion script for a shell.
|
|
12
12
|
*
|
|
13
|
-
* Completions always come from the cached API
|
|
14
|
-
* be generated without logging in. They may lag the
|
|
15
|
-
* Seam when config use-remote-
|
|
13
|
+
* Completions always come from the cached API schema so that they can
|
|
14
|
+
* be generated without logging in. They may lag the schema served by
|
|
15
|
+
* Seam when config use-remote-schema is enabled.
|
|
16
16
|
*
|
|
17
17
|
* Called by the entry before any auth or blueprint context exists, and by
|
|
18
18
|
* the registered command's executor — one implementation for both.
|
package/src/lib/commands/local/{config-use-remote-api-defs.ts → config-use-remote-schema.ts}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { Command } from 'lib/commands/registry.js'
|
|
2
2
|
import { NonInteractiveError } from 'lib/errors.js'
|
|
3
|
-
import {
|
|
3
|
+
import { interactForUseRemoteSchema } from 'lib/interactions/index.js'
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const configUseRemoteSchemaCommand: Command = {
|
|
6
6
|
definition: {
|
|
7
|
-
path: ['config', 'use-remote-
|
|
7
|
+
path: ['config', 'use-remote-schema'],
|
|
8
8
|
kind: 'cli',
|
|
9
|
-
title: 'Choose whether to use the
|
|
9
|
+
title: 'Choose whether to use the schema served by Seam.',
|
|
10
10
|
description: '',
|
|
11
11
|
flags: [],
|
|
12
12
|
},
|
|
@@ -14,10 +14,10 @@ export const configUseRemoteApiDefsCommand: Command = {
|
|
|
14
14
|
execute: async (_invocation, ctx) => {
|
|
15
15
|
if (ctx.interactivity === 'non-interactive') {
|
|
16
16
|
throw new NonInteractiveError(
|
|
17
|
-
'Cannot select whether to use remote
|
|
17
|
+
'Cannot select whether to use the remote schema in non-interactive mode',
|
|
18
18
|
)
|
|
19
19
|
}
|
|
20
|
-
await
|
|
20
|
+
await interactForUseRemoteSchema()
|
|
21
21
|
return { kind: 'done' }
|
|
22
22
|
},
|
|
23
23
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { isApiKey } from '@seamapi/http/connect'
|
|
4
|
+
import type { StorageAdapter, WizardAdapter, WizardAuth } from '@seamapi/wizard'
|
|
5
|
+
import Configstore from 'configstore'
|
|
6
|
+
|
|
1
7
|
import type { Command } from 'lib/commands/registry.js'
|
|
8
|
+
import { type CliConfig, getConfig, rootPaths } from 'lib/config/index.js'
|
|
9
|
+
import { resolveAuth } from 'lib/context.js'
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* Run the Seam setup wizard.
|
|
@@ -11,6 +19,7 @@ export const runWizard = async (argv: string[]): Promise<void> => {
|
|
|
11
19
|
await wizard({
|
|
12
20
|
argv,
|
|
13
21
|
commandName: 'seam wizard',
|
|
22
|
+
adapter: createWizardAdapter(),
|
|
14
23
|
})
|
|
15
24
|
}
|
|
16
25
|
|
|
@@ -29,3 +38,42 @@ export const wizardCommand: Command = {
|
|
|
29
38
|
return { kind: 'done' }
|
|
30
39
|
},
|
|
31
40
|
}
|
|
41
|
+
|
|
42
|
+
const wizardFileName = 'wizard.json'
|
|
43
|
+
|
|
44
|
+
export const createWizardAdapter = ({
|
|
45
|
+
cliConfig = getConfig(),
|
|
46
|
+
configPath = join(rootPaths.config, wizardFileName),
|
|
47
|
+
statePath = join(rootPaths.log, wizardFileName),
|
|
48
|
+
}: {
|
|
49
|
+
cliConfig?: CliConfig
|
|
50
|
+
configPath?: string
|
|
51
|
+
statePath?: string
|
|
52
|
+
} = {}): WizardAdapter => ({
|
|
53
|
+
getAuth: async () => toWizardAuth(cliConfig),
|
|
54
|
+
config: createStorage(configPath),
|
|
55
|
+
state: createStorage(statePath),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Only a workspace-scoped key may go in a project, so a personal access
|
|
59
|
+
// token is not handed over at all.
|
|
60
|
+
const toWizardAuth = (cliConfig: CliConfig): WizardAuth => {
|
|
61
|
+
const { endpoint, token, workspaceId } = resolveAuth(cliConfig)
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
endpoint,
|
|
65
|
+
apiKey: token != null && isApiKey(token) ? token : null,
|
|
66
|
+
workspaceId,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const createStorage = (path: string): StorageAdapter => {
|
|
71
|
+
const store = new Configstore('seam-cli', undefined, { configPath: path })
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
get: async (key) => store.get(key),
|
|
75
|
+
set: async (key, value) => {
|
|
76
|
+
store.set(key, value)
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -7,7 +7,7 @@ import type { CliContext } from 'lib/context.js'
|
|
|
7
7
|
import { executeApiCommand } from './api-command.js'
|
|
8
8
|
import { createCompletionCommands } from './local/completion.js'
|
|
9
9
|
import { configRevealLocationCommand } from './local/config-reveal-location.js'
|
|
10
|
-
import {
|
|
10
|
+
import { configUseRemoteSchemaCommand } from './local/config-use-remote-schema.js'
|
|
11
11
|
import { healthCommand } from './local/health.js'
|
|
12
12
|
import { loginCommand } from './local/login.js'
|
|
13
13
|
import { logoutCommand } from './local/logout.js'
|
|
@@ -73,7 +73,7 @@ const completionCommands = createCompletionCommands(
|
|
|
73
73
|
export const localCommands: Command[] = [
|
|
74
74
|
...completionCommands,
|
|
75
75
|
configRevealLocationCommand,
|
|
76
|
-
|
|
76
|
+
configUseRemoteSchemaCommand,
|
|
77
77
|
healthCommand,
|
|
78
78
|
loginCommand,
|
|
79
79
|
logoutCommand,
|
package/src/lib/commands/spec.ts
CHANGED
|
@@ -114,9 +114,9 @@ export const globalFlags: CommandFlag[] = [
|
|
|
114
114
|
isRequired: false,
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
long: 'remote-
|
|
117
|
+
long: 'remote-schema',
|
|
118
118
|
short: null,
|
|
119
|
-
description: 'Use the
|
|
119
|
+
description: 'Use the schema served by the Seam API.',
|
|
120
120
|
values: [],
|
|
121
121
|
takesValue: false,
|
|
122
122
|
isRequired: false,
|
|
@@ -124,7 +124,7 @@ export const globalFlags: CommandFlag[] = [
|
|
|
124
124
|
{
|
|
125
125
|
long: 'update',
|
|
126
126
|
short: null,
|
|
127
|
-
description: 'Force an update of the cached Seam API
|
|
127
|
+
description: 'Force an update of the cached Seam API schema.',
|
|
128
128
|
values: [],
|
|
129
129
|
takesValue: false,
|
|
130
130
|
isRequired: false,
|
|
@@ -156,7 +156,7 @@ export const flagTokens = (flag: CommandFlag): string[] => {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* Derive the command spec from the API
|
|
159
|
+
* Derive the command spec from the API schema, merged with the commands
|
|
160
160
|
* the CLI declares itself (see `commands/registry.ts`, the single source of
|
|
161
161
|
* those declarations).
|
|
162
162
|
*/
|
|
@@ -251,7 +251,7 @@ const toFlagValues = (parameter: Parameter): string[] => {
|
|
|
251
251
|
|
|
252
252
|
/**
|
|
253
253
|
* Whether a word is safe to write into a shell script. Command, flag, and
|
|
254
|
-
* enum names come from the API
|
|
254
|
+
* enum names come from the API schema and are embedded unquoted or
|
|
255
255
|
* single-quoted in completion scripts, so never emit one that a shell could
|
|
256
256
|
* read as syntax.
|
|
257
257
|
*/
|
|
@@ -298,7 +298,7 @@ const toCommandGroups = (commands: CommandDefinition[]): CommandGroup[] => {
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
// Groups have no description of their own in the API
|
|
301
|
+
// Groups have no description of their own in the API schema, so name
|
|
302
302
|
// the commands they hold instead. Leave the list whole: help wraps it, and
|
|
303
303
|
// completion shortens it to fit a menu column.
|
|
304
304
|
const summarizeGroup = (key: string): string =>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ConfigStore } from './config-store.js'
|
|
2
|
+
|
|
3
|
+
/** Tokens are stored per endpoint, e.g. `https://connect.getseam.com.pat`. */
|
|
4
|
+
const tokenKey = (endpoint: string): `${string}.pat` => `${endpoint}.pat`
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The CLI's configuration, in the terms the CLI thinks in. Keys are named
|
|
8
|
+
* here alone: a caller says what it wants stored, not where it goes.
|
|
9
|
+
*/
|
|
10
|
+
export interface CliConfig {
|
|
11
|
+
readonly path: string
|
|
12
|
+
getEndpoint: () => string | null
|
|
13
|
+
setEndpoint: (endpoint: string) => void
|
|
14
|
+
getToken: (endpoint: string) => string | null
|
|
15
|
+
setToken: (endpoint: string, token: string) => void
|
|
16
|
+
unsetToken: (endpoint: string) => void
|
|
17
|
+
getWorkspace: () => string | null
|
|
18
|
+
setWorkspace: (workspaceId: string) => void
|
|
19
|
+
unsetWorkspace: () => void
|
|
20
|
+
getUseRemoteSchema: () => boolean | null
|
|
21
|
+
setUseRemoteSchema: (useRemoteSchema: boolean) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const createCliConfig = (store: ConfigStore): CliConfig => ({
|
|
25
|
+
get path() {
|
|
26
|
+
return store.path
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
getEndpoint: () =>
|
|
30
|
+
// `server` is what an older CLI called the endpoint.
|
|
31
|
+
readString(store.get('endpoint')) ?? readString(store.get('server')),
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Store the endpoint, dropping what belonged to the previous one: the
|
|
35
|
+
* workspace selection, and any value left under the legacy key that
|
|
36
|
+
* {@link CliConfig.getEndpoint} would otherwise still fall back to.
|
|
37
|
+
*/
|
|
38
|
+
setEndpoint: (endpoint) => {
|
|
39
|
+
store.set('endpoint', endpoint)
|
|
40
|
+
store.delete('server')
|
|
41
|
+
store.delete('current_workspace_id')
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
getToken: (endpoint) => readString(store.get(tokenKey(endpoint))),
|
|
45
|
+
|
|
46
|
+
setToken: (endpoint, token) => {
|
|
47
|
+
store.set(tokenKey(endpoint), token)
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
unsetToken: (endpoint) => {
|
|
51
|
+
store.delete(tokenKey(endpoint))
|
|
52
|
+
// Configs written before tokens were stored per endpoint may still hold
|
|
53
|
+
// an un-namespaced one.
|
|
54
|
+
store.delete('pat')
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
getWorkspace: () => readString(store.get('current_workspace_id')),
|
|
58
|
+
|
|
59
|
+
setWorkspace: (workspaceId) => {
|
|
60
|
+
store.set('current_workspace_id', workspaceId)
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
unsetWorkspace: () => {
|
|
64
|
+
store.delete('current_workspace_id')
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
getUseRemoteSchema: () => {
|
|
68
|
+
// `use_remote_api_defs` is what an older CLI called the same setting.
|
|
69
|
+
const useRemoteSchema =
|
|
70
|
+
store.get('use_remote_schema') ?? store.get('use_remote_api_defs')
|
|
71
|
+
return typeof useRemoteSchema === 'boolean' ? useRemoteSchema : null
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/** Store the setting, dropping any value left under the legacy key. */
|
|
75
|
+
setUseRemoteSchema: (useRemoteSchema) => {
|
|
76
|
+
store.set('use_remote_schema', useRemoteSchema)
|
|
77
|
+
store.delete('use_remote_api_defs')
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const readString = (value: unknown): string | null => {
|
|
82
|
+
if (typeof value !== 'string') return null
|
|
83
|
+
|
|
84
|
+
const trimmedValue = value.trim()
|
|
85
|
+
|
|
86
|
+
return trimmedValue === '' ? null : trimmedValue
|
|
87
|
+
}
|
|
@@ -3,12 +3,15 @@ import { join } from 'node:path'
|
|
|
3
3
|
import Configstore from 'configstore'
|
|
4
4
|
import envPaths from 'env-paths'
|
|
5
5
|
|
|
6
|
+
import { type CliConfig, createCliConfig } from './cli-config.js'
|
|
6
7
|
import { migrateConfigStore } from './migrate.js'
|
|
7
8
|
import { isStateKey, mergeConfig, splitConfig } from './values.js'
|
|
8
9
|
|
|
9
10
|
const configFileName = 'cli.json'
|
|
10
11
|
const legacyConfigStoreId = 'seam-cli'
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
/** Every directory the CLI keeps files in, and what it mounts keeps its own. */
|
|
14
|
+
export const rootPaths = envPaths('seam', { suffix: '' })
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* What a config store can do, regardless of where it keeps the values.
|
|
@@ -28,21 +31,21 @@ export interface ConfigStore {
|
|
|
28
31
|
clear: () => void
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
let
|
|
34
|
+
let config: CliConfig | null = null
|
|
32
35
|
|
|
33
|
-
export const
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
+
export const getConfig = (): CliConfig => {
|
|
37
|
+
config ??= createCliConfig(createConfigStore())
|
|
38
|
+
return config
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/** Replace the
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
+
/** Replace the config, e.g., with an in-memory one for a test. */
|
|
42
|
+
export const setConfig = (nextConfig: CliConfig): void => {
|
|
43
|
+
config = nextConfig
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
/** Drop the current
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
+
/** Drop the current config so the next read builds the real one. */
|
|
47
|
+
export const resetConfig = (): void => {
|
|
48
|
+
config = null
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
const createConfigStore = (): PersistentConfigStore => {
|
|
@@ -121,11 +124,11 @@ export class PersistentConfigStore implements ConfigStore {
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
const getConfigPath = (): string => {
|
|
124
|
-
return join(
|
|
127
|
+
return join(rootPaths.config, configFileName)
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
const getStateConfigPath = (): string => {
|
|
128
|
-
return join(
|
|
131
|
+
return join(rootPaths.log, configFileName)
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
package/src/lib/config/index.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
export { type CliConfig, createCliConfig } from './cli-config.js'
|
|
1
2
|
export {
|
|
2
3
|
type ConfigStore,
|
|
3
|
-
|
|
4
|
+
getConfig,
|
|
4
5
|
type PersistentConfigStore,
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
resetConfig,
|
|
7
|
+
rootPaths,
|
|
8
|
+
setConfig,
|
|
7
9
|
} from './config-store.js'
|
|
8
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
createMemoryConfig,
|
|
12
|
+
createMemoryConfigStore,
|
|
13
|
+
} from './memory-config-store.js'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CliConfig, createCliConfig } from './cli-config.js'
|
|
1
2
|
import type { ConfigStore } from './config-store.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -60,3 +61,11 @@ export class MemoryConfigStore implements ConfigStore {
|
|
|
60
61
|
export const createMemoryConfigStore = (
|
|
61
62
|
initialValues: Record<string, unknown> = {},
|
|
62
63
|
): ConfigStore => new MemoryConfigStore(initialValues)
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A config held in memory, for tests. Values may be seeded by key, e.g., to
|
|
67
|
+
* stand for a config an older CLI wrote.
|
|
68
|
+
*/
|
|
69
|
+
export const createMemoryConfig = (
|
|
70
|
+
initialValues: Record<string, unknown> = {},
|
|
71
|
+
): CliConfig => createCliConfig(createMemoryConfigStore(initialValues))
|