@hanzo/build 0.2.85 → 0.2.87
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/lib/api/github.d.ts +7 -0
- package/lib/api/github.js +12 -0
- package/lib/api/provider.d.ts +6 -1
- package/lib/api/provider.js +8 -3
- package/lib/forge.js +44 -7
- package/lib/settings/integrations.js +1 -1
- package/package.json +1 -1
package/lib/api/github.d.ts
CHANGED
|
@@ -78,6 +78,13 @@ export interface Installation {
|
|
|
78
78
|
}
|
|
79
79
|
/** The accounts this org has bound the App on. An org admin adds one through `/v1/provider/github/connect`. */
|
|
80
80
|
export declare function installations(t: Target): Promise<Installation[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Whether this page reaches GitHub at all, and as whom: the person's own
|
|
83
|
+
* connection first, else the accounts the org has bound the App on. An org that
|
|
84
|
+
* installed the App (onboarding's Connect GitHub) has its repositories to sync
|
|
85
|
+
* without the person connecting their own, so it reads as connected too.
|
|
86
|
+
*/
|
|
87
|
+
export declare function linked(me: Connection, installed: Installation[]): Connection;
|
|
81
88
|
/** One repository the GitHub connection grants this organization. */
|
|
82
89
|
export interface Grant {
|
|
83
90
|
owner: string;
|
package/lib/api/github.js
CHANGED
|
@@ -98,6 +98,18 @@ export async function installations(t) {
|
|
|
98
98
|
.map((i) => ({ login: str(i.login), type: str(i.type), grant: str(i.grant), connected: i.connected === true }))
|
|
99
99
|
.filter((i) => i.login);
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether this page reaches GitHub at all, and as whom: the person's own
|
|
103
|
+
* connection first, else the accounts the org has bound the App on. An org that
|
|
104
|
+
* installed the App (onboarding's Connect GitHub) has its repositories to sync
|
|
105
|
+
* without the person connecting their own, so it reads as connected too.
|
|
106
|
+
*/
|
|
107
|
+
export function linked(me, installed) {
|
|
108
|
+
if (me.connected)
|
|
109
|
+
return me;
|
|
110
|
+
const bound = installed.filter((i) => i.connected).map((i) => i.login);
|
|
111
|
+
return bound.length ? { configured: me.configured, connected: true, login: bound.join(', @') } : me;
|
|
112
|
+
}
|
|
101
113
|
/**
|
|
102
114
|
* One row of GET /v1/provider/github/repos (apps/provider github_index.go
|
|
103
115
|
* repoView): `owner`, `name`, `full_name`, `private`, `default_branch`,
|
package/lib/api/provider.d.ts
CHANGED
|
@@ -29,7 +29,12 @@ export declare const SOCIAL: readonly {
|
|
|
29
29
|
}[];
|
|
30
30
|
/** The provider's consent page for this org; its return lands on `back` when that is a Hanzo app page. */
|
|
31
31
|
export declare function authorize(t: Target, id: string, back?: string): Promise<string>;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Forget the org's connection to a provider: every account, or the one `account`
|
|
34
|
+
* names (a GitHub login). For GitHub the platform also removes its App from an
|
|
35
|
+
* account no other org uses, so connecting that account again is a fresh install.
|
|
36
|
+
*/
|
|
37
|
+
export declare function disconnect(t: Target, id: string, account?: string): Promise<void>;
|
|
33
38
|
export interface Channel {
|
|
34
39
|
id: string;
|
|
35
40
|
name: string;
|
package/lib/api/provider.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* GET /v1/provider/{id} one connector and this org's connection to it
|
|
7
7
|
* POST /v1/provider/{id}/connect {return} → {authorizeUrl}: the provider's own consent page (org admin)
|
|
8
|
-
* POST /v1/provider/{id}/disconnect forget the org's connection (org admin)
|
|
8
|
+
* POST /v1/provider/{id}/disconnect {account?} → forget the org's connection, or one account of it (org admin)
|
|
9
9
|
* GET /v1/provider/slack/channels the connected workspace's channels, a page at a time
|
|
10
10
|
*
|
|
11
11
|
* Connecting leaves this page once, for the provider's consent screen, and only
|
|
@@ -76,8 +76,13 @@ export async function authorize(t, id, back) {
|
|
|
76
76
|
throw new Sentence(phrase('The platform did not name a {provider} address to connect at'), { provider: id });
|
|
77
77
|
return url;
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Forget the org's connection to a provider: every account, or the one `account`
|
|
81
|
+
* names (a GitHub login). For GitHub the platform also removes its App from an
|
|
82
|
+
* account no other org uses, so connecting that account again is a fresh install.
|
|
83
|
+
*/
|
|
84
|
+
export async function disconnect(t, id, account) {
|
|
85
|
+
await call(t, 'POST', `/v1/provider/${seg(id)}/disconnect`, account ? { account } : {});
|
|
81
86
|
}
|
|
82
87
|
export async function channels(t, cursor = '') {
|
|
83
88
|
const raw = obj(await call(t, 'GET', `/v1/provider/slack/channels${query({ cursor })}`));
|
package/lib/forge.js
CHANGED
|
@@ -30,7 +30,8 @@ import { useHost, useTarget } from './host.js';
|
|
|
30
30
|
import { path } from './route.js';
|
|
31
31
|
import { Customize } from './customize/index.js';
|
|
32
32
|
import { Sync } from './sync.js';
|
|
33
|
-
import { connect, connection, grants, syncIssues } from './api/github.js';
|
|
33
|
+
import { connect, connection, disconnect as unlink, grants, installations, linked, syncIssues } from './api/github.js';
|
|
34
|
+
import { disconnect } from './api/provider.js';
|
|
34
35
|
import { here, useBack } from './back.js';
|
|
35
36
|
import { Out } from './out.js';
|
|
36
37
|
import { ask, assemble, attach, closes, facets, handle, home, keyOf, matches, pickProjects, place } from './merge.js';
|
|
@@ -145,19 +146,31 @@ function Choice({ label, icon, says, onPress }) {
|
|
|
145
146
|
return (_jsxs(YStack, { render: "button", "aria-label": label, onPress: onPress, flex: 1, minW: 240, gap: "$2", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", items: "flex-start", hoverStyle: { borderColor: '$edge', bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [icon, _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(SizableText, { size: "$4", color: "$ink", fontWeight: "500", children: label }), _jsx(ArrowRight, { size: 14, 'data-i18n-mirror': '' })] }), _jsx(SizableText, { size: "$2", color: "$soft", text: "start", children: says })] }));
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
|
-
* The
|
|
149
|
-
*
|
|
150
|
-
*
|
|
149
|
+
* The GitHub connection, in one line: who it is connected as and the way to
|
|
150
|
+
* sync, or the one press that connects it. The person's own connection, or else
|
|
151
|
+
* the accounts the org has bound the App on (`linked`), so an org that installed
|
|
152
|
+
* the App in onboarding does not read as unconnected here. GitHub returns here,
|
|
153
|
+
* and this page finishes the connect (../back.ts).
|
|
154
|
+
*
|
|
155
|
+
* Disconnect lets go of it, so another account can be connected: the person's own
|
|
156
|
+
* link, or (for an org admin) the org's App accounts, which the platform also
|
|
157
|
+
* uninstalls on GitHub. It asks once before doing either.
|
|
151
158
|
*/
|
|
152
159
|
function GitHubLink({ onSync }) {
|
|
153
160
|
const host = useHost();
|
|
154
161
|
const target = useTarget();
|
|
155
162
|
const { t } = useI18n();
|
|
156
163
|
const me = useRead(() => connection(target), null, [target]);
|
|
164
|
+
// An org whose installations cannot be read falls back to the person's own connection.
|
|
165
|
+
const installed = useRead(() => installations(target).catch(() => []), [], [target]);
|
|
157
166
|
const [note, setNote] = useState('');
|
|
158
167
|
const [busy, setBusy] = useState(false);
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
const [asking, setAsking] = useState(false);
|
|
169
|
+
useBack(target, true, setNote, () => {
|
|
170
|
+
me.reload();
|
|
171
|
+
installed.reload();
|
|
172
|
+
});
|
|
173
|
+
const c = me.value ? linked(me.value, installed.value) : null;
|
|
161
174
|
// A connection that cannot be read says nothing here: Sync from GitHub above still works.
|
|
162
175
|
if (me.error || !c || !c.configured)
|
|
163
176
|
return note ? _jsx(Note, { children: note }) : null;
|
|
@@ -172,7 +185,31 @@ function GitHubLink({ onSync }) {
|
|
|
172
185
|
setBusy(false);
|
|
173
186
|
}
|
|
174
187
|
};
|
|
175
|
-
|
|
188
|
+
// The person's own link is theirs to drop; the org's App accounts are an org admin's.
|
|
189
|
+
const own = me.value?.connected === true;
|
|
190
|
+
const leave = c.connected && (own || host.admin);
|
|
191
|
+
const drop = async () => {
|
|
192
|
+
setBusy(true);
|
|
193
|
+
setNote('');
|
|
194
|
+
try {
|
|
195
|
+
await (own ? unlink(target) : disconnect(target, 'github'));
|
|
196
|
+
setAsking(false);
|
|
197
|
+
setNote(t('GitHub is disconnected. Connect it again to choose another account.'));
|
|
198
|
+
me.reload();
|
|
199
|
+
installed.reload();
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
setNote(e.message);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
setBusy(false);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", flexWrap: "wrap", children: [_jsx(Github, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 200, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: c.connected ? t('GitHub connected as {login}', { login: `@${c.login}` }) : t('Connect GitHub to bring your repositories here') }), _jsx(SizableText, { size: "$1", color: "$soft", children: asking
|
|
209
|
+
? own
|
|
210
|
+
? t('Disconnect your GitHub from Hanzo?')
|
|
211
|
+
: t('Disconnect @{login}? Hanzo’s GitHub App is removed from it too, unless another organization uses it.', { login: c.login })
|
|
212
|
+
: note || (c.connected ? t('Choose repositories to mirror; each stays in step with GitHub.') : t('One step on GitHub, then pick the repositories to sync.')) })] }), c.connected && asking ? (_jsxs(XStack, { gap: "$2", children: [_jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => setAsking(false), children: t('Cancel') }), _jsx(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void drop(), children: busy ? t('Disconnecting…') : t('Disconnect') })] })) : c.connected ? (_jsxs(XStack, { gap: "$2", children: [_jsxs(Button, { size: "sm", variant: "outline", onPress: onSync, children: [_jsx(RefreshCw, { size: 14 }), t('Sync repositories')] }), leave ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => setAsking(true), children: t('Disconnect') })) : null] })) : (_jsxs(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void link(), children: [_jsx(Github, { size: 14 }), busy ? t('Opening GitHub…') : t('Connect GitHub')] }))] }));
|
|
176
213
|
}
|
|
177
214
|
/** The menus' words, marked (`phrase`) and drawn through `t`; GitHub and Hanzo are names, drawn as given. */
|
|
178
215
|
const KINDS = { all: phrase('Issues and pull requests'), issue: phrase('Issues'), pr: phrase('Pull requests') };
|
|
@@ -85,7 +85,7 @@ export function Integrations() {
|
|
|
85
85
|
? t('This deployment cannot connect GitHub yet.')
|
|
86
86
|
: me.connected
|
|
87
87
|
? t('Connected as @{login}', { login: me.login })
|
|
88
|
-
: t('Not connected'), trailing: me?.configured ? (me.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => unlinkGithub(target), t('Your GitHub is disconnected'), mine.reload), children: t('Disconnect') })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => connect(target, here())), children: t('Connect') }))) : null }), installed.error ? (_jsx(Row, { title: t('App installations'), detail: told(installed.error, t) })) : (installed.value.map((i) => (_jsx(Row, { leading: _jsx(Github, { size: 16 }), title: i.login, detail: [i.type, i.grant === 'all' ? t('every repository') : i.grant === 'selected' ? t('selected repositories') : ''].filter(Boolean).join(' · '), trailing: _jsx(SizableText, { size: "$1", color: i.connected ? '$ink' : '$soft', children: i.connected ? t('Installed') : t('Removed on GitHub') }) }, i.login)))), !installed.error && !installed.loading && installed.value.length === 0 ? (_jsx(Row, { title: t('No GitHub accounts installed'), detail: t('Install the App on a GitHub account to bring its repositories here.') })) : null] }), host.admin ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(target, 'github', here())), children: t('Install on a GitHub account') }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin installs the App on a GitHub account.') }))] }), _jsxs(Group, { title: "Slack", detail: t('Talk to Hanzo from your workspace’s channels.'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Slack, { size: 16 }), title: s?.connected && s.account ? s.account : 'Slack', detail: slack.error
|
|
88
|
+
: t('Not connected'), trailing: me?.configured ? (me.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => unlinkGithub(target), t('Your GitHub is disconnected'), mine.reload), children: t('Disconnect') })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => connect(target, here())), children: t('Connect') }))) : null }), installed.error ? (_jsx(Row, { title: t('App installations'), detail: told(installed.error, t) })) : (installed.value.map((i) => (_jsx(Row, { leading: _jsx(Github, { size: 16 }), title: i.login, detail: [i.type, i.grant === 'all' ? t('every repository') : i.grant === 'selected' ? t('selected repositories') : ''].filter(Boolean).join(' · '), trailing: _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(SizableText, { size: "$1", color: i.connected ? '$ink' : '$soft', children: i.connected ? t('Installed') : t('Removed on GitHub') }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(target, 'github', i.login), t('@{login} is disconnected', { login: i.login }), installed.reload), children: t('Disconnect') })) : null] }) }, i.login)))), !installed.error && !installed.loading && installed.value.length === 0 ? (_jsx(Row, { title: t('No GitHub accounts installed'), detail: t('Install the App on a GitHub account to bring its repositories here.') })) : null] }), host.admin ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(target, 'github', here())), children: t('Install on a GitHub account') }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin installs the App on a GitHub account.') }))] }), _jsxs(Group, { title: "Slack", detail: t('Talk to Hanzo from your workspace’s channels.'), children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Slack, { size: 16 }), title: s?.connected && s.account ? s.account : 'Slack', detail: slack.error
|
|
89
89
|
? told(slack.error, t)
|
|
90
90
|
: !s
|
|
91
91
|
? t('Reading…')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.87",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|