Skip to content

Commit

Permalink
refactor: tighten typing constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
Hweinstock committed Mar 7, 2025
1 parent 8abdea8 commit 655af91
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 31 deletions.
5 changes: 1 addition & 4 deletions packages/core/src/shared/clients/clientWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import globals from '../extensionGlobals'
import { AwsClient, AwsClientConstructor, AwsCommand, AwsCommandConstructor } from '../awsClientBuilderV3'
import { PaginationConfiguration, Paginator } from '@aws-sdk/types'
import { AsyncCollection, toCollection } from '../utilities/asyncCollection'
import { isDefined } from '../utilities/tsUtils'

type SDKPaginator<C, CommandInput extends object, CommandOutput extends object> = (
config: Omit<PaginationConfiguration, 'client'> & { client: C },
Expand Down Expand Up @@ -49,10 +50,6 @@ export abstract class ClientWrapper<C extends AwsClient> implements vscode.Dispo
.map((o) => o.filter(isDefined))

return collection

function isDefined<T>(i: T | undefined): i is T {
return i !== undefined
}
}

protected async getFirst<CommandInput extends object, CommandOutput extends object, Output extends object>(
Expand Down
95 changes: 70 additions & 25 deletions packages/core/src/shared/clients/codecatalystClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ import { ServiceConfigurationOptions } from 'aws-sdk/lib/service'
import { CancellationError, Timeout, waitTimeout, waitUntil } from '../utilities/timeoutUtils'
import { isUserCancelledError } from '../../shared/errors'
import { showMessageWithCancel } from '../utilities/messages'
import { assertHasProps, ClassToInterfaceType, hasProps, isNonNullable, RequiredProps } from '../utilities/tsUtils'
import {
assertHasProps,
ClassToInterfaceType,
hasProps,
isDefined,
isNonNullable,
RequiredProps,
} from '../utilities/tsUtils'
import { AsyncCollection, toCollection } from '../utilities/asyncCollection'
import { joinAll, pageableToCollection } from '../utilities/collectionUtils'
import { CodeCatalyst } from 'aws-sdk'
Expand All @@ -33,6 +40,7 @@ import {
CreateAccessTokenCommand,
CreateAccessTokenRequest,
CreateAccessTokenResponse,
DevEnvironmentRepositorySummary,
DevEnvironmentSummary,
GetDevEnvironmentCommand,
GetDevEnvironmentRequest,
Expand All @@ -49,7 +57,6 @@ import {
GetUserDetailsCommandOutput,
GetUserDetailsRequest,
ListDevEnvironmentsCommand,
ListDevEnvironmentSessionsResponse,
ListDevEnvironmentsRequest,
ListDevEnvironmentsResponse,
ListProjectsCommand,
Expand All @@ -58,6 +65,8 @@ import {
ListSpacesCommand,
ListSpacesRequest,
ListSpacesResponse,
PersistentStorage,
ProjectSummary,
SpaceSummary,
} from '@aws-sdk/client-codecatalyst'
import { truncateProps } from '../utilities/textUtilities'
Expand All @@ -68,6 +77,16 @@ import { AwsCommand } from '../awsClientBuilderV3'
import { ClientWrapper } from './clientWrapper'
import { StandardRetryStrategy } from '@smithy/util-retry'

const requiredDevEnvProps = [
'id',
'status',
'inactivityTimeoutMinutes',
'repositories',
'instanceType',
'lastUpdatedTime',
] as const
type RequiredDevEnvProps = (typeof requiredDevEnvProps)[number]

export interface CodeCatalystConfig {
readonly region: string
readonly endpoint: string
Expand All @@ -91,11 +110,17 @@ export function getCodeCatalystConfig(): CodeCatalystConfig {
}
}

export interface DevEnvironment extends DevEnvironmentSummary {
interface CodeCatalystDevEnvironmentSummary extends RequiredProps<DevEnvironmentSummary, RequiredDevEnvProps> {
readonly persistentStorage: RequiredProps<PersistentStorage, 'sizeInGiB'>
readonly repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[]
}

export interface DevEnvironment extends CodeCatalystDevEnvironmentSummary {
readonly type: 'devEnvironment'
readonly id: string
readonly org: Pick<CodeCatalystOrg, 'name'>
readonly project: Pick<CodeCatalystProject, 'name'>
readonly repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[]
}

/** CodeCatalyst developer environment session. */
Expand Down Expand Up @@ -138,8 +163,8 @@ export type CodeCatalystResource =
function toDevEnv(
spaceName: string,
projectName: string,
summary: RequiredProps<DevEnvironmentSummary, 'id' | 'status'>
): RequiredProps<DevEnvironment, 'status'> {
summary: CodeCatalystDevEnvironmentSummary
): RequiredProps<DevEnvironment, RequiredDevEnvProps> {
return {
type: 'devEnvironment',
org: { name: spaceName },
Expand Down Expand Up @@ -525,11 +550,15 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {
public listSpaces(request: ListSpacesRequest = {}): AsyncCollection<CodeCatalystOrg[]> {
const requester: (request: ListSpacesRequest) => Promise<ListSpacesResponse> = async (request) =>
this.callV3(ListSpacesCommand, request, true, { items: [] })
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(
(summaries) => summaries !== undefined
)
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(isDefined)
// ts doesn't recognize nested assertion, so we add cast.This is safe because we assert it in the same line.
return collection.map((summaries) =>
summaries.filter((s) => hasProps(s, 'name')).map((s) => ({ type: 'org', ...s }))
(summaries.filter((s) => hasProps(s, 'name')) as Readonly<RequiredProps<SpaceSummary, 'name'>>[]).map(
(s) => ({
type: 'org',
...s,
})
)
)
}

Expand All @@ -551,34 +580,36 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {
const requester: (request: ListProjectsRequest) => Promise<ListProjectsResponse> = (request) =>
this.callV3(ListProjectsCommand, request, true, { items: [] })

const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(
(summaries) => summaries !== undefined
)

const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(isDefined)
// ts doesn't recognize nested assertion, so we add cast. This is safe because we assert it in the same line.
return collection.map((summaries) =>
summaries
.filter((s) => hasProps(s, 'name'))
.map((s) => ({
(summaries.filter((s) => hasProps(s, 'name')) as Readonly<RequiredProps<ProjectSummary, 'name'>>[]).map(
(s) => ({
type: 'project',
org: { name: request.spaceName },
...s,
}))
})
)
)
}

/**
* Gets a flat list of all devenvs for the given CodeCatalyst project.
*/
public listDevEnvironments(proj: CodeCatalystProject): AsyncCollection<DevEnvironment[]> {
public listDevEnvironments(
proj: CodeCatalystProject
): AsyncCollection<RequiredProps<DevEnvironment, RequiredDevEnvProps>[]> {
const initRequest = { spaceName: proj.org.name, projectName: proj.name }
const requester: (request: ListDevEnvironmentsRequest) => Promise<ListDevEnvironmentsResponse> = (request) =>
this.callV3(ListDevEnvironmentsCommand, request, true, { items: [] })
const collection = pageableToCollection(requester, initRequest, 'nextToken', 'items').filter(
(c) => c !== undefined
)

const collection = pageableToCollection(requester, initRequest, 'nextToken' as never, 'items').filter(isDefined)
// ts unable to recognize nested assertion here, so we need to cast. This is safe because we assert it in the same line.
return collection.map((envs) =>
envs.filter((s) => hasProps(s, 'id', 'status')).map((s) => toDevEnv(proj.org.name, proj.name, s))
(
envs.filter(
(s) => hasProps(s, ...requiredDevEnvProps) && hasPersistentStorage(s) && hasRepositories(s)
) as CodeCatalystDevEnvironmentSummary[]
).map((s) => toDevEnv(proj.org.name, proj.name, s))
)
}

Expand Down Expand Up @@ -730,14 +761,16 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {

public async getDevEnvironment(
args: RequiredProps<GetDevEnvironmentRequest, 'spaceName' | 'projectName'>
): Promise<RequiredProps<DevEnvironment, 'status'>> {
): Promise<RequiredProps<DevEnvironment, RequiredDevEnvProps>> {
const a = { ...args }
delete (a as any).ides
delete (a as any).repositories

const r: GetDevEnvironmentResponse = await this.callV3(GetDevEnvironmentCommand, a, false)
const summary = { ...args, ...r }
assertHasProps(summary, 'id', 'status')
if (!hasProps(summary, ...requiredDevEnvProps) || !hasPersistentStorage(summary) || !hasRepositories(summary)) {
throw new ToolkitError(`GetDevEnvironment failed due to response missing required properties`)
}

return toDevEnv(args.spaceName, args.projectName, summary)
}
Expand Down Expand Up @@ -974,3 +1007,15 @@ export async function isThirdPartyRepo(
!Uri.parse(url).authority.endsWith('caws.dev-tools.aws.dev')
)
}

function hasPersistentStorage<T extends DevEnvironmentSummary>(
s: T
): s is T & { persistentStorage: { sizeInGiB: number } } {
return hasProps(s, 'persistentStorage') && hasProps(s.persistentStorage, 'sizeInGiB')
}

function hasRepositories<T extends DevEnvironmentSummary>(
s: T
): s is T & { repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[] } {
return hasProps(s, 'repositories') && s.repositories.every((r) => hasProps(r, 'repositoryName'))
}
4 changes: 4 additions & 0 deletions packages/core/src/shared/utilities/tsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,7 @@ export function omitIfPresent<T extends Record<string, unknown>>(obj: T, keys: s
}
return objCopy
}

export function isDefined<T>(i: T | undefined): i is T {
return i !== undefined
}
4 changes: 2 additions & 2 deletions packages/core/src/testE2E/codecatalyst/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ describe.only('Test how this codebase uses the CodeCatalyst API', function () {
assert.strictEqual(actualDevEnv.org.name, spaceName)
assert.strictEqual(actualDevEnv.alias, differentDevEnvSettings.alias)
assert.strictEqual(actualDevEnv.instanceType, 'dev.standard1.medium')
assert.strictEqual(actualDevEnv.persistentStorage.sizeInGiB, 32)
assert.strictEqual(actualDevEnv.persistentStorage && actualDevEnv.persistentStorage.sizeInGiB, 32)
})

it.skip('creates a Dev Environment using an existing branch', async function () {
Expand Down Expand Up @@ -533,7 +533,7 @@ describe.only('Test how this codebase uses the CodeCatalyst API', function () {
)
}

async function getAllDevEnvs(projectName: CodeCatalystProject['name']): Promise<DevEnvironment[]> {
async function getAllDevEnvs(projectName: CodeCatalystProject['name']) {
const currentDevEnvs = await client
.listDevEnvironments({ name: projectName, org: { name: spaceName }, type: 'project' })
.flatten()
Expand Down

0 comments on commit 655af91

Please sign in to comment.