Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2e490bf
Initial plan
Copilot Mar 9, 2026
f286832
Add UTexas GitHub Enterprise Managed User login button
Copilot Mar 9, 2026
995c94d
Update README with OAuth App creation instructions
saracarl Jun 19, 2026
5d03eb8
fix: use window.location.origin for UTexas OAuth redirect_uri
Copilot Jun 19, 2026
32ac1fb
fix: use enterprise template for UTexas EMU project creation
Copilot Jun 19, 2026
94d5c4b
Remove Astro Basics section from README
saracarl Jun 30, 2026
97ab509
Improve GitHub API error logging for project creation
saracarl Jun 30, 2026
6ffbbaf
Merge pull request #442 from AVAnnotate/codex/debug-avannotate-projec…
saracarl Jun 30, 2026
e3e42c2
Log template repository creation request
saracarl Jun 30, 2026
bd04b1f
Merge branch 'copilot/add-utexas-login-button' into codex/update-crea…
saracarl Jun 30, 2026
537d27f
Merge pull request #443 from AVAnnotate/codex/update-createrepository…
saracarl Jun 30, 2026
d91ae2d
Enterprise-aware GitHub project creation & internal visibility support
saracarl Jun 30, 2026
2558f02
Merge branch 'copilot/add-utexas-login-button' into codex/implement-b…
saracarl Jul 6, 2026
5a6464a
Merge pull request #444 from AVAnnotate/codex/implement-bimodal-githu…
saracarl Jul 6, 2026
72d6291
Replace visibility with repoVisibility in API call
saracarl Jul 6, 2026
b98921f
Fix formatting issue in API project index file
saracarl Jul 6, 2026
4704dd1
Add 'internal' visibility option for repository creation
saracarl Jul 6, 2026
7a82ada
Implement GitHub failure logging function
saracarl Jul 6, 2026
7bfb961
Add publish include_slug_in_base flag
Copilot Jul 13, 2026
cc6d9a8
Preserve include_slug_in_base after project creation
Copilot Jul 14, 2026
264eab7
fix: auto-enable GitHub Pages (Actions source) for EMU repo creation
Copilot Jul 14, 2026
82aa210
fix: auto-enable GitHub Pages (Actions source) for EMU repo creation
Copilot Jul 14, 2026
d8bdddb
fix: create internal repos as private initially and fix template URL …
Copilot Jul 14, 2026
2e494eb
fix: correctly change EMU repo visibility to internal in both create …
Copilot Jul 17, 2026
a15f296
Merge pull request #445 from AVAnnotate/copilot/implement-two-step-re…
saracarl Jul 17, 2026
5466d24
fix: add delay and retry for EMU internal visibility change, improve …
Copilot Jul 17, 2026
559ab87
Merge pull request #446 from AVAnnotate/copilot/fix-repo-creation-vis…
saracarl Jul 17, 2026
6341d43
feat: exclude personal account for EMU users on new project form
Copilot Jul 17, 2026
8f60337
Merge pull request #447 from AVAnnotate/copilot/display-organization-…
saracarl Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,24 @@ GIT_REPO_ADMIN_DATA="https://github.com/AVAnnotate/admin-data.git"
GIT_REPO_ORG="AVAnnotate"
GIT_ADMIN_REPO="admin"
PUBLIC_GIT_REPO_PROJECT_TEMPLATE="project-template"
PUBLIC_REDIRECT_URL="http://localhost:4321"
PUBLIC_REDIRECT_URL="http://localhost:4321"

# UTexas GitHub Enterprise Managed User (EMU) OAuth credentials
# Create an OAuth App inside the UTexas enterprise at:
# https://github.com/enterprises/utexas-internal
# Set the Authorization callback URL to: {PUBLIC_REDIRECT_URL}/git-enterprise
# Leave blank to hide the UTexas EID login button.
PUBLIC_UTEXAS_GITHUB_CLIENT_ID=
UTEXAS_GITHUB_CLIENT_SECRET=

# UTexas enterprise project template
# GitHub Enterprise Managed Users cannot access templates outside their enterprise.
# Fork AVAnnotate/project-template into a UTexas enterprise org, mark it as a
# template repository, then set these two variables so AVAnnotate can use it.
#
# UTEXAS_GIT_REPO_ORG – the GitHub org (inside the enterprise) that owns
# the forked template (e.g. "utexas-avannotate")
# UTEXAS_GIT_REPO_PROJECT_TEMPLATE – the repo name of the fork; defaults to
# "project-template" if left blank
UTEXAS_GIT_REPO_ORG=
UTEXAS_GIT_REPO_PROJECT_TEMPLATE=
64 changes: 24 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,41 @@ You can also run a local environment without linking to Netlify by running `npm

Note that running the site locally still requires authentication via GitHub, and changes made to projects will be applied, so be sure to create testing projects for playing around with any new and untested features.

# Astro Basics
### UTexas GitHub Enterprise Managed User (EMU) login

```sh
npm create astro@latest -- --template basics
```
A second "Sign in with UTexas EID" button can be shown on the sign-in page to let UTexas users authenticate via the [GitHub Enterprise Managed Users SSO](https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/configuring-authentication-for-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users) for the `utexas-internal` enterprise (<https://github.com/enterprises/utexas-internal>).

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
To enable this button you need a separate OAuth App registered inside the UTexas enterprise and two additional environment variables:

> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
| Variable | Description |
| --- | --- |
| `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` | Client ID of the OAuth App created inside the UTexas enterprise |
| `UTEXAS_GITHUB_CLIENT_SECRET` | Client secret for that OAuth App (server-side only) |

![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
**Creating the OAuth App:**
1. Navigate to <https://github.com/enterprises/utexas-internal> (requires enterprise admin access).
2. Go to *Settings → OAuth Apps → New OAuth App*. (this is actually under the developer section at the very bottom of your org's settings on the left.)
3. Set **Authorization callback URL** to `{PUBLIC_REDIRECT_URL}/git-enterprise` (e.g. `https://avannotate.netlify.app/git-enterprise`).
4. Copy the **Client ID** into `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` and generate/copy a **Client secret** into `UTEXAS_GITHUB_CLIENT_SECRET`.

## 🚀 Project Structure
When `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` is blank (the default), the UTexas EID button is hidden, so the sign-in page works exactly as before for non-UTexas deployments.

Inside of your Astro project, you'll see the following folders and files:
#### Creating projects as a UTexas EMU user

```text
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
GitHub Enterprise Managed Users are isolated to their enterprise and cannot access GitHub repositories outside it — including the AVAnnotate project template (`AVAnnotate/project-template`). Without extra setup, UTexas users will see a "repo create failed" error when they try to create a new project.

Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
To fix this, fork the template into the UTexas enterprise and tell AVAnnotate where to find it:

There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
1. **Fork the template** — inside the UTexas enterprise, fork or import [`AVAnnotate/project-template`](https://github.com/AVAnnotate/project-template) into a UTexas enterprise org (e.g. `utexas-avannotate/project-template`).
2. **Mark it as a template** — in the forked repo's *Settings → General*, check **"Template repository"**.
3. **Set the two new environment variables**:

Any static assets, like images, can be placed in the `public/` directory.
| Variable | Description |
| --- | --- |
| `UTEXAS_GIT_REPO_ORG` | GitHub org (inside the enterprise) that owns the forked template, e.g. `utexas-avannotate` |
| `UTEXAS_GIT_REPO_PROJECT_TEMPLATE` | Repo name of the fork; leave blank to use `project-template` |

## 🧞 Commands
With these variables set, UTexas users' project creation calls will use the enterprise copy of the template instead of the one outside the enterprise.

All commands are run from the root of the project, from a terminal:

| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |

## 👀 Want to learn more?

Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
2 changes: 1 addition & 1 deletion src/apps/NewProject/NewProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const NewProject = (props: NewProjectProps) => {
additionalUsers: project.additional_users.map((u) => u.login_name),
language: project.language,
autoPopulateHomePage: project.auto_populate_home_page,
visibility: project.is_private ? 'private' : 'public',
is_private: !!project.is_private,
generate_pages_site: !!project.generate_pages_site,
tags: project.tags,
};
Expand Down
20 changes: 14 additions & 6 deletions src/backend/projectHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,23 @@ export const parseURL = (url: string) => {
const TEMPLATE_BRANCH = import.meta.env.OVERRIDE_TEMPLATE_BRANCH || 'main';

export const getOrgs = async (
userInfo: UserInfo
userInfo: UserInfo,
authProvider?: string
): Promise<GitHubOrganization[]> => {
const orgs = await getUserOrgs(userInfo.token);

orgs.unshift({
login: userInfo.profile.gitHubName,
url: `https://https://github.com/${userInfo.profile.gitHubName}`,
description: '',
});
// Enterprise Managed Users (EMU) cannot create repositories in their personal
// account — only in organisations. Skip prepending the personal account when
// the session was initiated via an enterprise OAuth app (e.g. utexas).
const isEnterpriseUser = !!authProvider && authProvider !== '';

if (!isEnterpriseUser) {
orgs.unshift({
login: userInfo.profile.gitHubName,
url: `https://github.com/${userInfo.profile.gitHubName}`,
description: '',
});
}

return orgs.map((o) => ({
orgName: o.login,
Expand Down
32 changes: 21 additions & 11 deletions src/components/ProjectForm/NewProjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const FormContents = (props: NewProjectFormProps) => {
const { headerMap } = useContext(SpreadsheetInputContext);

const emptyProject: Project = {
github_org: props.orgs[0].orgName,
github_org: props.orgs[0]?.orgName ?? '',
is_private: false,
generate_pages_site: true,
title: '',
Expand Down Expand Up @@ -106,15 +106,25 @@ const FormContents = (props: NewProjectFormProps) => {
return (
<Form>
<h2>{t['General']}</h2>
<SelectInput
label={t['GitHub Organization']}
name='github_org'
options={props.orgs.map((o) => ({
value: o.orgName,
label: o.orgName,
}))}
required
/>
{props.orgs.length === 0 ? (
<div className='project-form-no-orgs-warning'>
{
t[
'_no_orgs_warning_'
]
}
</div>
) : (
<SelectInput
label={t['GitHub Organization']}
name='github_org'
options={props.orgs.map((o) => ({
value: o.orgName,
label: o.orgName,
}))}
required
/>
)}

<ToggleInput
label={t['Use Private Repository']}
Expand Down Expand Up @@ -193,7 +203,7 @@ const FormContents = (props: NewProjectFormProps) => {
<Button
className='primary'
type='submit'
disabled={!isValid}
disabled={!isValid || props.orgs.length === 0}
>
{t['Create Project']}
</Button>
Expand Down
9 changes: 9 additions & 0 deletions src/components/ProjectForm/ProjectForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,12 @@
.project-form-actions-container > button {
min-width: 200px;
}

.project-form-no-orgs-warning {
padding: 12px 16px;
border: 1px solid var(--amber-7, #f5a623);
border-radius: 6px;
background-color: var(--amber-2, #fff8eb);
color: var(--amber-11, #7e4a0d);
margin-bottom: 16px;
}
7 changes: 6 additions & 1 deletion src/components/SignInGithub/SignInGitHub.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
border: 1px solid black;
color: white !important;
background-color: #015f86;
width: 2o0px;
width: 200px;
}

/* UTexas burnt-orange accent for the Enterprise Managed User button */
.sign-in-anchor-utexas {
background-color: #bf5700 !important;
}

.authorize-anchor {
Expand Down
83 changes: 49 additions & 34 deletions src/components/SignInGithub/SignInGitHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,41 @@ import type { Translations } from '@ty/Types.ts';
interface SignInGitHubProps {
i18n: Translations;
}
// href=

// GitHub logo SVG shared by all sign-in buttons.
const GitHubLogo = () => (
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
);

export const SignInGitHub = (props: SignInGitHubProps) => {
// Show the loading overlay whenever any sign-in link is clicked.
useEffect(() => {
const fireEvent = document.getElementById('sign-in');

if (fireEvent) {
fireEvent.addEventListener('click', () => {
const elOverlay = document.getElementById('page-loader-overlay');
const elSpinner = document.getElementById('page-loader-spinner');
const showLoader = () => {
const elOverlay = document.getElementById('page-loader-overlay');
const elSpinner = document.getElementById('page-loader-spinner');
if (elOverlay && elSpinner) {
elOverlay.classList.add('loading-state');
elSpinner.classList.add('loading');
}
};

if (elOverlay && elSpinner) {
elOverlay.classList.add('loading-state');
elSpinner.classList.add('loading');
}
});
}
['sign-in', 'sign-in-utexas'].forEach((id) => {
document.getElementById(id)?.addEventListener('click', showLoader);
});
}, []);

// Only show the UTexas EID button when the enterprise OAuth app is configured.
const utexasClientId = import.meta.env.PUBLIC_UTEXAS_GITHUB_CLIENT_ID;

return (
<div className='sign-in-container'>
<a
Expand All @@ -36,39 +53,37 @@ export const SignInGitHub = (props: SignInGitHubProps) => {
}/git&scope=repo%20workflow`}
>
<div className='sign-in-button-container'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Sign in with GitHub']}
</div>
</div>
</a>
{utexasClientId && (
<a
id='sign-in-utexas'
className='sign-in-anchor sign-in-anchor-utexas'
href={`https://github.com/login/oauth/authorize?client_id=${utexasClientId}&redirect_uri=${
window.location.origin
}/git-enterprise&scope=repo%20workflow`}
>
<div className='sign-in-button-container'>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Sign in with UTexas EID']}
</div>
</div>
</a>
)}
<a
id='sign-in'
id='sign-in-reauthorize'
className='authorize-anchor'
href={`https://github.com/settings/connections/applications/${
import.meta.env.PUBLIC_GITHUB_CLIENT_ID
}`}
>
<div className='sign-in-button-container'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Reauthorize App and Organizations']}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/i18n/en/error.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"Something went wrong.": "Something went wrong.",
"_repo_exists_": "The repository name already exists in the GitHub Organization.",
"_repo_create_failed_": "The repository was not successfully created",
"_repo_visibility_change_failed_": "Repository was created but could not be set to internal visibility. Please delete the repository from GitHub and try again.",
"_failed_pages_enable_": "Failed to enable GitHub Pages for this repository",
"_failed_adding_topic_": "Failed to add the AVAnnotate-Project topis to repository",
"_failed_adding_collaborators_": "Failed to add the additional collaborators to the project.",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/en/new-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@
"Use Private Repository": "Private Repository",
"_private_repository_helper_text_": "Private repositories do not generate GitHub Pages sites.",
"Generate GitHub Pages Site": "Generate GitHub Pages Site",
"_generate_pages_site_helper_text_": "Generate a GitHub Pages site. If unchecked a static site which you can deploy will be created and checked into your project repository."
"_generate_pages_site_helper_text_": "Generate a GitHub Pages site. If unchecked a static site which you can deploy will be created and checked into your project repository.",
"_no_orgs_warning_": "Your account is a GitHub Enterprise Managed User account. You must be a member of a GitHub organization to create a project. Please contact your organization administrator to be added to an organization."
}
1 change: 1 addition & 0 deletions src/i18n/en/sign-in.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"Sign in with GitHub": "Sign in with GitHub",
"Sign in with UTexas EID": "Sign in with UTexas EID",
"Welcome To": "Welcome To",
"Reauthorize App and Organizations": "Reauthorize App and Organizations"
}
Loading