wawesome 0.11.0 → 0.13.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 (3) hide show
  1. package/README.md +19 -1
  2. package/dist/index.mjs +110 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -117,6 +117,8 @@ npx wawesome deploy
117
117
  | `npx wawesome env list` | View environment variables for the current app |
118
118
  | `npx wawesome env set <key> <val>` | Set an environment variable (add `--secret` for write-only) |
119
119
  | `npx wawesome env rm <key>` | Delete an environment variable |
120
+ | `npx wawesome domains` | Show the app's domain, its state, and the DNS records to add |
121
+ | `npx wawesome domains detach <host>` | Stop serving the app at a domain you attached |
120
122
  | `npx wawesome credentials` | List deploy credentials: name, prefix, capabilities, Apps, last use |
121
123
  | `npx wawesome credentials mint <name>` | Mint a deploy credential and print its secret, once |
122
124
  | `npx wawesome credentials revoke <name>`| Revoke a deploy credential by name or prefix |
@@ -314,11 +316,14 @@ you say otherwise:
314
316
  ```bash
315
317
  # reads logs and cannot deploy, restricted to one App, never expires
316
318
  npx wawesome credentials mint agent -c logs:read -a prod --expires never
319
+
320
+ # ships code and attaches the domain the project declares
321
+ npx wawesome credentials mint ci -c deploy,domains:attach -a prod
317
322
  ```
318
323
 
319
324
  | Flag | Meaning |
320
325
  |:---------------------|:------------------------------------------------------------------------------|
321
- | `-c, --capability` | `deploy`, `logs:read`, or both. Repeatable or comma-separated. Default `deploy` |
326
+ | `-c, --capability` | `deploy`, `logs:read`, `domains:attach`, or any mix. Repeatable or comma-separated. Default `deploy` |
322
327
  | `-a, --app` | Restrict to these Apps, by slug. Repeatable. Default: every App in the workspace |
323
328
  | `--expires` | Days, or `never`. Default: 90 |
324
329
 
@@ -524,6 +529,19 @@ Your deploy waits for neither. It attaches the domain, prints the records and fi
524
529
  later deploy states where the domain got to: not verified, ownership verified, certificate issued, or
525
530
  serving. A domain that is already serving is a line saying so and nothing else.
526
531
 
532
+ The same block is there when you are not deploying:
533
+
534
+ ```bash
535
+ npx wawesome domains
536
+ ```
537
+
538
+ It prints the domain, where it got to, when the platform last checked, and the records to add. It
539
+ changes nothing, and a deploy credential carrying `deploy` can run it, so a pipeline reads the same
540
+ thing you do. The TXT record is minted a moment after the name is claimed, and an attach that outran
541
+ it prints a row saying so rather than dropping the record from the list. That row is what this
542
+ command fills in, and it is where the deploy sends you: deploying again would change nothing, and a
543
+ deploy that changes nothing is refused.
544
+
527
545
  A custom domain is granted from the Solo plan upwards, one per App. On a plan that grants none, the
528
546
  deploy prints the refusal and lands everything else it was doing.
529
547
 
package/dist/index.mjs CHANGED
@@ -883,7 +883,7 @@ async function buildJs(entryInput, options) {
883
883
  * that has to name this version — `--version`, the dependency a scaffolded
884
884
  * project pins — reads it here, so a release bumps one file.
885
885
  */
886
- const CLI_VERSION = "0.11.0";
886
+ const CLI_VERSION = "0.13.0";
887
887
  //#endregion
888
888
  //#region src/prompt.ts
889
889
  /**
@@ -1630,13 +1630,11 @@ async function detachDomain(creds, app, hostname) {
1630
1630
  });
1631
1631
  if (!res.ok) throw await asGatewayError(res, `'${hostname}' could not be detached (HTTP ${res.status}).`);
1632
1632
  }
1633
- /** A missing name is refused here rather than sent, so the reason reads at the keyboard. */
1634
- async function detachDomainCommand(hostname, options = {}) {
1635
- if (!hostname || !hostname.trim()) {
1636
- console.error("[wawesome] Error: Missing domain. Usage: wawesome domains detach <hostname>");
1637
- console.error("[wawesome] The domain is typed out because detaching one takes it dark.");
1638
- process.exit(1);
1639
- }
1633
+ /**
1634
+ * Who is asking and which App they are asking about. A deploy credential in the
1635
+ * environment is a caller here, so this never insists on a session.
1636
+ */
1637
+ function credentialsAndApp(options) {
1640
1638
  const creds = readCredentials();
1641
1639
  if (!creds) {
1642
1640
  console.error("[wawesome] Error: Not logged in. Run 'wawesome login' first.");
@@ -1648,6 +1646,19 @@ async function detachDomainCommand(hostname, options = {}) {
1648
1646
  console.error("[wawesome] Run this from your project, or name the app with --app <slug>.");
1649
1647
  process.exit(1);
1650
1648
  }
1649
+ return {
1650
+ creds,
1651
+ app
1652
+ };
1653
+ }
1654
+ /** A missing name is refused here rather than sent, so the reason reads at the keyboard. */
1655
+ async function detachDomainCommand(hostname, options = {}) {
1656
+ if (!hostname || !hostname.trim()) {
1657
+ console.error("[wawesome] Error: Missing domain. Usage: wawesome domains detach <hostname>");
1658
+ console.error("[wawesome] The domain is typed out because detaching one takes it dark.");
1659
+ process.exit(1);
1660
+ }
1661
+ const { creds, app } = credentialsAndApp(options);
1651
1662
  const name = hostname.trim();
1652
1663
  console.log(`[wawesome] Detaching '${name}' from app '${app}':`);
1653
1664
  console.log("[wawesome] • the domain stops resolving here, so anything pointed at it stops");
@@ -1668,10 +1679,40 @@ async function detachDomainCommand(hostname, options = {}) {
1668
1679
  console.log("[wawesome] or the next deploy attaches the domain again.");
1669
1680
  }
1670
1681
  }
1682
+ /**
1683
+ * What this App answers at, read and printed.
1684
+ *
1685
+ * The one place a Tenant can ask. The records used to exist only in the output
1686
+ * of a deploy that changed something, so a deploy that outran the vendor left
1687
+ * nothing to run but a second deploy, which is refused for changing nothing
1688
+ * (#617). Open to a deploy credential carrying `deploy`, since a pipeline asks
1689
+ * this too.
1690
+ */
1691
+ async function showDomainCommand(options = {}) {
1692
+ const { creds, app } = credentialsAndApp(options);
1693
+ let held;
1694
+ try {
1695
+ held = await listDomains(creds, app);
1696
+ } catch (err) {
1697
+ console.error(`[wawesome] Error: ${whatWentWrong(err)}`);
1698
+ process.exit(1);
1699
+ }
1700
+ if (held.length === 0) {
1701
+ console.log(`[wawesome] No domain is attached to app '${app}'.`);
1702
+ console.log("[wawesome] Declare one in wawesome-function.json and deploy:");
1703
+ console.log(`[wawesome] \x1b[36m${DOMAIN_CONFIG_LINE}\x1b[0m`);
1704
+ console.log("[wawesome] The deploy attaches it and prints the records to add.");
1705
+ return;
1706
+ }
1707
+ console.log(`[wawesome] The domain on app '${app}':`);
1708
+ for (const domain of held) for (const line of attachedDomainLines(domain, "domains")) console.log(line);
1709
+ }
1671
1710
  async function domainsCommand(action, target, options = {}) {
1672
- if ((action || "").toLowerCase().trim() === "detach") return detachDomainCommand(target, options);
1711
+ const verb = (action || "").toLowerCase().trim();
1712
+ if (verb === "detach") return detachDomainCommand(target, options);
1713
+ if (!verb) return showDomainCommand(options);
1673
1714
  console.error(`[wawesome] Error: Unknown command '${["domains", action].filter(Boolean).join(" ")}'.`);
1674
- console.error("[wawesome] Usage: wawesome domains detach <hostname>");
1715
+ console.error("[wawesome] Usage: wawesome domains [detach <hostname>]");
1675
1716
  console.error("[wawesome] A domain is attached by declaring it in wawesome-function.json.");
1676
1717
  process.exit(1);
1677
1718
  }
@@ -1685,17 +1726,34 @@ function domainsUrl(creds, app) {
1685
1726
  * added a day apart: the TXT issues the certificate, and the CNAME is pointed
1686
1727
  * here afterwards so a site that is already live never answers on a
1687
1728
  * certificate that is not there yet.
1729
+ *
1730
+ * Read off the records too, never the states alone: naming a record the payload
1731
+ * does not carry left a reader holding the CNAME alone (#610).
1688
1732
  */
1689
- function whereItGotTo(state) {
1733
+ function whereItGotTo(domain) {
1734
+ const { state } = domain;
1690
1735
  if (state.serving) return "serving";
1691
1736
  if (state.certificate_active) return "certificate issued, so point the CNAME here to cut over";
1692
1737
  if (state.ownership_verified) return "ownership verified, and the certificate is being issued";
1738
+ if (domain.certificate_record_pending) return "not verified yet, and the TXT record is not ready";
1693
1739
  return "not verified yet, so add the TXT record below";
1694
1740
  }
1695
- function whatItIsFor(record) {
1696
- return record.purpose === "certificate" ? "Proves you own the name, and issues the certificate" : "Points traffic here, once the certificate is issued";
1741
+ /**
1742
+ * What a record is for, and when to add it.
1743
+ *
1744
+ * The order is stated rather than implied. The TXT proves control and issues
1745
+ * the certificate; the CNAME is pointed here afterwards, so a name already
1746
+ * serving a live site never answers on a certificate that is not there yet.
1747
+ * That is why the platform validates before the cutover, and a reader holding
1748
+ * the CNAME alone could read the old caption as a label rather than as an
1749
+ * instruction to wait (#617).
1750
+ */
1751
+ function whatItIsFor(purpose) {
1752
+ return purpose === "certificate" ? "Add this first. It proves you own the name and issues the certificate" : "Add this once the certificate is active. It points traffic here";
1697
1753
  }
1698
1754
  const VALUE_COLUMN = " ".repeat(14);
1755
+ /** What the dashboard's empty state shows. Kept the same by hand, not by anything. */
1756
+ const DOMAIN_CONFIG_LINE = "\"domain\": \"shop.client.com\"";
1699
1757
  /**
1700
1758
  * The block a deploy prints for the domain its configuration declares, in the
1701
1759
  * columns the visibility and URL lines above it already use.
@@ -1725,7 +1783,18 @@ function domainLines(outcome) {
1725
1783
  ...outcome.held ? [`${VALUE_COLUMN}Nothing was detached. Take '${outcome.held}' off first with`, `${VALUE_COLUMN} \x1b[36mwawesome domains detach ${outcome.held}\x1b[0m`] : [],
1726
1784
  `${VALUE_COLUMN}The deploy landed, and this App's other address still answers.`
1727
1785
  ];
1728
- const { hostname, records, state, last_checked_at, detaching } = outcome.domain;
1786
+ return attachedDomainLines(outcome.domain, "deploy");
1787
+ }
1788
+ /**
1789
+ * The block for a domain the App carries: where it got to, when the platform
1790
+ * last looked, and the records to add.
1791
+ *
1792
+ * The deploy and `wawesome domains` print this same block. One renderer rather
1793
+ * than two, because two of them drift, and a record going missing from one is
1794
+ * the whole of #610 and #617.
1795
+ */
1796
+ function attachedDomainLines(domain, printedBy = "deploy") {
1797
+ const { hostname, records, state, last_checked_at, detaching, certificate_record_pending } = domain;
1729
1798
  if (detaching) return [
1730
1799
  "",
1731
1800
  ` Domain: \x1b[33m${hostname}: being detached\x1b[0m`,
@@ -1733,18 +1802,36 @@ function domainLines(outcome) {
1733
1802
  `${VALUE_COLUMN}Remove 'domain' from wawesome-function.json, or deploy again`,
1734
1803
  `${VALUE_COLUMN}once it is gone to attach it afresh.`
1735
1804
  ];
1736
- const lines = ["", ` Domain: \x1b[36m${hostname}\x1b[0m: ${whereItGotTo(state)}`];
1805
+ const lines = ["", ` Domain: \x1b[36m${hostname}\x1b[0m: ${whereItGotTo(domain)}`];
1806
+ if (last_checked_at) lines.push(`${VALUE_COLUMN}Last checked ${asUtc$1(last_checked_at)}.`);
1737
1807
  if (state.serving) {
1738
- lines.push(`${VALUE_COLUMN}The address above answers too, so nothing pointed at it breaks.`);
1808
+ lines.push(`${VALUE_COLUMN}This app's own address answers too, so nothing pointed at it breaks.`);
1739
1809
  return lines;
1740
1810
  }
1741
- if (last_checked_at) lines.push(`${VALUE_COLUMN}Last checked ${asUtc$1(last_checked_at)}.`);
1742
1811
  const nameWidth = Math.max(...records.map((record) => record.name.length));
1812
+ const row = (type, name, value = "") => ` ${type.padEnd(5)} ${name.padEnd(nameWidth)} ${value}`.trimEnd();
1743
1813
  lines.push("", " DNS records to add at your registrar:");
1744
- for (const record of records) lines.push("", ` ${whatItIsFor(record)}:`, ` ${record.type.padEnd(5)} ${record.name.padEnd(nameWidth)} ${record.value}`);
1745
- lines.push("", `${VALUE_COLUMN}The deploy is finished. This domain comes up on its own`, `${VALUE_COLUMN}once the records resolve.`);
1814
+ if (certificate_record_pending) lines.push("", ` ${whatItIsFor("certificate")}:`, row("TXT", "not ready yet"));
1815
+ for (const record of records) lines.push("", ` ${whatItIsFor(record.purpose)}:`, row(record.type, record.name, record.value));
1816
+ lines.push("", ...closingLines(certificate_record_pending, printedBy));
1746
1817
  return lines;
1747
1818
  }
1819
+ /**
1820
+ * What is left to do, which is nothing.
1821
+ *
1822
+ * The pending case pointed at another deploy until #617: a deploy that changed
1823
+ * nothing is refused, so the one piece of advice the reader was given was the
1824
+ * one they could not take.
1825
+ */
1826
+ function closingLines(certificateRecordPending, printedBy) {
1827
+ if (!certificateRecordPending) return indented(`${printedBy === "deploy" ? "The deploy is finished. " : ""}This domain comes up on its own once the records resolve.`);
1828
+ const asking = "The TXT record is not ready yet. The platform asks the certificate vendor again on its own.";
1829
+ if (printedBy === "domains") return indented(`${asking} Run this again in a minute.`);
1830
+ return [...indented(`${asking} The deploy is finished either way. To read the record when it lands, run`), `${VALUE_COLUMN} \x1b[36mwawesome domains\x1b[0m`];
1831
+ }
1832
+ function indented(said) {
1833
+ return wrapped(said).map((line) => `${VALUE_COLUMN}${line}`);
1834
+ }
1748
1835
  /** The platform's own prose, kept inside the block it is printed in. */
1749
1836
  function wrapped(said) {
1750
1837
  const width = 64;
@@ -5292,7 +5379,7 @@ cli.command("env [action] [key] [value]", "Manage environment variables (set, li
5292
5379
  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));
5293
5380
  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));
5294
5381
  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));
5295
- cli.command("domains [action] [hostname]", "Manage the custom domain on the current app (detach)").usage("domains <action> [hostname]\n\nActions:\n detach <hostname> Stop serving the app at a domain you attached\n\nA domain is attached by declaring it in wawesome-function.json and deploying.").example("wawesome domains detach shop.client.com").option("-a, --app <slug>", "App to act on (default: the app in wawesome-function.json)").action((action, hostname, options) => domainsCommand(action, hostname, options));
5382
+ cli.command("domains [action] [hostname]", "Show the custom domain on the current app, its state and its DNS records").usage("domains [action] [hostname]\n\nWith no action, prints the app's domain, where it got to, when the platform last checked, and the records to add.\n\nActions:\n detach <hostname> Stop serving the app at a domain you attached\n\nA domain is attached by declaring it in wawesome-function.json and deploying.").example("wawesome domains").example("wawesome domains detach shop.client.com").option("-a, --app <slug>", "App to act on (default: the app in wawesome-function.json)").action((action, hostname, options) => domainsCommand(action, hostname, options));
5296
5383
  cli.command("domains detach <hostname>", "Stop serving the current app at a domain you attached").option("-a, --app <slug>", "App to act on (default: the app in wawesome-function.json)").action((hostname, options) => detachDomainCommand(hostname, options));
5297
5384
  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("--accept-terms", "Accept the terms of service and privacy policy, when signing up without a terminal to prompt").option("-v, --verbose", "Enable verbose debug output").action((options) => login(options));
5298
5385
  cli.command("logout", "Clear stored authentication credentials").action(() => logout());
@@ -5315,10 +5402,11 @@ cli.command("credentials [action] [target]", "Mint, list and revoke deploy crede
5315
5402
  wawesome credentials mint ci-pipeline
5316
5403
  wawesome credentials mint agent -c logs:read --expires never
5317
5404
  wawesome credentials mint prod-deploy -c deploy -a prod --expires 30
5405
+ wawesome credentials mint ci -c deploy,domains:attach -a prod
5318
5406
  wawesome credentials mint ci-pipeline > /tmp/secret
5319
5407
  wawesome credentials revoke ci-pipeline
5320
- wawesome credentials revoke wawe_ab3k9x --yes`).option("-c, --capability <capability>", "Capability to grant: deploy, logs:read (repeatable or comma-separated, default: deploy)").option("-a, --app <app>", "Restrict the credential to this App (repeatable or comma-separated, default: every App)").option("--expires <days|never>", "Days until the credential expires, or 'never' (default: 90)").option("-y, --yes", "Skip the revoke confirmation").option("-v, --verbose", "Enable verbose debug output").action((action, target, options) => credentialsCommand(action, target, options));
5321
- cli.command("credentials mint <name>", "Mint a deploy credential, printing its secret exactly once").option("-c, --capability <capability>", "Capability to grant: deploy, logs:read (repeatable or comma-separated)").option("-a, --app <app>", "Restrict the credential to this App (repeatable or comma-separated)").option("--expires <days|never>", "Days until the credential expires, or 'never' (default: 90)").option("-v, --verbose", "Enable verbose debug output").action((name, options) => mintCredentialCommand(name, options));
5408
+ wawesome credentials revoke wawe_ab3k9x --yes`).option("-c, --capability <capability>", "Capability to grant: deploy, logs:read, domains:attach (repeatable or comma-separated, default: deploy)").option("-a, --app <app>", "Restrict the credential to this App (repeatable or comma-separated, default: every App)").option("--expires <days|never>", "Days until the credential expires, or 'never' (default: 90)").option("-y, --yes", "Skip the revoke confirmation").option("-v, --verbose", "Enable verbose debug output").action((action, target, options) => credentialsCommand(action, target, options));
5409
+ cli.command("credentials mint <name>", "Mint a deploy credential, printing its secret exactly once").option("-c, --capability <capability>", "Capability to grant: deploy, logs:read, domains:attach (repeatable or comma-separated)").option("-a, --app <app>", "Restrict the credential to this App (repeatable or comma-separated)").option("--expires <days|never>", "Days until the credential expires, or 'never' (default: 90)").option("-v, --verbose", "Enable verbose debug output").action((name, options) => mintCredentialCommand(name, options));
5322
5410
  cli.command("credentials list", "List deploy credentials, never their secrets").alias("credentials ls").option("-v, --verbose", "Enable verbose debug output").action((options) => listCredentialsCommand(options));
5323
5411
  cli.command("credentials revoke <name-or-prefix>", "Revoke a deploy credential, immediately and for good").option("-y, --yes", "Revoke without being asked to confirm").option("-v, --verbose", "Enable verbose debug output").action((target, options) => revokeCredentialCommand(target, options));
5324
5412
  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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {