@faable/faable 1.21.0 → 1.23.0

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.
@@ -7,7 +7,7 @@ import { CredentialsStore } from '../lib/CredentialsStore.js';
7
7
  import { loadLiveCredentials } from './session.js';
8
8
  import { log } from '../log.js';
9
9
 
10
- const context = async () => {
10
+ const context = async (targetAppId) => {
11
11
  let api;
12
12
  // Auth resolution: FAABLE_TOKEN → OIDC (CI) → local `faable login` credentials.
13
13
  if (process.env.FAABLE_TOKEN) {
@@ -19,7 +19,12 @@ const context = async () => {
19
19
  // Github actions environment
20
20
  try {
21
21
  const idToken = process.env.FAABLE_ID_TOKEN || (await getIDToken("https://faable.com"));
22
- api = FaableApi.create({ authStrategy: oidc_strategy, auth: { idToken } });
22
+ // targetAppId disambiguates a monorepo (several apps, one repo) in the
23
+ // OIDC exchange — from `faable deploy <app_id>`.
24
+ api = FaableApi.create({
25
+ authStrategy: oidc_strategy,
26
+ auth: { idToken, appId: targetAppId },
27
+ });
23
28
  }
24
29
  catch (_) {
25
30
  console.error("Error fetching token, configure 'permissions: id-token: write'");
@@ -54,8 +59,8 @@ const context = async () => {
54
59
  // Exits with a uniform message when there is no session at all. An expired
55
60
  // session that could not be refreshed still surfaces here as a working `api`
56
61
  // whose first call returns 401 — FaableApi maps that to the same re-login hint.
57
- const requireApi = async () => {
58
- const ctx = await context();
62
+ const requireApi = async (targetAppId) => {
63
+ const ctx = await context(targetAppId);
59
64
  if (!ctx.api) {
60
65
  log.error("❌ Not logged in. Run 'faable login' first.");
61
66
  process.exit(1);
@@ -1,11 +1,15 @@
1
1
  import axios from 'axios';
2
2
  import { create_base_client } from '../base_client.js';
3
3
 
4
- const exchangeGithubOidcToken = async (gh_token) => {
4
+ const exchangeGithubOidcToken = async (gh_token, target_app_id) => {
5
5
  const client = create_base_client();
6
6
  try {
7
+ // Monorepo: several apps share one repo, so the exchange can't infer which
8
+ // app the workflow targets — pass the explicit app_id (`faable deploy
9
+ // <app_id>`). The api verifies it's linked to the OIDC token's repository.
7
10
  const res = await client.post("/auth/github-oidc", {
8
- token: gh_token
11
+ token: gh_token,
12
+ ...(target_app_id ? { app_id: target_app_id } : {})
9
13
  });
10
14
  const { access_token, app_id } = res.data;
11
15
  return { access_token, app_id };
@@ -31,28 +35,24 @@ const exchangeGithubOidcToken = async (gh_token) => {
31
35
  }
32
36
  };
33
37
  const oidc_strategy = (config) => {
34
- const { idToken } = config;
38
+ const { idToken, appId } = config;
35
39
  if (!idToken) {
36
40
  throw new Error("Missing idToken.");
37
41
  }
38
42
  let token_ex;
43
+ const ensure = async () => {
44
+ if (!token_ex)
45
+ token_ex = await exchangeGithubOidcToken(idToken, appId);
46
+ return token_ex;
47
+ };
39
48
  return {
40
49
  headers: async () => {
41
- if (!token_ex) {
42
- const ex = await exchangeGithubOidcToken(idToken);
43
- token_ex = ex;
44
- }
50
+ const ex = await ensure();
45
51
  return {
46
- Authorization: `Bearer ${token_ex.access_token}`,
52
+ Authorization: `Bearer ${ex.access_token}`,
47
53
  };
48
54
  },
49
- app_id: async () => {
50
- if (!token_ex) {
51
- const ex = await exchangeGithubOidcToken(idToken);
52
- token_ex = ex;
53
- }
54
- return token_ex.app_id;
55
- }
55
+ app_id: async () => (await ensure()).app_id,
56
56
  };
57
57
  };
58
58
 
@@ -54,11 +54,20 @@ const deploy = {
54
54
  // into the build-log buffer) and cmd() (which captures subprocess output
55
55
  // into the same buffer). Must happen before any detect/build call.
56
56
  configure_buildpacks({ log, exec: cmd });
57
- const ctx = await requireApi();
57
+ // Pass the explicit app target to the OIDC exchange so a monorepo (several
58
+ // apps, one repo) can be disambiguated in CI.
59
+ const ctx = await requireApi(args.app_id);
58
60
  const { api } = ctx;
59
61
  const config = Configuration.instance().deployConfig();
60
62
  const app_id = await resolve_app_id(args.app_id, ctx.appId, api, workdir);
61
63
  const app = await api.getApp(app_id);
64
+ // Monorepo Root Directory: the server is the source of truth
65
+ // (App.root_dir), so no repo config file is needed. A local
66
+ // faable.json rootDir (dev override) still wins if set.
67
+ if (!config.rootDir && app.root_dir) {
68
+ config.rootDir = app.root_dir;
69
+ log.info(`📁 Monorepo root directory (from app): ${app.root_dir}`);
70
+ }
62
71
  // Capture the commit/ref/actor so the deployment records which commit
63
72
  // it came from and who pushed it (env in CI, git fallback locally).
64
73
  const git = await git_context({ workdir });
@@ -54,6 +54,7 @@ class Configuration {
54
54
  startCommand: this.config.startCommand,
55
55
  buildCommand: this.config.buildCommand,
56
56
  buildpack: this.config.buildpack,
57
+ rootDir: this.config.rootDir,
57
58
  next: this.config.next,
58
59
  };
59
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/faable",
3
- "version": "1.21.0",
3
+ "version": "1.23.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "MIT",
6
6
  "author": "Marc Pomar <marc@faable.com>",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "@actions/core": "^3.0.0",
30
- "@faabletools/buildpacks": "^1.0.0",
30
+ "@faabletools/buildpacks": "^1.7.0",
31
31
  "axios": "^1.13.2",
32
32
  "fs-extra": "^11.3.2",
33
33
  "handlebars": "^4.7.8",