@madewithremy/admin 0.1.38 → 0.1.40

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.
package/dist/prod.js CHANGED
@@ -274,20 +274,20 @@ function seg(value) {
274
274
  return encodeURIComponent(String(value));
275
275
  }
276
276
  function qs(params) {
277
- const search2 = new URLSearchParams();
277
+ const search3 = new URLSearchParams();
278
278
  for (const [key, value] of Object.entries(params)) {
279
279
  if (value === void 0 || value === null) {
280
280
  continue;
281
281
  }
282
282
  if (Array.isArray(value)) {
283
283
  for (const entry of value) {
284
- search2.append(key, String(entry));
284
+ search3.append(key, String(entry));
285
285
  }
286
286
  continue;
287
287
  }
288
- search2.set(key, String(value));
288
+ search3.set(key, String(value));
289
289
  }
290
- const text = search2.toString();
290
+ const text = search3.toString();
291
291
  return text ? `?${text}` : "";
292
292
  }
293
293
  function authHeaders(ctx) {
@@ -1510,8 +1510,113 @@ async function customRetry(ctx, hostname) {
1510
1510
  `/_internal/v2/apps/${ctx.appId}/settings/custom-domains/${seg(id)}/retry`
1511
1511
  );
1512
1512
  }
1513
+ var REGISTERED = (ctx) => `/_internal/v2/apps/${ctx.appId}/settings/registered-domains`;
1514
+ function search(ctx, query3) {
1515
+ return call(
1516
+ ctx,
1517
+ "POST",
1518
+ `${REGISTERED(ctx)}/search`,
1519
+ { query: query3 }
1520
+ );
1521
+ }
1522
+ function quote(ctx, name) {
1523
+ return call(
1524
+ ctx,
1525
+ "POST",
1526
+ `${REGISTERED(ctx)}/quote`,
1527
+ { name }
1528
+ );
1529
+ }
1530
+ function buy(ctx, params) {
1531
+ return call(
1532
+ ctx,
1533
+ "POST",
1534
+ `${REGISTERED(ctx)}/purchase`,
1535
+ { name: params.name, expectedPrice: params.price, acceptTerms: true }
1536
+ );
1537
+ }
1538
+ function owned(ctx) {
1539
+ return call(ctx, "GET", REGISTERED(ctx));
1540
+ }
1541
+ async function findOwned(ctx, name) {
1542
+ const wanted = name.toLowerCase();
1543
+ const { domains } = await owned(ctx);
1544
+ const domain = domains.find(
1545
+ (d) => d.name === wanted && (d.status === "active" || d.status === "expired" || d.status === "pending")
1546
+ );
1547
+ if (!domain) {
1548
+ throw new Error(`No domain "${name}" in this workspace`);
1549
+ }
1550
+ return domain;
1551
+ }
1552
+ function contactGet(ctx) {
1553
+ return call(
1554
+ ctx,
1555
+ "GET",
1556
+ `${REGISTERED(ctx)}/contact`
1557
+ );
1558
+ }
1559
+ function contactSet(ctx, contact) {
1560
+ return call(
1561
+ ctx,
1562
+ "POST",
1563
+ `${REGISTERED(ctx)}/contact`,
1564
+ { ...contact }
1565
+ );
1566
+ }
1567
+ async function zone(ctx, name) {
1568
+ const domain = await findOwned(ctx, name);
1569
+ return call(
1570
+ ctx,
1571
+ "GET",
1572
+ `${REGISTERED(ctx)}/${seg(domain.id)}/records`
1573
+ );
1574
+ }
1575
+ async function zoneAdd(ctx, name, record) {
1576
+ const domain = await findOwned(ctx, name);
1577
+ return call(
1578
+ ctx,
1579
+ "POST",
1580
+ `${REGISTERED(ctx)}/${seg(domain.id)}/records`,
1581
+ { ...record }
1582
+ );
1583
+ }
1584
+ async function zoneUpdate(ctx, name, recordId, record) {
1585
+ const domain = await findOwned(ctx, name);
1586
+ return call(
1587
+ ctx,
1588
+ "POST",
1589
+ `${REGISTERED(ctx)}/${seg(domain.id)}/records/${seg(recordId)}`,
1590
+ { ...record }
1591
+ );
1592
+ }
1593
+ async function zoneDelete(ctx, name, recordId) {
1594
+ const domain = await findOwned(ctx, name);
1595
+ return call(
1596
+ ctx,
1597
+ "POST",
1598
+ `${REGISTERED(ctx)}/${seg(domain.id)}/records/${seg(recordId)}/delete`
1599
+ );
1600
+ }
1513
1601
 
1514
1602
  // src/commands/domains.ts
1603
+ var RECORD_TYPES = [
1604
+ "A",
1605
+ "AAAA",
1606
+ "ANAME",
1607
+ "CNAME",
1608
+ "MX",
1609
+ "NS",
1610
+ "SRV",
1611
+ "TXT"
1612
+ ];
1613
+ var ZONE_RECORD_FLAGS = {
1614
+ type: { type: "string", choices: RECORD_TYPES },
1615
+ host: { type: "string" },
1616
+ answer: { type: "string" },
1617
+ ttl: { type: "number", min: 300 },
1618
+ priority: { type: "number", min: 0 }
1619
+ };
1515
1620
  var domainsSpecs = {
1516
1621
  "domains get": {
1517
1622
  usage: "Usage: remy-admin domains get"
@@ -1550,6 +1655,79 @@ var domainsSpecs = {
1550
1655
  "domains custom retry": {
1551
1656
  usage: "Usage: remy-admin domains custom retry <hostname>",
1552
1657
  positionals: [{ name: "hostname", required: true }]
1658
+ },
1659
+ "domains search": {
1660
+ usage: "Usage: remy-admin domains search <keyword or name>",
1661
+ positionals: [{ name: "query", required: true, variadic: true }]
1662
+ },
1663
+ "domains quote": {
1664
+ usage: "Usage: remy-admin domains quote <name>",
1665
+ positionals: [{ name: "name", required: true }]
1666
+ },
1667
+ "domains buy": {
1668
+ usage: "Usage: remy-admin domains buy <name> --price <dollars>\nBuys the domain for the workspace at once: the price isn't refundable, and it renews yearly at its renewal price. Only run this after the user has explicitly confirmed the name, the price and the renewal price. --price is the quote's price.",
1669
+ positionals: [{ name: "name", required: true }],
1670
+ flags: {
1671
+ price: { type: "string" }
1672
+ },
1673
+ requireAnyOf: {
1674
+ flags: ["price"],
1675
+ message: "Pass --price with the quote's price the user confirmed (remy-admin domains quote <name>)."
1676
+ }
1677
+ },
1678
+ "domains owned": {
1679
+ usage: "Usage: remy-admin domains owned"
1680
+ },
1681
+ "domains contact get": {
1682
+ usage: "Usage: remy-admin domains contact get"
1683
+ },
1684
+ "domains contact set": {
1685
+ usage: "Usage: remy-admin domains contact set --first-name <n> --last-name <n> [--company <org>] --address1 <line> [--address2 <line>] --city <c> --state <s> --zip <z> --country <XX> --email <e> --phone <+E.164>\nThe registrant: the legal owner of every domain the workspace buys (the organization when --company is set). ICANN emails that address to verify it.",
1686
+ flags: {
1687
+ "first-name": { type: "string" },
1688
+ "last-name": { type: "string" },
1689
+ company: { type: "string" },
1690
+ address1: { type: "string" },
1691
+ address2: { type: "string" },
1692
+ city: { type: "string" },
1693
+ state: { type: "string" },
1694
+ zip: { type: "string" },
1695
+ country: { type: "string" },
1696
+ email: { type: "string" },
1697
+ phone: { type: "string" }
1698
+ }
1699
+ },
1700
+ "domains zone list": {
1701
+ usage: "Usage: remy-admin domains zone list <name>",
1702
+ positionals: [{ name: "name", required: true }]
1703
+ },
1704
+ "domains zone add": {
1705
+ usage: "Usage: remy-admin domains zone add <name> --type <type> --answer <value> [--host <host>] [--ttl <seconds>] [--priority <n>]",
1706
+ positionals: [{ name: "name", required: true }],
1707
+ flags: ZONE_RECORD_FLAGS,
1708
+ requireAnyOf: {
1709
+ flags: ["answer"],
1710
+ message: "Pass --type and --answer."
1711
+ }
1712
+ },
1713
+ "domains zone update": {
1714
+ usage: "Usage: remy-admin domains zone update <name> <recordId> --type <type> --answer <value> [--host <host>] [--ttl <seconds>] [--priority <n>]",
1715
+ positionals: [
1716
+ { name: "name", required: true },
1717
+ { name: "recordId", required: true }
1718
+ ],
1719
+ flags: ZONE_RECORD_FLAGS,
1720
+ requireAnyOf: {
1721
+ flags: ["answer"],
1722
+ message: "Pass --type and --answer."
1723
+ }
1724
+ },
1725
+ "domains zone delete": {
1726
+ usage: "Usage: remy-admin domains zone delete <name> <recordId>",
1727
+ positionals: [
1728
+ { name: "name", required: true },
1729
+ { name: "recordId", required: true }
1730
+ ]
1553
1731
  }
1554
1732
  };
1555
1733
  async function domainsGet(ctx) {
@@ -1594,6 +1772,72 @@ async function customDomainsRemove(ctx, a) {
1594
1772
  async function customDomainsRetry(ctx, a) {
1595
1773
  out(await customRetry(ctx, a.req("hostname")));
1596
1774
  }
1775
+ async function domainsSearch(ctx, a) {
1776
+ out(await search(ctx, a.rest("query").join(" ")));
1777
+ }
1778
+ async function domainsQuote(ctx, a) {
1779
+ out(await quote(ctx, a.req("name")));
1780
+ }
1781
+ async function domainsBuy(ctx, a) {
1782
+ out(
1783
+ await buy(ctx, {
1784
+ name: a.req("name"),
1785
+ price: Number(a.str("price"))
1786
+ })
1787
+ );
1788
+ }
1789
+ async function domainsOwned(ctx) {
1790
+ out(await owned(ctx));
1791
+ }
1792
+ async function domainsContactGet(ctx) {
1793
+ out(await contactGet(ctx));
1794
+ }
1795
+ async function domainsContactSet(ctx, a) {
1796
+ out(
1797
+ await contactSet(ctx, {
1798
+ firstName: a.str("first-name") ?? "",
1799
+ lastName: a.str("last-name") ?? "",
1800
+ companyName: a.str("company"),
1801
+ address1: a.str("address1") ?? "",
1802
+ address2: a.str("address2"),
1803
+ city: a.str("city") ?? "",
1804
+ state: a.str("state") ?? "",
1805
+ zip: a.str("zip") ?? "",
1806
+ country: a.str("country") ?? "",
1807
+ email: a.str("email") ?? "",
1808
+ phone: a.str("phone") ?? ""
1809
+ })
1810
+ );
1811
+ }
1812
+ function zoneRecord(a) {
1813
+ const host = a.str("host") ?? "";
1814
+ return {
1815
+ host: host === "@" ? "" : host,
1816
+ type: a.str("type") ?? "",
1817
+ answer: a.str("answer") ?? "",
1818
+ ttl: a.num("ttl"),
1819
+ priority: a.num("priority")
1820
+ };
1821
+ }
1822
+ async function domainsZoneList(ctx, a) {
1823
+ out(await zone(ctx, a.req("name")));
1824
+ }
1825
+ async function domainsZoneAdd(ctx, a) {
1826
+ out(await zoneAdd(ctx, a.req("name"), zoneRecord(a)));
1827
+ }
1828
+ async function domainsZoneUpdate(ctx, a) {
1829
+ out(
1830
+ await zoneUpdate(
1831
+ ctx,
1832
+ a.req("name"),
1833
+ Number(a.req("recordId")),
1834
+ zoneRecord(a)
1835
+ )
1836
+ );
1837
+ }
1838
+ async function domainsZoneDelete(ctx, a) {
1839
+ out(await zoneDelete(ctx, a.req("name"), Number(a.req("recordId"))));
1840
+ }
1597
1841
  var domainsHandlers = {
1598
1842
  "domains get": domainsGet,
1599
1843
  "domains set": domainsSet,
@@ -1604,9 +1848,19 @@ var domainsHandlers = {
1604
1848
  "domains custom records": customDomainsRecords,
1605
1849
  "domains custom status": customDomainsStatus,
1606
1850
  "domains custom remove": customDomainsRemove,
1607
- "domains custom retry": customDomainsRetry
1851
+ "domains custom retry": customDomainsRetry,
1852
+ "domains search": domainsSearch,
1853
+ "domains quote": domainsQuote,
1854
+ "domains buy": domainsBuy,
1855
+ "domains owned": domainsOwned,
1856
+ "domains contact get": domainsContactGet,
1857
+ "domains contact set": domainsContactSet,
1858
+ "domains zone list": domainsZoneList,
1859
+ "domains zone add": domainsZoneAdd,
1860
+ "domains zone update": domainsZoneUpdate,
1861
+ "domains zone delete": domainsZoneDelete
1608
1862
  };
1609
- var domainsHelp = `remy-admin domains \u2014 Manage your app's domains.
1863
+ var domainsHelp = `remy-admin domains \u2014 Manage your app's domains, and buy one.
1610
1864
 
1611
1865
  Platform subdomain (e.g. my-app.madewithremy.com):
1612
1866
  get Get current custom subdomain
@@ -1622,6 +1876,18 @@ Custom domains (customer-owned hostnames, CNAME/A records):
1622
1876
  custom remove <hostname> Remove a hostname (apex also removes paired www)
1623
1877
  custom retry <hostname> Re-trigger validation (after customer fixes DNS)
1624
1878
 
1879
+ Buying a domain (the workspace owns it; Remy hosts its DNS):
1880
+ search <keyword or name> Names for sale, with price and renewal price
1881
+ quote <name> The definitive price for one name
1882
+ buy <name> --price <dollars> Buy it (not refundable: confirm with the user first)
1883
+ owned The workspace's domains: status, expiry, auto-renew, apps
1884
+ contact get The registrant contact every domain is registered to
1885
+ contact set --first-name ... Set it (needed before the first purchase)
1886
+ zone list <name> The domain's DNS records (ours marked managed)
1887
+ zone add <name> ... Add a record (--type, --answer, --host, --ttl, --priority)
1888
+ zone update <name> <id> ... Replace one of the owner's records
1889
+ zone delete <name> <id> Delete one of the owner's records
1890
+
1625
1891
  Usage:
1626
1892
  remy-admin domains get
1627
1893
  remy-admin domains set my-app
@@ -1634,14 +1900,44 @@ Usage:
1634
1900
  remy-admin domains custom status app.acme.com
1635
1901
  remy-admin domains custom retry app.acme.com
1636
1902
  remy-admin domains custom remove app.acme.com
1903
+ remy-admin domains search acme bakery
1904
+ remy-admin domains quote acmebakery.com
1905
+ remy-admin domains buy acmebakery.com --price 11.99
1906
+ remy-admin domains owned
1907
+ remy-admin domains contact set --first-name Ada --last-name Lovelace --address1 "1 Main St" --city Austin --state TX --zip 78701 --country US --email ada@acme.com --phone +15125550123
1908
+ remy-admin domains zone list acmebakery.com
1909
+ remy-admin domains zone add acmebakery.com --type TXT --host @ --answer "google-site-verification=abc"
1637
1910
 
1638
1911
  Notes:
1639
1912
  - 'add' with an apex (e.g. acme.com) auto-creates the www.apex pair and
1640
1913
  returns both. 'remove' on an apex also removes its paired www.
1641
1914
  - 'list'/'records'/'status' read a CF-synced cache that can be up to ~5
1642
1915
  minutes stale. After the customer adds DNS, use 'retry' to force a
1643
- synchronous re-check.
1644
- - 'uiStatus' values: waiting_for_dns | issuing_ssl | live | action_needed | reconnecting.`;
1916
+ synchronous re-check. On a domain bought through Remy there are no records
1917
+ to add, so there's nothing to retry while it waits on the registry.
1918
+ - 'uiStatus' values: waiting_for_dns | issuing_ssl | live | action_needed | reconnecting.
1919
+ - 'buy' charges the workspace the price at once, and it isn't refundable; the
1920
+ domain renews yearly at its renewal price while auto-renew is on (the owner
1921
+ turns it off, renews early or transfers out in Workspace Settings > Domains).
1922
+ Never run 'buy' without the user's explicit confirmation of the name, the
1923
+ price and the renewal price. --price is the quote's; if the price has moved,
1924
+ 'buy' is refused with price_changed and the new price, to confirm again.
1925
+ - The first purchase needs the registrant contact ('contact set'): the
1926
+ domain's legal owner. ICANN emails that address to verify it within 15
1927
+ days, or the domain stops resolving; tell the user to watch for it.
1928
+ - Other refusals: registrant_contact_required, insufficient_credits/*,
1929
+ entitlement_required:registered_domains (the plan holds no more domains:
1930
+ a paid plan holds 20), domain_registration_rate (3 in any 30 days),
1931
+ domain_unavailable, tld_not_supported,
1932
+ trademark_claim, registrar_busy (try again), registrar_payment (temporarily
1933
+ unavailable), namecom_not_configured (this deployment doesn't sell domains).
1934
+ - A bought domain is usually active when 'buy' returns; 'owned' shows a
1935
+ pending one settle. It connects to nothing by itself: 'custom add',
1936
+ 'email domains add' and 'email inbound-domains add' on it write their DNS
1937
+ records automatically (the response's registeredDomain says what was
1938
+ written), so there's nothing to hand the user.
1939
+ - Records the platform wrote for a connection are 'managed' in 'zone list' and
1940
+ can't be changed; they go when the connection is removed.`;
1645
1941
 
1646
1942
  // src/ops/users.ts
1647
1943
  function list4(ctx, params = {}) {
@@ -3375,7 +3671,7 @@ async function allDocuments(ctx, params) {
3375
3671
  function list8(ctx) {
3376
3672
  return call(ctx, "GET", base(ctx.appId));
3377
3673
  }
3378
- function search(ctx, params) {
3674
+ function search2(ctx, params) {
3379
3675
  const {
3380
3676
  slug,
3381
3677
  query: query3,
@@ -6211,7 +6507,7 @@ async function dataSourcesSearch(ctx, a) {
6211
6507
  const filter = filterFromFlags(a);
6212
6508
  const maxPerDocument = a.num("max-per-document");
6213
6509
  out(
6214
- await search(ctx, {
6510
+ await search2(ctx, {
6215
6511
  slug: sourceOf(a),
6216
6512
  query: a.req("query"),
6217
6513
  ...topK ? { topK } : {},
@@ -8619,11 +8915,11 @@ function logs(ctx, params) {
8619
8915
  if (params.limit) {
8620
8916
  query3.set("limit", String(params.limit));
8621
8917
  }
8622
- const search2 = query3.toString();
8918
+ const search3 = query3.toString();
8623
8919
  return call(
8624
8920
  ctx,
8625
8921
  "GET",
8626
- `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/logs${search2 ? `?${search2}` : ""}`
8922
+ `${base7(ctx.appId)}/${encodeURIComponent(params.id)}/logs${search3 ? `?${search3}` : ""}`
8627
8923
  );
8628
8924
  }
8629
8925
  function provision(ctx, params) {
@@ -9371,7 +9667,7 @@ Commands:
9371
9667
  analytics View traffic, top-N, geo, and AI-referral insights
9372
9668
  releases View and monitor releases
9373
9669
  diagnostics View the post-deploy Lighthouse audit (scores, issues, raw report)
9374
- domains Manage custom subdomain
9670
+ domains Subdomain, custom domains, and buying a domain
9375
9671
  users Manage app users and roles
9376
9672
  db Query the production database
9377
9673
  secrets Manage app secrets (env vars)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",