diff --git a/packages/cli/package.json b/packages/cli/package.json index fab0a67b..68c4de20 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,6 +31,7 @@ "@preevy/driver-lightsail": "0.0.56", "@preevy/plugin-github-pr-link": "0.0.56", "inquirer": "^8.0.0", + "inquirer-autocomplete-prompt": "^2.0.0", "iter-tools-es": "^7.5.3", "lodash": "^4.17.21", "shell-escape": "^0.2.0", @@ -39,6 +40,7 @@ "devDependencies": { "@oclif/test": "^2.3.4", "@types/inquirer": "^8.0.0", + "@types/inquirer-autocomplete-prompt": "^3.0.3", "@types/lodash": "^4.14.192", "@types/node": "18", "@types/shell-escape": "^0.2.1", diff --git a/packages/cli/src/fs.ts b/packages/cli/src/fs.ts index 21413fd3..44484f65 100644 --- a/packages/cli/src/fs.ts +++ b/packages/cli/src/fs.ts @@ -1,10 +1,13 @@ import { fsTypeFromUrl, localFsFromUrl } from '@preevy/core' import { googleCloudStorageFs, defaultBucketName as gsDefaultBucketName, defaultProjectId as defaultGceProjectId } from '@preevy/driver-gce' -import { s3fs, defaultBucketName as s3DefaultBucketName, AWS_REGIONS, awsUtils } from '@preevy/driver-lightsail' +import { s3fs, defaultBucketName as s3DefaultBucketName, awsUtils, S3_REGIONS } from '@preevy/driver-lightsail' import inquirer from 'inquirer' +import inquirerAutoComplete from 'inquirer-autocomplete-prompt' import { DriverName } from './drivers' import ambientAwsAccountId = awsUtils.ambientAccountId +inquirer.registerPrompt('autocomplete', inquirerAutoComplete) + export const fsFromUrl = async (url: string, localBaseDir: string) => { const fsType = fsTypeFromUrl(url) if (fsType === 'local') { @@ -57,12 +60,16 @@ export const chooseFs: Record = { // eslint-disable-next-line no-use-before-define const { region, bucket } = await inquirer.prompt<{ region: string; bucket: string }>([ { - type: 'list', + type: 'autocomplete', name: 'region', message: 'S3 bucket region', - choices: AWS_REGIONS, - default: driver?.name === 'lightsail' ? driver.flags.region as string : 'us-east-1', - }, + source: async (_opts, input) => S3_REGIONS.filter(r => !input || r.includes(input.toLowerCase())), + default: driver?.name === 'lightsail' && S3_REGIONS.includes(driver.flags.region as string) + ? driver.flags.region as string + : 'us-east-1', + suggestOnly: true, + filter: i => i.toLowerCase(), + } as inquirerAutoComplete.AutocompleteQuestionOptions, { type: 'input', name: 'bucket', diff --git a/packages/core/package.json b/packages/core/package.json index c3a425ec..2dc7d4bc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,6 @@ "@preevy/compose-tunnel-agent": "0.0.56", "chalk": "^4.1.2", "fast-safe-stringify": "^2.1.1", - "inquirer": "^8.0.0", "is-stream": "^2.0.1", "iter-tools-es": "^7.5.3", "jose": "^4.14.4", @@ -41,6 +40,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@types/inquirer": "^8.0.0", + "@types/inquirer-autocomplete-prompt": "^3.0.3", "@types/is-stream": "^2.0.0", "@types/lodash": "^4.14.192", "@types/node": "18", diff --git a/packages/driver-azure/package.json b/packages/driver-azure/package.json index 566ed2fe..70915733 100644 --- a/packages/driver-azure/package.json +++ b/packages/driver-azure/package.json @@ -20,11 +20,14 @@ "@oclif/core": "^2", "@preevy/core": "0.0.56", "inquirer": "^8.0.0", + "inquirer-autocomplete-prompt": "^2.0.0", "iter-tools-es": "^7.5.3", "lodash": "^4.17.21" }, "devDependencies": { "@types/azure": "^0.9.20", + "@types/inquirer": "^8.0.0", + "@types/inquirer-autocomplete-prompt": "^3.0.3", "@types/lodash": "^4.14.192", "@types/node": "18", "@typescript-eslint/eslint-plugin": "6.7.4", diff --git a/packages/driver-azure/src/driver/client.ts b/packages/driver-azure/src/driver/client.ts index 55179517..387a144f 100644 --- a/packages/driver-azure/src/driver/client.ts +++ b/packages/driver-azure/src/driver/client.ts @@ -63,6 +63,7 @@ export const REGIONS = [ 'centraluseuap', 'eastus2euap', 'qatarcentral', + 'israelcentral', ] type VMInstance = { diff --git a/packages/driver-azure/src/driver/index.ts b/packages/driver-azure/src/driver/index.ts index 0fa1a3e3..cb2e87ce 100644 --- a/packages/driver-azure/src/driver/index.ts +++ b/packages/driver-azure/src/driver/index.ts @@ -1,6 +1,7 @@ import { Flags, Interfaces } from '@oclif/core' import { asyncFirst, asyncMap } from 'iter-tools-es' -import { ListQuestion, Question } from 'inquirer' +import inquirer, { Question } from 'inquirer' +import inquirerAutoComplete from 'inquirer-autocomplete-prompt' import { InferredFlags } from '@oclif/core/lib/interfaces' import { Resource, VirtualMachine } from '@azure/arm-compute' import { inspect } from 'util' @@ -26,6 +27,8 @@ import { Client, client as createClient, REGIONS } from './client' import { CUSTOMIZE_BARE_MACHINE } from './scripts' import { AzureCustomTags, extractResourceGroupNameFromId } from './vm-creation-utils' +inquirer.registerPrompt('autocomplete', inquirerAutoComplete) + type RootObjectDetailsError = { code: string message: string @@ -142,13 +145,15 @@ const flags = { type FlagTypes = Omit, 'json'> -const questions = async (): Promise<(Question | ListQuestion)[]> => [ +const questions = async (): Promise => [ { - type: 'list', + type: 'autocomplete', name: 'region', message: flags.region.description, - choices: REGIONS, - }, + source: async (_opts, input) => !input || REGIONS.filter(r => r.includes(input.toLowerCase())), + suggestOnly: true, + filter: i => i.toLowerCase(), + } as inquirerAutoComplete.AutocompleteQuestionOptions, { type: 'input', name: 'subscription-id', diff --git a/packages/driver-gce/package.json b/packages/driver-gce/package.json index ded09d88..05e652d6 100644 --- a/packages/driver-gce/package.json +++ b/packages/driver-gce/package.json @@ -17,10 +17,13 @@ "google-auth-library": "^8.7.0", "google-gax": "^4.0.4", "inquirer": "^8.0.0", + "inquirer-autocomplete-prompt": "^2.0.0", "iter-tools-es": "^7.5.3", "lodash": "^4.17.21" }, "devDependencies": { + "@types/inquirer": "^8.0.0", + "@types/inquirer-autocomplete-prompt": "^3.0.3", "@types/lodash": "^4.14.192", "@types/node": "18", "@typescript-eslint/eslint-plugin": "6.7.4", diff --git a/packages/driver-gce/src/driver/index.ts b/packages/driver-gce/src/driver/index.ts index 8d8085f1..fe0efd17 100644 --- a/packages/driver-gce/src/driver/index.ts +++ b/packages/driver-gce/src/driver/index.ts @@ -1,6 +1,7 @@ import { Flags, Interfaces } from '@oclif/core' import { asyncMap } from 'iter-tools-es' -import { InputQuestion, ListQuestion } from 'inquirer' +import inquirer, { ListQuestion, Question } from 'inquirer' +import inquirerAutoComplete from 'inquirer-autocomplete-prompt' import { MachineDriver, SshMachine, MachineCreationDriver, MachineCreationDriverFactory, MachineDriverFactory, @@ -15,11 +16,13 @@ import { extractDefined, PartialMachine, } from '@preevy/core' -import { pick } from 'lodash' +import { memoize, pick } from 'lodash' import createClient, { Client, Instance, availableRegions, defaultProjectId, instanceError, shortResourceName } from './client' import { deserializeMetadata, metadataKey } from './metadata' import { LABELS } from './labels' +inquirer.registerPrompt('autocomplete', inquirerAutoComplete) + type DriverContext = { log: Logger debug: boolean @@ -108,26 +111,32 @@ const contextFromFlags = ({ zone, }) -const questions = async (): Promise<(InputQuestion | ListQuestion)[]> => [ - { - type: 'input', - name: 'project', - default: defaultProjectId, - message: flags['project-id'].description, - }, - { - type: 'list', - name: 'region', - choices: async ({ project }) => (await availableRegions(project)).map(r => r.name), - }, - { - type: 'list', - name: 'zone', - choices: async ( - { project, region }, - ) => (await availableRegions(project)).find(r => r.name === region)?.zones ?? [], - }, -] +const questions = async (): Promise => { + const memoizedAvailableRegions = memoize(availableRegions) + return [ + { + type: 'input', + name: 'project', + default: defaultProjectId, + message: flags['project-id'].description, + }, + { + type: 'autocomplete', + name: 'region', + source: async ({ project }, input) => (await memoizedAvailableRegions(project)) + .filter(({ name }) => !input || name.includes(input.toLowerCase())) + .map(r => r.name), + filter: i => i.toLowerCase(), + } as inquirerAutoComplete.AutocompleteQuestionOptions, + { + type: 'list', + name: 'zone', + choices: memoize( + async ({ project, region }) => (await availableRegions(project)).find(r => r.name === region)?.zones ?? [], + ), + } as ListQuestion, + ] +} const flagsFromAnswers = async (answers: Record): Promise => ({ 'project-id': answers.project as string, diff --git a/packages/driver-lightsail/package.json b/packages/driver-lightsail/package.json index a4a69154..fa8cd561 100644 --- a/packages/driver-lightsail/package.json +++ b/packages/driver-lightsail/package.json @@ -17,10 +17,13 @@ "@oclif/core": "^2", "@preevy/core": "0.0.56", "inquirer": "^8.0.0", + "inquirer-autocomplete-prompt": "^2.0.0", "iter-tools-es": "^7.5.3", "lodash": "^4.17.21" }, "devDependencies": { + "@types/inquirer": "^8.0.0", + "@types/inquirer-autocomplete-prompt": "^3.0.3", "@types/lodash": "^4.14.192", "@types/node": "18", "@typescript-eslint/eslint-plugin": "6.7.4", diff --git a/packages/driver-lightsail/src/driver/index.ts b/packages/driver-lightsail/src/driver/index.ts index 53d85f61..57655e49 100644 --- a/packages/driver-lightsail/src/driver/index.ts +++ b/packages/driver-lightsail/src/driver/index.ts @@ -4,7 +4,8 @@ import { asyncConcat, asyncMap } from 'iter-tools-es' import { Flags } from '@oclif/core' import { randomBytes } from 'crypto' import { InferredFlags } from '@oclif/core/lib/interfaces' -import { ListQuestion, Question } from 'inquirer' +import inquirer, { Question } from 'inquirer' +import inquirerAutoComplete from 'inquirer-autocomplete-prompt' import { telemetryEmitter, SshMachine, MachineDriver, MachineCreationDriver, MachineCreationDriverFactory, machineResourceType, @@ -19,6 +20,8 @@ import { CURRENT_MACHINE_VERSION, TAGS, requiredTag } from './tags' export { BundleId, BUNDLE_IDS, bundleIdFromString as bundleId } +inquirer.registerPrompt('autocomplete', inquirerAutoComplete) + type ResourceType = typeof machineResourceType | 'snapshot' | 'keypair' const machineFromInstance = ( @@ -110,7 +113,6 @@ const flags = { description: 'AWS region in which resources will be provisioned', required: true, env: 'AWS_REGION', - options: REGIONS.map(r => r), }), } as const @@ -120,14 +122,16 @@ const contextFromFlags = ({ region }: FlagTypes): { region: string } => ({ region: region as string, }) -const questions = async (): Promise<(Question | ListQuestion)[]> => [ +const questions = async (): Promise => [ { - type: 'list', + type: 'autocomplete', name: 'region', default: process.env.AWS_REGION ?? 'us-east-1', message: flags.region.description, - choices: flags.region.options, - }, + source: async (_opts, input) => REGIONS.filter(r => !input || r.includes(input.toLowerCase())), + suggestOnly: true, + filter: i => i.toLowerCase(), + } as inquirerAutoComplete.AutocompleteQuestionOptions, ] const flagsFromAnswers = async (answers: Record): Promise => ({ diff --git a/packages/driver-lightsail/src/fs/index.ts b/packages/driver-lightsail/src/fs/index.ts index 0f361dd5..5ddbbe48 100644 --- a/packages/driver-lightsail/src/fs/index.ts +++ b/packages/driver-lightsail/src/fs/index.ts @@ -92,3 +92,38 @@ export const s3fs = async (s3Url: string): Promise => { }, } } + +export const S3_REGIONS = [ + 'us-east-2', + 'us-east-1', + 'us-west-1', + 'us-west-2', + 'af-south-1', + 'ap-east-1', + 'ap-south-2', + 'ap-southeast-3', + 'ap-southeast-4', + 'ap-south-1', + 'ap-northeast-3', + 'ap-northeast-2', + 'ap-southeast-1', + 'ap-southeast-2', + 'ap-northeast-1', + 'ca-central-1', + 'cn-north-1', + 'cn-northwest-1', + 'eu-central-1', + 'eu-west-1', + 'eu-west-2', + 'eu-south-1', + 'eu-west-3', + 'eu-north-1', + 'eu-south-2', + 'eu-central-2', + 'me-south-1', + 'me-central-1', + 'il-central-1', + 'sa-east-1', + 'us-gov-east-1', + 'us-gov-west-1', +] diff --git a/packages/driver-lightsail/src/index.ts b/packages/driver-lightsail/src/index.ts index 0bc76e04..51e92220 100644 --- a/packages/driver-lightsail/src/index.ts +++ b/packages/driver-lightsail/src/index.ts @@ -2,5 +2,5 @@ import lightsail from './driver' export default lightsail export * as awsUtils from './aws-utils' -export { REGIONS as AWS_REGIONS } from './driver/client' -export { s3fs, defaultBucketName } from './fs' +export { REGIONS as LIGHTSAIL_REGIONS } from './driver/client' +export { s3fs, defaultBucketName, S3_REGIONS } from './fs' diff --git a/yarn.lock b/yarn.lock index 013e318e..5858a582 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3773,6 +3773,21 @@ dependencies: "@types/node" "*" +"@types/inquirer-autocomplete-prompt@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-3.0.3.tgz#8bbb3095454cb2ac9a26865c694e32fc317d7640" + integrity sha512-OQCW09mEECgvhcppbQRgZSmWskWv58l+WwyUvWB1oxTu3CZj8keYSDZR9U8owUzJ5Zeux5kacN9iVPJLXcoLXg== + dependencies: + "@types/inquirer" "*" + +"@types/inquirer@*": + version "9.0.7" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-9.0.7.tgz#61bb8d0e42f038b9a1738b08fba7fa98ad9b4b24" + integrity sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g== + dependencies: + "@types/through" "*" + rxjs "^7.2.0" + "@types/inquirer@^8.0.0": version "8.2.9" resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.9.tgz#bb29e7d2358e3af7d9f4d6c6410320498b428d48" @@ -8045,6 +8060,17 @@ init-package-json@3.0.2, init-package-json@^3.0.2: validate-npm-package-license "^3.0.4" validate-npm-package-name "^4.0.0" +inquirer-autocomplete-prompt@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-2.0.1.tgz#72868aada4d9d36814a6054cbd1ececc63aab0c6" + integrity sha512-jUHrH0btO7j5r8DTQgANf2CBkTZChoVySD8zF/wp5fZCOLIuUbleXhf4ZY5jNBOc1owA3gdfWtfZuppfYBhcUg== + dependencies: + ansi-escapes "^4.3.2" + figures "^3.2.0" + picocolors "^1.0.0" + run-async "^2.4.1" + rxjs "^7.5.4" + inquirer@8.2.4: version "8.2.4" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" @@ -12038,7 +12064,7 @@ rimraf@^5.0.0: dependencies: glob "^10.3.7" -run-async@^2.0.0, run-async@^2.4.0: +run-async@^2.0.0, run-async@^2.4.0, run-async@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== @@ -12062,6 +12088,13 @@ rxjs@^7.0.0, rxjs@^7.2.0, rxjs@^7.5.5: dependencies: tslib "^2.1.0" +rxjs@^7.5.4: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"