@rathnasgala/cli 0.0.20 → 0.0.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rathnasgala/cli",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -27,9 +27,12 @@ export async function createPublication({
27
27
  ask,
28
28
  openUrl = () => false,
29
29
  shareAttempts = 3,
30
- installationsUrl = 'https://github.com/settings/installations'
30
+ selfLogin
31
31
  }) {
32
32
  let created = false;
33
+ let shareUrl = 'https://github.com/settings/installations';
34
+ let repositoryOwner = selfLogin ?? '';
35
+ let repositoryName = name;
33
36
  for (let attempt = 0; attempt < Math.max(1, shareAttempts); attempt += 1) {
34
37
  const result = await requestPublication({
35
38
  apiBaseUrl, galaAccessToken, githubAccessToken, name, fetchImpl, authorize
@@ -47,20 +50,43 @@ export async function createPublication({
47
50
  * rather than NEEDS_SHARING — the same situation under a different name.
48
51
  */
49
52
  const shareable = result.status === 'NEEDS_SHARING' || created;
50
- created = created || result.status === 'NEEDS_SHARING';
53
+ if (result.status === 'NEEDS_SHARING') {
54
+ created = true;
55
+ shareUrl = installationSettingsUrl(result.installationId, result.owner, selfLogin);
56
+ repositoryName = result.repository ?? repositoryName;
57
+ repositoryOwner = result.owner ?? repositoryOwner;
58
+ }
51
59
  if (!shareable || typeof ask !== 'function') throw result.failure;
52
60
 
53
- notify(`${result.owner ?? ''}/${result.repository ?? name} exists, but the Gala GitHub App `
54
- + 'cannot reach it yet — its installation covers only selected repositories.');
55
- notify(`${openUrl(installationsUrl) ? 'Opened' : 'Open'} ${installationsUrl}`);
61
+ notify(`${repositoryOwner}/${repositoryName} was created, but the Gala GitHub App cannot reach `
62
+ + 'it yet — its installation covers only selected repositories, which is the right way to '
63
+ + 'have it. Add this one repository to the installation; nothing else needs granting.');
64
+ notify(`${openUrl(shareUrl) ? 'Opened' : 'Open'} ${shareUrl}`);
56
65
  await ask('Press enter once the App can access that repository. ');
57
66
  }
58
67
  throw new Error(
59
- `The Gala GitHub App still cannot reach the repository for ${name}. Give it access at `
60
- + `${installationsUrl}, then run scaffold again.`
68
+ `The Gala GitHub App still cannot reach ${repositoryOwner}/${repositoryName}. Add that `
69
+ + `repository to the installation at ${shareUrl}, then run scaffold again.`
61
70
  );
62
71
  }
63
72
 
73
+ /**
74
+ * The page that grants one repository, rather than the list of every app ever installed.
75
+ *
76
+ * GitHub keeps user and organisation installation settings on different paths, and only the caller
77
+ * knows which this is: the created owner differing from the token's own account means the
78
+ * installation lives on an organisation.
79
+ */
80
+ export function installationSettingsUrl(installationId, owner, selfLogin) {
81
+ const generic = 'https://github.com/settings/installations';
82
+ if (!Number.isSafeInteger(Number(installationId)) || Number(installationId) <= 0) return generic;
83
+ const isOrganization = typeof owner === 'string' && typeof selfLogin === 'string'
84
+ && owner.toLowerCase() !== selfLogin.toLowerCase();
85
+ return isOrganization
86
+ ? `https://github.com/organizations/${encodeURIComponent(owner)}/settings/installations/${installationId}`
87
+ : `https://github.com/settings/installations/${installationId}`;
88
+ }
89
+
64
90
  async function requestPublication({
65
91
  apiBaseUrl, galaAccessToken, githubAccessToken, name, fetchImpl, authorize
66
92
  }) {
@@ -87,6 +113,9 @@ async function requestPublication({
87
113
  if (status !== 'READY') {
88
114
  return {
89
115
  ready: false, status, owner, repository,
116
+ // Carried even though the repository is not in the installation yet: it is what makes the
117
+ // grant a deep link rather than a hunt.
118
+ installationId: payload?.installationId,
90
119
  failure: new Error(
91
120
  `Gala could not create the publication repository (${payload?.outcome ?? status}). `
92
121
  + 'Give the Gala GitHub App access to it at https://github.com/settings/installations, or '
@@ -112,7 +112,7 @@ export async function scaffoldSite({
112
112
  const created = await createRepository({
113
113
  apiBaseUrl: gala.apiBaseUrl, galaAccessToken: gala.accessToken,
114
114
  githubAccessToken: github.accessToken, name: repositoryName,
115
- notify, ask, openUrl
115
+ notify, ask, openUrl, selfLogin: requestedOwner
116
116
  });
117
117
  repositoryOwner = segment(created.owner, 'owner');
118
118
  repositoryName = segment(created.repository, 'repository');