@faable/faable 1.5.23 β 1.5.25
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/dist/commands/deploy/check_environment.js +1 -2
- package/dist/commands/deploy/index.js +35 -2
- package/dist/commands/deploy/runtime-detect/strategies/docker.js +1 -1
- package/dist/commands/deploy/runtime-detect/strategies/nodejs.js +1 -1
- package/dist/commands/link/index.js +1 -1
- package/dist/commands/login/index.js +99 -33
- package/dist/commands/whoami/index.js +1 -1
- package/dist/lib/cmd.js +1 -1
- package/package.json +2 -1
|
@@ -59,8 +59,41 @@ const deploy = {
|
|
|
59
59
|
// Upload to Faable registry
|
|
60
60
|
const { upload_tagname } = await upload_tag({ app, api });
|
|
61
61
|
// Create a deployment for this image
|
|
62
|
-
await api.createDeployment({
|
|
63
|
-
|
|
62
|
+
const deployment = await api.createDeployment({
|
|
63
|
+
app_id: app.id,
|
|
64
|
+
image: upload_tagname,
|
|
65
|
+
type
|
|
66
|
+
});
|
|
67
|
+
const dashboard_url = `https://dashboard.faable.com/deploy/${app.team}/app/${app.id}`;
|
|
68
|
+
log.info(`π Deployment created (${deployment.id}) -> https://${app.url}`);
|
|
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
|
+
}
|
|
64
97
|
}
|
|
65
98
|
};
|
|
66
99
|
|
|
@@ -30,7 +30,7 @@ const strategy_nodejs = async (workdir) => {
|
|
|
30
30
|
runtime_version = out.stdout.toString().trim();
|
|
31
31
|
log.info(`Using node@${runtime_version} from engines in package.json (${engines.node})`);
|
|
32
32
|
}
|
|
33
|
-
catch
|
|
33
|
+
catch {
|
|
34
34
|
log.info(`Node version defined in engines in package.json is not valid (${engines.node}), using current version ${runtime_version}`);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import { FaableApi } from '../../api/FaableApi.js';
|
|
2
2
|
import { getDeviceCode, getDeviceToken } from '../../api/auth.js';
|
|
3
3
|
import { CredentialsStore } from '../../lib/CredentialsStore.js';
|
|
4
|
+
import { bearer_strategy } from '../../api/strategies/bearer.strategy.js';
|
|
4
5
|
import open from 'open';
|
|
6
|
+
import ora from 'ora';
|
|
5
7
|
import { log } from '../../log.js';
|
|
6
8
|
|
|
9
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
const renderUserCodeBlock = (code) => {
|
|
11
|
+
const padded = ` ${code} `;
|
|
12
|
+
const border = "β".repeat(padded.length);
|
|
13
|
+
return [
|
|
14
|
+
"",
|
|
15
|
+
` β${border}β`,
|
|
16
|
+
` β${padded}β`,
|
|
17
|
+
` β${border}β`,
|
|
18
|
+
"",
|
|
19
|
+
].join("\n");
|
|
20
|
+
};
|
|
21
|
+
const extractOAuthError = (e) => {
|
|
22
|
+
const data = e?.response?.data || e?.cause?.response?.data || {};
|
|
23
|
+
if (typeof data === "object")
|
|
24
|
+
return data;
|
|
25
|
+
return {};
|
|
26
|
+
};
|
|
7
27
|
const login = {
|
|
8
28
|
command: "login",
|
|
9
29
|
describe: "Login to Faable",
|
|
@@ -22,7 +42,6 @@ const login = {
|
|
|
22
42
|
handler: async (args) => {
|
|
23
43
|
const { apikey, token } = args;
|
|
24
44
|
const store = new CredentialsStore();
|
|
25
|
-
FaableApi.create(); // Base client for device flow
|
|
26
45
|
if (apikey) {
|
|
27
46
|
log.info("Logging in with API Key...");
|
|
28
47
|
const tempApi = FaableApi.create({ auth: { apikey }, authStrategy: (await import('../../api/strategies/apikey.strategy.js')).apikey_strategy });
|
|
@@ -31,7 +50,7 @@ const login = {
|
|
|
31
50
|
await store.saveCredentials({ apikey, email: me.email });
|
|
32
51
|
log.info(`β
Successfully logged in as ${me.email}`);
|
|
33
52
|
}
|
|
34
|
-
catch
|
|
53
|
+
catch {
|
|
35
54
|
log.error("β Invalid API Key");
|
|
36
55
|
process.exit(1);
|
|
37
56
|
}
|
|
@@ -45,33 +64,69 @@ const login = {
|
|
|
45
64
|
await store.saveCredentials({ token, email: me.email });
|
|
46
65
|
log.info(`β
Successfully logged in as ${me.email}`);
|
|
47
66
|
}
|
|
48
|
-
catch
|
|
67
|
+
catch {
|
|
49
68
|
log.error("β Invalid OIDC token");
|
|
50
69
|
process.exit(1);
|
|
51
70
|
}
|
|
52
71
|
return;
|
|
53
72
|
}
|
|
54
|
-
// Interactive Device
|
|
73
|
+
// Interactive Device Authorization Grant (RFC 8628)
|
|
55
74
|
log.info("Starting browser-based authentication...");
|
|
75
|
+
let device_code;
|
|
76
|
+
let user_code;
|
|
77
|
+
let verification_uri;
|
|
78
|
+
let verification_uri_complete;
|
|
79
|
+
let interval;
|
|
80
|
+
let expires_in;
|
|
56
81
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
const dc = await getDeviceCode();
|
|
83
|
+
device_code = dc.device_code;
|
|
84
|
+
user_code = dc.user_code;
|
|
85
|
+
verification_uri = dc.verification_uri;
|
|
86
|
+
verification_uri_complete = dc.verification_uri_complete;
|
|
87
|
+
interval = dc.interval;
|
|
88
|
+
expires_in = dc.expires_in;
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
log.error(`β Failed to start authentication: ${e.message}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
process.stdout.write(renderUserCodeBlock(user_code));
|
|
95
|
+
log.info(`If your browser doesn't open automatically, visit: ${verification_uri}`);
|
|
96
|
+
try {
|
|
97
|
+
await open(verification_uri_complete);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
log.warn("Could not open browser automatically.");
|
|
101
|
+
}
|
|
102
|
+
const spinner = ora({
|
|
103
|
+
text: "Waiting for confirmation in browserβ¦",
|
|
104
|
+
spinner: "dots",
|
|
105
|
+
}).start();
|
|
106
|
+
let cancelled = false;
|
|
107
|
+
const onSigint = () => {
|
|
108
|
+
cancelled = true;
|
|
109
|
+
spinner.stop();
|
|
110
|
+
process.stderr.write("\nLogin cancelled.\n");
|
|
111
|
+
process.exit(130);
|
|
112
|
+
};
|
|
113
|
+
process.once("SIGINT", onSigint);
|
|
114
|
+
const start = Date.now();
|
|
115
|
+
const timeoutMs = expires_in * 1000;
|
|
116
|
+
let currentInterval = interval;
|
|
117
|
+
try {
|
|
118
|
+
while (!cancelled && Date.now() - start < timeoutMs) {
|
|
119
|
+
await wait(currentInterval * 1000);
|
|
120
|
+
if (cancelled)
|
|
121
|
+
return;
|
|
70
122
|
try {
|
|
71
123
|
const { access_token } = await getDeviceToken(device_code);
|
|
72
124
|
if (access_token) {
|
|
73
|
-
|
|
74
|
-
const tempApi = FaableApi.create({
|
|
125
|
+
spinner.stop();
|
|
126
|
+
const tempApi = FaableApi.create({
|
|
127
|
+
auth: { token: access_token },
|
|
128
|
+
authStrategy: bearer_strategy,
|
|
129
|
+
});
|
|
75
130
|
const me = await tempApi.getMe();
|
|
76
131
|
await store.saveCredentials({ token: access_token, email: me.email });
|
|
77
132
|
log.info(`β
Successfully logged in as ${me.email}`);
|
|
@@ -79,27 +134,38 @@ const login = {
|
|
|
79
134
|
}
|
|
80
135
|
}
|
|
81
136
|
catch (e) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (errData?.error === "authorization_pending") {
|
|
86
|
-
// Wait and continue
|
|
137
|
+
const { error, error_description } = extractOAuthError(e);
|
|
138
|
+
if (error === "authorization_pending") {
|
|
139
|
+
continue;
|
|
87
140
|
}
|
|
88
|
-
|
|
89
|
-
|
|
141
|
+
if (error === "slow_down") {
|
|
142
|
+
currentInterval += 5;
|
|
143
|
+
spinner.text = `Waiting for confirmation in browser⦠(slowing polling to ${currentInterval}s)`;
|
|
144
|
+
continue;
|
|
90
145
|
}
|
|
91
|
-
|
|
92
|
-
|
|
146
|
+
if (error === "access_denied") {
|
|
147
|
+
spinner.stop();
|
|
148
|
+
log.error("β Authorization denied. Run `faable login` again to retry.");
|
|
149
|
+
process.exit(1);
|
|
93
150
|
}
|
|
151
|
+
if (error === "expired_token") {
|
|
152
|
+
spinner.stop();
|
|
153
|
+
log.error("β Code expired. Run `faable login` again to start over.");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
// Unknown OAuth error or non-OAuth failure
|
|
157
|
+
spinner.stop();
|
|
158
|
+
const detail = error_description || error || e?.message || "unknown error";
|
|
159
|
+
log.error(`β Authentication failed: ${detail}`);
|
|
160
|
+
process.exit(1);
|
|
94
161
|
}
|
|
95
|
-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
96
162
|
}
|
|
97
|
-
|
|
163
|
+
spinner.stop();
|
|
164
|
+
log.error("β Code expired. Run `faable login` again to start over.");
|
|
98
165
|
process.exit(1);
|
|
99
166
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
process.exit(1);
|
|
167
|
+
finally {
|
|
168
|
+
process.removeListener("SIGINT", onSigint);
|
|
103
169
|
}
|
|
104
170
|
},
|
|
105
171
|
};
|
package/dist/lib/cmd.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faable/faable",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.25",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Pomar <marc@faable.com>",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"fs-extra": "^11.3.2",
|
|
32
32
|
"handlebars": "^4.7.8",
|
|
33
33
|
"open": "^11.0.0",
|
|
34
|
+
"ora": "^9.4.0",
|
|
34
35
|
"pino": "^10.1.0",
|
|
35
36
|
"pino-pretty": "^13.1.3",
|
|
36
37
|
"promisify-child-process": "^4.1.2",
|