Skip to content

Commit 511c37d

Browse files
authored
fix(deps): drop @octokit/rest and inquirer-autocomplete-prompt (#8549)
1 parent 1900952 commit 511c37d

15 files changed

Lines changed: 442 additions & 411 deletions

‎package-lock.json‎

Lines changed: 123 additions & 284 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
},
5454
"dependencies": {
5555
"@fastify/static": "^10.0.0",
56+
"@inquirer/search": "^4.3.3",
5657
"@netlify/ai": "^1.0.1",
5758
"@netlify/api": "^15.1.2",
5859
"@netlify/blobs": "^11.1.3",
@@ -71,7 +72,6 @@
7172
"@netlify/redirect-parser": "^16.1.1",
7273
"@netlify/server-dev": "^0.1.1",
7374
"@netlify/zip-it-and-ship-it": "^16.2.3",
74-
"@octokit/rest": "^22.0.0",
7575
"@opentelemetry/api": "~1.9.0",
7676
"@pnpm/tabtab": "^0.5.4",
7777
"ansi-escapes": "^7.3.0",
@@ -109,7 +109,6 @@
109109
"http-proxy-middleware": "^3.0.5",
110110
"https-proxy-agent": "^8.0.0",
111111
"inquirer": "^8.2.7",
112-
"inquirer-autocomplete-prompt": "^1.4.0",
113112
"is-docker": "^4.0.0",
114113
"is-stream": "^4.0.1",
115114
"is-wsl": "^3.1.0",
@@ -169,7 +168,6 @@
169168
"@types/folder-walker": "^3.2.5",
170169
"@types/gitconfiglocal": "^2.0.3",
171170
"@types/inquirer": "^9.0.9",
172-
"@types/inquirer-autocomplete-prompt": "^3.0.3",
173171
"@types/jsonwebtoken": "^9.0.10",
174172
"@types/lodash.shuffle": "^4.2.9",
175173
"@types/multiparty": "^4.2.1",

‎src/commands/base-command.ts‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ import { NodeFS, NoopLogger } from '@netlify/build-info/node'
99
import { resolveConfig } from '@netlify/config'
1010
import { getGlobalConfigStore, LocalState } from '@netlify/dev-utils'
1111
import { isCI } from 'ci-info'
12+
import search from '@inquirer/search'
1213
import { Command, CommanderError, Help, Option, type OptionValues } from 'commander'
1314
import debug from 'debug'
1415
import { findUp } from 'find-up'
15-
import inquirer from 'inquirer'
16-
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
1716
import { deepMerge, pick } from '../utils/object-utilities.js'
1817

1918
import { getAgent } from '../lib/http-agent.js'
@@ -36,6 +35,7 @@ import {
3635
logError,
3736
} from '../utils/command-helpers.js'
3837
import { handleOptionError, isOptionError } from '../utils/command-error-handler.js'
38+
import { EXIT_CODES } from '../utils/exit-codes.js'
3939
import type { FeatureFlags } from '../utils/feature-flags.js'
4040
import { getFrameworksAPIPaths } from '../utils/frameworks-api.js'
4141
import { getSiteByName } from '../utils/get-site.js'
@@ -52,8 +52,6 @@ type Analytics = {
5252
payload?: Record<string, unknown>
5353
}
5454

55-
// load the autocomplete plugin
56-
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
5755
/** Netlify CLI client id. Lives in bot@netlify.com */
5856
// TODO: setup client for multiple environments
5957
export const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
@@ -149,13 +147,9 @@ async function selectWorkspace(project: Project, filter?: string): Promise<strin
149147
)
150148
}
151149

152-
const { result } = await inquirer.prompt({
153-
name: 'result',
154-
// @ts-expect-error(serhalp) -- I think this is because `inquirer-autocomplete-prompt` extends known
155-
// `type`s but TS doesn't know about it
156-
type: 'autocomplete',
150+
return await search({
157151
message: 'Select the project you want to work with',
158-
source: (_unused: unknown, input = '') =>
152+
source: (input = '') =>
159153
(project.workspace?.packages || [])
160154
.filter((pkg) => pkg.path.includes(input))
161155
.map((pkg) => ({
@@ -165,8 +159,6 @@ async function selectWorkspace(project: Project, filter?: string): Promise<strin
165159
value: pkg.path,
166160
})),
167161
})
168-
169-
return result
170162
}
171163
return selected.path
172164
}
@@ -474,6 +466,11 @@ export default class BaseCommand extends Command {
474466
)
475467
}
476468

469+
// Inquirer prompts reject with this on Ctrl+C, but the error class isn't exported.
470+
if (error_ instanceof Error && error_.name === 'ExitPromptError') {
471+
return exit(EXIT_CODES.INTERRUPTED)
472+
}
473+
477474
if (error_ !== undefined) {
478475
logError(error_ instanceof Error ? error_ : format(error_))
479476
exit(1)

‎src/commands/create/create-action.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import fetch from 'node-fetch'
1313

1414
import type { NetlifyAPI } from '@netlify/api'
1515
import { LocalState } from '@netlify/dev-utils'
16-
import { Octokit } from '@octokit/rest'
1716

1817
import { chalk, logAndThrowError, log, logJson, warn, type APIError } from '../../utils/command-helpers.js'
1918
import { ensureNetlifyIgnore } from '../../utils/gitignore.js'
2019
import { getGitHubToken as promptForGitHubToken } from '../../utils/gh-auth.js'
20+
import { requestGitHub, type GitHubUser } from '../../utils/github-api.js'
2121
import { startSpinner, stopSpinner } from '../../lib/spinner.js'
2222
import { isInteractive } from '../../utils/scripted-commands.js'
2323
import { track } from '../../utils/telemetry/index.js'
@@ -38,8 +38,7 @@ const resolveGitHubToken = async (globalConfig: {
3838
const cached = globalConfig.get(`users.${userId}.auth.github`) as { token?: string; user?: string } | undefined
3939
if (cached?.token) {
4040
try {
41-
const octokit = new Octokit({ auth: `token ${cached.token}` })
42-
await octokit.rest.users.getAuthenticated()
41+
await requestGitHub<GitHubUser>(cached.token, 'GET', '/user')
4342
return cached.token
4443
} catch {
4544
// Token expired or invalid, fall through to re-auth
@@ -144,9 +143,8 @@ const selectRepoOwner = async (ghToken: string, repoOwnerFlag?: string): Promise
144143
return repoOwnerFlag
145144
}
146145

147-
const octokit = new Octokit({ auth: `token ${ghToken}` })
148-
const { data: user } = await octokit.rest.users.getAuthenticated()
149-
const { data: orgs } = await octokit.rest.orgs.listForAuthenticatedUser()
146+
const user = await requestGitHub<GitHubUser>(ghToken, 'GET', '/user')
147+
const orgs = await requestGitHub<GitHubUser[]>(ghToken, 'GET', '/user/orgs')
150148

151149
if (orgs.length === 0) {
152150
return user.login

‎src/commands/functions/functions-create.ts‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path, { dirname, join, relative } from 'path'
66
import process from 'process'
77
import { fileURLToPath, pathToFileURL } from 'url'
88

9+
import search, { Separator } from '@inquirer/search'
910
import { OptionValues } from 'commander'
1011
import { findUp } from 'find-up'
1112
import fuzzy from 'fuzzy'
@@ -178,7 +179,7 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) {
178179
// @ts-expect-error TS(7031) FIXME: Binding element 'languageFromFlag' implicitly has ... Remove this comment to see the full error message
179180
const pickTemplate = async function ({ language: languageFromFlag, template: templateFromFlag }, funcType) {
180181
const specialCommands = [
181-
new inquirer.Separator(),
182+
new Separator(),
182183
{
183184
name: `Clone template from GitHub URL`,
184185
value: 'url',
@@ -189,7 +190,7 @@ const pickTemplate = async function ({ language: languageFromFlag, template: tem
189190
value: 'report',
190191
short: 'gh-report',
191192
},
192-
new inquirer.Separator(),
193+
new Separator(),
193194
]
194195

195196
let language = languageFromFlag
@@ -230,12 +231,9 @@ const pickTemplate = async function ({ language: languageFromFlag, template: tem
230231
return match.value
231232
}
232233

233-
const { chosenTemplate } = await inquirer.prompt({
234-
name: 'chosenTemplate',
234+
const chosenTemplate = await search({
235235
message: 'Pick a template',
236-
// @ts-expect-error TS(2769) FIXME: No overload matches this call.
237-
type: 'autocomplete',
238-
source(_answersSoFar: unknown, input: string | undefined) {
236+
source(input: string | undefined) {
239237
// if Edge Functions template, don't show url option
240238
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'Separator... Remove this comment to see the full error message
241239
const edgeCommands = specialCommands.filter((val) => val.value !== 'url')

‎src/utils/build-info.ts‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Settings } from '@netlify/build-info'
2+
import search from '@inquirer/search'
23
import { isCI } from 'ci-info'
34
import fuzzy from 'fuzzy'
4-
import inquirer from 'inquirer'
55

66
import type BaseCommand from '../commands/base-command.js'
77
import { chalk, log } from './command-helpers.js'
@@ -96,12 +96,9 @@ export const detectFrameworkSettings = async (
9696

9797
// multiple matching detectors, make the user choose
9898
const scriptInquirerOptions = formatSettingsArrForInquirer(settings, type)
99-
const { chosenSettings } = await inquirer.prompt<{ chosenSettings: Settings }>({
100-
name: 'chosenSettings',
99+
const chosenSettings = await search({
101100
message: `Multiple possible ${type} commands found`,
102-
// @ts-expect-error is not known by the types as it uses the autocomplete plugin
103-
type: 'autocomplete',
104-
source(_: string, input = '') {
101+
source(input = '') {
105102
if (!input) return scriptInquirerOptions
106103
// only show filtered results
107104
return filterSettings(scriptInquirerOptions, input)

‎src/utils/exit-codes.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const EXIT_CODES = {
2020
USAGE_ERROR: 2,
2121
/** An interactive prompt was required but the session is non-interactive (CI or `--non-interactive`) */
2222
NON_INTERACTIVE_PROMPT: 4,
23+
/** The user cancelled an interactive prompt with Ctrl+C, matching a SIGINT-terminated process */
24+
INTERRUPTED: 130,
2325
} as const
2426

2527
export type ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES]

‎src/utils/gh-auth.ts‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import http from 'http'
33
import process from 'process'
44

5-
import { Octokit } from '@octokit/rest'
65
import getPort from 'get-port'
76
import inquirer from 'inquirer'
87

98
import { log } from './command-helpers.js'
109
import createDeferred from './create-deferred.js'
10+
import { requestGitHub, type GitHubUser } from './github-api.js'
1111
import openBrowser from './open-browser.js'
1212

1313
const SERVER_PORT = 3000
@@ -104,10 +104,7 @@ const authWithToken = async (): Promise<Token> => {
104104
throw new Error('GitHub authentication failed')
105105
}
106106

107-
const octokit = new Octokit({ auth: `token ${token}` })
108-
const {
109-
data: { login: user },
110-
} = await octokit.users.getAuthenticated()
107+
const { login: user } = await requestGitHub<GitHubUser>(token, 'GET', '/user')
111108

112109
return { token, user, provider: 'github' }
113110
}

‎src/utils/github-api.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const GITHUB_API_URL = 'https://api.github.com'
2+
3+
export class GitHubAPIError extends Error {
4+
status: number
5+
6+
constructor(message: string, status: number) {
7+
super(message)
8+
// Keeps error output identical to what Octokit printed before it was replaced.
9+
this.name = 'HttpError'
10+
this.status = status
11+
}
12+
}
13+
14+
export interface GitHubUser {
15+
login: string
16+
}
17+
18+
export interface GitHubRepo {
19+
id: number
20+
full_name: string
21+
default_branch: string
22+
}
23+
24+
export interface GitHubWebhook {
25+
config: { url?: string }
26+
}
27+
28+
interface GitHubErrorBody {
29+
message?: string
30+
errors?: unknown[]
31+
documentation_url?: string
32+
}
33+
34+
// Mirrors Octokit's message format, which callers rely on to detect specific validation errors.
35+
const readErrorMessage = async (response: Response): Promise<string> => {
36+
const text = await response.text()
37+
let body: GitHubErrorBody
38+
try {
39+
body = JSON.parse(text) as GitHubErrorBody
40+
} catch {
41+
return text || response.statusText
42+
}
43+
const message = body.message ?? response.statusText
44+
const details = body.errors?.length ? `: ${body.errors.map((error) => JSON.stringify(error)).join(', ')}` : ''
45+
const suffix = body.documentation_url ? ` - ${body.documentation_url}` : ''
46+
return `${message}${details}${suffix}`
47+
}
48+
49+
export const requestGitHub = async <T>(
50+
token: string,
51+
method: string,
52+
path: string,
53+
{ query, body }: { query?: Record<string, string | number>; body?: unknown } = {},
54+
): Promise<T> => {
55+
const url = new URL(path, GITHUB_API_URL)
56+
for (const [key, value] of Object.entries(query ?? {})) {
57+
url.searchParams.set(key, String(value))
58+
}
59+
60+
const response = await fetch(url, {
61+
method,
62+
headers: {
63+
Accept: 'application/vnd.github.v3+json',
64+
Authorization: `token ${token}`,
65+
'User-Agent': 'netlify-cli',
66+
...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }),
67+
},
68+
body: body === undefined ? undefined : JSON.stringify(body),
69+
})
70+
71+
if (!response.ok) {
72+
throw new GitHubAPIError(await readErrorMessage(response), response.status)
73+
}
74+
75+
const text = await response.text()
76+
return (text ? JSON.parse(text) : undefined) as T
77+
}

0 commit comments

Comments
 (0)