| author | |
| committer | |
| log | 549201fcfd3baa6cf92f09e5d868c536c80d2d55 |
| tree | e4fe7e2ac3ae1921540a172591f08b95c833e8e0 |
| parent | 9f12eab383ab0fd348c9d35cd248cd939a2c5796 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>
Co-authored-by: maria <254055478+maria-rcks@users.noreply.github.com>4 files changed, 68 insertions(+), 4 deletions(-)
apps/mobile/src/features/projects/AddProjectScreen.tsx+4-2| ... | ... | @@ -10,6 +10,8 @@ import { |
| 10 | 10 | getCloneDestinationBrowsePath, |
| 11 | 11 | getCloneDestinationPath, |
| 12 | 12 | getCloneDirectoryName, |
| 13 | getDefaultCloneUrl, | |
| 14 | normalizePastedCloneUrl, | |
| 13 | 15 | resolveAddProjectPath, |
| 14 | 16 | sortAddProjectProviderSources, |
| 15 | 17 | type AddProjectRemoteSource, |
| ... | ... | @@ -662,7 +664,7 @@ export function AddProjectRepositoryScreen(props: { |
| 662 | 664 | setIsSubmitting(true); |
| 663 | 665 | const provider = addProjectRemoteSourceProvider(source); |
| 664 | 666 | if (!provider) { |
| 665 | const remoteUrl = repositoryInput.trim(); | |
| 667 | const remoteUrl = normalizePastedCloneUrl(repositoryInput); | |
| 666 | 668 | navigation.dispatch( |
| 667 | 669 | StackActions.push("AddProjectDestination", { |
| 668 | 670 | environmentId: environment.environmentId, |
| ... | ... | @@ -691,7 +693,7 @@ export function AddProjectRepositoryScreen(props: { |
| 691 | 693 | StackActions.push("AddProjectDestination", { |
| 692 | 694 | environmentId: environment.environmentId, |
| 693 | 695 | source, |
| 694 | remoteUrl: repository.sshUrl, | |
| 696 | remoteUrl: getDefaultCloneUrl(repository), | |
| 695 | 697 | repositoryTitle: repository.nameWithOwner, |
| 696 | 698 | repositoryName: getCloneDirectoryName(repository.nameWithOwner), |
| 697 | 699 | }), |
apps/web/src/components/CommandPalette.tsx+4-2| ... | ... | @@ -6,6 +6,8 @@ import { |
| 6 | 6 | getCloneDestinationBrowsePath, |
| 7 | 7 | getCloneDestinationPath, |
| 8 | 8 | getCloneDirectoryName, |
| 9 | getDefaultCloneUrl, | |
| 10 | normalizePastedCloneUrl, | |
| 9 | 11 | } from "@t3tools/client-runtime/operations/projects"; |
| 10 | 12 | import { connectionStatusText } from "@t3tools/client-runtime/connection"; |
| 11 | 13 | import { threadSearchMatchKey } from "@t3tools/client-runtime/state/thread-search"; |
| ... | ... | @@ -1874,7 +1876,7 @@ function OpenCommandPaletteDialog(props: { |
| 1874 | 1876 | source: addProjectCloneFlow.source, |
| 1875 | 1877 | repositoryInput: rawRepository, |
| 1876 | 1878 | repository: null, |
| 1877 | remoteUrl: rawRepository, | |
| 1879 | remoteUrl: normalizePastedCloneUrl(rawRepository), | |
| 1878 | 1880 | }); |
| 1879 | 1881 | setHighlightedItemValue(null); |
| 1880 | 1882 | setQuery(destinationPath); |
| ... | ... | @@ -1914,7 +1916,7 @@ function OpenCommandPaletteDialog(props: { |
| 1914 | 1916 | source: addProjectCloneFlow.source, |
| 1915 | 1917 | repositoryInput: rawRepository, |
| 1916 | 1918 | repository, |
| 1917 | remoteUrl: repository.sshUrl, | |
| 1919 | remoteUrl: getDefaultCloneUrl(repository), | |
| 1918 | 1920 | }); |
| 1919 | 1921 | setHighlightedItemValue(null); |
| 1920 | 1922 | setQuery(destinationPath); |
packages/client-runtime/src/operations/projects.test.ts+42| ... | ... | @@ -16,6 +16,8 @@ import { |
| 16 | 16 | getCloneDestinationBrowsePath, |
| 17 | 17 | getCloneDestinationPath, |
| 18 | 18 | getCloneDirectoryName, |
| 19 | getDefaultCloneUrl, | |
| 20 | normalizePastedCloneUrl, | |
| 19 | 21 | resolveAddProjectPath, |
| 20 | 22 | sortAddProjectProviderSources, |
| 21 | 23 | } from "./projects.ts"; |
| ... | ... | @@ -46,6 +48,46 @@ describe("add project shared logic", () => { |
| 46 | 48 | expect(getCloneDirectoryName(null)).toBe(""); |
| 47 | 49 | }); |
| 48 | 50 | |
| 51 | it("routes owner/repository shorthand to GitHub over HTTPS", () => { | |
| 52 | expect(normalizePastedCloneUrl("imputnet/helium")).toBe( | |
| 53 | "https://github.com/imputnet/helium.git", | |
| 54 | ); | |
| 55 | expect(normalizePastedCloneUrl(" pingdotgg/t3code ")).toBe( | |
| 56 | "https://github.com/pingdotgg/t3code.git", | |
| 57 | ); | |
| 58 | }); | |
| 59 | ||
| 60 | it("keeps explicit clone URLs and local paths unchanged", () => { | |
| 61 | expect(normalizePastedCloneUrl("https://gitlab.com/group/project.git")).toBe( | |
| 62 | "https://gitlab.com/group/project.git", | |
| 63 | ); | |
| 64 | expect(normalizePastedCloneUrl("git@github.com:owner/repo.git")).toBe( | |
| 65 | "git@github.com:owner/repo.git", | |
| 66 | ); | |
| 67 | expect(normalizePastedCloneUrl("group/subgroup/project")).toBe("group/subgroup/project"); | |
| 68 | expect(normalizePastedCloneUrl("/srv/git/repo.git")).toBe("/srv/git/repo.git"); | |
| 69 | }); | |
| 70 | ||
| 71 | it("uses HTTPS for repositories selected through a provider", () => { | |
| 72 | expect( | |
| 73 | getDefaultCloneUrl({ | |
| 74 | provider: "github", | |
| 75 | url: "https://github.com/imputnet/helium", | |
| 76 | sshUrl: "git@github.com:imputnet/helium.git", | |
| 77 | }), | |
| 78 | ).toBe("https://github.com/imputnet/helium"); | |
| 79 | }); | |
| 80 | ||
| 81 | it("preserves existing clone transport behavior for other providers", () => { | |
| 82 | expect( | |
| 83 | getDefaultCloneUrl({ | |
| 84 | provider: "gitlab", | |
| 85 | url: "https://gitlab.com/group/project.git", | |
| 86 | sshUrl: "git@gitlab.com:group/project.git", | |
| 87 | }), | |
| 88 | ).toBe("git@gitlab.com:group/project.git"); | |
| 89 | }); | |
| 90 | ||
| 49 | 91 | it("derives the clone folder name from any pasted clone URL", () => { |
| 50 | 92 | expect(getCloneDirectoryName("https://github.com/owner/repo.git")).toBe("repo"); |
| 51 | 93 | expect(getCloneDirectoryName("https://github.com/owner/repo")).toBe("repo"); |
packages/client-runtime/src/operations/projects.ts+18| ... | ... | @@ -106,6 +106,24 @@ export function addProjectRemoteSourceProvider( |
| 106 | 106 | return source === "url" ? null : source; |
| 107 | 107 | } |
| 108 | 108 | |
| 109 | const GITHUB_REPOSITORY_SHORTHAND = | |
| 110 | /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9._-]+(?:\.git)?$/; | |
| 111 | ||
| 112 | /** Treat the common owner/repository shorthand as a public GitHub HTTPS URL. */ | |
| 113 | export function normalizePastedCloneUrl(input: string): string { | |
| 114 | const trimmed = input.trim(); | |
| 115 | if (!GITHUB_REPOSITORY_SHORTHAND.test(trimmed)) return trimmed; | |
| 116 | const repository = trimmed.endsWith(".git") ? trimmed : `${trimmed}.git`; | |
| 117 | return `https://github.com/${repository}`; | |
| 118 | } | |
| 119 | ||
| 120 | /** GitHub defaults to HTTPS; other providers retain their existing SSH default. */ | |
| 121 | export function getDefaultCloneUrl( | |
| 122 | repository: Pick<SourceControlRepositoryInfo, "provider" | "url" | "sshUrl">, | |
| 123 | ): string { | |
| 124 | return repository.provider === "github" ? repository.url : repository.sshUrl; | |
| 125 | } | |
| 126 | ||
| 109 | 127 | export function sortAddProjectProviderSources( |
| 110 | 128 | readinessBySource: AddProjectRemoteSourceReadiness, |
| 111 | 129 | ): ReadonlyArray<AddProjectRemoteProviderKind> { |