mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 16:03:24 +00:00
* feat: rework git sync settings into a flat repo list and setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the connect step for gitlab and github in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: open the github app install page from the git sync setup next step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: let git sync setup set the repository folder Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add the configure sync step to git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: even out the git sync filter settings columns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: pin the filter input to the bottom of the filter list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: frame the git sync path filters in a box with an empty hint Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: use a multiselect for the git sync path filters Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: grey out the skipped connect step in git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the check job line to the bottom of the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hide the check job line once the repository check resolved Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: center the repository check loader in the setup modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the setup modal shifting when the check job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the existing resource hint to the setup modal footer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: always show the existing resource option when one exists Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: drop the card and collapse from the git sync filter settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use a success alert for the pull from git notice Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: make initializing the repository a step of the git sync setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: hide the push job lines once they succeed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: reword the push step intro Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: copy button on the cli snippet and a taller changes list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: match the push step loading to the check step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the push step shifting when a job id arrives Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: let the changes list fill the push step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: end the git sync setup on a done step instead of a modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: delete a git sync repository from a row menu with a confirmation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: lift the git sync done message off the notices Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: restore github app installation transfer between instances Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address regressions from the git sync setup rework Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the overlay z-index floor in step with the ai chat Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the push state in the setup dialog, not in the step it destroys Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: block every dismissal while setup work is in flight and keep sync reachable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: regenerate the resource path when the repository changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: configure the resource the connect step actually created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a modal open when a drawer above it takes the click Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: snapshot the topmost surface for keyboard clicks too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: size the add repository buttons and explain git promotion Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * revert: drop the promotion-first action from the empty state Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: shrink the test connection result line Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the fork sync and fork pull request toggles during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: group promotion repositories under their own label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: offer the deploy branch pull request toggle during setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: wait for the repository credential before saving the setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a superseded credential load and drop the bindable default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key setup readiness to the repository it was loaded for Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: tie setup readiness to the attempt that asked for it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: link to where the access token is created Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: say plainly how each provider connects Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: sync resources, variables, schedules and triggers by default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: use the accent colour for the token help link Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * style: move the CI/CD deploy hint next to the pull toggle Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep setup gated when the connection could not be read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only explain pull-from-git where the workspace can turn it on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: offer a save retry once the repository is initialized Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: say when pulling fell back to polling on the last setup step Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clear the initialized flag when a new draft starts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import { GitSyncService, ResourceService, VariableService } from '$lib/gen'
|
|
|
|
/** What a provider form hands back once the user has picked a repository. */
|
|
export type RepoConnection = {
|
|
/** The repository URL, never carrying a secret. */
|
|
url: string
|
|
isGithubApp?: boolean
|
|
/** A token for Windmill to store and renew server-side (EE GitLab). */
|
|
heldToken?: string
|
|
/** `url` with the token embedded, filed as a secret variable because the
|
|
* resource value itself is readable by anyone who can read the resource. */
|
|
tokenUrl?: string
|
|
}
|
|
|
|
/** Username git sends alongside a token: GitHub ignores it for PATs, GitLab
|
|
* requires a non-empty one and documents `oauth2`. */
|
|
const TOKEN_USERNAME = { github: 'x-access-token', gitlab: 'oauth2' } as const
|
|
|
|
export type TokenProvider = keyof typeof TOKEN_USERNAME
|
|
|
|
/** Parses a pasted repository URL, dropping any credentials it carried. */
|
|
export function parseRepoUrl(raw: string): URL | undefined {
|
|
let u: URL
|
|
try {
|
|
u = new URL(raw.trim())
|
|
} catch {
|
|
return undefined
|
|
}
|
|
// https only: the token is embedded in this URL, and plain http would put it on the wire.
|
|
if (u.protocol !== 'https:') return undefined
|
|
if (u.pathname.replace(/\/+$/, '').split('/').filter(Boolean).length < 2) return undefined
|
|
u.username = ''
|
|
u.password = ''
|
|
u.search = ''
|
|
u.hash = ''
|
|
return u
|
|
}
|
|
|
|
export function withToken(url: URL, token: string, provider: TokenProvider): string {
|
|
const u = new URL(url.toString())
|
|
u.username = TOKEN_USERNAME[provider]
|
|
u.password = token.trim()
|
|
return u.toString()
|
|
}
|
|
|
|
/** A resource name derived from the repository, e.g. `acme/infra.git` → `infra`. */
|
|
export function repoSlug(url: string): string {
|
|
const last =
|
|
url
|
|
.replace(/\.git\/?$/, '')
|
|
.replace(/\/+$/, '')
|
|
.split('/')
|
|
.pop() ?? ''
|
|
return last.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase() || 'git_repository'
|
|
}
|
|
|
|
/**
|
|
* Writes everything a connection needs and creates the `git_repository` resource at
|
|
* `path`. A token-bearing URL goes to a secret variable at the same path, which the
|
|
* backend renames along with the resource.
|
|
*/
|
|
export async function createRepositoryResource(
|
|
workspace: string,
|
|
path: string,
|
|
conn: RepoConnection,
|
|
location: { branch?: string; folder?: string }
|
|
): Promise<void> {
|
|
const branch = location.branch?.trim()
|
|
// Relative to the repository root, which is how the sync script joins it.
|
|
const folder = location.folder?.trim().replace(/^\/+|\/+$/g, '')
|
|
let url = conn.url
|
|
let createdVariable = false
|
|
if (conn.tokenUrl) {
|
|
if (await VariableService.existsVariable({ workspace, path })) {
|
|
throw new Error(`A variable already exists at ${path}. Pick another path.`)
|
|
}
|
|
await VariableService.createVariable({
|
|
workspace,
|
|
requestBody: {
|
|
path,
|
|
value: conn.tokenUrl,
|
|
is_secret: true,
|
|
description: `Authenticated URL of the git repository ${conn.url}`
|
|
}
|
|
})
|
|
createdVariable = true
|
|
url = `$var:${path}`
|
|
}
|
|
try {
|
|
if (conn.heldToken) {
|
|
await GitSyncService.setGitCredential({
|
|
workspace,
|
|
requestBody: { repo_url: conn.url, token: conn.heldToken }
|
|
})
|
|
}
|
|
await ResourceService.createResource({
|
|
workspace,
|
|
requestBody: {
|
|
path,
|
|
resource_type: 'git_repository',
|
|
description: `Git repository ${conn.url}`,
|
|
value: {
|
|
url,
|
|
...(branch ? { branch } : {}),
|
|
...(folder ? { folder } : {}),
|
|
is_github_app: !!conn.isGithubApp
|
|
}
|
|
}
|
|
})
|
|
} catch (e) {
|
|
// Otherwise a retry at the same path is refused over this run's own secret.
|
|
if (createdVariable) {
|
|
await VariableService.deleteVariable({ workspace, path }).catch(() => {})
|
|
}
|
|
throw e
|
|
}
|
|
}
|