@cat-factory/app 0.249.0 → 0.250.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/auth/LoginScreen.vue +8 -2
- package/app/components/board/nodes/TaskCard.vue +1 -1
- package/app/components/bootstrap/BootstrapModal.vue +5 -3
- package/app/components/github/GitHubPanel.vue +49 -10
- package/app/components/panels/InspectorPanel.vue +14 -9
- package/app/components/vcs/GitLabConnect.vue +8 -2
- package/app/composables/usePipelineErrorToast.ts +2 -0
- package/app/stores/github/vcsConnect.ts +19 -0
- package/app/stores/github.spec.ts +116 -6
- package/app/stores/github.ts +23 -6
- package/app/utils/vcs.spec.ts +105 -14
- package/app/utils/vcs.ts +113 -29
- package/i18n/locales/de.json +36 -19
- package/i18n/locales/en.json +36 -19
- package/i18n/locales/es.json +36 -19
- package/i18n/locales/fr.json +36 -19
- package/i18n/locales/he.json +36 -19
- package/i18n/locales/it.json +36 -19
- package/i18n/locales/ja.json +36 -19
- package/i18n/locales/pl.json +36 -19
- package/i18n/locales/tr.json +36 -19
- package/i18n/locales/uk.json +36 -19
- package/package.json +2 -2
package/app/utils/vcs.spec.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
2
|
import {
|
|
3
3
|
appInstallationManageUrl,
|
|
4
|
+
branchWebUrl,
|
|
5
|
+
issueWebUrl,
|
|
4
6
|
newRepoUrl,
|
|
7
|
+
pullWebUrl,
|
|
8
|
+
repoWebUrl,
|
|
5
9
|
VCS_PROVIDER_ICONS,
|
|
6
10
|
VCS_PROVIDER_LABELS,
|
|
7
|
-
|
|
11
|
+
vcsTokenCreateUrl,
|
|
8
12
|
} from './vcs'
|
|
9
13
|
import type { GitHubConnection, VcsProvider } from '~/types/domain'
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
|
-
* The one place VCS presentation switches on the provider. What is pinned here is the
|
|
16
|
+
* The one place VCS presentation switches on the provider. What is pinned here is the set of
|
|
13
17
|
* decisions a component must never make for itself: which affordances belong to a GitHub-App
|
|
14
|
-
* installation (and therefore vanish on a pasted token),
|
|
15
|
-
*
|
|
18
|
+
* installation (and therefore vanish on a pasted token), how each provider addresses a merge
|
|
19
|
+
* request / issue / branch, and which links may fall back to a provider's public instance when
|
|
20
|
+
* the deployment could not name its own host — one may, the rest must withhold.
|
|
16
21
|
*/
|
|
17
22
|
const connection = (over: Partial<GitHubConnection> = {}): GitHubConnection => ({
|
|
18
23
|
installationId: 42,
|
|
@@ -21,6 +26,7 @@ const connection = (over: Partial<GitHubConnection> = {}): GitHubConnection => (
|
|
|
21
26
|
connectedAt: 0,
|
|
22
27
|
provider: 'github',
|
|
23
28
|
method: 'app',
|
|
29
|
+
webUrl: 'https://github.com',
|
|
24
30
|
canCreateRepos: false,
|
|
25
31
|
canManageWorkflows: true,
|
|
26
32
|
...over,
|
|
@@ -39,6 +45,14 @@ describe('appInstallationManageUrl', () => {
|
|
|
39
45
|
)
|
|
40
46
|
})
|
|
41
47
|
|
|
48
|
+
// A GitHub Enterprise Server installation's settings live on that server, and its installation
|
|
49
|
+
// id means nothing anywhere else.
|
|
50
|
+
it('links an Enterprise installation to its own host', () => {
|
|
51
|
+
expect(appInstallationManageUrl(connection({ webUrl: 'https://ghe.acme.dev' }))).toBe(
|
|
52
|
+
'https://ghe.acme.dev/settings/installations/42',
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
|
|
42
56
|
// The whole point of the helper: a pasted token has no installation, so there is no page to
|
|
43
57
|
// send the user to. Both modals used to build the github.com URL from the connection
|
|
44
58
|
// unconditionally, which put a "Grant the App access" button that 404s in front of every
|
|
@@ -52,6 +66,10 @@ describe('appInstallationManageUrl', () => {
|
|
|
52
66
|
).toBeUndefined()
|
|
53
67
|
})
|
|
54
68
|
|
|
69
|
+
it('has no URL when the deployment could not name the host', () => {
|
|
70
|
+
expect(appInstallationManageUrl(connection({ webUrl: null }))).toBeUndefined()
|
|
71
|
+
})
|
|
72
|
+
|
|
55
73
|
it('has no URL when there is no connection', () => {
|
|
56
74
|
expect(appInstallationManageUrl(null)).toBeUndefined()
|
|
57
75
|
})
|
|
@@ -60,7 +78,8 @@ describe('appInstallationManageUrl', () => {
|
|
|
60
78
|
describe('newRepoUrl', () => {
|
|
61
79
|
it('prefills the GitHub new-repository form with everything the caller knows', () => {
|
|
62
80
|
const url = new URL(
|
|
63
|
-
newRepoUrl('github', { owner: 'acme', name: 'api', private: true }) ??
|
|
81
|
+
newRepoUrl('github', 'https://github.com', { owner: 'acme', name: 'api', private: true }) ??
|
|
82
|
+
'about:blank',
|
|
64
83
|
)
|
|
65
84
|
expect(url.origin + url.pathname).toBe('https://github.com/new')
|
|
66
85
|
expect(url.searchParams.get('owner')).toBe('acme')
|
|
@@ -69,22 +88,94 @@ describe('newRepoUrl', () => {
|
|
|
69
88
|
})
|
|
70
89
|
|
|
71
90
|
it('omits what the caller has not filled in yet, and marks a public repo public', () => {
|
|
72
|
-
const url = new URL(
|
|
91
|
+
const url = new URL(
|
|
92
|
+
newRepoUrl('github', 'https://github.com', { name: '', private: false }) ?? 'about:blank',
|
|
93
|
+
)
|
|
73
94
|
expect(url.searchParams.has('owner')).toBe(false)
|
|
74
95
|
expect(url.searchParams.has('name')).toBe(false)
|
|
75
96
|
expect(url.searchParams.get('visibility')).toBe('public')
|
|
76
97
|
})
|
|
77
98
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
// Now that the connection states its host, a self-managed GitLab gets the button back — on its
|
|
100
|
+
// OWN instance. GitLab's form takes no prefill, so it is the bare page.
|
|
101
|
+
it('opens the new-project page on the GitLab instance the workspace is bound to', () => {
|
|
102
|
+
expect(newRepoUrl('gitlab', 'https://gitlab.acme.dev', { name: 'api', private: false })).toBe(
|
|
103
|
+
'https://gitlab.acme.dev/projects/new',
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Withheld rather than guessed at: the public instance would look like it worked, and the user
|
|
108
|
+
// would create the project on a server the bootstrap run never pushes to.
|
|
109
|
+
it('withholds a page when the deployment could not name a host', () => {
|
|
110
|
+
expect(newRepoUrl('gitlab', null, { name: 'api', private: false })).toBeUndefined()
|
|
84
111
|
})
|
|
85
112
|
|
|
86
113
|
it('withholds a page when no provider is resolved', () => {
|
|
87
|
-
expect(
|
|
114
|
+
expect(
|
|
115
|
+
newRepoUrl(null, 'https://gitlab.acme.dev', { name: 'api', private: false }),
|
|
116
|
+
).toBeUndefined()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('repo / pull / issue / branch links', () => {
|
|
121
|
+
const repo = { owner: 'acme/platform', name: 'api' }
|
|
122
|
+
|
|
123
|
+
// The repo path itself is provider-neutral: `owner` already carries GitLab's full group path,
|
|
124
|
+
// nested groups included. Only what hangs off it differs.
|
|
125
|
+
it('builds a repo page under the connection’s own host', () => {
|
|
126
|
+
expect(repoWebUrl('https://gitlab.acme.dev', repo)).toBe(
|
|
127
|
+
'https://gitlab.acme.dev/acme/platform/api',
|
|
128
|
+
)
|
|
129
|
+
expect(repoWebUrl('https://github.com/', { owner: 'acme', name: 'api' })).toBe(
|
|
130
|
+
'https://github.com/acme/api',
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('addresses a pull request and a merge request the way each provider does', () => {
|
|
135
|
+
const base = 'https://gitlab.acme.dev/acme/api'
|
|
136
|
+
expect(pullWebUrl('gitlab', base, 7)).toBe(`${base}/-/merge_requests/7`)
|
|
137
|
+
expect(pullWebUrl('github', 'https://github.com/acme/api', 7)).toBe(
|
|
138
|
+
'https://github.com/acme/api/pull/7',
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('addresses an issue and a branch the way each provider does', () => {
|
|
143
|
+
const base = 'https://gitlab.acme.dev/acme/api'
|
|
144
|
+
expect(issueWebUrl('gitlab', base, 3)).toBe(`${base}/-/issues/3`)
|
|
145
|
+
expect(branchWebUrl('gitlab', base, 'feat/sso')).toBe(`${base}/-/tree/feat/sso`)
|
|
146
|
+
expect(issueWebUrl('github', 'https://github.com/acme/api', 3)).toBe(
|
|
147
|
+
'https://github.com/acme/api/issues/3',
|
|
148
|
+
)
|
|
149
|
+
expect(branchWebUrl('github', 'https://github.com/acme/api', 'feat/sso')).toBe(
|
|
150
|
+
'https://github.com/acme/api/tree/feat/sso',
|
|
151
|
+
)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// With no host there is no repo page, and everything built on one goes with it rather than
|
|
155
|
+
// being rendered against a guessed instance.
|
|
156
|
+
it('withholds every link when the host is unknown', () => {
|
|
157
|
+
expect(repoWebUrl(null, repo)).toBeNull()
|
|
158
|
+
expect(pullWebUrl('gitlab', null, 7)).toBeNull()
|
|
159
|
+
expect(issueWebUrl('gitlab', null, 3)).toBeNull()
|
|
160
|
+
expect(branchWebUrl('gitlab', null, 'main')).toBeNull()
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('vcsTokenCreateUrl', () => {
|
|
165
|
+
it('points at the token page on the instance being connected', () => {
|
|
166
|
+
expect(vcsTokenCreateUrl('gitlab', 'https://gitlab.acme.dev')).toBe(
|
|
167
|
+
'https://gitlab.acme.dev/-/user_settings/personal_access_tokens',
|
|
168
|
+
)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// The one builder that may fall back: it renders during connect (and on the sign-in screen,
|
|
172
|
+
// where nothing is connected at all), and a settings page on the wrong instance costs a click
|
|
173
|
+
// rather than a run.
|
|
174
|
+
it('falls back to the provider’s public instance when no host is known', () => {
|
|
175
|
+
expect(vcsTokenCreateUrl('gitlab')).toBe(
|
|
176
|
+
'https://gitlab.com/-/user_settings/personal_access_tokens',
|
|
177
|
+
)
|
|
178
|
+
expect(vcsTokenCreateUrl('github', null)).toBe('https://github.com/settings/tokens/new')
|
|
88
179
|
})
|
|
89
180
|
})
|
|
90
181
|
|
|
@@ -96,6 +187,6 @@ describe('provider presentation maps', () => {
|
|
|
96
187
|
it.each(providers)('has a label, icon and token URL for %s', (provider) => {
|
|
97
188
|
expect(VCS_PROVIDER_LABELS[provider]).toBeTruthy()
|
|
98
189
|
expect(VCS_PROVIDER_ICONS[provider]).toMatch(/^i-lucide-/)
|
|
99
|
-
expect(
|
|
190
|
+
expect(vcsTokenCreateUrl(provider)).toMatch(/^https:\/\//)
|
|
100
191
|
})
|
|
101
192
|
})
|
package/app/utils/vcs.ts
CHANGED
|
@@ -11,6 +11,13 @@ import type { GitHubConnection, VcsProvider } from '~/types/domain'
|
|
|
11
11
|
//
|
|
12
12
|
// Each map is an exhaustive `Record<VcsProvider, …>`: adding a provider to the union fails
|
|
13
13
|
// the typecheck here instead of silently rendering a GitHub icon for it.
|
|
14
|
+
//
|
|
15
|
+
// EVERY link builder here takes the connection's `webUrl` — the browser-facing host the
|
|
16
|
+
// backend derived from the instance the workspace is actually bound to. The SPA used to
|
|
17
|
+
// hand-build `https://github.com/…`, which is right for exactly one deployment shape and sends
|
|
18
|
+
// a self-managed GitLab (or GitHub Enterprise) user to whatever lives at that path on the
|
|
19
|
+
// public instance. A null host means the deployment could not name one, and a builder that
|
|
20
|
+
// needs one answers `undefined` so its caller drops the affordance.
|
|
14
21
|
// ---------------------------------------------------------------------------
|
|
15
22
|
|
|
16
23
|
/** Brand name, as rendered in titles and buttons. */
|
|
@@ -24,30 +31,58 @@ export const VCS_PROVIDER_ICONS: Record<VcsProvider, string> = {
|
|
|
24
31
|
gitlab: 'i-lucide-gitlab',
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The public instance of each provider, used ONLY where no host is known and a wrong link costs
|
|
36
|
+
* nothing but a click: the token-creation page during connect (see {@link vcsTokenCreateUrl}).
|
|
37
|
+
* Never for a link to a repo, a project or a namespace, where the public instance may well
|
|
38
|
+
* serve somebody else's page under the same path.
|
|
39
|
+
*/
|
|
40
|
+
const VCS_PROVIDER_PUBLIC_WEB_URLS: Record<VcsProvider, string> = {
|
|
41
|
+
github: 'https://github.com',
|
|
42
|
+
gitlab: 'https://gitlab.com',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Where a user creates a personal access token, relative to the instance's web root. */
|
|
46
|
+
const TOKEN_SETTINGS_PATHS: Record<VcsProvider, string> = {
|
|
47
|
+
github: '/settings/tokens/new',
|
|
48
|
+
gitlab: '/-/user_settings/personal_access_tokens',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Where a user creates a repository/project by hand, relative to the instance's web root. */
|
|
52
|
+
const NEW_REPO_PATHS: Record<VcsProvider, string> = {
|
|
53
|
+
github: '/new',
|
|
54
|
+
gitlab: '/projects/new',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** How each provider addresses a pull/merge request and an issue under a repo's web path. */
|
|
58
|
+
const PULL_PATHS: Record<VcsProvider, (n: number) => string> = {
|
|
59
|
+
github: (n) => `/pull/${n}`,
|
|
60
|
+
gitlab: (n) => `/-/merge_requests/${n}`,
|
|
61
|
+
}
|
|
62
|
+
const ISSUE_PATHS: Record<VcsProvider, (n: number) => string> = {
|
|
63
|
+
github: (n) => `/issues/${n}`,
|
|
64
|
+
gitlab: (n) => `/-/issues/${n}`,
|
|
65
|
+
}
|
|
66
|
+
const BRANCH_PATHS: Record<VcsProvider, (branch: string) => string> = {
|
|
67
|
+
github: (branch) => `/tree/${branch}`,
|
|
68
|
+
gitlab: (branch) => `/-/tree/${branch}`,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Strip a trailing slash so a host and a path never join into a double slash. */
|
|
72
|
+
function root(webUrl: string): string {
|
|
73
|
+
return webUrl.replace(/\/+$/, '')
|
|
31
74
|
}
|
|
32
75
|
|
|
33
76
|
/**
|
|
34
|
-
* Where a user creates a
|
|
35
|
-
* can target it, or `null` where the SPA cannot name the instance the workspace is connected
|
|
36
|
-
* to, in which case the affordance is WITHHELD rather than pointed somewhere plausible.
|
|
77
|
+
* Where a user creates a personal access token on the instance they are connecting.
|
|
37
78
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* the
|
|
41
|
-
*
|
|
42
|
-
* project created on the wrong instance looks like success until the bootstrap push cannot
|
|
43
|
-
* find it. This is the same rule the callers already apply when no provider is resolved at
|
|
44
|
-
* all, so the two cases collapse into {@link newRepoUrl} returning `undefined`.
|
|
45
|
-
*
|
|
46
|
-
* A `Record` rather than a switch so a provider joining the union has to state its answer.
|
|
79
|
+
* The one builder that FALLS BACK to the provider's public instance when no host is known,
|
|
80
|
+
* deliberately unlike its repo-facing siblings: this renders during connect, before anything is
|
|
81
|
+
* bound, and the cost of being wrong is a settings page that isn't theirs — a click, noticed
|
|
82
|
+
* immediately. A wrong REPOSITORY link is silent and can cost a run, so those withhold instead.
|
|
47
83
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
gitlab: null,
|
|
84
|
+
export function vcsTokenCreateUrl(provider: VcsProvider, webUrl?: string | null): string {
|
|
85
|
+
return `${root(webUrl || VCS_PROVIDER_PUBLIC_WEB_URLS[provider])}${TOKEN_SETTINGS_PATHS[provider]}`
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
/**
|
|
@@ -58,20 +93,27 @@ const NEW_REPO_PAGES: Record<VcsProvider, string | null> = {
|
|
|
58
93
|
* token's scope and the user's project membership on the host, so there is nothing to link
|
|
59
94
|
* to and the callers drop the affordance rather than pointing at a URL that 404s. Keyed on the
|
|
60
95
|
* connection's own `method` (see the contract) rather than on `provider`, and asked as
|
|
61
|
-
* `=== 'app'` so anything that is not an App installation withholds the link
|
|
96
|
+
* `=== 'app'` so anything that is not an App installation withholds the link — as does an
|
|
97
|
+
* installation whose host the deployment could not name, since an installation id means nothing
|
|
98
|
+
* on an instance other than its own.
|
|
62
99
|
*/
|
|
63
100
|
export function appInstallationManageUrl(connection: GitHubConnection | null): string | undefined {
|
|
64
|
-
if (!connection || connection.method !== 'app') return undefined
|
|
101
|
+
if (!connection || connection.method !== 'app' || !connection.webUrl) return undefined
|
|
102
|
+
const base = root(connection.webUrl)
|
|
65
103
|
return connection.targetType === 'Organization'
|
|
66
|
-
?
|
|
67
|
-
:
|
|
104
|
+
? `${base}/organizations/${connection.accountLogin}/settings/installations/${connection.installationId}`
|
|
105
|
+
: `${base}/settings/installations/${connection.installationId}`
|
|
68
106
|
}
|
|
69
107
|
|
|
70
108
|
/**
|
|
71
|
-
* The host's new-repository page for a manual create, or `undefined`
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
109
|
+
* The host's new-repository page for a manual create, or `undefined` when the deployment could
|
|
110
|
+
* not name the instance (a null/absent `webUrl`) or no provider is resolved at all, which is
|
|
111
|
+
* what a surface rendering before anything is connected has when the deployment offers several.
|
|
112
|
+
* A caller that gets `undefined` hides the affordance.
|
|
113
|
+
*
|
|
114
|
+
* Withholding is the whole point: a project created on the wrong instance looks like success
|
|
115
|
+
* until the bootstrap push cannot find it, which is a failed run rather than a dead link. Now
|
|
116
|
+
* that the host is on the wire, a GitLab deployment that HAS one gets the button back.
|
|
75
117
|
*
|
|
76
118
|
* GitHub's form is the only one that takes a prefill, so what the bootstrap flow already
|
|
77
119
|
* knows is carried over and the user creates the right repo in one click. `visibility` is
|
|
@@ -79,10 +121,11 @@ export function appInstallationManageUrl(connection: GitHubConnection | null): s
|
|
|
79
121
|
*/
|
|
80
122
|
export function newRepoUrl(
|
|
81
123
|
provider: VcsProvider | null,
|
|
124
|
+
webUrl: string | null | undefined,
|
|
82
125
|
prefill: { owner?: string; name?: string; description?: string; private: boolean },
|
|
83
126
|
): string | undefined {
|
|
84
|
-
|
|
85
|
-
|
|
127
|
+
if (!provider || !webUrl) return undefined
|
|
128
|
+
const page = `${root(webUrl)}${NEW_REPO_PATHS[provider]}`
|
|
86
129
|
if (provider !== 'github') return page
|
|
87
130
|
const params = new URLSearchParams()
|
|
88
131
|
if (prefill.owner) params.set('owner', prefill.owner)
|
|
@@ -91,3 +134,44 @@ export function newRepoUrl(
|
|
|
91
134
|
params.set('visibility', prefill.private ? 'private' : 'public')
|
|
92
135
|
return `${page}?${params.toString()}`
|
|
93
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A repository's page on the instance it lives on, or null when no host is known.
|
|
140
|
+
*
|
|
141
|
+
* `owner` is the full namespace on both providers (GitHub's `owner`, GitLab's group path,
|
|
142
|
+
* nested groups included), so the repo path itself needs no provider switch — only what hangs
|
|
143
|
+
* off it does.
|
|
144
|
+
*/
|
|
145
|
+
export function repoWebUrl(
|
|
146
|
+
webUrl: string | null | undefined,
|
|
147
|
+
repo: { owner: string; name: string },
|
|
148
|
+
): string | null {
|
|
149
|
+
return webUrl ? `${root(webUrl)}/${repo.owner}/${repo.name}` : null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** A pull request (GitHub) / merge request (GitLab) under a repo's page. */
|
|
153
|
+
export function pullWebUrl(
|
|
154
|
+
provider: VcsProvider,
|
|
155
|
+
repoUrl: string | null,
|
|
156
|
+
pullNumber: number,
|
|
157
|
+
): string | null {
|
|
158
|
+
return repoUrl ? `${repoUrl}${PULL_PATHS[provider](pullNumber)}` : null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** An issue under a repo's page. */
|
|
162
|
+
export function issueWebUrl(
|
|
163
|
+
provider: VcsProvider,
|
|
164
|
+
repoUrl: string | null,
|
|
165
|
+
issueNumber: number,
|
|
166
|
+
): string | null {
|
|
167
|
+
return repoUrl ? `${repoUrl}${ISSUE_PATHS[provider](issueNumber)}` : null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A branch's file listing under a repo's page. */
|
|
171
|
+
export function branchWebUrl(
|
|
172
|
+
provider: VcsProvider,
|
|
173
|
+
repoUrl: string | null,
|
|
174
|
+
branch: string,
|
|
175
|
+
): string | null {
|
|
176
|
+
return repoUrl ? `${repoUrl}${BRANCH_PATHS[provider](branch)}` : null
|
|
177
|
+
}
|
package/i18n/locales/de.json
CHANGED
|
@@ -3050,7 +3050,6 @@
|
|
|
3050
3050
|
},
|
|
3051
3051
|
"pr": "PR",
|
|
3052
3052
|
"prNumber": "PR #{number}",
|
|
3053
|
-
"openPrOnGithub": "{pr} auf GitHub öffnen",
|
|
3054
3053
|
"readOutcome": "Ergebnis",
|
|
3055
3054
|
"readOutcomeHint": "Lies nach, was sich geändert hat, mit den Belegen dafür",
|
|
3056
3055
|
"review": "Überprüfen",
|
|
@@ -3064,7 +3063,8 @@
|
|
|
3064
3063
|
"module": "Modul: {name}",
|
|
3065
3064
|
"buildSteps": "Build-Schritte",
|
|
3066
3065
|
"reviewAndApprove": "Überprüfen & freigeben",
|
|
3067
|
-
"clickToViewStep": "Klicken, um Schrittdetails & Ausgabe anzuzeigen"
|
|
3066
|
+
"clickToViewStep": "Klicken, um Schrittdetails & Ausgabe anzuzeigen",
|
|
3067
|
+
"openPrLink": "{pr} öffnen"
|
|
3068
3068
|
},
|
|
3069
3069
|
"dryRunToast": {
|
|
3070
3070
|
"title": "Probelauf gestartet",
|
|
@@ -3319,14 +3319,11 @@
|
|
|
3319
3319
|
"loading": "Wird geladen…",
|
|
3320
3320
|
"tabs": {
|
|
3321
3321
|
"repos": "Repositorys",
|
|
3322
|
-
"pulls": "Pull Requests",
|
|
3323
3322
|
"issues": "Issues"
|
|
3324
3323
|
},
|
|
3325
3324
|
"linkedToBoard": "Mit diesem Board verknüpft",
|
|
3326
3325
|
"manageRepos": "Repos verwalten",
|
|
3327
|
-
"manageHint": "Wählen Sie die Repositorys aus, die dieses Board verfolgen soll. Die GitHub-Verbindung wird über den Account geteilt, und jedes Board verknüpft seine eigenen Repos.",
|
|
3328
3326
|
"loadingRepos": "Repositorys werden geladen…",
|
|
3329
|
-
"noAvailableRepos": "Die Installation kann noch auf keine Repositorys zugreifen.",
|
|
3330
3327
|
"private": "privat",
|
|
3331
3328
|
"saveSelection": "Auswahl speichern",
|
|
3332
3329
|
"noLinkedRepos": "Noch keine Repositorys verknüpft. Verwenden Sie \"Repos verwalten\", um auszuwählen, welche Repositorys dieses Board verfolgt.",
|
|
@@ -3336,15 +3333,11 @@
|
|
|
3336
3333
|
"fromSha": "Von SHA",
|
|
3337
3334
|
"commitShaPlaceholder": "Commit-SHA",
|
|
3338
3335
|
"createBranch": "Branch erstellen",
|
|
3339
|
-
"openPr": "PR öffnen",
|
|
3340
3336
|
"repository": "Repository",
|
|
3341
3337
|
"chooseRepository": "Repository auswählen",
|
|
3342
3338
|
"prTitle": "Titel",
|
|
3343
3339
|
"headBranch": "Head-Branch",
|
|
3344
3340
|
"baseBranch": "Base-Branch",
|
|
3345
|
-
"openPullRequest": "Pull Request öffnen",
|
|
3346
|
-
"noPulls": "Keine Pull Requests synchronisiert.",
|
|
3347
|
-
"mergePr": "Pull Request mergen",
|
|
3348
3341
|
"noIssues": "Keine Issues synchronisiert.",
|
|
3349
3342
|
"prState": {
|
|
3350
3343
|
"open": "offen",
|
|
@@ -3358,9 +3351,7 @@
|
|
|
3358
3351
|
"toast": {
|
|
3359
3352
|
"resync": "Neusynchronisierung {status}",
|
|
3360
3353
|
"reposUpdated": "Verknüpfte Repositorys aktualisiert",
|
|
3361
|
-
"branchCreated": "Branch {name} erstellt"
|
|
3362
|
-
"prOpened": "Pull Request geöffnet",
|
|
3363
|
-
"prMerged": "PR #{number} gemergt"
|
|
3354
|
+
"branchCreated": "Branch {name} erstellt"
|
|
3364
3355
|
},
|
|
3365
3356
|
"errors": {
|
|
3366
3357
|
"disconnect": "Trennen nicht möglich",
|
|
@@ -3369,7 +3360,6 @@
|
|
|
3369
3360
|
"updateRepos": "Repositorys konnten nicht aktualisiert werden",
|
|
3370
3361
|
"loadBranches": "Branches konnten nicht geladen werden",
|
|
3371
3362
|
"createBranch": "Branch konnte nicht erstellt werden",
|
|
3372
|
-
"openPr": "Pull Request konnte nicht geöffnet werden",
|
|
3373
3363
|
"merge": "Mergen nicht möglich"
|
|
3374
3364
|
}
|
|
3375
3365
|
},
|
|
@@ -5474,7 +5464,8 @@
|
|
|
5474
5464
|
"unavailable": {
|
|
5475
5465
|
"description": {
|
|
5476
5466
|
"binary_generators_unreachable": "Die generativen Integrationen dieser Installation konnten gerade nicht gelesen werden, deshalb wurde der Lauf nicht gestartet. Es ist nichts falsch konfiguriert und keine Änderung nötig: versuchen Sie es erneut, sobald die Verbindung wieder steht.",
|
|
5477
|
-
"foundational_builtins_unreachable": "Die integrierten Basisdienste dieser Installation konnten gerade nicht gelesen werden. Es ist nichts falsch konfiguriert und keine Änderung nötig: versuchen Sie es erneut, sobald die Verbindung wieder steht."
|
|
5467
|
+
"foundational_builtins_unreachable": "Die integrierten Basisdienste dieser Installation konnten gerade nicht gelesen werden. Es ist nichts falsch konfiguriert und keine Änderung nötig: versuchen Sie es erneut, sobald die Verbindung wieder steht.",
|
|
5468
|
+
"connection_credentials_unreadable": "Die gespeicherten Zugangsdaten dieser Verbindung konnten nicht gelesen werden. Wenn diese Installation den Dienst mit dem zugehörigen Schlüssel erreicht, verbinden Sie die Quelle neu, um sie zu ersetzen; andernfalls versuchen Sie es erneut, sobald diese Verbindung wiederhergestellt ist."
|
|
5478
5469
|
}
|
|
5479
5470
|
},
|
|
5480
5471
|
"action": {
|
|
@@ -5546,7 +5537,7 @@
|
|
|
5546
5537
|
"agent_backend_unconfigured": "Es ist kein Runner-Backend verfügbar, um Container-Agenten auszuführen. Registriere einen selbst gehosteten Runner-Pool oder aktiviere Cloudflare Containers in deinem Deployment.",
|
|
5547
5538
|
"run_not_retryable": "Nur eine fehlgeschlagene oder abgeschlossene Ausführung kann wiederholt werden. Diese Ausführung ist noch aktiv oder in einem Zustand, der keine Wiederholung erlaubt.",
|
|
5548
5539
|
"no_pr_to_merge": "Diese Ausführung hat noch keinen Pull Request geöffnet, es gibt also nichts zu mergen.",
|
|
5549
|
-
"github_not_connected": "
|
|
5540
|
+
"github_not_connected": "Verbinden Sie diesen Workspace mit einem Repository-Host, damit Agenten das Repository klonen, Änderungen pushen und Pull Requests öffnen können.",
|
|
5550
5541
|
"bootstrap_not_retryable": "Dieser Bootstrap-Auftrag kann in seinem aktuellen Zustand nicht wiederholt werden (er läuft noch oder wurde bereits erfolgreich abgeschlossen).",
|
|
5551
5542
|
"bootstrap_reference_missing": "Das Referenz-Repository, das dieser Bootstrap übernimmt, ist nicht mehr erreichbar. Es wurde gelöscht oder der Zugriff wurde entzogen. Wähle eine andere Referenz oder erstelle von Grund auf neu.",
|
|
5552
5543
|
"preset_unsatisfiable": "Das Modell eines Inline-Schritts kann in diesem Deployment nicht inline laufen (ein reines Abo-Modell ohne Inline-Unterstützung). Wähle ein inline-fähiges Modell oder Preset.",
|
|
@@ -5736,7 +5727,7 @@
|
|
|
5736
5727
|
"suffixFixing": "· Fixer bearbeitet Kommentare…",
|
|
5737
5728
|
"suffixFailing": "· zu bearbeitende Review-Kommentare",
|
|
5738
5729
|
"suffixAwaiting": "· wartet auf Review",
|
|
5739
|
-
"reviewPr": "Pull Request
|
|
5730
|
+
"reviewPr": "Pull Request prüfen",
|
|
5740
5731
|
"requestFixHeading": "Eine Korrektur anfordern",
|
|
5741
5732
|
"requestFixDescription": "Beschreibe eine Änderung, die der Fixer jetzt am PR-Branch vornehmen soll (zusätzlich zu etwaigen Review-Kommentaren, die er automatisch bearbeitet).",
|
|
5742
5733
|
"requestFixPlaceholder": "z. B. benenne den Helfer um und füge einen Unit-Test für den Fall leerer Eingabe hinzu",
|
|
@@ -5750,12 +5741,12 @@
|
|
|
5750
5741
|
},
|
|
5751
5742
|
"conflicts": {
|
|
5752
5743
|
"mergeability": "Zusammenführbarkeit",
|
|
5753
|
-
"viewPr": "Pull Request
|
|
5744
|
+
"viewPr": "Pull Request ansehen"
|
|
5754
5745
|
},
|
|
5755
5746
|
"docQuality": {
|
|
5756
5747
|
"findings": "Dokumentprobleme",
|
|
5757
5748
|
"findingsFallback": "Das Dokument hat die strukturellen Prüfungen bestanden.",
|
|
5758
|
-
"viewPr": "Pull Request
|
|
5749
|
+
"viewPr": "Pull Request ansehen"
|
|
5759
5750
|
},
|
|
5760
5751
|
"attemptsHeading": "{helper}-Versuche",
|
|
5761
5752
|
"attempt": "Versuch {number}",
|
|
@@ -6906,7 +6897,33 @@
|
|
|
6906
6897
|
},
|
|
6907
6898
|
"toast": {
|
|
6908
6899
|
"disconnected": "{provider} getrennt"
|
|
6909
|
-
}
|
|
6900
|
+
},
|
|
6901
|
+
"pulls": {
|
|
6902
|
+
"github": {
|
|
6903
|
+
"tab": "Pull Requests",
|
|
6904
|
+
"open": "PR öffnen",
|
|
6905
|
+
"openSubmit": "Pull Request öffnen",
|
|
6906
|
+
"merge": "Diesen Pull Request mergen",
|
|
6907
|
+
"none": "Keine Pull Requests synchronisiert.",
|
|
6908
|
+
"opened": "Pull Request geöffnet",
|
|
6909
|
+
"mergedToast": "PR #{number} gemergt",
|
|
6910
|
+
"openFailed": "Pull Request konnte nicht geöffnet werden"
|
|
6911
|
+
},
|
|
6912
|
+
"gitlab": {
|
|
6913
|
+
"tab": "Merge Requests",
|
|
6914
|
+
"open": "MR öffnen",
|
|
6915
|
+
"openSubmit": "Merge Request öffnen",
|
|
6916
|
+
"merge": "Diesen Merge Request mergen",
|
|
6917
|
+
"none": "Keine Merge Requests synchronisiert.",
|
|
6918
|
+
"opened": "Merge Request geöffnet",
|
|
6919
|
+
"mergedToast": "MR !{number} gemergt",
|
|
6920
|
+
"openFailed": "Merge Request konnte nicht geöffnet werden"
|
|
6921
|
+
}
|
|
6922
|
+
},
|
|
6923
|
+
"manageHintApp": "Wählen Sie die Repositories aus, die dieses Board verfolgen soll. Die App-Installation wird kontoweit geteilt, und jedes Board verknüpft seine eigenen Repositories.",
|
|
6924
|
+
"manageHintToken": "Wählen Sie die Repositories aus, die dieses Board verfolgen soll. Die Liste zeigt, was das verbundene Token erreicht, und jedes Board verknüpft seine eigenen Repositories.",
|
|
6925
|
+
"noAvailableReposApp": "Die App-Installation hat noch auf keine Repositories Zugriff.",
|
|
6926
|
+
"noAvailableReposToken": "Das verbundene Token erreicht noch keine Repositories. Prüfen Sie seinen Scope und Ihren Zugriff darauf."
|
|
6910
6927
|
},
|
|
6911
6928
|
"connect": {
|
|
6912
6929
|
"or": "oder",
|