@faable/faable 1.5.17-next.1 → 1.5.17-next.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/dist/api/FaableApi.js +35 -3
- package/dist/api/base_client.js +7 -5
- package/dist/api/context.js +31 -6
- package/dist/api/strategies/bearer.strategy.js +15 -0
- package/dist/api/strategies/oidc.strategy.js +17 -10
- package/dist/commands/auth/index.js +31 -0
- package/dist/commands/deploy/deploy_command.js +15 -5
- package/dist/commands/init/index.js +1 -1
- package/dist/commands/link/index.js +82 -0
- package/dist/commands/login/index.js +104 -0
- package/dist/commands/logout/index.js +14 -0
- package/dist/commands/whoami/index.js +24 -0
- package/dist/index.js +12 -4
- package/dist/lib/Configuration.js +14 -3
- package/dist/lib/CredentialsStore.js +22 -5
- package/package.json +64 -18
- package/dist/commands/configure/index.js +0 -34
package/dist/api/FaableApi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
|
-
import {
|
|
2
|
+
import { create_base_client } from './base_client.js';
|
|
3
3
|
|
|
4
4
|
function handleError() {
|
|
5
5
|
return function (target, propertyKey, descriptor) {
|
|
@@ -34,10 +34,12 @@ const data = async (res) => {
|
|
|
34
34
|
};
|
|
35
35
|
class FaableApi {
|
|
36
36
|
client;
|
|
37
|
+
strategy;
|
|
37
38
|
constructor(config) {
|
|
38
39
|
const { authStrategy, auth } = config;
|
|
39
|
-
this.client =
|
|
40
|
-
|
|
40
|
+
this.client = create_base_client();
|
|
41
|
+
this.strategy = authStrategy && authStrategy(auth);
|
|
42
|
+
const strategy = this.strategy;
|
|
41
43
|
this.client.interceptors.request.use(async function (config) {
|
|
42
44
|
// Do something before request is sent
|
|
43
45
|
const headers = strategy ? await strategy.headers() : {};
|
|
@@ -59,6 +61,9 @@ class FaableApi {
|
|
|
59
61
|
async getBySlug(slug) {
|
|
60
62
|
return data(this.client.get(`/app/slug/${slug}`));
|
|
61
63
|
}
|
|
64
|
+
async getApp(app_id) {
|
|
65
|
+
return data(this.client.get(`/app/${app_id}`));
|
|
66
|
+
}
|
|
62
67
|
async getRegistry(app_id) {
|
|
63
68
|
return data(this.client.get(`/app/${app_id}/registry`));
|
|
64
69
|
}
|
|
@@ -68,6 +73,18 @@ class FaableApi {
|
|
|
68
73
|
async getAppSecrets(app_id) {
|
|
69
74
|
return firstPage(data(this.client.get(`/secret/${app_id}`)));
|
|
70
75
|
}
|
|
76
|
+
async updateApp(app_id, params) {
|
|
77
|
+
return data(this.client.post(`/app/${app_id}`, params));
|
|
78
|
+
}
|
|
79
|
+
async getDeviceCode() {
|
|
80
|
+
return data(this.client.post(`/auth/device/code`));
|
|
81
|
+
}
|
|
82
|
+
async getDeviceToken(device_code) {
|
|
83
|
+
return data(this.client.post(`/auth/device/token`, { device_code }));
|
|
84
|
+
}
|
|
85
|
+
async getMe() {
|
|
86
|
+
return data(this.client.get(`/auth/me`));
|
|
87
|
+
}
|
|
71
88
|
}
|
|
72
89
|
__decorate([
|
|
73
90
|
handleError()
|
|
@@ -75,6 +92,9 @@ __decorate([
|
|
|
75
92
|
__decorate([
|
|
76
93
|
handleError()
|
|
77
94
|
], FaableApi.prototype, "getBySlug", null);
|
|
95
|
+
__decorate([
|
|
96
|
+
handleError()
|
|
97
|
+
], FaableApi.prototype, "getApp", null);
|
|
78
98
|
__decorate([
|
|
79
99
|
handleError()
|
|
80
100
|
], FaableApi.prototype, "getRegistry", null);
|
|
@@ -84,5 +104,17 @@ __decorate([
|
|
|
84
104
|
__decorate([
|
|
85
105
|
handleError()
|
|
86
106
|
], FaableApi.prototype, "getAppSecrets", null);
|
|
107
|
+
__decorate([
|
|
108
|
+
handleError()
|
|
109
|
+
], FaableApi.prototype, "updateApp", null);
|
|
110
|
+
__decorate([
|
|
111
|
+
handleError()
|
|
112
|
+
], FaableApi.prototype, "getDeviceCode", null);
|
|
113
|
+
__decorate([
|
|
114
|
+
handleError()
|
|
115
|
+
], FaableApi.prototype, "getDeviceToken", null);
|
|
116
|
+
__decorate([
|
|
117
|
+
handleError()
|
|
118
|
+
], FaableApi.prototype, "getMe", null);
|
|
87
119
|
|
|
88
120
|
export { FaableApi };
|
package/dist/api/base_client.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
|
|
3
3
|
const BASE_URL = process.env.BASE_URL || "https://api.faable.com";
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const create_base_client = () => {
|
|
5
|
+
return axios.create({
|
|
6
|
+
baseURL: BASE_URL,
|
|
7
|
+
timeout: 10000
|
|
8
|
+
});
|
|
9
|
+
};
|
|
8
10
|
|
|
9
|
-
export {
|
|
11
|
+
export { create_base_client };
|
package/dist/api/context.js
CHANGED
|
@@ -2,28 +2,53 @@ import { FaableApi } from './FaableApi.js';
|
|
|
2
2
|
import { apikey_strategy } from './strategies/apikey.strategy.js';
|
|
3
3
|
import { getIDToken } from '@actions/core';
|
|
4
4
|
import { oidc_strategy } from './strategies/oidc.strategy.js';
|
|
5
|
+
import { bearer_strategy } from './strategies/bearer.strategy.js';
|
|
6
|
+
import { CredentialsStore } from '../lib/CredentialsStore.js';
|
|
5
7
|
|
|
6
|
-
// import { CredentialsStore } from "../lib/CredentialsStore";
|
|
7
8
|
const context = async () => {
|
|
8
9
|
let api;
|
|
9
10
|
if (process.env.FAABLE_APIKEY) {
|
|
11
|
+
// Apikey in environment
|
|
10
12
|
const apikey = process.env.FAABLE_APIKEY;
|
|
11
13
|
api = FaableApi.create({ authStrategy: apikey_strategy, auth: { apikey } });
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
else if (process.env.FAABLE_TOKEN) {
|
|
16
|
+
// Token in environment
|
|
17
|
+
const token = process.env.FAABLE_TOKEN;
|
|
18
|
+
api = FaableApi.create({ authStrategy: bearer_strategy, auth: { token } });
|
|
19
|
+
}
|
|
20
|
+
else if (process.env.GITHUB_ACTIONS === "true") {
|
|
21
|
+
// Github actions environment
|
|
17
22
|
try {
|
|
18
|
-
const idToken = await getIDToken("https://faable.com");
|
|
23
|
+
const idToken = process.env.FAABLE_ID_TOKEN || (await getIDToken("https://faable.com"));
|
|
19
24
|
api = FaableApi.create({ authStrategy: oidc_strategy, auth: { idToken } });
|
|
20
25
|
}
|
|
21
26
|
catch (_) {
|
|
22
27
|
console.error("Error fetching token, configure 'permissions: id-token: write'");
|
|
23
28
|
}
|
|
24
29
|
}
|
|
30
|
+
else {
|
|
31
|
+
const store = new CredentialsStore();
|
|
32
|
+
const config = await store.loadCredentials();
|
|
33
|
+
if (config) {
|
|
34
|
+
if (config.token) {
|
|
35
|
+
api = FaableApi.create({
|
|
36
|
+
authStrategy: bearer_strategy,
|
|
37
|
+
auth: { token: config.token },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (config.apikey) {
|
|
41
|
+
api = FaableApi.create({
|
|
42
|
+
authStrategy: apikey_strategy,
|
|
43
|
+
auth: { apikey: config.apikey },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const appId = await api?.strategy?.app_id?.();
|
|
25
49
|
return {
|
|
26
50
|
api,
|
|
51
|
+
appId,
|
|
27
52
|
};
|
|
28
53
|
};
|
|
29
54
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const bearer_strategy = (config) => {
|
|
2
|
+
const { token } = config;
|
|
3
|
+
if (!token) {
|
|
4
|
+
throw new Error("Missing token.");
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
headers: async () => {
|
|
8
|
+
return {
|
|
9
|
+
Authorization: `Bearer ${token}`,
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { bearer_strategy };
|
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { create_base_client } from '../base_client.js';
|
|
2
2
|
|
|
3
3
|
const exchangeGithubOidcToken = async (gh_token) => {
|
|
4
|
-
const
|
|
4
|
+
const client = create_base_client();
|
|
5
|
+
const res = await client.post("/auth/github-oidc", {
|
|
5
6
|
token: gh_token
|
|
6
7
|
});
|
|
7
|
-
const { token } = res.data;
|
|
8
|
-
|
|
9
|
-
console.log(token);
|
|
10
|
-
return token;
|
|
8
|
+
const { token, app_id } = res.data;
|
|
9
|
+
return { token, app_id };
|
|
11
10
|
};
|
|
12
11
|
const oidc_strategy = (config) => {
|
|
13
12
|
const { idToken } = config;
|
|
14
13
|
if (!idToken) {
|
|
15
14
|
throw new Error("Missing idToken.");
|
|
16
15
|
}
|
|
17
|
-
let
|
|
16
|
+
let token_ex;
|
|
18
17
|
return {
|
|
19
18
|
headers: async () => {
|
|
20
|
-
if (!
|
|
21
|
-
|
|
19
|
+
if (!token_ex) {
|
|
20
|
+
const ex = await exchangeGithubOidcToken(idToken);
|
|
21
|
+
token_ex = ex;
|
|
22
22
|
}
|
|
23
23
|
return {
|
|
24
|
-
Authorization: `Bearer ${token}`,
|
|
24
|
+
Authorization: `Bearer ${token_ex.token}`,
|
|
25
25
|
};
|
|
26
26
|
},
|
|
27
|
+
app_id: async () => {
|
|
28
|
+
if (!token_ex) {
|
|
29
|
+
const ex = await exchangeGithubOidcToken(idToken);
|
|
30
|
+
token_ex = ex;
|
|
31
|
+
}
|
|
32
|
+
return token_ex.app_id;
|
|
33
|
+
}
|
|
27
34
|
};
|
|
28
35
|
};
|
|
29
36
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
2
|
+
import { context } from '../../api/context.js';
|
|
3
|
+
|
|
4
|
+
const auth = {
|
|
5
|
+
command: "auth",
|
|
6
|
+
describe: "Manage authentication",
|
|
7
|
+
builder: (yargs) => {
|
|
8
|
+
return yargs.command({
|
|
9
|
+
command: "status",
|
|
10
|
+
describe: "Check authentication status",
|
|
11
|
+
handler: async () => {
|
|
12
|
+
const { api } = await context();
|
|
13
|
+
if (!api) {
|
|
14
|
+
log.error("❌ Not authenticated. Run 'faable login' first.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const me = await api.getMe();
|
|
19
|
+
log.info(`✅ Authenticated as ${me.email}`);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
log.error("❌ Not authenticated or session expired");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
handler: () => { },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { auth };
|
|
@@ -8,15 +8,25 @@ import { cmd } from '../../lib/cmd.js';
|
|
|
8
8
|
|
|
9
9
|
const deploy_command = async (args) => {
|
|
10
10
|
const workdir = args.workdir || process.cwd();
|
|
11
|
-
const { api } = await context();
|
|
11
|
+
const { api, appId } = await context();
|
|
12
12
|
// Resolve runtime
|
|
13
13
|
const { app_name, runtime } = await runtime_detection(workdir);
|
|
14
|
-
|
|
15
|
-
if (
|
|
14
|
+
let app;
|
|
15
|
+
if (args.app_slug) {
|
|
16
|
+
app = await api.getBySlug(args.app_slug);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const oidc_app_id = appId;
|
|
20
|
+
if (oidc_app_id) {
|
|
21
|
+
app = await api.getApp(oidc_app_id);
|
|
22
|
+
}
|
|
23
|
+
else if (app_name) {
|
|
24
|
+
app = await api.getBySlug(app_name);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (!app) {
|
|
16
28
|
throw new Error("Missing <app_name>");
|
|
17
29
|
}
|
|
18
|
-
// Get app from Faable API
|
|
19
|
-
const app = await api.getBySlug(name);
|
|
20
30
|
// Check if we can build docker images
|
|
21
31
|
await check_environment();
|
|
22
32
|
log.info(`🚀 Deploying ${app.name} (${app.id}) runtime=${runtime.name}-${runtime.version}`);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { context } from '../../api/context.js';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { log } from '../../log.js';
|
|
4
|
+
import { cmd } from '../../lib/cmd.js';
|
|
5
|
+
import { Configuration } from '../../lib/Configuration.js';
|
|
6
|
+
|
|
7
|
+
const getGitRemoteUrl = async (workdir) => {
|
|
8
|
+
try {
|
|
9
|
+
const { stdout } = await cmd("git remote get-url origin", { cwd: workdir });
|
|
10
|
+
return stdout?.toString().trim();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
log.warn("Could not detect git remote origin URL.");
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const link = {
|
|
18
|
+
command: "link",
|
|
19
|
+
describe: "Link the local repository with a Faable app",
|
|
20
|
+
builder: (yargs) => {
|
|
21
|
+
return yargs
|
|
22
|
+
.option("workdir", {
|
|
23
|
+
alias: "w",
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Working directory",
|
|
26
|
+
})
|
|
27
|
+
.showHelpOnFail(false);
|
|
28
|
+
},
|
|
29
|
+
handler: async (args) => {
|
|
30
|
+
const workdir = args.workdir || process.cwd();
|
|
31
|
+
const config = Configuration.instance();
|
|
32
|
+
if (config.app_slug) {
|
|
33
|
+
log.info(`This repository is already linked to app: ${config.app_slug}`);
|
|
34
|
+
const { relink } = await prompts({
|
|
35
|
+
type: "toggle",
|
|
36
|
+
name: "relink",
|
|
37
|
+
message: "Do you want to link it to a different app?",
|
|
38
|
+
initial: false,
|
|
39
|
+
active: "yes",
|
|
40
|
+
inactive: "no",
|
|
41
|
+
});
|
|
42
|
+
if (!relink) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const { api } = await context();
|
|
47
|
+
log.info("Checking local git repository...");
|
|
48
|
+
const gitUrl = await getGitRemoteUrl(workdir);
|
|
49
|
+
const apps = await api.list();
|
|
50
|
+
if (apps.length === 0) {
|
|
51
|
+
log.error("No apps found in your account. Create one first at https://faable.cloud");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const { selectedApp } = await prompts({
|
|
55
|
+
type: "select",
|
|
56
|
+
name: "selectedApp",
|
|
57
|
+
message: "Select the Faable app to link with this repository:",
|
|
58
|
+
choices: apps.map((app) => ({
|
|
59
|
+
title: `${app.name} (${app.url})`,
|
|
60
|
+
value: app,
|
|
61
|
+
})),
|
|
62
|
+
});
|
|
63
|
+
if (!selectedApp) {
|
|
64
|
+
log.info("Link cancelled.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
log.info(`Linking to ${selectedApp.name}...`);
|
|
68
|
+
// Update the app in the API
|
|
69
|
+
if (gitUrl) {
|
|
70
|
+
await api.updateApp(selectedApp.id, { github_repo: gitUrl });
|
|
71
|
+
log.info(`Updated app ${selectedApp.name} with github_repo: ${gitUrl}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
log.warn("No git remote URL detected. Skipping API update for github_repo.");
|
|
75
|
+
}
|
|
76
|
+
// Save locally for CLI convenience
|
|
77
|
+
Configuration.instance().saveConfig({ app_slug: selectedApp.name });
|
|
78
|
+
log.info(`Successfully linked local repository to ${selectedApp.name}.`);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export { link };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { FaableApi } from '../../api/FaableApi.js';
|
|
2
|
+
import { CredentialsStore } from '../../lib/CredentialsStore.js';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { log } from '../../log.js';
|
|
5
|
+
|
|
6
|
+
const login = {
|
|
7
|
+
command: "login",
|
|
8
|
+
describe: "Login to Faable",
|
|
9
|
+
builder: (yargs) => {
|
|
10
|
+
return yargs
|
|
11
|
+
.option("apikey", {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Login using an API Key",
|
|
14
|
+
})
|
|
15
|
+
.option("token", {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Login using an OIDC token",
|
|
18
|
+
})
|
|
19
|
+
.showHelpOnFail(false);
|
|
20
|
+
},
|
|
21
|
+
handler: async (args) => {
|
|
22
|
+
const { apikey, token } = args;
|
|
23
|
+
const store = new CredentialsStore();
|
|
24
|
+
const api = FaableApi.create(); // Base client for device flow
|
|
25
|
+
if (apikey) {
|
|
26
|
+
log.info("Logging in with API Key...");
|
|
27
|
+
const tempApi = FaableApi.create({ auth: { apikey }, authStrategy: (await import('../../api/strategies/apikey.strategy.js')).apikey_strategy });
|
|
28
|
+
try {
|
|
29
|
+
const me = await tempApi.getMe();
|
|
30
|
+
await store.saveCredentials({ apikey, email: me.email });
|
|
31
|
+
log.info(`✅ Successfully logged in as ${me.email}`);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
log.error("❌ Invalid API Key");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (token) {
|
|
40
|
+
log.info("Logging in with OIDC token...");
|
|
41
|
+
const tempApi = FaableApi.create({ auth: { idToken: token }, authStrategy: (await import('../../api/strategies/oidc.strategy.js')).oidc_strategy });
|
|
42
|
+
try {
|
|
43
|
+
const me = await tempApi.getMe();
|
|
44
|
+
await store.saveCredentials({ token, email: me.email });
|
|
45
|
+
log.info(`✅ Successfully logged in as ${me.email}`);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
log.error("❌ Invalid OIDC token");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Interactive Device Flow
|
|
54
|
+
log.info("Starting browser-based authentication...");
|
|
55
|
+
try {
|
|
56
|
+
const { device_code, user_code, verification_uri, interval, expires_in } = await api.getDeviceCode();
|
|
57
|
+
log.info(`\nVerification code: ${user_code}\n`);
|
|
58
|
+
log.info(`If your browser doesn't open automatically, please visit:\n${verification_uri}\n`);
|
|
59
|
+
try {
|
|
60
|
+
await open(verification_uri);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
log.warn("Could not open browser automatically.");
|
|
64
|
+
}
|
|
65
|
+
// Polling
|
|
66
|
+
const start = Date.now();
|
|
67
|
+
const timeout = expires_in * 1000;
|
|
68
|
+
while (Date.now() - start < timeout) {
|
|
69
|
+
try {
|
|
70
|
+
const { access_token } = await api.getDeviceToken(device_code);
|
|
71
|
+
if (access_token) {
|
|
72
|
+
log.info("Token received!");
|
|
73
|
+
const tempApi = FaableApi.create({ auth: { token: access_token }, authStrategy: () => ({ headers: async () => ({ Authorization: `Bearer ${access_token}` }) }) });
|
|
74
|
+
const me = await tempApi.getMe();
|
|
75
|
+
await store.saveCredentials({ token: access_token, email: me.email });
|
|
76
|
+
log.info(`✅ Successfully logged in as ${me.email}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// Typically returns 400 with "authorization_pending"
|
|
82
|
+
if (e.cause?.response?.data?.error === "authorization_pending") {
|
|
83
|
+
// Wait and continue
|
|
84
|
+
}
|
|
85
|
+
else if (e.cause?.response?.data?.error === "slow_down") {
|
|
86
|
+
// Should increase interval but for now we just wait
|
|
87
|
+
}
|
|
88
|
+
else if (e.cause?.response?.headers?.["content-type"]?.includes("application/json")) {
|
|
89
|
+
// Other error
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
93
|
+
}
|
|
94
|
+
log.error("❌ Authentication timed out");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
log.error(`❌ Failed to start authentication: ${e.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { login };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CredentialsStore } from '../../lib/CredentialsStore.js';
|
|
2
|
+
import { log } from '../../log.js';
|
|
3
|
+
|
|
4
|
+
const logout = {
|
|
5
|
+
command: "logout",
|
|
6
|
+
describe: "Logout from Faable",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
const store = new CredentialsStore();
|
|
9
|
+
await store.deleteCredentials();
|
|
10
|
+
log.info("✅ Successfully logged out");
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { logout };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { log } from '../../log.js';
|
|
2
|
+
import { context } from '../../api/context.js';
|
|
3
|
+
|
|
4
|
+
const whoami = {
|
|
5
|
+
command: "whoami",
|
|
6
|
+
describe: "Display the current logged in user",
|
|
7
|
+
handler: async () => {
|
|
8
|
+
const { api } = await context();
|
|
9
|
+
if (!api) {
|
|
10
|
+
log.error("❌ Not logged in. Run 'faable login' first.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const me = await api.getMe();
|
|
15
|
+
log.info(`Logged in as: ${me.email}`);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
log.error("❌ Not logged in or session expired");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { whoami };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import yargs from 'yargs';
|
|
2
2
|
import { hideBin } from 'yargs/helpers';
|
|
3
3
|
import { apps } from './commands/apps/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { login } from './commands/login/index.js';
|
|
5
|
+
import { logout } from './commands/logout/index.js';
|
|
6
|
+
import { whoami } from './commands/whoami/index.js';
|
|
7
|
+
import { auth } from './commands/auth/index.js';
|
|
5
8
|
import { deploy } from './commands/deploy/index.js';
|
|
6
9
|
import { log } from './log.js';
|
|
10
|
+
import { link } from './commands/link/index.js';
|
|
7
11
|
import { init } from './commands/init/index.js';
|
|
8
12
|
import { version } from './config.js';
|
|
9
13
|
import { Configuration } from './lib/Configuration.js';
|
|
10
14
|
|
|
11
15
|
const yg = yargs();
|
|
12
16
|
yg.scriptName("faable")
|
|
13
|
-
.middleware(function (
|
|
14
|
-
|
|
17
|
+
.middleware(function (_argv) {
|
|
18
|
+
log.info(`Faable CLI ${version}`);
|
|
15
19
|
}, true)
|
|
16
20
|
.option("c", {
|
|
17
21
|
alias: "config",
|
|
@@ -30,8 +34,12 @@ yg.scriptName("faable")
|
|
|
30
34
|
}, true)
|
|
31
35
|
.command(deploy)
|
|
32
36
|
.command(apps)
|
|
33
|
-
.command(
|
|
37
|
+
.command(login)
|
|
38
|
+
.command(logout)
|
|
39
|
+
.command(whoami)
|
|
40
|
+
.command(auth)
|
|
34
41
|
.command(init)
|
|
42
|
+
.command(link)
|
|
35
43
|
.demandCommand(1)
|
|
36
44
|
.help()
|
|
37
45
|
.fail(function (msg, err) {
|
|
@@ -5,14 +5,16 @@ import { log } from '../log.js';
|
|
|
5
5
|
class Configuration {
|
|
6
6
|
static _instance;
|
|
7
7
|
config = {};
|
|
8
|
+
config_file = "faable.json";
|
|
8
9
|
constructor() {
|
|
9
10
|
// Try to read default config file
|
|
10
11
|
this.setConfigFile("faable.json", { ignoreWarnings: true });
|
|
11
12
|
}
|
|
12
13
|
setConfigFile(file, options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
this.config_file = file;
|
|
15
|
+
const config_path = path__default.join(process.cwd(), file);
|
|
16
|
+
if (fs.existsSync(config_path)) {
|
|
17
|
+
this.config = fs.readJSONSync(config_path);
|
|
16
18
|
log.info(`Loaded configuration from: ${file}`);
|
|
17
19
|
}
|
|
18
20
|
else {
|
|
@@ -21,6 +23,12 @@ class Configuration {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
saveConfig(updates) {
|
|
27
|
+
this.config = { ...this.config, ...updates };
|
|
28
|
+
const config_path = path__default.join(process.cwd(), this.config_file);
|
|
29
|
+
fs.writeJSONSync(config_path, this.config, { spaces: 2 });
|
|
30
|
+
log.info(`Configuration saved to: ${this.config_file}`);
|
|
31
|
+
}
|
|
24
32
|
static instance() {
|
|
25
33
|
if (!Configuration._instance) {
|
|
26
34
|
Configuration._instance = new Configuration();
|
|
@@ -33,6 +41,9 @@ class Configuration {
|
|
|
33
41
|
get buildCommand() {
|
|
34
42
|
return this.config.buildCommand;
|
|
35
43
|
}
|
|
44
|
+
get app_slug() {
|
|
45
|
+
return this.config.app_slug;
|
|
46
|
+
}
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
export { Configuration };
|
|
@@ -11,18 +11,35 @@ class CredentialsStore {
|
|
|
11
11
|
this.faable_home = path__default.join(os.homedir(), ".faable");
|
|
12
12
|
}
|
|
13
13
|
async deleteCredentials() {
|
|
14
|
-
|
|
14
|
+
if (fs.existsSync(this.credentials_path)) {
|
|
15
|
+
await fs.remove(this.credentials_path);
|
|
16
|
+
}
|
|
15
17
|
this.log.info(`Deleted credentials`);
|
|
16
18
|
}
|
|
17
19
|
get credentials_path() {
|
|
18
|
-
return path__default.join(this.faable_home, "
|
|
20
|
+
return path__default.join(this.faable_home, "auth.json");
|
|
19
21
|
}
|
|
20
|
-
async
|
|
22
|
+
async saveCredentials(config) {
|
|
21
23
|
await fs.ensureDir(this.faable_home);
|
|
22
|
-
await fs.writeJSON(this.credentials_path, config);
|
|
23
|
-
this.
|
|
24
|
+
await fs.writeJSON(this.credentials_path, config, { spaces: 2 });
|
|
25
|
+
await fs.chmod(this.credentials_path, 0o600);
|
|
26
|
+
this.log.info(`Stored credentials`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated use saveCredentials
|
|
30
|
+
*/
|
|
31
|
+
async saveApiKey(config) {
|
|
32
|
+
return this.saveCredentials(config);
|
|
24
33
|
}
|
|
25
34
|
async loadCredentials() {
|
|
35
|
+
const old_path = path__default.join(this.faable_home, "credentials");
|
|
36
|
+
// Migration from old path if it exists
|
|
37
|
+
if (fs.existsSync(old_path) && !fs.existsSync(this.credentials_path)) {
|
|
38
|
+
const config = await fs.readJSON(old_path);
|
|
39
|
+
await this.saveCredentials(config);
|
|
40
|
+
await fs.remove(old_path);
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
26
43
|
if (!fs.existsSync(this.credentials_path)) {
|
|
27
44
|
// No credentials found
|
|
28
45
|
return;
|
package/package.json
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faable/faable",
|
|
3
|
+
"version": "1.5.17-next.21",
|
|
3
4
|
"main": "dist/index.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Marc Pomar <marc@faable.com>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/faablecloud/faable/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/faablecloud/faable.git"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"faable": "bin/faable.js"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org/",
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/faablecloud/faable#readme",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"bin"
|
|
27
|
+
],
|
|
4
28
|
"dependencies": {
|
|
5
29
|
"@actions/core": "^3.0.0",
|
|
6
30
|
"axios": "^1.13.2",
|
|
7
31
|
"fs-extra": "^11.3.2",
|
|
8
32
|
"handlebars": "^4.7.8",
|
|
33
|
+
"open": "^11.0.0",
|
|
9
34
|
"pino": "^10.1.0",
|
|
10
35
|
"pino-pretty": "^13.1.3",
|
|
11
36
|
"promisify-child-process": "^4.1.2",
|
|
@@ -15,24 +40,45 @@
|
|
|
15
40
|
"yaml": "^2.8.2",
|
|
16
41
|
"yargs": "^18.0.0"
|
|
17
42
|
},
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^10.0.1",
|
|
45
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
46
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
47
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
48
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
49
|
+
"@types/bluebird": "^3.5.38",
|
|
50
|
+
"@types/fs-extra": "^11.0.4",
|
|
51
|
+
"@types/node": "^24.10.2",
|
|
52
|
+
"@types/ramda": "^0.31.1",
|
|
53
|
+
"@types/yargs": "^17.0.35",
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
55
|
+
"ava": "^7.0.0",
|
|
56
|
+
"eslint-config-prettier": "^10.1.8",
|
|
57
|
+
"globals": "^17.4.0",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"rimraf": "^6.1.2",
|
|
60
|
+
"rollup": "^4.53.3",
|
|
61
|
+
"rollup-plugin-auto-external": "^2.0.0",
|
|
62
|
+
"rollup-plugin-copy": "^3.4.0",
|
|
63
|
+
"rollup-plugin-generate-package-json": "^3.2.0",
|
|
64
|
+
"semantic-release": "^25.0.3",
|
|
65
|
+
"ts-node": "^10.9.2",
|
|
66
|
+
"tsx": "^4.21.0",
|
|
67
|
+
"type-fest": "^5.3.1",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
32
69
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
70
|
+
"scripts": {
|
|
71
|
+
"start": "npm run cli",
|
|
72
|
+
"cli": "tsx src/index.ts",
|
|
73
|
+
"gh-action-cli": "GITHUB_ACTIONS=true FAABLE_ID_TOKEN=fake-token tsx src/index.ts",
|
|
74
|
+
"build": "rimraf dist && rollup --config rollup.config.mjs",
|
|
75
|
+
"test": "ava",
|
|
76
|
+
"lint": "npx eslint .",
|
|
77
|
+
"lint:fix": "npx eslint . --fix",
|
|
78
|
+
"prepare": "husky",
|
|
79
|
+
"release": "semantic-release"
|
|
36
80
|
},
|
|
37
|
-
"
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=22.8"
|
|
83
|
+
}
|
|
38
84
|
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { CredentialsStore } from '../../lib/CredentialsStore.js';
|
|
2
|
-
import prompts from 'prompts';
|
|
3
|
-
|
|
4
|
-
const configure = {
|
|
5
|
-
command: "configure",
|
|
6
|
-
describe: "Configure Faable CLI",
|
|
7
|
-
builder: (yargs) => {
|
|
8
|
-
return yargs
|
|
9
|
-
.option("remove", {
|
|
10
|
-
alias: "d",
|
|
11
|
-
type: "boolean",
|
|
12
|
-
description: "Delete current configuration",
|
|
13
|
-
default: false,
|
|
14
|
-
})
|
|
15
|
-
.showHelpOnFail(false);
|
|
16
|
-
},
|
|
17
|
-
handler: async (args) => {
|
|
18
|
-
const { app_name, workdir, api, remove } = args;
|
|
19
|
-
const store = new CredentialsStore();
|
|
20
|
-
if (remove) {
|
|
21
|
-
await store.deleteCredentials();
|
|
22
|
-
}
|
|
23
|
-
const { apikey } = await prompts([
|
|
24
|
-
{
|
|
25
|
-
type: "text",
|
|
26
|
-
name: "apikey",
|
|
27
|
-
message: "What is your Faable ApiKey?",
|
|
28
|
-
},
|
|
29
|
-
]);
|
|
30
|
-
await store.saveApiKey({ apikey });
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export { configure };
|