wawesome 0.7.0 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.mjs +255 -107
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -18,6 +18,7 @@ import zlib from "node:zlib";
18
18
  * These are public values — the anon key is designed to be embedded in clients.
19
19
  */
20
20
  const SUPABASE_URL = "https://vclasavxxoufwymrutai.supabase.co";
21
+ const SUPABASE_ANON_KEY = "sb_publishable_ZlgDAI9cNZkefEwVPKIngA_ktuvTpjD";
21
22
  /** Path to the user-level settings file (~/.wawesome/settings.json) */
22
23
  function getSettingsPath() {
23
24
  return path.join(os.homedir(), ".wawesome", "settings.json");
@@ -90,12 +91,23 @@ function readCredentials() {
90
91
  }
91
92
  /**
92
93
  * Write credentials to disk, creating the directory if needed.
94
+ *
95
+ * Owner-only, because the file holds a refresh token. The `chmod` is what
96
+ * tightens a file an older CLI left world-readable; a mode passed to
97
+ * `writeFileSync` applies only where it creates the file.
93
98
  */
94
99
  function writeCredentials(creds) {
95
100
  const credPath = getCredentialsPath();
96
101
  const dir = path.dirname(credPath);
97
- fs.mkdirSync(dir, { recursive: true });
98
- fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), "utf-8");
102
+ fs.mkdirSync(dir, {
103
+ recursive: true,
104
+ mode: 448
105
+ });
106
+ fs.writeFileSync(credPath, JSON.stringify(creds, null, 2), {
107
+ encoding: "utf-8",
108
+ mode: 384
109
+ });
110
+ fs.chmodSync(credPath, 384);
99
111
  }
100
112
  /**
101
113
  * Delete stored credentials.
@@ -759,7 +771,7 @@ async function buildJs(entryInput, options) {
759
771
  * that has to name this version — `--version`, the dependency a scaffolded
760
772
  * project pins — reads it here, so a release bumps one file.
761
773
  */
762
- const CLI_VERSION = "0.7.0";
774
+ const CLI_VERSION = "0.8.0";
763
775
  //#endregion
764
776
  //#region src/prompt.ts
765
777
  /**
@@ -816,6 +828,126 @@ function isInteractive() {
816
828
  return Boolean(process.stdin.isTTY);
817
829
  }
818
830
  //#endregion
831
+ //#region src/session.ts
832
+ /**
833
+ * A gateway request, retried once against a renewed session if it comes back 401.
834
+ *
835
+ * A caller's `Authorization` is a snapshot of what was on disk when the command
836
+ * started, so a renewal here replaces it. Otherwise every later request in the
837
+ * same command would spend a 401 learning what this already knows.
838
+ *
839
+ * A body that can only be read once, an asset streamed off disk, passes a
840
+ * function so the retry is built one of its own.
841
+ */
842
+ async function authorizedFetch(url, init = {}) {
843
+ const request = typeof init === "function" ? init : () => init;
844
+ const sentAt = Date.now();
845
+ const res = await fetch(url, renewedJwt ? withBearer(request(), renewedJwt) : request());
846
+ if (res.status !== 401) return res;
847
+ if (renewedJwt && renewedAt > sentAt) return retry(url, request, renewedJwt, res);
848
+ if (renewedJwt && Date.now() - renewedAt < JUST_RENEWED_MS) return res;
849
+ const renewed = await renewSession();
850
+ if (!renewed) return res;
851
+ return retry(url, request, renewed.tenant_jwt, res);
852
+ }
853
+ /** Thirty minutes of token life leaves this nowhere near a genuine expiry. */
854
+ const JUST_RENEWED_MS = 6e4;
855
+ async function retry(url, request, tenantJwt, refused) {
856
+ await refused.body?.cancel().catch(() => {});
857
+ return fetch(url, withBearer(request(), tenantJwt));
858
+ }
859
+ function withBearer(init, tenantJwt) {
860
+ return {
861
+ ...init,
862
+ headers: {
863
+ ...headerRecord(init.headers),
864
+ Authorization: `Bearer ${tenantJwt}`
865
+ }
866
+ };
867
+ }
868
+ function headerRecord(headers) {
869
+ if (!headers) return {};
870
+ if (headers instanceof Headers || Array.isArray(headers)) return Object.fromEntries(new Headers(headers).entries());
871
+ return { ...headers };
872
+ }
873
+ let renewal = null;
874
+ let renewedJwt = null;
875
+ let renewedAt = 0;
876
+ /** Drop what this process learned about the session, as ending one does. */
877
+ function forgetRenewedSession() {
878
+ renewal = null;
879
+ renewedJwt = null;
880
+ renewedAt = 0;
881
+ }
882
+ /**
883
+ * Supabase rotates the refresh token on every use and revokes the whole family
884
+ * if a spent one comes back, so requests that meet the 401 together share one
885
+ * renewal instead of racing with the same token.
886
+ */
887
+ function renewSession() {
888
+ if (!renewal) {
889
+ const inFlight = renewOnce().finally(() => {
890
+ if (renewal === inFlight) renewal = null;
891
+ });
892
+ renewal = inFlight;
893
+ }
894
+ return renewal;
895
+ }
896
+ /**
897
+ * Null where there is nothing to renew from. Credentials written before refresh
898
+ * tokens were stored still describe a session, so the 401 goes back to the
899
+ * caller, whose own message for it is the right one.
900
+ */
901
+ async function renewOnce() {
902
+ const creds = readCredentials();
903
+ if (!creds?.refresh_token) return null;
904
+ const session = await refreshSupabaseSession(creds.refresh_token);
905
+ const exchanged = await exchangeForTenantJwt(creds, session.access_token);
906
+ const renewed = {
907
+ ...creds,
908
+ tenant_jwt: exchanged.tenant_jwt,
909
+ refresh_token: session.refresh_token || creds.refresh_token,
910
+ tenant_slug: exchanged.tenant_slug || creds.tenant_slug,
911
+ tenant_name: exchanged.tenant_name || creds.tenant_name
912
+ };
913
+ writeCredentials(renewed);
914
+ renewedJwt = renewed.tenant_jwt;
915
+ renewedAt = Date.now();
916
+ return renewed;
917
+ }
918
+ async function refreshSupabaseSession(refreshToken) {
919
+ const res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
920
+ method: "POST",
921
+ headers: {
922
+ apikey: SUPABASE_ANON_KEY,
923
+ "Content-Type": "application/json"
924
+ },
925
+ body: JSON.stringify({ refresh_token: refreshToken })
926
+ });
927
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) endSession("Your sign-in was revoked or has expired, so the session could not be renewed.");
928
+ if (!res.ok) throw new Error(`Could not renew your session (Supabase HTTP ${res.status}).`);
929
+ const session = await res.json();
930
+ if (!session.access_token) throw new Error("Could not renew your session (Supabase returned no access token).");
931
+ return session;
932
+ }
933
+ async function exchangeForTenantJwt(creds, supabaseAccessToken) {
934
+ const res = await fetch(`${creds.gateway_url}/api/v1/auth/token-exchange`, {
935
+ method: "POST",
936
+ headers: {
937
+ Authorization: `Bearer ${supabaseAccessToken}`,
938
+ "Content-Type": "application/json"
939
+ },
940
+ body: JSON.stringify({ tenant_id: creds.tenant_id })
941
+ });
942
+ if (res.status === 401 || res.status === 403) endSession("Your access to this workspace has been withdrawn.");
943
+ if (!res.ok) throw new Error(`Could not renew your session (token exchange HTTP ${res.status}).`);
944
+ return await res.json();
945
+ }
946
+ function endSession(reason) {
947
+ console.error(`[wawesome] Error: ${reason} Run 'wawesome login' to sign in again.`);
948
+ process.exit(1);
949
+ }
950
+ //#endregion
819
951
  //#region src/errors.ts
820
952
  /** A rejection carrying the gateway's own prose and, where given, its reason. */
821
953
  var GatewayError = class extends Error {
@@ -862,7 +994,7 @@ function errorText(err) {
862
994
  //#endregion
863
995
  //#region src/tenant.ts
864
996
  async function fetchTenantDetails(creds) {
865
- const res = await fetch(`${creds.gateway_url}/v1/tenant`, { headers: { Authorization: `Bearer ${creds.tenant_jwt}` } });
997
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/tenant`, { headers: { Authorization: `Bearer ${creds.tenant_jwt}` } });
866
998
  if (!res.ok) throw await asGatewayError(res, `Failed to read workspace (HTTP ${res.status}).`);
867
999
  return await res.json();
868
1000
  }
@@ -928,7 +1060,7 @@ function renameAdvice(reason) {
928
1060
  * unusable name comes back as a `malformed` rejection instead.
929
1061
  */
930
1062
  async function renameTenantSlug(creds, slug) {
931
- const res = await fetch(`${creds.gateway_url}/v1/tenant/slug`, {
1063
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/tenant/slug`, {
932
1064
  method: "PUT",
933
1065
  headers: {
934
1066
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -972,6 +1104,80 @@ async function promptForWorkspaceName(options) {
972
1104
  throw new Error("No workspace name given. Nothing was created.");
973
1105
  }
974
1106
  /**
1107
+ * Turn a finished OAuth callback into stored credentials.
1108
+ *
1109
+ * Split from the callback server so what login stores can be exercised without
1110
+ * a browser and a port.
1111
+ */
1112
+ async function completeLogin({ gatewayUrl, dashboardUrl, accessToken, refreshToken, options }) {
1113
+ const isVerbose = Boolean(options.verbose);
1114
+ console.log("[wawesome] Fetching your workspaces...");
1115
+ const tenantsRes = await fetch(`${gatewayUrl}/api/v1/me/tenants`, { headers: { Authorization: `Bearer ${accessToken}` } });
1116
+ if (!tenantsRes.ok) throw new Error(`Failed to fetch tenants (HTTP ${tenantsRes.status}). Have you completed onboarding?`);
1117
+ const tenants = await tenantsRes.json();
1118
+ let primaryTenantId;
1119
+ let workspaceName;
1120
+ let workspaceSlug;
1121
+ if (tenants.length === 0) {
1122
+ const chosenName = await promptForWorkspaceName(options);
1123
+ console.log(`[wawesome] Creating workspace "${chosenName}"...`);
1124
+ const initRes = await fetch(`${gatewayUrl}/api/v1/onboarding/init`, {
1125
+ method: "POST",
1126
+ headers: {
1127
+ Authorization: `Bearer ${accessToken}`,
1128
+ "Content-Type": "application/json"
1129
+ },
1130
+ body: JSON.stringify({ tenant_name: chosenName })
1131
+ });
1132
+ if (!initRes.ok) throw new Error(`Failed to initialize workspace (HTTP ${initRes.status}).`);
1133
+ const initData = await initRes.json();
1134
+ primaryTenantId = initData.tenant_id;
1135
+ workspaceSlug = initData.tenant_slug;
1136
+ workspaceName = initData.tenant_name || chosenName;
1137
+ } else primaryTenantId = tenants[0].tenant_id;
1138
+ if (isVerbose) console.log(`[wawesome:verbose] Using tenant: ${primaryTenantId}`);
1139
+ console.log("[wawesome] Exchanging for tenant-scoped credentials...");
1140
+ const exchangeRes = await fetch(`${gatewayUrl}/api/v1/auth/token-exchange`, {
1141
+ method: "POST",
1142
+ headers: {
1143
+ Authorization: `Bearer ${accessToken}`,
1144
+ "Content-Type": "application/json"
1145
+ },
1146
+ body: JSON.stringify({ tenant_id: primaryTenantId })
1147
+ });
1148
+ if (!exchangeRes.ok) throw new Error(`Token exchange failed (HTTP ${exchangeRes.status}).`);
1149
+ const exchangeData = await exchangeRes.json();
1150
+ workspaceSlug = exchangeData.tenant_slug || workspaceSlug;
1151
+ workspaceName = exchangeData.tenant_name || workspaceName;
1152
+ let userEmail = exchangeData.email || "unknown";
1153
+ try {
1154
+ const payloadPart = exchangeData.tenant_jwt.split(".")[1];
1155
+ const decoded = JSON.parse(Buffer.from(payloadPart, "base64url").toString("utf-8"));
1156
+ if (decoded.email) userEmail = decoded.email;
1157
+ } catch {}
1158
+ forgetRenewedSession();
1159
+ writeCredentials({
1160
+ gateway_url: gatewayUrl,
1161
+ dashboard_url: dashboardUrl,
1162
+ tenant_jwt: exchangeData.tenant_jwt,
1163
+ refresh_token: refreshToken,
1164
+ tenant_id: primaryTenantId,
1165
+ user_email: userEmail,
1166
+ tenant_slug: workspaceSlug,
1167
+ tenant_name: workspaceName
1168
+ });
1169
+ console.log("\n======================================================");
1170
+ console.log("🎉 \x1B[32mLOGIN SUCCESSFUL!\x1B[0m");
1171
+ console.log("======================================================");
1172
+ if (workspaceName) console.log(`\n Workspace: ${workspaceName}`);
1173
+ if (workspaceSlug) console.log(` Address: ${workspaceSlug}`);
1174
+ console.log(` Tenant: ${primaryTenantId}`);
1175
+ console.log(` Gateway: ${gatewayUrl}`);
1176
+ console.log(` Email: ${userEmail}`);
1177
+ console.log("\n Credentials saved to ~/.wawesome/credentials.json");
1178
+ console.log("======================================================\n");
1179
+ }
1180
+ /**
975
1181
  * OAuth login flow:
976
1182
  * 1. Build Supabase OAuth URL
977
1183
  * 2. Open browser
@@ -1025,6 +1231,7 @@ async function login(options) {
1025
1231
  }
1026
1232
  if (reqUrl.pathname === "/token") {
1027
1233
  const accessToken = reqUrl.searchParams.get("access_token");
1234
+ const refreshToken = reqUrl.searchParams.get("refresh_token");
1028
1235
  res.writeHead(200, { "Content-Type": "text/plain" });
1029
1236
  res.end("Token received by CLI.");
1030
1237
  if (!accessToken) {
@@ -1035,69 +1242,13 @@ async function login(options) {
1035
1242
  }
1036
1243
  if (isVerbose) console.log("[wawesome:verbose] Supabase access token received.");
1037
1244
  try {
1038
- console.log("[wawesome] Fetching your workspaces...");
1039
- const tenantsRes = await fetch(`${gatewayUrl}/api/v1/me/tenants`, { headers: { Authorization: `Bearer ${accessToken}` } });
1040
- if (!tenantsRes.ok) throw new Error(`Failed to fetch tenants (HTTP ${tenantsRes.status}). Have you completed onboarding?`);
1041
- const tenants = await tenantsRes.json();
1042
- let primaryTenantId;
1043
- let workspaceName;
1044
- let workspaceSlug;
1045
- if (tenants.length === 0) {
1046
- const chosenName = await promptForWorkspaceName(options);
1047
- console.log(`[wawesome] Creating workspace "${chosenName}"...`);
1048
- const initRes = await fetch(`${gatewayUrl}/api/v1/onboarding/init`, {
1049
- method: "POST",
1050
- headers: {
1051
- Authorization: `Bearer ${accessToken}`,
1052
- "Content-Type": "application/json"
1053
- },
1054
- body: JSON.stringify({ tenant_name: chosenName })
1055
- });
1056
- if (!initRes.ok) throw new Error(`Failed to initialize workspace (HTTP ${initRes.status}).`);
1057
- const initData = await initRes.json();
1058
- primaryTenantId = initData.tenant_id;
1059
- workspaceSlug = initData.tenant_slug;
1060
- workspaceName = initData.tenant_name || chosenName;
1061
- } else primaryTenantId = tenants[0].tenant_id;
1062
- if (isVerbose) console.log(`[wawesome:verbose] Using tenant: ${primaryTenantId}`);
1063
- console.log("[wawesome] Exchanging for tenant-scoped credentials...");
1064
- const exchangeRes = await fetch(`${gatewayUrl}/api/v1/auth/token-exchange`, {
1065
- method: "POST",
1066
- headers: {
1067
- Authorization: `Bearer ${accessToken}`,
1068
- "Content-Type": "application/json"
1069
- },
1070
- body: JSON.stringify({ tenant_id: primaryTenantId })
1071
- });
1072
- if (!exchangeRes.ok) throw new Error(`Token exchange failed (HTTP ${exchangeRes.status}).`);
1073
- const exchangeData = await exchangeRes.json();
1074
- workspaceSlug = exchangeData.tenant_slug || workspaceSlug;
1075
- workspaceName = exchangeData.tenant_name || workspaceName;
1076
- let userEmail = exchangeData.email || "unknown";
1077
- try {
1078
- const payloadPart = exchangeData.tenant_jwt.split(".")[1];
1079
- const decoded = JSON.parse(Buffer.from(payloadPart, "base64url").toString("utf-8"));
1080
- if (decoded.email) userEmail = decoded.email;
1081
- } catch {}
1082
- writeCredentials({
1083
- gateway_url: gatewayUrl,
1084
- dashboard_url: dashboardUrl,
1085
- tenant_jwt: exchangeData.tenant_jwt,
1086
- tenant_id: primaryTenantId,
1087
- user_email: userEmail,
1088
- tenant_slug: workspaceSlug,
1089
- tenant_name: workspaceName
1245
+ await completeLogin({
1246
+ gatewayUrl,
1247
+ dashboardUrl,
1248
+ accessToken,
1249
+ refreshToken: refreshToken || void 0,
1250
+ options
1090
1251
  });
1091
- console.log("\n======================================================");
1092
- console.log("🎉 \x1B[32mLOGIN SUCCESSFUL!\x1B[0m");
1093
- console.log("======================================================");
1094
- if (workspaceName) console.log(`\n Workspace: ${workspaceName}`);
1095
- if (workspaceSlug) console.log(` Address: ${workspaceSlug}`);
1096
- console.log(` Tenant: ${primaryTenantId}`);
1097
- console.log(` Gateway: ${gatewayUrl}`);
1098
- console.log(` Email: ${userEmail}`);
1099
- console.log("\n Credentials saved to ~/.wawesome/credentials.json");
1100
- console.log("======================================================\n");
1101
1252
  server.close();
1102
1253
  resolve();
1103
1254
  } catch (err) {
@@ -1127,6 +1278,7 @@ async function login(options) {
1127
1278
  * Clear stored credentials.
1128
1279
  */
1129
1280
  function logout() {
1281
+ forgetRenewedSession();
1130
1282
  if (deleteCredentials()) console.log("[wawesome] ✅ Logged out. Credentials removed.");
1131
1283
  else console.log("[wawesome] Not logged in (no credentials found).");
1132
1284
  }
@@ -1201,7 +1353,7 @@ async function fetchTenantUsage(creds, timeoutMs = USAGE_TIMEOUT_MS) {
1201
1353
  const controller = new AbortController();
1202
1354
  const timer = setTimeout(() => controller.abort(), timeoutMs);
1203
1355
  try {
1204
- const res = await fetch(`${creds.gateway_url}/v1/tenant/usage`, {
1356
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/tenant/usage`, {
1205
1357
  headers: { Authorization: `Bearer ${creds.tenant_jwt}` },
1206
1358
  signal: controller.signal
1207
1359
  });
@@ -1697,7 +1849,7 @@ async function deploy(entryInput, options) {
1697
1849
  const uploadUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/code`;
1698
1850
  if (isVerbose) console.log(`[wawesome:verbose] POST ${uploadUrl}`);
1699
1851
  const declaredOnTheWire = declaredFields(declared);
1700
- const uploadRes = assets.length > 0 || declaresSchedules || Object.keys(declaredOnTheWire).length > 0 ? await fetch(uploadUrl, {
1852
+ const uploadRes = assets.length > 0 || declaresSchedules || Object.keys(declaredOnTheWire).length > 0 ? await authorizedFetch(uploadUrl, {
1701
1853
  method: "POST",
1702
1854
  headers: {
1703
1855
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1709,7 +1861,7 @@ async function deploy(entryInput, options) {
1709
1861
  ...declaresSchedules ? { schedules: config.schedules } : {},
1710
1862
  ...declaredOnTheWire
1711
1863
  })
1712
- }) : await fetch(uploadUrl, {
1864
+ }) : await authorizedFetch(uploadUrl, {
1713
1865
  method: "POST",
1714
1866
  headers: {
1715
1867
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1755,7 +1907,7 @@ async function deploy(entryInput, options) {
1755
1907
  if (isVerbose) console.log(`[wawesome:verbose] POST ${deployUrl}`);
1756
1908
  const deployBody = {};
1757
1909
  if (version !== void 0) deployBody.version_number = version;
1758
- const deployRes = await fetch(deployUrl, {
1910
+ const deployRes = await authorizedFetch(deployUrl, {
1759
1911
  method: "POST",
1760
1912
  headers: {
1761
1913
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1836,8 +1988,7 @@ async function deploy(entryInput, options) {
1836
1988
  * changed nothing at all is refused here — before a byte of it has moved.
1837
1989
  */
1838
1990
  async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVerbose, continueWhenUnchanged) {
1839
- const manifestUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/assets/manifest`;
1840
- const manifestRes = await fetch(manifestUrl, {
1991
+ const manifestRes = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/assets/manifest`, {
1841
1992
  method: "POST",
1842
1993
  headers: {
1843
1994
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1880,7 +2031,7 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
1880
2031
  console.log(`[wawesome] Uploading ${toUpload.length} of ${assets.length} asset(s)...`);
1881
2032
  for (const asset of toUpload) {
1882
2033
  if (isVerbose) console.log(`[wawesome:verbose] PUT ${asset.path} (${asset.size_bytes} bytes)`);
1883
- const res = await fetch(`${creds.gateway_url}/v1/assets/${asset.content_hash}`, {
2034
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/assets/${asset.content_hash}`, () => ({
1884
2035
  method: "PUT",
1885
2036
  headers: {
1886
2037
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -1889,7 +2040,7 @@ async function uploadAssets(creds, app, funcName, bundle, assets, declared, isVe
1889
2040
  },
1890
2041
  body: Readable.toWeb(fs.createReadStream(asset.source)),
1891
2042
  duplex: "half"
1892
- });
2043
+ }));
1893
2044
  if (!res.ok) {
1894
2045
  const errorBody = await res.text();
1895
2046
  const refusal = rejectionOf(errorBody, res.status, `Upload of '${asset.path}' failed (HTTP ${res.status}).`);
@@ -1937,8 +2088,7 @@ function loadClientContext() {
1937
2088
  * the time it does, so it needs to say what did land before it gives up.
1938
2089
  */
1939
2090
  async function putEnvVar(creds, app, key, value, isSecret) {
1940
- const url = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/env`;
1941
- const res = await fetch(url, {
2091
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/env`, {
1942
2092
  method: "POST",
1943
2093
  headers: {
1944
2094
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -2001,7 +2151,7 @@ async function listEnvVars(options) {
2001
2151
  const { creds, app } = loadClientContext();
2002
2152
  const url = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/env`;
2003
2153
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
2004
- const res = await fetch(url, {
2154
+ const res = await authorizedFetch(url, {
2005
2155
  method: "GET",
2006
2156
  headers: { Authorization: `Bearer ${creds.tenant_jwt}` }
2007
2157
  });
@@ -2055,7 +2205,7 @@ async function removeEnvVar(key, options) {
2055
2205
  const { creds, app } = loadClientContext();
2056
2206
  const url = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(key)}`;
2057
2207
  if (isVerbose) console.log(`[wawesome:verbose] DELETE ${url}`);
2058
- const res = await fetch(url, {
2208
+ const res = await authorizedFetch(url, {
2059
2209
  method: "DELETE",
2060
2210
  headers: { Authorization: `Bearer ${creds.tenant_jwt}` }
2061
2211
  });
@@ -2158,10 +2308,10 @@ function authHeaders(creds) {
2158
2308
  * between is still treated as the success it is.
2159
2309
  */
2160
2310
  async function ensureApp(creds, appSlug) {
2161
- const listRes = await fetch(`${creds.gateway_url}/v1/apps`, { headers: { Authorization: `Bearer ${creds.tenant_jwt}` } });
2311
+ const listRes = await authorizedFetch(`${creds.gateway_url}/v1/apps`, { headers: { Authorization: `Bearer ${creds.tenant_jwt}` } });
2162
2312
  if (!listRes.ok) throw await rejected(listRes, "Could not list the Tenant's apps");
2163
2313
  if ((await listRes.json()).applications?.some((app) => app.slug === appSlug)) return;
2164
- const createRes = await fetch(`${creds.gateway_url}/v1/apps`, {
2314
+ const createRes = await authorizedFetch(`${creds.gateway_url}/v1/apps`, {
2165
2315
  method: "POST",
2166
2316
  headers: authHeaders(creds),
2167
2317
  body: JSON.stringify({
@@ -2182,8 +2332,7 @@ async function ensureApp(creds, appSlug) {
2182
2332
  * scaffolded template and an outbound request that silently fails at runtime.
2183
2333
  */
2184
2334
  async function enableEgressProvider(creds, appSlug, providerKey) {
2185
- const url = `${creds.gateway_url}/v1/apps/${encodeURIComponent(appSlug)}/egress/subscriptions`;
2186
- const res = await fetch(url, {
2335
+ const res = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(appSlug)}/egress/subscriptions`, {
2187
2336
  method: "POST",
2188
2337
  headers: authHeaders(creds),
2189
2338
  body: JSON.stringify({ provider_key: providerKey })
@@ -3086,7 +3235,7 @@ const RECLAIMED = "⚠ [assets reclaimed]";
3086
3235
  async function fetchVersions(gatewayUrl, tenantJwt, app, funcName, env, isVerbose) {
3087
3236
  const url = `${gatewayUrl}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/versions?environment=${encodeURIComponent(env)}`;
3088
3237
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
3089
- const res = await fetch(url, {
3238
+ const res = await authorizedFetch(url, {
3090
3239
  method: "GET",
3091
3240
  headers: { Authorization: `Bearer ${tenantJwt}` }
3092
3241
  });
@@ -3199,8 +3348,7 @@ async function switchVersion(targetInput, options) {
3199
3348
  }
3200
3349
  }
3201
3350
  console.log(`[wawesome] Promoting v${selectedVersionNum} to ${env}...`);
3202
- const deployUrl = `${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/deploy`;
3203
- const deployRes = await fetch(deployUrl, {
3351
+ const deployRes = await authorizedFetch(`${creds.gateway_url}/v1/apps/${encodeURIComponent(app)}/functions/${encodeURIComponent(funcName)}/deploy`, {
3204
3352
  method: "POST",
3205
3353
  headers: {
3206
3354
  Authorization: `Bearer ${creds.tenant_jwt}`,
@@ -3290,7 +3438,7 @@ async function logsCommand(target, options = {}) {
3290
3438
  async function fetchInvocationLogBody(gatewayUrl, tenantJwt, invocationId, isVerbose) {
3291
3439
  const url = `${gatewayUrl}/v1/invocations/${encodeURIComponent(invocationId)}/logs`;
3292
3440
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
3293
- const res = await fetch(url, {
3441
+ const res = await authorizedFetch(url, {
3294
3442
  method: "GET",
3295
3443
  headers: { Authorization: `Bearer ${tenantJwt}` }
3296
3444
  });
@@ -3366,7 +3514,7 @@ async function listInvocations(gatewayUrl, tenantJwt, funcNameInput, appOverride
3366
3514
  async function fetchInvocationsResponse(gatewayUrl, tenantJwt, funcName, appSlug, appOverride, queryString, isVerbose) {
3367
3515
  const url = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/invocations${queryString}` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/invocations${queryString}`;
3368
3516
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
3369
- let res = await fetch(url, {
3517
+ let res = await authorizedFetch(url, {
3370
3518
  method: "GET",
3371
3519
  headers: { Authorization: `Bearer ${tenantJwt}` }
3372
3520
  });
@@ -3374,7 +3522,7 @@ async function fetchInvocationsResponse(gatewayUrl, tenantJwt, funcName, appSlug
3374
3522
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/invocations${queryString}`;
3375
3523
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: GET ${fallbackUrl}`);
3376
3524
  try {
3377
- const fallbackRes = await fetch(fallbackUrl, {
3525
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
3378
3526
  method: "GET",
3379
3527
  headers: { Authorization: `Bearer ${tenantJwt}` }
3380
3528
  });
@@ -3486,7 +3634,7 @@ async function followInvocationLog(gatewayUrl, tenantJwt, invocationId, isVerbos
3486
3634
  while (true) {
3487
3635
  let res = null;
3488
3636
  try {
3489
- res = await fetch(url, {
3637
+ res = await authorizedFetch(url, {
3490
3638
  method: "GET",
3491
3639
  headers: {
3492
3640
  Authorization: `Bearer ${tenantJwt}`,
@@ -3618,18 +3766,18 @@ async function followFunctionLog(gatewayUrl, tenantJwt, funcNameInput, appOverri
3618
3766
  let res = null;
3619
3767
  try {
3620
3768
  if (scopedUrl) {
3621
- res = await fetch(scopedUrl, {
3769
+ res = await authorizedFetch(scopedUrl, {
3622
3770
  headers,
3623
3771
  signal: controller.signal
3624
3772
  });
3625
3773
  if (res.status === 404) {
3626
3774
  if (isVerbose) console.error(`[wawesome:verbose] 404 on app-scoped route, retrying unscoped.`);
3627
- res = await fetch(unscopedUrl, {
3775
+ res = await authorizedFetch(unscopedUrl, {
3628
3776
  headers,
3629
3777
  signal: controller.signal
3630
3778
  });
3631
3779
  }
3632
- } else res = await fetch(unscopedUrl, {
3780
+ } else res = await authorizedFetch(unscopedUrl, {
3633
3781
  headers,
3634
3782
  signal: controller.signal
3635
3783
  });
@@ -3733,7 +3881,7 @@ function printOutcome(meta) {
3733
3881
  async function triggerRun(gatewayUrl, tenantJwt, funcName, appSlug, appOverride, payload, isVerbose) {
3734
3882
  const url = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/runs` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/runs`;
3735
3883
  if (isVerbose) console.log(`[wawesome:verbose] POST ${url}`);
3736
- let res = await fetch(url, {
3884
+ let res = await authorizedFetch(url, {
3737
3885
  method: "POST",
3738
3886
  headers: {
3739
3887
  Authorization: `Bearer ${tenantJwt}`,
@@ -3745,7 +3893,7 @@ async function triggerRun(gatewayUrl, tenantJwt, funcName, appSlug, appOverride,
3745
3893
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/runs`;
3746
3894
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: POST ${fallbackUrl}`);
3747
3895
  try {
3748
- const fallbackRes = await fetch(fallbackUrl, {
3896
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
3749
3897
  method: "POST",
3750
3898
  headers: {
3751
3899
  Authorization: `Bearer ${tenantJwt}`,
@@ -3761,7 +3909,7 @@ async function triggerRun(gatewayUrl, tenantJwt, funcName, appSlug, appOverride,
3761
3909
  async function fetchInvocationMetadata(gatewayUrl, tenantJwt, invocationId, isVerbose) {
3762
3910
  const url = `${gatewayUrl}/v1/invocations/${encodeURIComponent(invocationId)}`;
3763
3911
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
3764
- const res = await fetch(url, {
3912
+ const res = await authorizedFetch(url, {
3765
3913
  method: "GET",
3766
3914
  headers: { Authorization: `Bearer ${tenantJwt}` }
3767
3915
  });
@@ -3771,7 +3919,7 @@ async function fetchInvocationMetadata(gatewayUrl, tenantJwt, invocationId, isVe
3771
3919
  async function fetchInvocationLogs(gatewayUrl, tenantJwt, invocationId, isVerbose) {
3772
3920
  const url = `${gatewayUrl}/v1/invocations/${encodeURIComponent(invocationId)}/logs`;
3773
3921
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
3774
- const res = await fetch(url, {
3922
+ const res = await authorizedFetch(url, {
3775
3923
  method: "GET",
3776
3924
  headers: { Authorization: `Bearer ${tenantJwt}` }
3777
3925
  });
@@ -3795,7 +3943,7 @@ async function followRun(gatewayUrl, tenantJwt, invocationId, isVerbose, pollInt
3795
3943
  if (controller.signal.aborted) return;
3796
3944
  let res = null;
3797
3945
  try {
3798
- res = await fetch(streamUrl, {
3946
+ res = await authorizedFetch(streamUrl, {
3799
3947
  method: "GET",
3800
3948
  headers: {
3801
3949
  Authorization: `Bearer ${tenantJwt}`,
@@ -4145,7 +4293,7 @@ function formatRunHistoryTable(runs) {
4145
4293
  async function fetchFunctionSchedules(gatewayUrl, tenantJwt, funcName, appSlug, isVerbose) {
4146
4294
  const scopedUrl = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/schedules` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules`;
4147
4295
  if (isVerbose) console.log(`[wawesome:verbose] GET ${scopedUrl}`);
4148
- let res = await fetch(scopedUrl, {
4296
+ let res = await authorizedFetch(scopedUrl, {
4149
4297
  method: "GET",
4150
4298
  headers: { Authorization: `Bearer ${tenantJwt}` }
4151
4299
  });
@@ -4153,7 +4301,7 @@ async function fetchFunctionSchedules(gatewayUrl, tenantJwt, funcName, appSlug,
4153
4301
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules`;
4154
4302
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: GET ${fallbackUrl}`);
4155
4303
  try {
4156
- const fallbackRes = await fetch(fallbackUrl, {
4304
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
4157
4305
  method: "GET",
4158
4306
  headers: { Authorization: `Bearer ${tenantJwt}` }
4159
4307
  });
@@ -4167,7 +4315,7 @@ async function fetchFunctionSchedules(gatewayUrl, tenantJwt, funcName, appSlug,
4167
4315
  async function fetchAppFunctions(gatewayUrl, tenantJwt, appSlug, isVerbose) {
4168
4316
  const url = `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions`;
4169
4317
  if (isVerbose) console.log(`[wawesome:verbose] GET ${url}`);
4170
- const res = await fetch(url, {
4318
+ const res = await authorizedFetch(url, {
4171
4319
  method: "GET",
4172
4320
  headers: { Authorization: `Bearer ${tenantJwt}` }
4173
4321
  });
@@ -4178,7 +4326,7 @@ async function pauseScheduleApi(gatewayUrl, tenantJwt, funcName, scheduleName, a
4178
4326
  const scopedUrl = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/pause` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/pause`;
4179
4327
  const body = reason?.trim() ? JSON.stringify({ reason: reason.trim() }) : void 0;
4180
4328
  if (isVerbose) console.log(`[wawesome:verbose] POST ${scopedUrl}`);
4181
- let res = await fetch(scopedUrl, {
4329
+ let res = await authorizedFetch(scopedUrl, {
4182
4330
  method: "POST",
4183
4331
  headers: {
4184
4332
  Authorization: `Bearer ${tenantJwt}`,
@@ -4190,7 +4338,7 @@ async function pauseScheduleApi(gatewayUrl, tenantJwt, funcName, scheduleName, a
4190
4338
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/pause`;
4191
4339
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: POST ${fallbackUrl}`);
4192
4340
  try {
4193
- const fallbackRes = await fetch(fallbackUrl, {
4341
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
4194
4342
  method: "POST",
4195
4343
  headers: {
4196
4344
  Authorization: `Bearer ${tenantJwt}`,
@@ -4206,7 +4354,7 @@ async function pauseScheduleApi(gatewayUrl, tenantJwt, funcName, scheduleName, a
4206
4354
  async function resumeScheduleApi(gatewayUrl, tenantJwt, funcName, scheduleName, appSlug, isVerbose) {
4207
4355
  const scopedUrl = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/resume` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/resume`;
4208
4356
  if (isVerbose) console.log(`[wawesome:verbose] POST ${scopedUrl}`);
4209
- let res = await fetch(scopedUrl, {
4357
+ let res = await authorizedFetch(scopedUrl, {
4210
4358
  method: "POST",
4211
4359
  headers: { Authorization: `Bearer ${tenantJwt}` }
4212
4360
  });
@@ -4214,7 +4362,7 @@ async function resumeScheduleApi(gatewayUrl, tenantJwt, funcName, scheduleName,
4214
4362
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/schedules/${encodeURIComponent(scheduleName)}/resume`;
4215
4363
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: POST ${fallbackUrl}`);
4216
4364
  try {
4217
- const fallbackRes = await fetch(fallbackUrl, {
4365
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
4218
4366
  method: "POST",
4219
4367
  headers: { Authorization: `Bearer ${tenantJwt}` }
4220
4368
  });
@@ -4231,7 +4379,7 @@ async function fetchBackgroundRunsApi(gatewayUrl, tenantJwt, funcName, appSlug,
4231
4379
  const qs = params.toString() ? `?${params.toString()}` : "";
4232
4380
  const scopedUrl = appSlug ? `${gatewayUrl}/v1/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(funcName)}/runs${qs}` : `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/runs${qs}`;
4233
4381
  if (isVerbose) console.log(`[wawesome:verbose] GET ${scopedUrl}`);
4234
- let res = await fetch(scopedUrl, {
4382
+ let res = await authorizedFetch(scopedUrl, {
4235
4383
  method: "GET",
4236
4384
  headers: { Authorization: `Bearer ${tenantJwt}` }
4237
4385
  });
@@ -4239,7 +4387,7 @@ async function fetchBackgroundRunsApi(gatewayUrl, tenantJwt, funcName, appSlug,
4239
4387
  const fallbackUrl = `${gatewayUrl}/v1/functions/${encodeURIComponent(funcName)}/runs${qs}`;
4240
4388
  if (isVerbose) console.log(`[wawesome:verbose] 404 on app-scoped route. Retrying unscoped: GET ${fallbackUrl}`);
4241
4389
  try {
4242
- const fallbackRes = await fetch(fallbackUrl, {
4390
+ const fallbackRes = await authorizedFetch(fallbackUrl, {
4243
4391
  method: "GET",
4244
4392
  headers: { Authorization: `Bearer ${tenantJwt}` }
4245
4393
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {