import '@johnlindquist/kit';
const apiBaseUrl = 'https://api.vercel.com';
const dashboardBaseUrl = 'https://vercel.com';
const VERCEL_ACCESS_TOKEN = await env('VERCEL_ACCESS_TOKEN', {
panel: md(`## Get a [Vercel API Access Token](https://vercel.com/account/tokens)`),
ignoreBlur: true,
secret: true,
});
const user = await fetchUser();
const projectsType = await selectProjectsType();
let team: Team | undefined | null = null;
if (projectsType === 'team') {
const teams = await fetchTeams();
team = await selectTeam(teams);
}
const projects = await fetchProjects(team?.id);
const project = await selectProject(projects);
if (!project) exit(-1);
await browse(`${dashboardBaseUrl}/${projectsType === 'team' ? team.slug : user.username}/${project.name}`);
type VercelApiError = {
error?: {
code: string;
message: string;
};
};
async function selectProjectsType() {
return arg<'personal' | 'team'>('Show personal or team projects', [
{
value: 'personal',
name: '[P]ersonal',
shortcut: 'p',
},
{
value: 'team',
name: '[T]eam',
shortcut: 't',
},
]);
}
type User = {
id: string;
email: string;
name: string | null;
username: string;
};
type GetUserResponse = { user: User } & VercelApiError;
async function fetchUser() {
try {
const res = await get<GetUserResponse>(`${apiBaseUrl}/v2/user`, {
headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },
});
if (res.status !== 200 || res.data.error) exit();
return res.data.user;
} catch (e) {
exit(-1);
}
}
type Team = { id: string; name: string; slug: string; avatar: string | null };
type GetTeamsResponse = { teams?: Team[] } & VercelApiError;
async function fetchTeams() {
try {
const res = await get<GetTeamsResponse>(`${apiBaseUrl}/v2/teams`, {
headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },
});
if (res.status !== 200 || res.data.error) exit();
return res.data.teams;
} catch (e) {
exit(-1);
}
}
async function selectTeam(teams: Team[]) {
return await arg<Team>(
{
placeholder: teams.length ? 'Select a team' : 'No teams found',
onChoiceFocus: (input, { focused }) => {
setPlaceholder(focused.name);
},
enter: `Select team`,
},
teams.map((team) => ({
value: team,
name: team.name,
img: team.avatar ? `https://vercel.com/api/www/avatar/${team.avatar}?s=128` : '',
}))
);
}
type Project = { id: string; name: string; latestDeployments: { alias: string[] }[] };
type GetProjectsResponse = { projects?: Project[] } & VercelApiError;
async function fetchProjects(teamId?: string | null | undefined) {
try {
const res = await get<GetProjectsResponse>(`${apiBaseUrl}/v9/projects${teamId ? `?teamId=${teamId}` : ''}`, {
headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },
});
if (res.status !== 200 || res.data.error) exit();
return res.data.projects;
} catch (e) {
exit(-1);
}
}
async function selectProject(projects: Project[]) {
return await arg<Project>(
{
placeholder: projects.length ? 'Select a project' : 'No projects found',
onChoiceFocus: (input, { focused }) => {
setPlaceholder(focused.name);
},
enter: `Open project in dashboard`,
},
projects.map((project) => ({
value: project,
name: project.name,
}))
);
}