@hanzo/build 0.2.86 → 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.
@@ -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
- export declare function disconnect(t: Target, id: string): Promise<void>;
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;
@@ -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
- export async function disconnect(t, id) {
80
- await call(t, 'POST', `/v1/provider/${seg(id)}/disconnect`, {});
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, installations, linked, 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';
@@ -150,6 +151,10 @@ function Choice({ label, icon, says, onPress }) {
150
151
  * the accounts the org has bound the App on (`linked`), so an org that installed
151
152
  * the App in onboarding does not read as unconnected here. GitHub returns here,
152
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.
153
158
  */
154
159
  function GitHubLink({ onSync }) {
155
160
  const host = useHost();
@@ -160,6 +165,7 @@ function GitHubLink({ onSync }) {
160
165
  const installed = useRead(() => installations(target).catch(() => []), [], [target]);
161
166
  const [note, setNote] = useState('');
162
167
  const [busy, setBusy] = useState(false);
168
+ const [asking, setAsking] = useState(false);
163
169
  useBack(target, true, setNote, () => {
164
170
  me.reload();
165
171
  installed.reload();
@@ -179,7 +185,31 @@ function GitHubLink({ onSync }) {
179
185
  setBusy(false);
180
186
  }
181
187
  };
182
- 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: 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 ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: onSync, children: [_jsx(RefreshCw, { size: 14 }), t('Sync repositories')] })) : (_jsxs(Button, { size: "sm", variant: "primary", disabled: busy, onPress: () => void link(), children: [_jsx(Github, { size: 14 }), busy ? t('Opening GitHub…') : t('Connect GitHub')] }))] }));
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')] }))] }));
183
213
  }
184
214
  /** The menus' words, marked (`phrase`) and drawn through `t`; GitHub and Hanzo are names, drawn as given. */
185
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.86",
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.",