Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 0 additions & 48 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
findLocalCommand,
findLocalCommandTakingPositional,
} from 'lib/commands/registry.js'
import { getConfigStore } from 'lib/config/index.js'
import { getConfig } from 'lib/config/index.js'
import { type CliContext, resolveAuth } from 'lib/context.js'
import { tokenEnvVar } from 'lib/env.js'
import { reportErrorAndExit } from 'lib/errors.js'
Expand All @@ -41,7 +41,7 @@ import { renderHelp } from 'lib/render/help.js'
import seamapiCliVersion from 'lib/version.js'

async function cli(args: ParsedArgs, argv: string[]) {
const config = getConfigStore()
const config = getConfig()
const output = getOutput()

// Scoped to this one command, and read wherever auth resolves, so they are
Expand Down Expand Up @@ -158,7 +158,7 @@ async function cli(args: ParsedArgs, argv: string[]) {
}

const useRemoteApiDefs =
args['remote_api_defs'] ?? config.get('use_remote_api_defs')
args['remote_api_defs'] ?? config.getUseRemoteApiDefs()

const blueprint = await getApiBlueprint({
useRemoteDefinitions: useRemoteApiDefs ?? false,
Expand Down
44 changes: 15 additions & 29 deletions src/lib/auth/operations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ConfigStore, getConfigStore } from 'lib/config/index.js'
import { type CliConfig, getConfig } from 'lib/config/index.js'
import { type AuthContext, resolveAuth } from 'lib/context.js'
import {
assertEnvVarUnset,
Expand Down Expand Up @@ -57,7 +57,7 @@ export const assertMutable = (
*/
export const login = async (
token: string,
config: ConfigStore = getConfigStore(),
config: CliConfig = getConfig(),
validate: typeof validateToken = validateToken,
): Promise<void> => {
const auth = resolveAuth(config)
Expand All @@ -68,30 +68,27 @@ export const login = async (

await validate(token, auth.workspaceId ?? undefined)

config.set(`${auth.endpoint}.pat`, token)
config.setToken(auth.endpoint, token)
// The selection belongs to whoever was logged in before.
config.delete('current_workspace_id')
config.unsetWorkspace()
}

/** Store the token for the current endpoint, e.g., one just prompted for. */
export const storeToken = (
token: string,
config: ConfigStore = getConfigStore(),
config: CliConfig = getConfig(),
): void => {
const auth = resolveAuth(config)
assertMutable(auth, 'token', 'log in')
config.set(`${auth.endpoint}.pat`, token)
config.setToken(auth.endpoint, token)
}

/** Remove the stored token and workspace selection. */
export const logout = (config: ConfigStore = getConfigStore()): void => {
export const logout = (config: CliConfig = getConfig()): void => {
const auth = resolveAuth(config)
assertMutable(auth, 'token', 'log out')
config.delete(`${auth.endpoint}.pat`)
// Configs written before tokens were stored per endpoint may still hold an
// un-namespaced token, so drop that too.
config.delete('pat')
config.delete('current_workspace_id')
config.unsetToken(auth.endpoint)
config.unsetWorkspace()
}

/**
Expand All @@ -101,36 +98,25 @@ export const logout = (config: ConfigStore = getConfigStore()): void => {
*/
export const selectEndpoint = (
endpoint: string,
config: ConfigStore = getConfigStore(),
config: CliConfig = getConfig(),
): void => {
assertMutable(resolveAuth(config), 'endpoint', 'select an endpoint')
storeEndpoint(endpoint, config)
config.setEndpoint(endpoint)
}

/** Store the workspace requests are made against. */
export const selectWorkspace = (
workspaceId: string,
config: ConfigStore = getConfigStore(),
config: CliConfig = getConfig(),
): void => {
assertMutable(resolveAuth(config), 'workspaceId', 'select a workspace')
config.set('current_workspace_id', workspaceId)
config.setWorkspace(workspaceId)
}

/** Store whether API definitions come from the endpoint instead of npm. */
export const setUseRemoteApiDefs = (
useRemoteApiDefs: boolean,
config: ConfigStore = getConfigStore(),
config: CliConfig = getConfig(),
): void => {
config.set('use_remote_api_defs', useRemoteApiDefs)
}

/**
* Write the endpoint, dropping what belonged to the previous one: the
* workspace selection, and any value left under the legacy `server` key that
* {@link resolveAuth} would otherwise still fall back to.
*/
const storeEndpoint = (endpoint: string, config: ConfigStore): void => {
config.set('endpoint', endpoint)
config.delete('server')
config.delete('current_workspace_id')
config.setUseRemoteApiDefs(useRemoteApiDefs)
}
5 changes: 2 additions & 3 deletions src/lib/blueprint/source-npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { join } from 'node:path'
import { pathToFileURL } from 'node:url'

import type { Blueprint, TypesModuleInput } from '@seamapi/blueprint'
import envPaths from 'env-paths'
import { extract } from 'tar'

import { rootPaths } from 'lib/config/index.js'
import { withLoading } from 'lib/output/with-loading.js'

import {
Expand Down Expand Up @@ -38,8 +38,7 @@ export const getBlueprint = async (
options: GetBlueprintOptions = {},
): Promise<Blueprint> => {
const update = options.update ?? false
const cacheDirectory =
options.cacheDirectory ?? envPaths('seam', { suffix: '' }).cache
const cacheDirectory = options.cacheDirectory ?? rootPaths.cache
const cacheFile = getCacheFile(cacheDirectory)
const blueprintVersion = await getBlueprintVersion()

Expand Down
83 changes: 83 additions & 0 deletions src/lib/config/cli-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { ConfigStore } from './config-store.js'

/** Tokens are stored per endpoint, e.g. `https://connect.getseam.com.pat`. */
const tokenKey = (endpoint: string): `${string}.pat` => `${endpoint}.pat`

/**
* The CLI's configuration, in the terms the CLI thinks in. Keys are named
* here alone: a caller says what it wants stored, not where it goes.
*/
export interface CliConfig {
readonly path: string
getEndpoint: () => string | null
setEndpoint: (endpoint: string) => void
getToken: (endpoint: string) => string | null
setToken: (endpoint: string, token: string) => void
unsetToken: (endpoint: string) => void
getWorkspace: () => string | null
setWorkspace: (workspaceId: string) => void
unsetWorkspace: () => void
getUseRemoteApiDefs: () => boolean | null
setUseRemoteApiDefs: (useRemoteApiDefs: boolean) => void
}

export const createCliConfig = (store: ConfigStore): CliConfig => ({
get path() {
return store.path
},

getEndpoint: () =>
// `server` is what an older CLI called the endpoint.
readString(store.get('endpoint')) ?? readString(store.get('server')),

/**
* Store the endpoint, dropping what belonged to the previous one: the
* workspace selection, and any value left under the legacy key that
* {@link CliConfig.getEndpoint} would otherwise still fall back to.
*/
setEndpoint: (endpoint) => {
store.set('endpoint', endpoint)
store.delete('server')
store.delete('current_workspace_id')
},

getToken: (endpoint) => readString(store.get(tokenKey(endpoint))),

setToken: (endpoint, token) => {
store.set(tokenKey(endpoint), token)
},

unsetToken: (endpoint) => {
store.delete(tokenKey(endpoint))
// Configs written before tokens were stored per endpoint may still hold
// an un-namespaced one.
store.delete('pat')
},

getWorkspace: () => readString(store.get('current_workspace_id')),

setWorkspace: (workspaceId) => {
store.set('current_workspace_id', workspaceId)
},

unsetWorkspace: () => {
store.delete('current_workspace_id')
},

getUseRemoteApiDefs: () => {
const useRemoteApiDefs = store.get('use_remote_api_defs')
return typeof useRemoteApiDefs === 'boolean' ? useRemoteApiDefs : null
},

setUseRemoteApiDefs: (useRemoteApiDefs) => {
store.set('use_remote_api_defs', useRemoteApiDefs)
},
})

const readString = (value: unknown): string | null => {
if (typeof value !== 'string') return null

const trimmedValue = value.trim()

return trimmedValue === '' ? null : trimmedValue
}
Loading
Loading