@faable/faable 0.0.0-dev → 0.0.0-development

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,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
40
  this.client = create_base_client();
40
- const strategy = authStrategy && authStrategy(auth);
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,9 @@ 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.patch(`/app/${app_id}`, params));
78
+ }
71
79
  }
72
80
  __decorate([
73
81
  handleError()
@@ -75,6 +83,9 @@ __decorate([
75
83
  __decorate([
76
84
  handleError()
77
85
  ], FaableApi.prototype, "getBySlug", null);
86
+ __decorate([
87
+ handleError()
88
+ ], FaableApi.prototype, "getApp", null);
78
89
  __decorate([
79
90
  handleError()
80
91
  ], FaableApi.prototype, "getRegistry", null);
@@ -84,5 +95,8 @@ __decorate([
84
95
  __decorate([
85
96
  handleError()
86
97
  ], FaableApi.prototype, "getAppSecrets", null);
98
+ __decorate([
99
+ handleError()
100
+ ], FaableApi.prototype, "updateApp", null);
87
101
 
88
102
  export { FaableApi };
@@ -2,18 +2,18 @@ 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 { CredentialsStore } from '../lib/CredentialsStore.js';
5
6
 
6
7
  // 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
- //const store = new CredentialsStore();
14
- //(await store.loadCredentials())?.apikey
15
- // Github actions environment
16
- if (process.env.GITHUB_ACTIONS === 'true') {
15
+ else if (process.env.GITHUB_ACTIONS === 'true') {
16
+ // Github actions environment
17
17
  try {
18
18
  const idToken = process.env.FAABLE_ID_TOKEN || await getIDToken("https://faable.com");
19
19
  api = FaableApi.create({ authStrategy: oidc_strategy, auth: { idToken } });
@@ -22,8 +22,18 @@ const context = async () => {
22
22
  console.error("Error fetching token, configure 'permissions: id-token: write'");
23
23
  }
24
24
  }
25
+ else {
26
+ const store = new CredentialsStore();
27
+ const { apikey } = await store.loadCredentials();
28
+ if (!apikey) {
29
+ throw new Error("No apikey found, please run 'faable login' first");
30
+ }
31
+ api = FaableApi.create({ authStrategy: apikey_strategy, auth: { apikey } });
32
+ }
33
+ const appId = await api?.strategy?.app_id?.();
25
34
  return {
26
35
  api,
36
+ appId
27
37
  };
28
38
  };
29
39
 
@@ -5,26 +5,32 @@ const exchangeGithubOidcToken = async (gh_token) => {
5
5
  const res = await client.post("/auth/github-oidc", {
6
6
  token: gh_token
7
7
  });
8
- const { token } = res.data;
9
- console.log("Obtained github token exchange");
10
- console.log(token);
11
- return token;
8
+ const { token, app_id } = res.data;
9
+ return { token, app_id };
12
10
  };
13
11
  const oidc_strategy = (config) => {
14
12
  const { idToken } = config;
15
13
  if (!idToken) {
16
14
  throw new Error("Missing idToken.");
17
15
  }
18
- let token = "";
16
+ let token_ex;
19
17
  return {
20
18
  headers: async () => {
21
- if (!token) {
22
- token = await exchangeGithubOidcToken(idToken);
19
+ if (!token_ex) {
20
+ const ex = await exchangeGithubOidcToken(idToken);
21
+ token_ex = ex;
23
22
  }
24
23
  return {
25
- Authorization: `Bearer ${token}`,
24
+ Authorization: `Bearer ${token_ex.token}`,
26
25
  };
27
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
+ }
28
34
  };
29
35
  };
30
36
 
@@ -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
- const name = args.app_slug || app_name;
15
- if (!name) {
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}`);
@@ -1,7 +1,7 @@
1
1
  import { writeGithubAction } from './writeGithubAction.js';
2
2
 
3
3
  const init = {
4
- command: ["initialize", "$0"],
4
+ command: ["init", "initialize"],
5
5
  describe: "Initialize Faable",
6
6
  builder: (yargs) => {
7
7
  return yargs
@@ -0,0 +1,67 @@
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 { api } = await context();
32
+ log.info("Checking local git repository...");
33
+ const gitUrl = await getGitRemoteUrl(workdir);
34
+ const apps = await api.list();
35
+ if (apps.length === 0) {
36
+ log.error("No apps found in your account. Create one first at https://faable.cloud");
37
+ return;
38
+ }
39
+ const { selectedApp } = await prompts({
40
+ type: "select",
41
+ name: "selectedApp",
42
+ message: "Select the Faable app to link with this repository:",
43
+ choices: apps.map((app) => ({
44
+ title: `${app.name} (${app.url})`,
45
+ value: app,
46
+ })),
47
+ });
48
+ if (!selectedApp) {
49
+ log.info("Link cancelled.");
50
+ return;
51
+ }
52
+ log.info(`Linking to ${selectedApp.name}...`);
53
+ // Update the app in the API
54
+ if (gitUrl) {
55
+ await api.updateApp(selectedApp.id, { github_repo: gitUrl });
56
+ log.info(`Updated app ${selectedApp.name} with github_repo: ${gitUrl}`);
57
+ }
58
+ else {
59
+ log.warn("No git remote URL detected. Skipping API update for github_repo.");
60
+ }
61
+ // Save locally for CLI convenience
62
+ Configuration.instance().saveConfig({ app_slug: selectedApp.name });
63
+ log.info(`Successfully linked local repository to ${selectedApp.name}.`);
64
+ },
65
+ };
66
+
67
+ export { link };
package/dist/index.js CHANGED
@@ -4,14 +4,15 @@ import { apps } from './commands/apps/index.js';
4
4
  import { configure } from './commands/configure/index.js';
5
5
  import { deploy } from './commands/deploy/index.js';
6
6
  import { log } from './log.js';
7
+ import { link } from './commands/link/index.js';
7
8
  import { init } from './commands/init/index.js';
8
9
  import { version } from './config.js';
9
10
  import { Configuration } from './lib/Configuration.js';
10
11
 
11
12
  const yg = yargs();
12
13
  yg.scriptName("faable")
13
- .middleware(function (argv) {
14
- console.log(`Faable CLI ${version}`);
14
+ .middleware(function (_argv) {
15
+ log.info(`Faable CLI ${version}`);
15
16
  }, true)
16
17
  .option("c", {
17
18
  alias: "config",
@@ -32,6 +33,7 @@ yg.scriptName("faable")
32
33
  .command(apps)
33
34
  .command(configure)
34
35
  .command(init)
36
+ .command(link)
35
37
  .demandCommand(1)
36
38
  .help()
37
39
  .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
- const config_file = path__default.join(process.cwd(), file);
14
- if (fs.existsSync(config_file)) {
15
- this.config = fs.readJSONSync(config_file);
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 };
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "yaml": "^2.8.2",
16
16
  "yargs": "^18.0.0"
17
17
  },
18
- "version": "0.0.0-dev",
18
+ "version": "0.0.0-development",
19
19
  "bin": {
20
20
  "faable": "bin/faable.js"
21
21
  },
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "repository": {
34
34
  "type": "git",
35
- "url": "https://github.com/faablecloud/faable.git"
35
+ "url": "git+https://github.com/faablecloud/faable.git"
36
36
  },
37
37
  "homepage": "https://github.com/faablecloud/faable#readme"
38
38
  }