@faable/faable 1.5.24 → 1.5.26

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.
@@ -34,9 +34,15 @@ class FaableApi {
34
34
  const url = e.config?.url || "";
35
35
  if (res) {
36
36
  const serverMessage = res.data?.message || res.statusText || "Unknown Error";
37
- throw new Error(`FaableApi ${url} ${res.status}: ${serverMessage}`, {
38
- cause: error,
39
- });
37
+ const wrapped = new Error(`FaableApi ${url} ${res.status}: ${serverMessage}`, { cause: error });
38
+ // Surface the structured error contract (e.g. the repository-link
39
+ // flow returns { code, action }) so callers can branch on it.
40
+ wrapped.status = res.status;
41
+ if (res.data?.code)
42
+ wrapped.code = res.data.code;
43
+ if (res.data?.action)
44
+ wrapped.action = res.data.action;
45
+ throw wrapped;
40
46
  }
41
47
  else {
42
48
  throw new Error(`FaableApi ${url} ${e.message}`, { cause: error });
@@ -69,6 +75,17 @@ class FaableApi {
69
75
  async updateApp(app_id, params) {
70
76
  return data(this.client.post(`/app/${app_id}`, params));
71
77
  }
78
+ async linkRepository(app_id, params) {
79
+ return data(this.client.post(`/app/${app_id}/link-repository`, params));
80
+ }
81
+ // Organizations/accounts where the Faable GitHub App is installed.
82
+ async listGithubInstallations() {
83
+ return data(this.client.get(`/github/installations`)).then((res) => res.installations);
84
+ }
85
+ // Top repositories for a single installation (org), optionally filtered.
86
+ async listGithubRepos(installation_id, params = {}) {
87
+ return data(this.client.get(`/github/installations/${installation_id}/repositories`, { params })).then((res) => res.repositories);
88
+ }
72
89
  async getMe() {
73
90
  return data(this.client.get(`/auth/me`));
74
91
  }
@@ -67,6 +67,33 @@ const deploy = {
67
67
  const dashboard_url = `https://dashboard.faable.com/deploy/${app.team}/app/${app.id}`;
68
68
  log.info(`🌍 Deployment created (${deployment.id}) -> https://${app.url}`);
69
69
  log.info(`📊 View it in the dashboard -> ${dashboard_url}`);
70
+ // Wait (up to 5 minutes) for the deployment to be promoted (live)
71
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
72
+ const timeoutMs = 5 * 60 * 1000;
73
+ const intervalMs = 5000;
74
+ const start = Date.now();
75
+ log.info(`⏳ Waiting for deployment to be promoted...`);
76
+ let promoted = false;
77
+ while (Date.now() - start < timeoutMs) {
78
+ await wait(intervalMs);
79
+ try {
80
+ const current = await api.getApp(app.id);
81
+ if (current.status?.deployment === deployment.id) {
82
+ promoted = true;
83
+ break;
84
+ }
85
+ }
86
+ catch (error) {
87
+ // Ignore transient errors while polling and keep waiting
88
+ log.debug(`Polling app status failed, retrying...`);
89
+ }
90
+ }
91
+ if (promoted) {
92
+ log.info(`✅ Deployment promoted and live -> https://${app.url}`);
93
+ }
94
+ else {
95
+ log.warn(`⌛ Timed out after 5min waiting for promotion. The deployment is still rolling out, check the dashboard -> ${dashboard_url}`);
96
+ }
70
97
  }
71
98
  };
72
99
 
@@ -84,15 +84,37 @@ const link = {
84
84
  return;
85
85
  }
86
86
  log.info(`Linking to "${selectedApp.name}" (${selectedApp.id})...`);
87
- // Update the app in the API
88
- if (gitUrl) {
89
- await api.updateApp(selectedApp.id, { repository: gitUrl });
90
- log.info(`Updated app with github_repo: ${gitUrl}`);
87
+ if (!gitUrl) {
88
+ log.error("No git remote URL detected. Add a GitHub 'origin' remote and try again.");
89
+ return;
91
90
  }
92
- else {
93
- log.warn("No git remote URL detected. Skipping API update for github_repo.");
91
+ // The API verifies that the user has a connected GitHub identity AND
92
+ // access to the repository before persisting the link.
93
+ try {
94
+ await api.linkRepository(selectedApp.id, { repository: gitUrl });
95
+ }
96
+ catch (err) {
97
+ const code = err?.code;
98
+ switch (code) {
99
+ case "github_identity_missing":
100
+ log.error("You have not connected a GitHub account to Faable. Connect GitHub in the dashboard, then re-run `faable link`.");
101
+ break;
102
+ case "github_token_invalid":
103
+ log.error("Your GitHub authorization has expired. Reconnect GitHub in the dashboard, then re-run `faable link`.");
104
+ break;
105
+ case "github_installation_missing":
106
+ log.error("The Faable GitHub App is not installed on your repositories. Install it from the Faable dashboard, then re-run `faable link`.");
107
+ break;
108
+ case "github_repository_not_found":
109
+ log.error(`Faable can't access "${gitUrl}". Check the repository name and that the Faable GitHub App is installed on it.`);
110
+ break;
111
+ default:
112
+ log.error(`Could not link repository: ${err?.message ?? err}`);
113
+ }
114
+ return;
94
115
  }
95
- // Save locally for CLI convenience
116
+ log.info(`Linked repository ${gitUrl} to ${selectedApp.name}.`);
117
+ // Save locally for CLI convenience (only after the API confirms the link)
96
118
  Configuration.instance().saveConfig({ app_slug: selectedApp.name, app_id: selectedApp.id });
97
119
  log.info(`Successfully linked local repository to ${selectedApp.name}.`);
98
120
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",