wawesome 0.0.9 → 0.0.11

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 (3) hide show
  1. package/README.md +15 -0
  2. package/dist/index.mjs +72 -23
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -200,6 +200,21 @@ Every project directory includes a `wawesome-function.json` file generated durin
200
200
  `app` is the App this Function is deployed into, and it is client-facing — every deploy from this
201
201
  directory is scoped to it.
202
202
 
203
+ ### Reserved headers
204
+
205
+ `x-wawesome-*` belongs to the platform in both directions. It is stripped off the request before your
206
+ handler sees it, and off your response before the caller does — so **do not name a header of your own
207
+ on that prefix**: it is dropped silently rather than rejected, and you will not get an error telling
208
+ you why it vanished.
209
+
210
+ Three headers arrive or leave on it, and the stripping is what makes them worth trusting:
211
+
212
+ | Header | Direction | What it means |
213
+ | --- | --- | --- |
214
+ | `x-wawesome-forwarded-prefix` | inbound | The mount that was stripped from the path. Join it to the path you observe to rebuild the caller's URL. |
215
+ | `x-wawesome-invocation-id` | outbound | The id of this run — the key to fetch its logs with `npx wawesome logs --invocation <id>`. |
216
+ | `x-wawesome-error` | outbound | Present only when the platform failed, never when your Function did. Its *absence* means the status on the wire is yours. |
217
+
203
218
  ### Local Development / Gateway Overrides
204
219
 
205
220
  If you are running a local gateway or self-hosted instance, you can configure your CLI Gateway URL using any of the
package/dist/index.mjs CHANGED
@@ -161,7 +161,7 @@ async function buildJs(entryInput, options) {
161
161
  * that has to name this version — `--version`, the dependency a scaffolded
162
162
  * project pins — reads it here, so a release bumps one file.
163
163
  */
164
- const CLI_VERSION = "0.0.9";
164
+ const CLI_VERSION = "0.0.11";
165
165
  //#endregion
166
166
  //#region src/prompt.ts
167
167
  /**
@@ -335,10 +335,6 @@ async function renameTenantSlug(creds, slug) {
335
335
  });
336
336
  return result;
337
337
  }
338
- /** The address a deployed Function answers on, for a workspace and app. */
339
- function publicInvokeUrl(gatewayUrl, tenantSlug, appSlug, functionName) {
340
- return `${gatewayUrl}/v1/s/${encodeURIComponent(tenantSlug)}/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(functionName)}/invoke`;
341
- }
342
338
  //#endregion
343
339
  //#region src/auth.ts
344
340
  /**
@@ -548,6 +544,22 @@ async function whoami() {
548
544
  console.log(` Gateway: ${creds.gateway_url}\n`);
549
545
  }
550
546
  //#endregion
547
+ //#region ../shared/public-address.ts
548
+ const INVOCATION_PREFIX = "/x";
549
+ const SUBTREE_NOTE = "Every path beneath this address reaches the Function.";
550
+ function mountBase(origin, tenantSlug) {
551
+ return `${origin.replace(/\/+$/, "")}${INVOCATION_PREFIX}/${encodeURIComponent(tenantSlug)}`;
552
+ }
553
+ function publicAddress(origin, tenantSlug, appSlug, functionSlug) {
554
+ const base = `${mountBase(origin, tenantSlug)}/${encodeURIComponent(appSlug)}`;
555
+ if (functionSlug === "root") return base;
556
+ return `${base}/${encodeURIComponent(functionSlug)}`;
557
+ }
558
+ /** The same address with the app and function still to be chosen. */
559
+ function publicAddressTemplate(origin, tenantSlug) {
560
+ return `${mountBase(origin, tenantSlug)}/<app>/<function>`;
561
+ }
562
+ //#endregion
551
563
  //#region src/deploy.ts
552
564
  /**
553
565
  * Deploy a function: build → upload JS to gateway → promote.
@@ -644,10 +656,10 @@ async function deploy(entryInput, options) {
644
656
  }
645
657
  process.exit(1);
646
658
  }
647
- let invokeUrl = null;
659
+ let address = null;
648
660
  try {
649
661
  const { slug } = await resolveWorkspace(creds);
650
- invokeUrl = publicInvokeUrl(creds.gateway_url, slug, app, funcName);
662
+ address = publicAddress(creds.gateway_url, slug, app, funcName);
651
663
  } catch (err) {
652
664
  if (isVerbose) console.log(`[wawesome:verbose] Could not resolve the workspace address: ${err instanceof Error ? err.message : err}`);
653
665
  }
@@ -657,13 +669,16 @@ async function deploy(entryInput, options) {
657
669
  console.log(`\n App: ${app}`);
658
670
  console.log(` Function: ${funcName}`);
659
671
  if (version !== void 0) console.log(` Version: ${version}`);
660
- if (invokeUrl) console.log(`\n URL: \x1b[36m${invokeUrl}\x1b[0m`);
672
+ if (address) {
673
+ console.log(`\n URL: \x1b[36m${address}\x1b[0m`);
674
+ console.log(` ${SUBTREE_NOTE}`);
675
+ }
661
676
  console.log("======================================================\n");
662
677
  return {
663
678
  app,
664
679
  functionName: funcName,
665
680
  version,
666
- invokeUrl
681
+ address
667
682
  };
668
683
  }
669
684
  //#endregion
@@ -1014,11 +1029,9 @@ function alignCliDependency(dir, version) {
1014
1029
  const file = path.join(dir, PACKAGE_FILE);
1015
1030
  const pkg = readJson(file);
1016
1031
  const wanted = `^${version}`;
1017
- for (const field of ["dependencies", "devDependencies"]) {
1032
+ for (const { field, range } of declaredCliRanges(pkg)) {
1033
+ if (range === wanted) continue;
1018
1034
  const deps = pkg[field];
1019
- if (!isRecord(deps)) continue;
1020
- const current = deps[CLI_PACKAGE];
1021
- if (typeof current !== "string" || current === wanted) continue;
1022
1035
  writeJson(file, {
1023
1036
  ...pkg,
1024
1037
  [field]: {
@@ -1027,12 +1040,22 @@ function alignCliDependency(dir, version) {
1027
1040
  }
1028
1041
  });
1029
1042
  return {
1030
- from: current,
1043
+ from: range,
1031
1044
  to: wanted
1032
1045
  };
1033
1046
  }
1034
1047
  return null;
1035
1048
  }
1049
+ /** Where a template declares the CLI, and at which range. */
1050
+ function declaredCliRanges(pkg) {
1051
+ return ["dependencies", "devDependencies"].flatMap((field) => {
1052
+ const deps = pkg[field];
1053
+ return isRecord(deps) && typeof deps[CLI_PACKAGE] === "string" ? [{
1054
+ field,
1055
+ range: deps[CLI_PACKAGE]
1056
+ }] : [];
1057
+ });
1058
+ }
1036
1059
  function isRecord(value) {
1037
1060
  return typeof value === "object" && value !== null && !Array.isArray(value);
1038
1061
  }
@@ -1066,9 +1089,9 @@ function collidingPaths(dir, relativePaths) {
1066
1089
  * not be resolved the message still prints, with the placeholder left visible
1067
1090
  * rather than a confident "undefined".
1068
1091
  */
1069
- function renderPostDeploy(postDeploy, invokeUrl) {
1070
- if (!invokeUrl) return postDeploy;
1071
- return postDeploy.split("{{url}}").join(invokeUrl);
1092
+ function renderPostDeploy(postDeploy, address) {
1093
+ if (!address) return postDeploy;
1094
+ return postDeploy.split("{{url}}").join(address);
1072
1095
  }
1073
1096
  /**
1074
1097
  * Normalise arbitrary input into a legal DNS label, returning an empty string
@@ -1509,7 +1532,8 @@ const MAX_RENAME_ATTEMPTS = 3;
1509
1532
  /** Show the address the Function will answer on, once it is deployed. */
1510
1533
  function announceUrl(creds, slug, appSlug, functionName) {
1511
1534
  console.log("\n[wawesome] Your Function will answer on:\n");
1512
- console.log(` \x1b[36m${publicInvokeUrl(creds.gateway_url, slug, appSlug, functionName)}\x1b[0m\n`);
1535
+ console.log(` \x1b[36m${publicAddress(creds.gateway_url, slug, appSlug, functionName)}\x1b[0m`);
1536
+ console.log(` ${SUBTREE_NOTE}\n`);
1513
1537
  }
1514
1538
  /**
1515
1539
  * Show the URL, and offer to fix the workspace address while it can still be fixed.
@@ -1697,7 +1721,7 @@ async function initFromTemplate(templateName, options) {
1697
1721
  verbose: options.verbose
1698
1722
  });
1699
1723
  if (manifest.post_deploy) {
1700
- console.log(renderPostDeploy(manifest.post_deploy, result.invokeUrl));
1724
+ console.log(renderPostDeploy(manifest.post_deploy, result.address));
1701
1725
  console.log("");
1702
1726
  }
1703
1727
  }
@@ -1714,7 +1738,10 @@ async function init(options) {
1714
1738
  let functionName;
1715
1739
  let appSlug;
1716
1740
  try {
1717
- functionName = await promptForSlug(session, "Function name", dirName);
1741
+ if (options.root) {
1742
+ functionName = "root";
1743
+ console.log(" Function name? root (locked by --root)");
1744
+ } else functionName = await promptForSlug(session, "Function name", dirName);
1718
1745
  console.log(" (an App groups the Functions of one project — its slug is part of the public URL)");
1719
1746
  appSlug = await promptForSlug(session, "App slug", dirName);
1720
1747
  } finally {
@@ -1738,7 +1765,26 @@ async function init(options) {
1738
1765
  if (fs.existsSync(indexPath)) console.log("[wawesome] src/index.ts already exists, skipping.");
1739
1766
  else {
1740
1767
  fs.mkdirSync(srcDir, { recursive: true });
1741
- fs.writeFileSync(indexPath, `/**
1768
+ fs.writeFileSync(indexPath, options.root ? `/**
1769
+ * Root Function Router
1770
+ * Handles all requests that don't match a specific function path.
1771
+ */
1772
+ export default {
1773
+ async fetch(request: Request): Promise<Response> {
1774
+ const url = new URL(request.url);
1775
+
1776
+ // Example router
1777
+ switch (url.pathname) {
1778
+ case "/":
1779
+ return Response.json({ message: "Welcome to the App Root!" });
1780
+ case "/health":
1781
+ return Response.json({ status: "ok" });
1782
+ default:
1783
+ return Response.json({ error: "Not Found" }, { status: 404 });
1784
+ }
1785
+ },
1786
+ };
1787
+ ` : `/**
1742
1788
  * Serverless Function Handler
1743
1789
  * Standard HTTP fetch request handler
1744
1790
  */
@@ -2465,7 +2511,10 @@ async function showWorkspace(options) {
2465
2511
  console.log(` Name: ${tenant.name}`);
2466
2512
  console.log(` Address: ${tenant.tenant_slug}`);
2467
2513
  console.log(` Tenant: ${tenant.id}`);
2468
- if (options.verbose) console.log(` Invoke: ${creds.gateway_url}/v1/s/${tenant.tenant_slug}/apps/<app>/functions/<function>/invoke`);
2514
+ if (options.verbose) {
2515
+ console.log(` URLs: ${publicAddressTemplate(creds.gateway_url, tenant.tenant_slug)}`);
2516
+ console.log(` ${SUBTREE_NOTE}`);
2517
+ }
2469
2518
  if (tenant.slug_locked) console.log("\n 🔒 The address is fixed — a Function version has been promoted and live URLs carry it.");
2470
2519
  else {
2471
2520
  console.log("\n The address can still be changed: \x1B[36mwawesome workspace rename <name>\x1B[0m");
@@ -2546,7 +2595,7 @@ cli.command("logout", "Clear stored authentication credentials").action(() => lo
2546
2595
  cli.command("whoami", "Show current login session info").action(() => whoami());
2547
2596
  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));
2548
2597
  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));
2549
- 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("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
2598
+ 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));
2550
2599
  cli.command("logs [function-name-or-invocation-id]", "View invocation history, fetch log output, or follow live").usage(`logs [target] [options]
2551
2600
 
2552
2601
  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.9",
3
+ "version": "0.0.11",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "build": "tsdown",
17
17
  "dev": "tsdown --watch",
18
18
  "test": "vitest run",
19
+ "test:templates": "vitest run --config vitest.live.config.ts",
19
20
  "check": "publint --pack npm",
20
21
  "changeset": "changeset"
21
22
  },