-
Notifications
You must be signed in to change notification settings - Fork 468
fix: convert https URL to SSH format in init prompt default for GitHub and GitLab #8418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,6 +37,7 @@ import { | |||||||||||||||
| logJson, | ||||||||||||||||
| warn, | ||||||||||||||||
| type APIError, | ||||||||||||||||
| NETLIFYDEVWARN, | ||||||||||||||||
| } from '../../utils/command-helpers.js' | ||||||||||||||||
| import { DEFAULT_CONCURRENT_HASH, DEFAULT_DEPLOY_TIMEOUT } from '../../utils/deploy/constants.js' | ||||||||||||||||
| import { type DeployEvent, deploySite } from '../../utils/deploy/deploy-site.js' | ||||||||||||||||
|
|
@@ -944,6 +945,22 @@ const prepAndRunDeploy = async ({ | |||||||||||||||
|
|
||||||||||||||||
| const deployFolder = await getDeployFolder({ command, options, config, site, siteData }) | ||||||||||||||||
| const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData }) | ||||||||||||||||
| // When deploying without running a build, warn if build plugins are configured | ||||||||||||||||
| // because their config mutations are lost without a build run | ||||||||||||||||
| // (see https://github.com/netlify/cli/issues/3792). | ||||||||||||||||
| if (!options.build) { | ||||||||||||||||
| type ConfigPlugin = { package?: unknown; origin?: string } | ||||||||||||||||
| const plugins = | ||||||||||||||||
| (config?.plugins as ConfigPlugin[] | undefined) ?? | ||||||||||||||||
| (command.netlify.cachedConfig.config as { plugins?: ConfigPlugin[] } | undefined)?.plugins | ||||||||||||||||
| const configuredPlugins = plugins?.filter((plugin) => plugin.origin !== 'default') ?? [] | ||||||||||||||||
| if (configuredPlugins.length > 0) { | ||||||||||||||||
| log( | ||||||||||||||||
| `${NETLIFYDEVWARN} Site uses build plugins (${configuredPlugins.map((p) => p.package).join(', ')}) but no build is being run.\n` + | ||||||||||||||||
| ` Config changes made by these plugins will not be applied. Use ${chalk.cyanBright('netlify deploy --build')} to build and deploy together.`, | ||||||||||||||||
|
Comment on lines
+958
to
+960
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve When Suggested fix+ const buildCommand = `netlify deploy --build${deployToProduction ? ' --prod' : ''}`
log(
- `${NETLIFYDEVWARN} Site uses build plugins (${configuredPlugins.map((p) => p.package).join(', ')}) but no build is being run.\n` +
- ` Config changes made by these plugins will not be applied. Use ${chalk.cyanBright('netlify deploy --build')} to build and deploy together.`,
+ `${NETLIFYDEVWARN} Site uses build plugins (${configuredPlugins.map((p) => p.package).join(', ')}) but no build is being run.\n` +
+ ` Config changes made by these plugins will not be applied. Use ${chalk.cyanBright(buildCommand)} to build and deploy together.`,
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| const { configPath } = site | ||||||||||||||||
|
|
||||||||||||||||
| // build flag wasn't used and edge functions directories exist | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -310,7 +310,7 @@ const detectServerSettings = async ( | |||||
| return { | ||||||
| ...settings, | ||||||
| port: acquiredPort, | ||||||
| jwtSecret: devConfig.jwtSecret || 'secret', | ||||||
| jwtSecret: devConfig.jwtSecret || process.env.NETLIFY_DEV_JWT_SECRET || 'secret', | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Honor process-environment precedence for
Proposed fix- jwtSecret: devConfig.jwtSecret || process.env.NETLIFY_DEV_JWT_SECRET || 'secret',
+ jwtSecret: process.env.NETLIFY_DEV_JWT_SECRET || devConfig.jwtSecret || 'secret',📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||
| jwtRolePath: devConfig.jwtRolePath || 'app_metadata.authorization.roles', | ||||||
| functions: functionsDir, | ||||||
| functionsPort: await getPort({ port: devConfig.functionsPort || 0 }), | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,14 +35,49 @@ const getRepoPath = async ({ repoData }: { repoData: RepoData }): Promise<string | |
| type: 'input', | ||
| name: 'repoPath', | ||
| message: 'The SSH URL of the remote git repo:', | ||
| default: repoData.url, | ||
| default: toSshUrl(repoData.url, repoData.provider), | ||
| validate: (url: string) => (SSH_URL_REGEXP.test(url) ? true : 'The URL provided does not use the SSH protocol'), | ||
| }, | ||
| ]) | ||
|
|
||
| return repoPath | ||
| } | ||
|
|
||
| /** | ||
| * Converts an https:// URL to its SSH equivalent for known Git providers. | ||
| * Returns the original URL if already SSH or if the provider is unknown. | ||
| */ | ||
| export const toSshUrl = (url: string, provider: string | null): string => { | ||
| if (SSH_URL_REGEXP.test(url)) { | ||
| return url | ||
|
Comment on lines
+50
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Use an SSH-specific URL check before bypassing conversion.
Use an anchored SCP-style SSH pattern or parse the URL and check for 🤖 Prompt for AI Agents |
||
| } | ||
| if (provider === 'github') { | ||
| return githubHttpsToSsh(url) | ||
| } | ||
| if (provider === 'gitlab') { | ||
| return gitlabHttpsToSsh(url) | ||
| } | ||
| return url | ||
| } | ||
|
|
||
| const githubHttpsToSsh = (url: string): string => { | ||
| try { | ||
| const parsed = new URL(url) | ||
| return `git@${parsed.hostname}:${parsed.pathname.replace(/^\//, '').replace(/\.git$/, '')}.git` | ||
| } catch { | ||
| return url | ||
| } | ||
| } | ||
|
|
||
| const gitlabHttpsToSsh = (url: string): string => { | ||
| try { | ||
| const parsed = new URL(url) | ||
| return `git@${parsed.hostname}:${parsed.pathname.replace(/^\//, '').replace(/\.git$/, '')}.git` | ||
| } catch { | ||
| return url | ||
| } | ||
| } | ||
|
|
||
| const addDeployHook = async (deployHook: string | undefined): Promise<boolean> => { | ||
| log('\nConfigure the following webhook for your repository:\n') | ||
| // FIXME(serhalp): Handle nullish `deployHook` by throwing user-facing error or fixing upstream type. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,11 @@ const getErrorMessage = function ({ message }) { | |||||||
| // - `from` is called `origin` | ||||||||
| // - `query` is called `params` | ||||||||
| // - `conditions.role|country|language` are capitalized | ||||||||
| // Leading and trailing whitespace in `from` and `to` is trimmed so that typos | ||||||||
| // such as `to = " https://example.com"` do not silently break redirects | ||||||||
| // (see https://github.com/netlify/cli/issues/4707). | ||||||||
|
Comment on lines
+39
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Remove the explanatory comments. Lines 39-41 describe the behavior of Proposed diff-// Leading and trailing whitespace in `from` and `to` is trimmed so that typos
-// such as `to = " https://example.com"` do not silently break redirects
-// (see https://github.com/netlify/cli/issues/4707).As per coding guidelines: “Do not write comments describing what the code does; make the code self-explanatory instead.” 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||
| const trimValue = (value: string | unknown): string | unknown => (typeof value === 'string' ? value.trim() : value) | ||||||||
|
|
||||||||
| const normalizeRedirect = function ({ | ||||||||
| // @ts-expect-error TS(7031) FIXME: Binding element 'country' implicitly has an 'any' ... Remove this comment to see the full error message | ||||||||
| conditions: { country, language, role, ...conditions }, | ||||||||
|
|
@@ -45,11 +50,15 @@ const normalizeRedirect = function ({ | |||||||
| query, | ||||||||
| // @ts-expect-error TS(7031) FIXME: Binding element 'signed' implicitly has an 'any' t... Remove this comment to see the full error message | ||||||||
| signed, | ||||||||
| // @ts-expect-error TS(7031) FIXME: Binding element 'to' implicitly has an 'any type... | ||||||||
| to, | ||||||||
| ...redirect | ||||||||
| }) { | ||||||||
| return { | ||||||||
| ...redirect, | ||||||||
| origin: from, | ||||||||
| origin: trimValue(from), | ||||||||
| path: trimValue(from), | ||||||||
| to: trimValue(to), | ||||||||
| params: query, | ||||||||
| conditions: { | ||||||||
| ...conditions, | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { describe, expect, test } from 'vitest' | ||
|
|
||
| import { toSshUrl } from '../../../src/utils/init/config-manual.js' | ||
|
|
||
| describe('toSshUrl', () => { | ||
| test('returns ssh url unchanged for github', () => { | ||
| const url = 'git@github.com:user/repo.git' | ||
| expect(toSshUrl(url, 'github')).toBe(url) | ||
| }) | ||
|
|
||
| test('converts https github url to ssh format', () => { | ||
| const url = 'https://github.com/user/repo.git' | ||
| expect(toSshUrl(url, 'github')).toBe('git@github.com:user/repo.git') | ||
| }) | ||
|
|
||
| test('converts https github url without .git extension', () => { | ||
| const url = 'https://github.com/user/repo' | ||
| expect(toSshUrl(url, 'github')).toBe('git@github.com:user/repo.git') | ||
| }) | ||
|
|
||
| test('converts https gitlab url to ssh format', () => { | ||
| const url = 'https://gitlab.com/group/subgroup/repo.git' | ||
| expect(toSshUrl(url, 'gitlab')).toBe('git@gitlab.com:group/subgroup/repo.git') | ||
| }) | ||
|
|
||
| test('returns https url unchanged for unknown provider', () => { | ||
| const url = 'https://bitbucket.org/user/repo.git' | ||
| expect(toSshUrl(url, 'bitbucket')).toBe(url) | ||
| }) | ||
|
|
||
| test('returns https url unchanged for null provider', () => { | ||
| const url = 'https://example.com/user/repo.git' | ||
| expect(toSshUrl(url, null)).toBe(url) | ||
| }) | ||
|
|
||
| test('returns invalid url unchanged', () => { | ||
| const url = 'not-a-valid-url' | ||
| expect(toSshUrl(url, 'github')).toBe(url) | ||
| }) | ||
|
|
||
| test('handles ssh:// protocol', () => { | ||
| const url = 'ssh://git@github.com/user/repo.git' | ||
| expect(toSshUrl(url, 'github')).toBe(url) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove the comments that describe the implementation.
Lines 948-950 explain the behavior implemented immediately below. Remove these comments so the code remains self-explanatory. Retain an issue reference only if maintainers require traceability, without behavioral prose.
As per coding guidelines,
**/*.{ts,tsx}says: “Do not write comments describing what the code does; make the code self-explanatory instead.”🤖 Prompt for AI Agents
Source: Coding guidelines