Accept HTTPS git repo URLs for storefront provision.

Normalize https://git.meshkee.com/... to SSH for the websites VM deploy key while keeping the URL users paste.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 14:25:13 +03:30
co-authored by Cursor
parent 7d123502f8
commit 5cd762c65d
4 changed files with 31 additions and 14 deletions
+5 -4
View File
@@ -41,6 +41,7 @@ import { WebsiteDeployAgentService } from '../website-deploy/website-deploy-agen
import { import {
deploySlugFromHost, deploySlugFromHost,
isValidGitRepoUrl, isValidGitRepoUrl,
normalizeGitRepoUrlForClone,
} from '../website-deploy/website-deploy.util'; } from '../website-deploy/website-deploy.util';
type BusinessRow = { type BusinessRow = {
@@ -470,7 +471,7 @@ export class BusinessAdminService {
if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) { if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) {
throw new BadRequestException( throw new BadRequestException(
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)', 'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
); );
} }
@@ -512,7 +513,7 @@ export class BusinessAdminService {
await this.websiteDeployAgent.provision({ await this.websiteDeployAgent.provision({
slug: deploySlug, slug: deploySlug,
host, host,
gitRepoUrl, gitRepoUrl: normalizeGitRepoUrlForClone(gitRepoUrl),
}); });
} catch (err) { } catch (err) {
if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') { if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') {
@@ -561,7 +562,7 @@ export class BusinessAdminService {
if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) { if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) {
throw new BadRequestException( throw new BadRequestException(
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)', 'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
); );
} }
@@ -600,7 +601,7 @@ export class BusinessAdminService {
await this.websiteDeployAgent.provision({ await this.websiteDeployAgent.provision({
slug, slug,
host, host,
gitRepoUrl, gitRepoUrl: normalizeGitRepoUrlForClone(gitRepoUrl),
}); });
deploySlug = slug; deploySlug = slug;
nextGitRepoUrl = gitRepoUrl; nextGitRepoUrl = gitRepoUrl;
+4 -4
View File
@@ -1,4 +1,5 @@
import { IsBoolean, IsOptional, IsString, Matches, MinLength } from 'class-validator'; import { IsBoolean, IsOptional, IsString, Matches, MinLength } from 'class-validator';
import { GIT_REPO_URL_RE } from '../../website-deploy/website-deploy.util';
export class AddDomainDto { export class AddDomainDto {
@IsString() @IsString()
@@ -9,13 +10,12 @@ export class AddDomainDto {
@IsBoolean() @IsBoolean()
isPrimary?: boolean; isPrimary?: boolean;
/** SSH git URL — when set, provisions storefront deploy on the websites VM. */ /** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
@IsOptional() @IsOptional()
@IsString() @IsString()
@Matches(/^(?:git@[\w.-]+:[\w./-]+\.git|ssh:\/\/git@[\w.-]+(?::\d+)?\/[\w./-]+\.git)$/i, { @Matches(GIT_REPO_URL_RE, {
message: message:
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)', 'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
}) })
gitRepoUrl?: string; gitRepoUrl?: string;
} }
+4 -3
View File
@@ -1,16 +1,17 @@
import { IsOptional, IsString, Matches, MinLength } from 'class-validator'; import { IsOptional, IsString, Matches, MinLength } from 'class-validator';
import { GIT_REPO_URL_RE } from '../../website-deploy/website-deploy.util';
export class UpdateDomainDto { export class UpdateDomainDto {
@IsString() @IsString()
@MinLength(1) @MinLength(1)
host!: string; host!: string;
/** SSH git URL — when set, provisions storefront deploy on the websites VM. */ /** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
@IsOptional() @IsOptional()
@IsString() @IsString()
@Matches(/^(?:git@[\w.-]+:[\w./-]+\.git|ssh:\/\/git@[\w.-]+(?::\d+)?\/[\w./-]+\.git)$/i, { @Matches(GIT_REPO_URL_RE, {
message: message:
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)', 'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
}) })
gitRepoUrl?: string; gitRepoUrl?: string;
} }
+18 -3
View File
@@ -9,11 +9,26 @@ export function deploySlugFromHost(host: string): string {
.replace(/^-|-$/g, ''); .replace(/^-|-$/g, '');
} }
const GIT_SSH_RE = /** HTTPS or SSH git clone URLs (Meshkee Gitea and similar). */
/^(?:git@[\w.-]+:[\w./-]+\.git|ssh:\/\/git@[\w.-]+(?::\d+)?\/[\w./-]+\.git)$/i; export const GIT_REPO_URL_RE =
/^(?:https?:\/\/[\w.-]+(?::\d+)?\/[\w./-]+\.git|git@[\w.-]+:[\w./-]+\.git|ssh:\/\/git@[\w.-]+(?::\d+)?\/[\w./-]+\.git)$/i;
export function isValidGitRepoUrl(url: string): boolean { export function isValidGitRepoUrl(url: string): boolean {
return GIT_SSH_RE.test(url.trim()); return GIT_REPO_URL_RE.test(url.trim());
}
/**
* Websites VM clones with an SSH deploy key — convert HTTPS Gitea URLs to SSH.
* https://git.meshkee.com/Meshkee-Websites/oaktasty.git
* → git@git.meshkee.com:Meshkee-Websites/oaktasty.git
*/
export function normalizeGitRepoUrlForClone(url: string): string {
const trimmed = url.trim().replace(/\/+$/, '');
const https = trimmed.match(/^https?:\/\/([^/:]+)(?::\d+)?\/(.+\.git)$/i);
if (https) {
return `git@${https[1]}:${https[2]}`;
}
return trimmed;
} }
/** Turn .../deploy into .../provision (or append /provision if bare). */ /** Turn .../deploy into .../provision (or append /provision if bare). */