wawesome 0.0.13 → 0.0.14

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 +75 -29
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -31,25 +31,40 @@ function readSettings() {
31
31
  return {};
32
32
  }
33
33
  }
34
+ const GATEWAY_ENV_NAMES = ["GATEWAY_URL", "WAWESOME_GATEWAY_URL"];
35
+ const DASHBOARD_ENV_NAMES = ["DASHBOARD_URL", "WAWESOME_DASHBOARD_URL"];
36
+ const GATEWAY_URL_FALLBACK = "https://api.wawesome.io";
37
+ const DASHBOARD_URL_FALLBACK = "https://dashboard.wawesome.io";
38
+ function resolveAddress(overrideUrl, envNames, settingsKey, stored, fallback) {
39
+ if (overrideUrl) return overrideUrl;
40
+ for (const name of envNames) {
41
+ const fromEnv = process.env[name];
42
+ if (fromEnv) return fromEnv;
43
+ }
44
+ const configured = readSettings()[settingsKey];
45
+ if (configured && typeof configured === "string") return configured;
46
+ return stored || fallback;
47
+ }
48
+ function getGatewayUrl(overrideUrl) {
49
+ return resolveAddress(overrideUrl, GATEWAY_ENV_NAMES, "gateway_url", readCredentials()?.gateway_url, GATEWAY_URL_FALLBACK);
50
+ }
51
+ getGatewayUrl();
52
+ function getDashboardUrl(overrideUrl) {
53
+ return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", readCredentials()?.dashboard_url, DASHBOARD_URL_FALLBACK);
54
+ }
34
55
  /**
35
- * Resolve gateway URL with resolution hierarchy:
36
- * 1. Explicit override argument (CLI flag --gateway / --api)
37
- * 2. Environment variables GATEWAY_URL or WAWESOME_GATEWAY_URL
38
- * 3. User settings file ~/.wawesome/settings.json (gateway_url)
39
- * 4. Stored login credentials (~/.wawesome/credentials.json)
40
- * 5. Default fallback: "https://api.wawesome.io"
56
+ * The dashboard for the environment this login is going to.
57
+ *
58
+ * The two addresses describe one environment, so the stored rung is read only
59
+ * where the gateway being logged into is the one the credentials already name.
60
+ * Carrying a dashboard across a change of gateway is the wrong workspace, and
61
+ * so is dropping one where the gateway did not change at all.
41
62
  */
42
- function getGatewayUrl(overrideUrl) {
43
- if (overrideUrl) return overrideUrl;
44
- if (process.env.GATEWAY_URL) return process.env.GATEWAY_URL;
45
- if (process.env.WAWESOME_GATEWAY_URL) return process.env.WAWESOME_GATEWAY_URL;
46
- const settings = readSettings();
47
- if (settings.gateway_url && typeof settings.gateway_url === "string") return settings.gateway_url;
63
+ function resolveDashboardUrlForLogin(overrideUrl, gatewayUrl) {
48
64
  const creds = readCredentials();
49
- if (creds?.gateway_url) return creds.gateway_url;
50
- return "https://api.wawesome.io";
65
+ const sameEnvironment = creds?.gateway_url === gatewayUrl;
66
+ return resolveAddress(overrideUrl, DASHBOARD_ENV_NAMES, "dashboard_url", sameEnvironment ? creds?.dashboard_url : void 0, DASHBOARD_URL_FALLBACK);
51
67
  }
52
- getGatewayUrl();
53
68
  /** Localhost port used during OAuth callback */
54
69
  const OAUTH_CALLBACK_PORT = 9999;
55
70
  /** Path to the user-level credentials file */
@@ -166,7 +181,7 @@ async function buildJs(entryInput, options) {
166
181
  * that has to name this version — `--version`, the dependency a scaffolded
167
182
  * project pins — reads it here, so a release bumps one file.
168
183
  */
169
- const CLI_VERSION = "0.0.13";
184
+ const CLI_VERSION = "0.0.14";
170
185
  //#endregion
171
186
  //#region src/prompt.ts
172
187
  /**
@@ -247,14 +262,21 @@ var GatewayError = class extends Error {
247
262
  * was doing, so callers handle it before reaching this.
248
263
  */
249
264
  async function asGatewayError(res, fallback) {
250
- const body = await res.text().catch(() => "");
265
+ return rejectionOf(await res.text().catch(() => ""), res.status, fallback);
266
+ }
267
+ /** The same reading, for a caller that has already taken the body off the wire. */
268
+ function rejectionOf(body, status, fallback) {
251
269
  try {
252
270
  const parsed = JSON.parse(body);
253
- return new GatewayError(parsed.error || fallback, res.status, parsed.reason, body);
271
+ return new GatewayError(parsed.error || fallback, status, parsed.reason, body);
254
272
  } catch {
255
- return new GatewayError(fallback, res.status, void 0, body);
273
+ return new GatewayError(fallback, status, void 0, body);
256
274
  }
257
275
  }
276
+ /** Why the gateway refused, where what was thrown might not be a refusal at all. */
277
+ function reasonOf(err) {
278
+ return err instanceof GatewayError ? err.reason : void 0;
279
+ }
258
280
  /** What went wrong, as text, whatever was thrown. */
259
281
  function errorText(err) {
260
282
  return err instanceof Error ? err.message : String(err);
@@ -306,6 +328,10 @@ function renameAdvice(reason) {
306
328
  text: "Use lowercase letters, numbers and single hyphens.",
307
329
  retryable: true
308
330
  };
331
+ case "too-short": return {
332
+ text: "Use at least three characters.",
333
+ retryable: true
334
+ };
309
335
  case "locked": return {
310
336
  text: "A deploy landed while this was running, which fixed the address for good.",
311
337
  retryable: false
@@ -377,6 +403,7 @@ async function promptForWorkspaceName(options) {
377
403
  */
378
404
  async function login(options) {
379
405
  const gatewayUrl = getGatewayUrl(options.gateway || options.api);
406
+ const dashboardUrl = resolveDashboardUrlForLogin(options.dashboard, gatewayUrl);
380
407
  const provider = options.provider || "github";
381
408
  const isVerbose = Boolean(options.verbose);
382
409
  const authUrl = `${SUPABASE_URL}/auth/v1/authorize?provider=${provider}&redirect_to=${encodeURIComponent(`http://localhost:${OAUTH_CALLBACK_PORT}/callback`)}`;
@@ -476,6 +503,7 @@ async function login(options) {
476
503
  } catch {}
477
504
  writeCredentials({
478
505
  gateway_url: gatewayUrl,
506
+ dashboard_url: dashboardUrl,
479
507
  tenant_jwt: exchangeData.tenant_jwt,
480
508
  tenant_id: primaryTenantId,
481
509
  user_email: userEmail,
@@ -638,8 +666,15 @@ function headroomLines(usage) {
638
666
  const line = ` ${allowanceLabel(key).padEnd(labelWidth)} ${amount(key, allowance.used, allowance.limit).padEnd(amountWidth)}${percent}`;
639
667
  lines.push(line.trimEnd());
640
668
  }
669
+ const refused = refusedAtShare(usage);
670
+ if (refused !== null) lines.push(` Refused: ${formatCount(refused)} request${refused === 1 ? "" : "s"} at your share`);
641
671
  return lines;
642
672
  }
673
+ function refusedAtShare(usage) {
674
+ const refused = usage.refusals?.at_granted_share;
675
+ if (typeof refused !== "number" || !Number.isFinite(refused) || refused <= 0) return null;
676
+ return refused;
677
+ }
643
678
  function allowanceLabel(key) {
644
679
  if (KNOWN_LABELS[key]) return KNOWN_LABELS[key];
645
680
  const words = key.replace(/[_-]+/g, " ").trim();
@@ -765,12 +800,8 @@ async function deploy(entryInput, options) {
765
800
  const errorBody = await uploadRes.text();
766
801
  if (uploadRes.status === 401) console.error("[wawesome] Error: Authentication expired. Run 'wawesome login' again.");
767
802
  else if (uploadRes.status === 409) {
768
- let msg = "Version with this code bundle already exists.";
769
- try {
770
- const parsed = JSON.parse(errorBody);
771
- if (parsed.error) msg = parsed.error;
772
- } catch {}
773
- console.error(`\n[wawesome] \x1b[31mError: ${msg}\x1b[0m`);
803
+ const refusal = rejectionOf(errorBody, uploadRes.status, "Version with this code bundle already exists.");
804
+ console.error(`\n[wawesome] \x1b[31mError: ${refusal.message}\x1b[0m`);
774
805
  console.error("[wawesome] Code versions are immutable and cannot be overwritten.");
775
806
  console.error("[wawesome] To switch active version, run: \x1B[36mwawesome version switch\x1B[0m\n");
776
807
  } else {
@@ -839,6 +870,20 @@ async function deploy(entryInput, options) {
839
870
  };
840
871
  }
841
872
  //#endregion
873
+ //#region src/billing.ts
874
+ function billingPageUrl() {
875
+ const base = getDashboardUrl().replace(/\/+$/, "");
876
+ try {
877
+ return new URL("billing", `${base}/`).toString();
878
+ } catch {
879
+ return `${base}/billing`;
880
+ }
881
+ }
882
+ function appSlotAdvice(reason) {
883
+ if (reason !== "app-slots-exhausted") return "";
884
+ return `Where to resolve it: ${billingPageUrl()}`;
885
+ }
886
+ //#endregion
842
887
  //#region src/env.ts
843
888
  const STANDARD_SECRET_MESSAGES = [
844
889
  "Encrypted at rest using AES-256",
@@ -1675,6 +1720,7 @@ async function ensureSession(options) {
1675
1720
  try {
1676
1721
  await login({
1677
1722
  api: options.api,
1723
+ dashboard: options.dashboard,
1678
1724
  verbose: options.verbose
1679
1725
  });
1680
1726
  } catch (err) {
@@ -1737,7 +1783,7 @@ async function offerWorkspaceAddress(session, creds, tenant, appSlug, functionNa
1737
1783
  return;
1738
1784
  } catch (err) {
1739
1785
  console.log(`[wawesome] ${errorText(err)}`);
1740
- refusal = err instanceof GatewayError ? err.reason : void 0;
1786
+ refusal = reasonOf(err);
1741
1787
  const { text, retryable } = renameAdvice(refusal);
1742
1788
  if (text) console.log(`[wawesome] ${text}`);
1743
1789
  if (!retryable) break;
@@ -1757,7 +1803,7 @@ async function wireUp(creds, appSlug, manifest, answers) {
1757
1803
  try {
1758
1804
  await ensureApp(creds, appSlug);
1759
1805
  } catch (err) {
1760
- fail(errorText(err), scaffolded);
1806
+ fail(errorText(err), ...[appSlotAdvice(reasonOf(err)), scaffolded].filter(Boolean));
1761
1807
  }
1762
1808
  for (const { declared, value } of answers) {
1763
1809
  if (!value) {
@@ -2747,12 +2793,12 @@ cli.command("env [action] [key] [value]", "Manage environment variables (set, li
2747
2793
  cli.command("env set <key> <value>", "Set or overwrite an environment variable on the current app").option("-s, --secret", "Flag variable as secret (write-only)").option("-v, --verbose", "Enable verbose debug output").action((key, value, options) => setEnvVar(key, value, options));
2748
2794
  cli.command("env list", "List environment variables for the current app").alias("env ls").option("-v, --verbose", "Enable verbose debug output").action((options) => listEnvVars(options));
2749
2795
  cli.command("env rm <key>", "Delete an environment variable from the current app").alias("env remove").alias("env delete").alias("env unset").option("-v, --verbose", "Enable verbose debug output").action((key, options) => removeEnvVar(key, options));
2750
- cli.command("login", "Authenticate with the wawesome.io platform").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--gateway <url>", "Alias for --api <url>").option("--provider <name>", "OAuth provider (default: github)").option("--workspace <name>", "Name for the workspace, when signing up without a terminal to prompt").option("-v, --verbose", "Enable verbose debug output").action((options) => login(options));
2796
+ cli.command("login", "Authenticate with the wawesome.io platform").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--gateway <url>", "Alias for --api <url>").option("--dashboard <url>", "Dashboard URL (default: https://dashboard.wawesome.io)").option("--provider <name>", "OAuth provider (default: github)").option("--workspace <name>", "Name for the workspace, when signing up without a terminal to prompt").option("-v, --verbose", "Enable verbose debug output").action((options) => login(options));
2751
2797
  cli.command("logout", "Clear stored authentication credentials").action(() => logout());
2752
2798
  cli.command("whoami", "Show current login session info").action(() => whoami());
2753
2799
  cli.command("workspace [action] [name]", "Show the workspace, or rename its public address").usage("workspace <action> [name]\n\nActions:\n show Show the workspace name, address, and whether it can still change\n rename <name> Change the public address, while nothing live depends on it").example("wawesome workspace").example("wawesome workspace rename northwind").option("-v, --verbose", "Enable verbose debug output").action((action, name, options) => workspaceCommand(action, name, options));
2754
2800
  cli.command("templates [action]", "Browse the template catalog").usage("templates [action]\n\nActions:\n list (ls) Show every available template (default)").example("wawesome templates").example("wawesome templates list").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("-v, --verbose", "Enable verbose debug output").action((action, options) => templatesCommand(action, options));
2755
- cli.command("init", "Scaffold a new function project in the current directory").usage("init [options]\n\nWith --template, the project is fetched from the template catalog, wired up\nfrom what the template declares it needs, and deployed. Run 'wawesome templates'\nto see what is available.").example("wawesome init").example("wawesome init --template stripe-webhook").option("-t, --template <name>", "Scaffold from a catalog template and deploy it").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--no-install", "Skip installing dependencies after scaffolding").option("--root", "Generate a root function router template").option("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
2801
+ cli.command("init", "Scaffold a new function project in the current directory").usage("init [options]\n\nWith --template, the project is fetched from the template catalog, wired up\nfrom what the template declares it needs, and deployed. Run 'wawesome templates'\nto see what is available.").example("wawesome init").example("wawesome init --template stripe-webhook").option("-t, --template <name>", "Scaffold from a catalog template and deploy it").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--dashboard <url>", "Dashboard URL (default: https://dashboard.wawesome.io)").option("--no-install", "Skip installing dependencies after scaffolding").option("--root", "Generate a root function router template").option("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
2756
2802
  cli.command("logs [function-name-or-invocation-id]", "View invocation history, fetch log output, or follow live").usage(`logs [target] [options]
2757
2803
 
2758
2804
  The target argument determines what the command does:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {