Skip to content
Open
17 changes: 17 additions & 0 deletions src/lib/helpers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,20 @@ export async function getDefaultBranch(owner: string, name: string): Promise<str
return null;
}
}

export async function getBranches(owner: string, name: string): Promise<string[] | null> {
try {
const branchesResponse = await fetch(
`https://api.github.com/repos/${owner}/${name}/branches`
);
if (!branchesResponse.ok) {
return null;
}

const branches = await branchesResponse.json();
return branches.map((branch) => branch.name);
} catch (error) {
console.error('Failed to fetch branches from GitHub:', error);
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { iconPath } from '$lib/stores/app';
import type { PageData } from './$types';
import { getLatestTag } from '$lib/helpers/github';
import { getDefaultBranch, getBranches } from '$lib/helpers/github';
import { writable } from 'svelte/store';
import Link from '$lib/elements/link.svelte';

Expand All @@ -42,8 +42,9 @@
let selectedScopes = $state<string[]>([]);
let rootDir = $state(data.repository?.rootDirectory);
let variables = $state<Array<{ key: string; value: string; secret: boolean }>>([]);

let latestTag = $state(null);
let branches = $state<string[]>([]);
let selectedBranch = $state<string>('');
let loadingBranches = $state(false);

const specificationOptions = $derived(
data.specificationsList?.specifications?.map((size) => ({
Expand All @@ -63,7 +64,7 @@
})) || []
);

onMount(() => {
onMount(async () => {
const runtimeParam = data.runtime || page.url.searchParams.get('runtime') || 'node-18.0';
runtime = runtimeParam as Runtime;

Expand All @@ -80,19 +81,61 @@
variables = data.envKeys.map((key) => ({ key, value: '', secret: false }));
}

getLatestTag(data.repository.owner, data.repository.name).then(
(tagName) => (latestTag = tagName)
);
// Load branches and set default branch
if (data.repository?.owner && data.repository?.name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coupling us very hard to GitHub, this logic has to be part of Utopia/VCS if its not already there, which is also very likely.

loadingBranches = true;
try {
const [branchList, defaultBranch] = await Promise.all([
getBranches(data.repository.owner, data.repository.name),
getDefaultBranch(data.repository.owner, data.repository.name)
]);

if (branchList && branchList.length > 0) {
branches = branchList;
// Pre-select default branch, or first branch if default not found
selectedBranch =
defaultBranch && branchList.includes(defaultBranch)
? defaultBranch
: branchList[0];
} else {
// Branch list is empty or null
addNotification({
type: 'error',
message:
'Failed to load branches from repository. Please check the repository URL or try again.'
});
}
} catch (error) {
console.error('Failed to load branches:', error);
addNotification({
type: 'error',
message:
'Failed to load branches from repository. Please check the repository URL or try again.'
});
} finally {
loadingBranches = false;
}
} else {
// Repository info is missing
addNotification({
type: 'error',
message: 'Repository information is missing. Please check the repository URL.'
});
}
});

async function create() {
if (!selectedBranch || branches.length === 0) {
addNotification({
type: 'error',
message: 'Please wait for branches to load or check the repository URL.'
});
return;
}

$isSubmitting = true;

try {
if (!latestTag) {
latestTag = await getLatestTag(data.repository.owner, data.repository.name);
}

// Create function with configuration
const func = await sdk
.forProject(page.params.region, page.params.project)
Expand Down Expand Up @@ -126,16 +169,16 @@

await Promise.all(promises);

// Create deployment from GitHub repository using the latest tag
// Create deployment from GitHub repository using the selected branch
await sdk
.forProject(page.params.region, page.params.project)
.functions.createTemplateDeployment({
functionId: func.$id,
repository: data.repository.name,
owner: data.repository.owner,
rootDirectory: rootDir || '.',
type: Type.Tag,
reference: latestTag ?? '1.0.0',
type: Type.Branch,
reference: selectedBranch,
activate: true
});

Expand Down Expand Up @@ -220,6 +263,22 @@
</Layout.Stack>
</Fieldset>

<Fieldset legend="Git configuration">
<Layout.Stack gap="m">
<Input.Select
id="branch"
label="Branch"
required
placeholder={loadingBranches ? 'Loading branches...' : 'Select branch'}
bind:value={selectedBranch}
disabled={loadingBranches}
options={branches.map((branch) => ({
value: branch,
label: branch
}))} />
</Layout.Stack>
</Fieldset>

<Fieldset legend="Build configuration">
<Layout.Stack gap="m">
<Input.Text
Expand Down Expand Up @@ -276,7 +335,12 @@
fullWidthMobile
submissionLoader
forceShowLoader={$isSubmitting}
disabled={!name || !runtime || !specification || $isSubmitting}>
disabled={!name ||
!runtime ||
!specification ||
!selectedBranch ||
branches.length === 0 ||
$isSubmitting}>
Deploy function
</Button>
</Layout.Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { iconPath } from '$lib/stores/app';
import type { PageData } from './$types';
import { writable } from 'svelte/store';
import { getLatestTag } from '$lib/helpers/github';
import { getDefaultBranch, getBranches } from '$lib/helpers/github';
import Link from '$lib/elements/link.svelte';

let {
Expand All @@ -44,6 +44,9 @@
let domainIsValid = $state(false);
let framework = $state<Framework>(Framework.Nextjs);
let variables = $state<Array<{ key: string; value: string; secret: boolean }>>([]);
let branches = $state<string[]>([]);
let selectedBranch = $state<string>('');
let loadingBranches = $state(false);

// Track if we have custom commands from URL
let hasCustomCommands = $state(false);
Expand Down Expand Up @@ -86,7 +89,7 @@
}
});

onMount(() => {
onMount(async () => {
const preset = page.url.searchParams.get('preset') || 'nextjs';

// Map preset string to Framework enum
Expand Down Expand Up @@ -117,6 +120,48 @@
if (data.envKeys.length > 0) {
variables = data.envKeys.map((key) => ({ key, value: '', secret: false }));
}

// Load branches and set default branch
if (data.repository?.owner && data.repository?.name) {
loadingBranches = true;
try {
const [branchList, defaultBranch] = await Promise.all([
getBranches(data.repository.owner, data.repository.name),
getDefaultBranch(data.repository.owner, data.repository.name)
]);

if (branchList && branchList.length > 0) {
branches = branchList;
// Pre-select default branch, or first branch if default not found
selectedBranch =
defaultBranch && branchList.includes(defaultBranch)
? defaultBranch
: branchList[0];
} else {
// Branch list is empty or null
addNotification({
type: 'error',
message:
'Failed to load branches from repository. Please check the repository URL or try again.'
});
}
} catch (error) {
console.error('Failed to load branches:', error);
addNotification({
type: 'error',
message:
'Failed to load branches from repository. Please check the repository URL or try again.'
});
} finally {
loadingBranches = false;
}
} else {
// Repository info is missing
addNotification({
type: 'error',
message: 'Repository information is missing. Please check the repository URL.'
});
}
});

async function create() {
Expand All @@ -128,6 +173,14 @@
return;
}

if (!selectedBranch || branches.length === 0) {
addNotification({
type: 'error',
message: 'Please wait for branches to load or check the repository URL.'
});
return;
}

$isSubmitting = true;

try {
Expand Down Expand Up @@ -161,19 +214,16 @@
);
await Promise.all(promises);

// Fetch latest tag from GitHub
const latestTag = await getLatestTag(data.repository.owner, data.repository.name);

// Create deployment from GitHub repository using the latest tag
// Create deployment from GitHub repository using the selected branch
const deployment = await sdk
.forProject(page.params.region, page.params.project)
.sites.createTemplateDeployment({
siteId: site.$id,
repository: data.repository.name,
owner: data.repository.owner,
rootDirectory: rootDir || '.',
type: Type.Tag,
reference: latestTag ?? '1.0.0',
type: Type.Branch,
reference: selectedBranch,
activate: true
});

Expand Down Expand Up @@ -252,6 +302,17 @@

<Fieldset legend="Git configuration">
<Layout.Stack gap="m">
<Input.Select
id="branch"
label="Branch"
required
placeholder={loadingBranches ? 'Loading branches...' : 'Select branch'}
bind:value={selectedBranch}
disabled={loadingBranches}
options={branches.map((branch) => ({
value: branch,
label: branch
}))} />
<Input.Text label="Root directory" placeholder="./" bind:value={rootDir} />
</Layout.Stack>
</Fieldset>
Expand Down