mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-22 23:28:33 -04:00
fix: handle GH app error cases and add read/write warnings (#8373)
* fix: handle GH app error cases and add read/write warnings
This commit is contained in:
@@ -7,6 +7,10 @@ export default (app: Application) => {
|
||||
id: 123456,
|
||||
full_name: 'kong-test/sleepless',
|
||||
clone_url: 'https://github.com/kong-test/sleepless.git',
|
||||
permissions: {
|
||||
push: true,
|
||||
pull: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -422,11 +422,13 @@ export const cloneGitRepoAction = async ({
|
||||
if (e instanceof Errors.HttpError) {
|
||||
return {
|
||||
errors: [`${e.message}, ${e.data.response}`],
|
||||
gitRepository: repoSettingsPatch,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
errors: [e.message],
|
||||
gitRepository: repoSettingsPatch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1184,6 +1186,7 @@ export const deleteGitBranchAction = async ({
|
||||
|
||||
export interface PushToGitRemoteResult {
|
||||
errors?: string[];
|
||||
gitRepository?: GitRepository;
|
||||
}
|
||||
|
||||
export const pushToGitRemoteAction = async ({
|
||||
@@ -1206,16 +1209,18 @@ export const pushToGitRemoteAction = async ({
|
||||
if (err instanceof Errors.HttpError) {
|
||||
return {
|
||||
errors: [`${err.message}, ${err.data.response}`],
|
||||
gitRepository,
|
||||
};
|
||||
}
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown Error';
|
||||
|
||||
return { errors: [errorMessage] };
|
||||
return { errors: [errorMessage], gitRepository };
|
||||
}
|
||||
// If nothing to push, display that to the user
|
||||
if (!canPush) {
|
||||
return {
|
||||
errors: ['Nothing to push'],
|
||||
gitRepository,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1249,11 +1254,13 @@ export const pushToGitRemoteAction = async ({
|
||||
if (err instanceof Errors.PushRejectedError) {
|
||||
return {
|
||||
errors: [`Push Rejected, ${errorMessage}`],
|
||||
gitRepository,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
errors: [`Error Pushing Repository, ${errorMessage}`],
|
||||
gitRepository,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1660,6 +1667,101 @@ async function signOutOfGitHub() {
|
||||
}
|
||||
}
|
||||
|
||||
interface GitHubRepositoryApiResponse {
|
||||
id: string;
|
||||
full_name: string;
|
||||
clone_url: string;
|
||||
permissions: {
|
||||
push: boolean;
|
||||
pull: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
type GitHubRepositoriesApiResponse = GitHubRepositoryApiResponse[];
|
||||
|
||||
const GITHUB_USER_REPOS_URL = `${getGitHubRestApiUrl()}/user/repos`;
|
||||
|
||||
async function getGitHubRepositories(
|
||||
{ url = `${GITHUB_USER_REPOS_URL}?per_page=100`, repos = [] }:
|
||||
{ url?: string; repos?: GitHubRepositoriesApiResponse }
|
||||
) {
|
||||
const credentials = await models.gitCredentials.getByProvider('github');
|
||||
const opts = {
|
||||
headers: {
|
||||
Authorization: `token ${credentials?.token}`,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(url, opts);
|
||||
if (!response.ok) {
|
||||
const raw = await response.text();
|
||||
if (response.status === 401) {
|
||||
|
||||
return {
|
||||
errors: [`User token not authorized to fetch repositories, please sign out and back in.\nResponse: ${raw}`],
|
||||
repos: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
errors: [`Failed to fetch repositories from GitHub: ${response.statusText}\nResponse: ${raw}`],
|
||||
repos: [],
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
let pullableRepos = data.filter((repo: GitHubRepositoryApiResponse) => repo.permissions.pull);
|
||||
repos.push(...pullableRepos);
|
||||
|
||||
const link = response.headers.get('link');
|
||||
if (link && link.includes('rel="last"')) {
|
||||
const last = link.match(/<([^>]+)>; rel="last"/)?.[1];
|
||||
if (last) {
|
||||
const lastUrl = new URL(last);
|
||||
const lastPage = lastUrl.searchParams.get('page');
|
||||
if (lastPage) {
|
||||
const pages = Number(lastPage);
|
||||
const pageList = await Promise.all(Array.from({ length: pages - 1 }, (_, i) => fetch(`${GITHUB_USER_REPOS_URL}?per_page=100&page=${i + 2}`, opts)));
|
||||
for (const page of pageList) {
|
||||
const pageData = await page.json();
|
||||
pullableRepos = pageData.filter((repo: GitHubRepositoryApiResponse) => repo.permissions.pull);
|
||||
repos.push(...pullableRepos);
|
||||
}
|
||||
return { repos, errors: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (link && link.includes('rel="next"')) {
|
||||
const next = link.match(/<([^>]+)>; rel="next"/)?.[1];
|
||||
if (next) {
|
||||
return getGitHubRepositories({ url: next, repos });
|
||||
}
|
||||
}
|
||||
return { repos, errors: [] };
|
||||
}
|
||||
|
||||
async function getGitHubRepository({ uri }: { uri: string }) {
|
||||
const [owner, name] = uri.replace('.git', '').split('/').slice(-2); // extracts the owner + name
|
||||
|
||||
const credentials = await models.gitCredentials.getByProvider('github');
|
||||
const opts = {
|
||||
headers: {
|
||||
Authorization: `token ${credentials?.token}`,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(`${getGitHubRestApiUrl()}/repos/${owner}/${name}`, opts);
|
||||
if (!response.ok) {
|
||||
const raw = await response.text();
|
||||
return {
|
||||
errors: [`Failed to fetch repository from GitHub: ${response.statusText}\nResponse: ${raw}`],
|
||||
notFound: response.status === 404,
|
||||
};
|
||||
}
|
||||
|
||||
return { repo: await response.json() as GitHubRepositoryApiResponse, errors: [], notFound: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* This cache stores the states that are generated for the OAuth flow.
|
||||
* This is used to check if a command to exchange a code for a token has been initiated by the app or not.
|
||||
@@ -1856,9 +1958,13 @@ export interface GitServiceAPI {
|
||||
stageChanges: typeof stageChangesAction;
|
||||
unstageChanges: typeof unstageChangesAction;
|
||||
diffFileLoader: typeof diffFileLoader;
|
||||
|
||||
initSignInToGitHub: typeof initSignInToGitHub;
|
||||
completeSignInToGitHub: typeof completeSignInToGitHub;
|
||||
signOutOfGitHub: typeof signOutOfGitHub;
|
||||
getGitHubRepositories: typeof getGitHubRepositories;
|
||||
getGitHubRepository: typeof getGitHubRepository;
|
||||
|
||||
initSignInToGitLab: typeof initSignInToGitLab;
|
||||
completeSignInToGitLab: typeof completeSignInToGitLab;
|
||||
signOutOfGitLab: typeof signOutOfGitLab;
|
||||
@@ -1888,10 +1994,14 @@ export const registerGitServiceAPI = () => {
|
||||
ipcMainHandle('git.stageChanges', (_, options: Parameters<typeof stageChangesAction>[0]) => stageChangesAction(options));
|
||||
ipcMainHandle('git.unstageChanges', (_, options: Parameters<typeof unstageChangesAction>[0]) => unstageChangesAction(options));
|
||||
ipcMainHandle('git.diffFileLoader', (_, options: Parameters<typeof diffFileLoader>[0]) => diffFileLoader(options));
|
||||
ipcMainHandle('git.completeSignInToGitHub', (_, options: Parameters<typeof completeSignInToGitHub>[0]) => completeSignInToGitHub(options));
|
||||
|
||||
ipcMainHandle('git.initSignInToGitHub', () => initSignInToGitHub());
|
||||
ipcMainHandle('git.completeSignInToGitHub', (_, options: Parameters<typeof completeSignInToGitHub>[0]) => completeSignInToGitHub(options));
|
||||
ipcMainHandle('git.signOutOfGitHub', () => signOutOfGitHub());
|
||||
ipcMainHandle('git.completeSignInToGitLab', (_, options: Parameters<typeof completeSignInToGitLab>[0]) => completeSignInToGitLab(options));
|
||||
ipcMainHandle('git.getGitHubRepositories', (_, options: Parameters<typeof getGitHubRepositories>[0]) => getGitHubRepositories(options));
|
||||
ipcMainHandle('git.getGitHubRepository', (_, options: Parameters<typeof getGitHubRepository>[0]) => getGitHubRepository(options));
|
||||
|
||||
ipcMainHandle('git.initSignInToGitLab', () => initSignInToGitLab());
|
||||
ipcMainHandle('git.completeSignInToGitLab', (_, options: Parameters<typeof completeSignInToGitLab>[0]) => completeSignInToGitLab(options));
|
||||
ipcMainHandle('git.signOutOfGitLab', () => signOutOfGitLab());
|
||||
};
|
||||
|
||||
@@ -60,6 +60,8 @@ export type HandleChannels =
|
||||
| 'git.initSignInToGitHub'
|
||||
| 'git.completeSignInToGitHub'
|
||||
| 'git.signOutOfGitHub'
|
||||
| 'git.getGitHubRepositories'
|
||||
| 'git.getGitHubRepository'
|
||||
| 'git.initSignInToGitLab'
|
||||
| 'git.completeSignInToGitLab'
|
||||
| 'git.signOutOfGitLab';
|
||||
|
||||
@@ -75,12 +75,16 @@ const git: GitServiceAPI = {
|
||||
stageChanges: options => ipcRenderer.invoke('git.stageChanges', options),
|
||||
unstageChanges: options => ipcRenderer.invoke('git.unstageChanges', options),
|
||||
diffFileLoader: options => ipcRenderer.invoke('git.diffFileLoader', options),
|
||||
|
||||
initSignInToGitHub: () => ipcRenderer.invoke('git.initSignInToGitHub'),
|
||||
completeSignInToGitHub: options => ipcRenderer.invoke('git.completeSignInToGitHub', options),
|
||||
signOutOfGitHub: () => ipcRenderer.invoke('git.signOutOfGitHub'),
|
||||
getGitHubRepositories: options => ipcRenderer.invoke('git.getGitHubRepositories', options),
|
||||
getGitHubRepository: options => ipcRenderer.invoke('git.getGitHubRepository', options),
|
||||
|
||||
initSignInToGitLab: () => ipcRenderer.invoke('git.initSignInToGitLab'),
|
||||
signOutOfGitLab: () => ipcRenderer.invoke('git.signOutOfGitLab'),
|
||||
completeSignInToGitLab: options => ipcRenderer.invoke('git.completeSignInToGitLab', options),
|
||||
signOutOfGitLab: () => ipcRenderer.invoke('git.signOutOfGitLab'),
|
||||
};
|
||||
|
||||
const main: Window['main'] = {
|
||||
|
||||
@@ -601,7 +601,7 @@ export class GitVCS {
|
||||
url: remote.url,
|
||||
});
|
||||
const logs = (await this.log({ depth: 1 })) || [];
|
||||
const localHead = logs[0].oid;
|
||||
const localHead = logs[0]?.oid;
|
||||
const remoteRefs = remoteInfo.refs || {};
|
||||
const remoteHeads = remoteRefs.heads || {};
|
||||
const remoteHead = remoteHeads[branch];
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
pullFromGitRemote,
|
||||
type PushToGitRemoteResult,
|
||||
} from '../../routes/git-actions';
|
||||
import { ConfigLink } from '../github-app-config-link';
|
||||
import { Icon } from '../icon';
|
||||
import { showAlert, showModal } from '../modals';
|
||||
import { GitBranchesModal } from '../modals/git-branches-modal';
|
||||
@@ -92,10 +93,13 @@ export const GitSyncDropdown: FC<Props> = ({ gitRepository, isInsomniaSyncEnable
|
||||
if (errors.length > 0) {
|
||||
showAlert({
|
||||
title: 'Push Failed',
|
||||
message: errors.join('\n'),
|
||||
message: <>
|
||||
{errors.join('\n')}
|
||||
<ConfigLink {...gitPushFetcher.data} />
|
||||
</>,
|
||||
});
|
||||
}
|
||||
}, [gitPushFetcher.data?.errors]);
|
||||
}, [gitPushFetcher.data]);
|
||||
|
||||
useEffect(() => {
|
||||
const gitRepoDataErrors =
|
||||
@@ -104,12 +108,15 @@ export const GitSyncDropdown: FC<Props> = ({ gitRepository, isInsomniaSyncEnable
|
||||
: [];
|
||||
const errors = [...gitRepoDataErrors];
|
||||
if (errors.length > 0) {
|
||||
if (isGitRepoSettingsModalOpen) { // user just clicked 'Reset'
|
||||
return;
|
||||
}
|
||||
showAlert({
|
||||
title: 'Loading of Git Repository Failed',
|
||||
message: errors.join('\n'),
|
||||
});
|
||||
}
|
||||
}, [gitRepoDataFetcher.data]);
|
||||
}, [isGitRepoSettingsModalOpen, gitRepoDataFetcher.data]);
|
||||
|
||||
useEffect(() => {
|
||||
const errors = [...(gitCheckoutFetcher.data?.errors ?? [])];
|
||||
@@ -211,7 +218,11 @@ export const GitSyncDropdown: FC<Props> = ({ gitRepository, isInsomniaSyncEnable
|
||||
} else {
|
||||
showAlert({
|
||||
title: 'Pull Failed',
|
||||
message: err.message,
|
||||
message: <>
|
||||
{err.message}
|
||||
<ConfigLink {...{ gitRepository, errors: [err.message] }} />
|
||||
</>,
|
||||
|
||||
bodyClassName: 'whitespace-break-spaces',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getAppWebsiteBaseURL } from '../../common/constants';
|
||||
import type { GitRepository } from '../../models/git-repository';
|
||||
import { getOauth2FormatName } from '../../sync/git/utils';
|
||||
|
||||
interface ConfigLinkProps {
|
||||
small?: boolean;
|
||||
gitRepository?: GitRepository | null;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
export function isGitHubAppUserToken(token?: string) {
|
||||
// old oauth tokens start with 'gho_' and app user tokens start with 'ghu_'
|
||||
return `${token}`.startsWith('ghu_');
|
||||
}
|
||||
|
||||
export const ConfigLink = ({ small = false, gitRepository = null, errors = [] }: ConfigLinkProps) => {
|
||||
const show = gitRepository?.credentials && 'oauth2format' in gitRepository?.credentials && getOauth2FormatName(gitRepository?.credentials) === 'github' && isGitHubAppUserToken(gitRepository?.credentials.token) && errors && errors?.length > 0 && errors[0].startsWith('HTTP Error: 40');
|
||||
|
||||
return show && <p className={`text-${small ? 'sm' : 'md'}`}>You may need to <a className="underline text-purple-500" href={`${getAppWebsiteBaseURL()}/oauth/github-app`}>Configure the App <i className="fa-solid fa-up-right-from-square" /></a></p>;
|
||||
};
|
||||
@@ -84,7 +84,7 @@ export const GitProjectLogModal: FC<Props> = ({ onClose }) => {
|
||||
</div>
|
||||
)}
|
||||
className="divide divide-[--hl-sm] divide-solid"
|
||||
items={log.map(logEntry => ({ id: logEntry.oid, ...logEntry }))}
|
||||
items={log.filter(l => !!l).map(logEntry => ({ id: logEntry.oid, ...logEntry }))}
|
||||
>
|
||||
{item => (
|
||||
<Row className="group focus:outline-none focus-within:bg-[--hl-xxs] transition-colors">
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button as ComboButton, ComboBox, Input, ListBox, ListBoxItem, Popover } from 'react-aria-components';
|
||||
|
||||
// import { useFetcher, useParams } from 'react-router-dom';
|
||||
import { getAppWebsiteBaseURL, getGitHubRestApiUrl } from '../../../../common/constants';
|
||||
import { getAppWebsiteBaseURL } from '../../../../common/constants';
|
||||
import { isGitHubAppUserToken } from '../../github-app-config-link';
|
||||
import { Icon } from '../../icon';
|
||||
import { Button } from '../../themed-button';
|
||||
import { showError } from '..';
|
||||
|
||||
// fragment of what we receive from the GitHub API
|
||||
interface GitHubRepository {
|
||||
id: string;
|
||||
full_name: string;
|
||||
clone_url: string;
|
||||
}
|
||||
|
||||
const GITHUB_USER_REPOS_URL = `${getGitHubRestApiUrl()}/user/repos`;
|
||||
|
||||
function isGitHubAppUserToken(token: string) {
|
||||
// old oauth tokens start with 'gho_' and app user tokens start with 'ghu_'
|
||||
return token.startsWith('ghu_');
|
||||
}
|
||||
type GitHubRepository = Awaited<ReturnType<typeof window.main.git.getGitHubRepositories>>['repos'][number];
|
||||
|
||||
export const GitHubRepositorySelect = (
|
||||
{ uri, token }: {
|
||||
@@ -28,61 +18,42 @@ export const GitHubRepositorySelect = (
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [repositories, setRepositories] = useState<GitHubRepository[]>([]);
|
||||
const [selectedRepository, setSelectedRepository] = useState<GitHubRepository | null>(null);
|
||||
const [cannotFindRepository, setCannotFindRepository] = useState(false);
|
||||
|
||||
// this method assumes that GitHub will not change how it paginates this endpoint
|
||||
const fetchRepositories = useCallback(async (url: string = `${GITHUB_USER_REPOS_URL}?per_page=100`) => {
|
||||
try {
|
||||
const opts = {
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
},
|
||||
};
|
||||
const response = await fetch(url, opts);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch repositories');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setRepositories(repos => ([...repos, ...data]));
|
||||
const link = response.headers.get('link');
|
||||
if (link && link.includes('rel="last"')) {
|
||||
const last = link.match(/<([^>]+)>; rel="last"/)?.[1];
|
||||
if (last) {
|
||||
const lastUrl = new URL(last);
|
||||
const lastPage = lastUrl.searchParams.get('page');
|
||||
if (lastPage) {
|
||||
const pages = Number(lastPage);
|
||||
const pageList = await Promise.all(Array.from({ length: pages - 1 }, (_, i) => fetch(`${GITHUB_USER_REPOS_URL}?per_page=100&page=${i + 2}`, opts)));
|
||||
for (const page of pageList) {
|
||||
const pageData = await page.json();
|
||||
setRepositories(repos => ([...repos, ...pageData]));
|
||||
setLoading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (link && link.includes('rel="next"')) {
|
||||
const next = link.match(/<([^>]+)>; rel="next"/)?.[1];
|
||||
fetchRepositories(next);
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
const getRepositories = async () => {
|
||||
setLoading(true);
|
||||
setRepositories([]);
|
||||
const { repos, errors } = await window.main.git.getGitHubRepositories({});
|
||||
if (errors.length) {
|
||||
showError({
|
||||
title: 'Error fetching repositories',
|
||||
message: errors.join('\n'),
|
||||
});
|
||||
}
|
||||
}, [token]);
|
||||
setRepositories(repos);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!token || uri) {
|
||||
return;
|
||||
}
|
||||
getRepositories();
|
||||
}, [token, uri]);
|
||||
|
||||
setLoading(true);
|
||||
|
||||
fetchRepositories();
|
||||
}, [token, uri, fetchRepositories]);
|
||||
useEffect(() => {
|
||||
if (!uri) {
|
||||
setCannotFindRepository(false);
|
||||
return;
|
||||
}
|
||||
if ((!selectedRepository) && token && isGitHubAppUserToken(token)) {
|
||||
(async function getRepository() {
|
||||
const { repo, errors, notFound } = await window.main.git.getGitHubRepository({ uri });
|
||||
setCannotFindRepository(notFound);
|
||||
setSelectedRepository(errors.length ? null : repo!);
|
||||
})();
|
||||
}
|
||||
}, [selectedRepository, token, uri]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -126,17 +97,20 @@ export const GitHubRepositorySelect = (
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
setRepositories([]);
|
||||
fetchRepositories();
|
||||
getRepositories();
|
||||
}}
|
||||
>
|
||||
<Icon icon="refresh" />
|
||||
</Button>
|
||||
</div>
|
||||
{isGitHubAppUserToken(token) && <div className="flex gap-1 text-sm">
|
||||
Can't find a repository?
|
||||
<a className="underline text-purple-500" href={`${getAppWebsiteBaseURL()}/oauth/github-app`}>Configure the App <i className="fa-solid fa-up-right-from-square" /></a>
|
||||
</div>}</>}
|
||||
{isGitHubAppUserToken(token) &&
|
||||
<div className="flex gap-1 text-sm">
|
||||
Can't find a repository?
|
||||
<a className="underline text-purple-500" href={`${getAppWebsiteBaseURL()}/oauth/github-app`}>Configure the App <i className="fa-solid fa-up-right-from-square" /></a>
|
||||
</div>}
|
||||
</>}
|
||||
{cannotFindRepository && <div className="text-sm text-red-500"><Icon icon="warning" /> Repository information could not be retrieved. Please <code>Reset</code> and select a different repository.</div>}
|
||||
{selectedRepository !== null && !selectedRepository.permissions.push && <div className="text-sm text-orange-500 mt-2"><Icon icon="warning" /> You do not have write access to this repository</div>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -206,7 +206,7 @@ const GitHubSignInForm = () => {
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<input name="link" />
|
||||
<Button type="submit" name="add-token">Authenticate</Button>
|
||||
<Button className="bg-violet-400 bold p-2 rounded" type="submit" name="add-token">Authenticate</Button>
|
||||
</div>
|
||||
</label>
|
||||
{error && (
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { type FC, useEffect } from 'react';
|
||||
import { Button, Dialog, GridList, GridListItem, Heading, Label, Modal, ModalOverlay, TextArea, TextField, Tooltip, TooltipTrigger } from 'react-aria-components';
|
||||
import { useFetcher, useParams } from 'react-router-dom';
|
||||
|
||||
import type { GitRepository } from '../../../models/git-repository';
|
||||
import type { GitChangesLoaderData, GitDiffResult } from '../../routes/git-actions';
|
||||
import { DiffEditor } from '../diff-view-editor';
|
||||
import { ConfigLink } from '../github-app-config-link';
|
||||
import { Icon } from '../icon';
|
||||
import { showAlert } from '.';
|
||||
|
||||
@@ -101,7 +103,7 @@ export const GitStagingModal: FC<{ onClose: () => void }> = ({
|
||||
statusNames: {},
|
||||
};
|
||||
|
||||
const { Form, formAction, state, data } = useFetcher<{ errors?: string[] }>();
|
||||
const { Form, formAction, state, data } = useFetcher<{ errors?: string[]; gitRepository: GitRepository }>();
|
||||
|
||||
const isCreatingSnapshot = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit';
|
||||
const isPushing = state === 'loading' && formAction === '/organization/:organizationId/project/:projectId/workspace/:workspaceId/git/commit-and-push';
|
||||
@@ -185,6 +187,7 @@ export const GitStagingModal: FC<{ onClose: () => void }> = ({
|
||||
{data && data.errors && data.errors.length > 0 && (
|
||||
<p className="bg-opacity-20 text-sm text-[--color-font-danger] p-2 rounded-sm bg-[rgba(var(--color-danger-rgb),var(--tw-bg-opacity))]">
|
||||
<Icon icon="exclamation-triangle" /> {data.errors.join('\n')}
|
||||
<ConfigLink small {...data} />
|
||||
</p>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -229,6 +229,7 @@ export const resetGitRepoAction: ActionFunction = async ({ params }) => {
|
||||
|
||||
export interface CommitToGitRepoResult {
|
||||
errors?: string[];
|
||||
gitRepository?: GitRepository;
|
||||
}
|
||||
|
||||
export const commitToGitRepoAction: ActionFunction = async ({
|
||||
@@ -353,6 +354,7 @@ export const deleteGitBranchAction: ActionFunction = async ({
|
||||
|
||||
export interface PushToGitRemoteResult {
|
||||
errors?: string[];
|
||||
gitRepository?: GitRepository;
|
||||
}
|
||||
|
||||
export const pushToGitRemoteAction: ActionFunction = async ({
|
||||
|
||||
@@ -30,15 +30,11 @@
|
||||
"include": [
|
||||
".eslintrc.js",
|
||||
"config",
|
||||
"electron-builder.config.js",
|
||||
"esbuild.main.ts",
|
||||
"esbuild.sr.ts",
|
||||
"package.json",
|
||||
"scripts",
|
||||
"send-request",
|
||||
"src",
|
||||
"tailwind.config.js",
|
||||
"postcss.config.js",
|
||||
"vite.config.ts",
|
||||
"vite-plugin-electron-node-require",
|
||||
"**/*.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user