@zuvo/cli 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -30,6 +30,10 @@ zuvo hosting env set API_KEY=secret FOO=bar
30
30
  zuvo hosting env set --env-file ./.env.hosting
31
31
  zuvo hosting env unset API_KEY
32
32
  zuvo hosting secrets list # alias for hosting env
33
+ zuvo hosting domains
34
+ zuvo hosting domains add app.example.com --app sklive-admin
35
+ zuvo hosting domains verify app.example.com
36
+ zuvo hosting domains rm app.example.com
33
37
  ```
34
38
 
35
39
  Token: Studio → Account → Access Tokens, or `zuvo login --token zpat_…`.
@@ -39,3 +43,4 @@ API override: `ZUVO_API_URL` / `--api-url` (default `https://api.zuvodev.com`).
39
43
  - Hosting env keys must be `UPPER_SNAKE` (`^[A-Z][A-Z0-9_]{0,127}$`).
40
44
  - After `hosting env set|unset`, run `zuvo hosting deploy --wait` so the container picks up new vars.
41
45
  - `zuvo secrets` = project Edge Function secrets; `zuvo hosting env` = App Hosting container env.
46
+ - Custom domains: add TXT `_zuvo-hosting-challenge.<host>` + CNAME → `app.zuvodev.com` or `sin.app.zuvodev.com`, then `domains verify`. Requires plan `allow_custom_domains`.
package/dist/hosting.js CHANGED
@@ -86,3 +86,35 @@ export function formatFunctionLogLine(row) {
86
86
  ].filter(Boolean);
87
87
  return bits.join(' ');
88
88
  }
89
+ export function normalizeHostnameInput(raw) {
90
+ return String(raw || '')
91
+ .trim()
92
+ .toLowerCase()
93
+ .replace(/^https?:\/\//, '')
94
+ .replace(/\/.*$/, '')
95
+ .replace(/\.$/, '');
96
+ }
97
+ export function formatDomainDnsInstructions(domain) {
98
+ const hostname = domain.hostname || '';
99
+ const txtName = domain.challenge_name || `_zuvo-hosting-challenge.${hostname}`;
100
+ const txtValue = domain.challenge_value || domain.verification_token || '';
101
+ const cnameTarget = domain.dns_cname_target || 'app.zuvodev.com';
102
+ return [
103
+ `Hostname: ${hostname}`,
104
+ `Status: ${domain.status || 'unknown'}`,
105
+ '',
106
+ 'Add these DNS records at your registrar:',
107
+ '',
108
+ ` TXT ${txtName}`,
109
+ ` ${txtValue}`,
110
+ '',
111
+ ` CNAME ${hostname}`,
112
+ ` ${cnameTarget}`,
113
+ '',
114
+ 'Then run: zuvo hosting domains verify ' + (hostname || '<hostname>'),
115
+ ].join('\n');
116
+ }
117
+ export function formatDomainListLine(domain) {
118
+ return [domain.id || '', domain.hostname || '', domain.status || '', domain.tls_status || '']
119
+ .join('\t');
120
+ }
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { apiRequest, ApiError } from './api.js';
4
4
  import { argvAfterCommand } from './argv.js';
5
5
  import { apiUrlFromEnv, deleteAccessToken, isAccessToken, requireLinkedRef, saveLinkedRef, } from './config.js';
6
6
  import { encodeFunctionForm, listFunctionSlugs, loadFunctionBundle } from './functions.js';
7
- import { formatFunctionLogLine, formatHostingLogLine, formatLogTimestamp, functionLogsSql, hostingLogsSql, parseSince, sortLogsChronological, } from './hosting.js';
7
+ import { formatDomainDnsInstructions, formatDomainListLine, formatFunctionLogLine, formatHostingLogLine, formatLogTimestamp, functionLogsSql, hostingLogsSql, normalizeHostnameInput, parseSince, sortLogsChronological, } from './hosting.js';
8
8
  import { loginBrowser, loginWithToken } from './login.js';
9
9
  import { loadLocalMigrations, pendingMigrations } from './migrations.js';
10
10
  import { loadSecretsEnvFile, parseSecretArgs } from './secrets.js';
@@ -33,6 +33,10 @@ Commands:
33
33
  hosting env set NAME=VALUE [...] [--env-file <path>] [--app <appId|slug>]
34
34
  hosting env unset NAME [...] [--app <appId|slug>]
35
35
  hosting secrets … Alias for hosting env …
36
+ hosting domains [list] [--app <appId|slug>]
37
+ hosting domains add <hostname> [--app <appId|slug>]
38
+ hosting domains verify <hostname|id> [--app <appId|slug>]
39
+ hosting domains rm <hostname|id> [--app <appId|slug>]
36
40
 
37
41
  Global:
38
42
  --api-url <url> Default https://api.zuvodev.com (or ZUVO_API_URL)
@@ -656,6 +660,124 @@ async function cmdHostingEnv(apiUrl, argv) {
656
660
  throw new Error('Usage: zuvo hosting env list|set|unset … (alias: zuvo hosting secrets …)');
657
661
  }
658
662
  }
663
+ async function fetchHostingDomains(apiUrl, ref, appId) {
664
+ return apiRequest(apiUrl, 'GET', `/platform/projects/${ref}/hosting/apps/${appId}/domains`);
665
+ }
666
+ function resolveDomain(domains, key) {
667
+ const needle = normalizeHostnameInput(key);
668
+ const match = domains.find((d) => d.id === key || (d.hostname && normalizeHostnameInput(d.hostname) === needle));
669
+ if (!match)
670
+ throw new Error(`Domain not found: ${key}`);
671
+ return match;
672
+ }
673
+ async function cmdHostingDomainsList(apiUrl, argv) {
674
+ const { values } = parseArgs({
675
+ args: argv,
676
+ options: {
677
+ app: { type: 'string' },
678
+ 'api-url': { type: 'string' },
679
+ },
680
+ allowPositionals: true,
681
+ strict: false,
682
+ });
683
+ const ref = await requireLinkedRef();
684
+ const app = await resolveHostingApp(apiUrl, ref, typeof values.app === 'string' ? values.app : undefined);
685
+ const data = await fetchHostingDomains(apiUrl, ref, app.id);
686
+ if (data.platform_url)
687
+ console.error(`Platform URL: ${data.platform_url}`);
688
+ if (data.allow_custom_domains === false) {
689
+ console.error('Custom domains are not enabled for this organization plan.');
690
+ }
691
+ const domains = Array.isArray(data.domains) ? data.domains : [];
692
+ if (!domains.length) {
693
+ console.log('No custom domains.');
694
+ return;
695
+ }
696
+ for (const domain of domains)
697
+ console.log(formatDomainListLine(domain));
698
+ }
699
+ async function cmdHostingDomainsAdd(apiUrl, argv) {
700
+ const { values, positionals } = parseArgs({
701
+ args: argv,
702
+ options: {
703
+ app: { type: 'string' },
704
+ 'api-url': { type: 'string' },
705
+ },
706
+ allowPositionals: true,
707
+ strict: false,
708
+ });
709
+ const hostname = normalizeHostnameInput(positionals[0] || '');
710
+ if (!hostname) {
711
+ throw new Error('Usage: zuvo hosting domains add <hostname> [--app <id|slug>]');
712
+ }
713
+ const ref = await requireLinkedRef();
714
+ const app = await resolveHostingApp(apiUrl, ref, typeof values.app === 'string' ? values.app : undefined);
715
+ const data = await apiRequest(apiUrl, 'POST', `/platform/projects/${ref}/hosting/apps/${app.id}/domains`, { json: { hostname } });
716
+ console.log(formatDomainDnsInstructions(data.domain || { hostname }));
717
+ }
718
+ async function cmdHostingDomainsVerify(apiUrl, argv) {
719
+ const { values, positionals } = parseArgs({
720
+ args: argv,
721
+ options: {
722
+ app: { type: 'string' },
723
+ 'api-url': { type: 'string' },
724
+ },
725
+ allowPositionals: true,
726
+ strict: false,
727
+ });
728
+ const key = positionals[0];
729
+ if (!key) {
730
+ throw new Error('Usage: zuvo hosting domains verify <hostname|id> [--app <id|slug>]');
731
+ }
732
+ const ref = await requireLinkedRef();
733
+ const app = await resolveHostingApp(apiUrl, ref, typeof values.app === 'string' ? values.app : undefined);
734
+ const listed = await fetchHostingDomains(apiUrl, ref, app.id);
735
+ const domain = resolveDomain(listed.domains || [], key);
736
+ const data = await apiRequest(apiUrl, 'POST', `/platform/projects/${ref}/hosting/apps/${app.id}/domains/${domain.id}/verify`, { json: {} });
737
+ const updated = data.domain || domain;
738
+ console.log([updated.id, updated.hostname, updated.status, updated.tls_status || '']
739
+ .filter(Boolean)
740
+ .join('\t'));
741
+ if (updated.status && !['active', 'provisioning', 'verified'].includes(updated.status)) {
742
+ console.log(formatDomainDnsInstructions(updated));
743
+ }
744
+ }
745
+ async function cmdHostingDomainsRm(apiUrl, argv) {
746
+ const { values, positionals } = parseArgs({
747
+ args: argv,
748
+ options: {
749
+ app: { type: 'string' },
750
+ 'api-url': { type: 'string' },
751
+ },
752
+ allowPositionals: true,
753
+ strict: false,
754
+ });
755
+ const key = positionals[0];
756
+ if (!key) {
757
+ throw new Error('Usage: zuvo hosting domains rm <hostname|id> [--app <id|slug>]');
758
+ }
759
+ const ref = await requireLinkedRef();
760
+ const app = await resolveHostingApp(apiUrl, ref, typeof values.app === 'string' ? values.app : undefined);
761
+ const listed = await fetchHostingDomains(apiUrl, ref, app.id);
762
+ const domain = resolveDomain(listed.domains || [], key);
763
+ await apiRequest(apiUrl, 'DELETE', `/platform/projects/${ref}/hosting/apps/${app.id}/domains/${domain.id}`);
764
+ console.log(`Removed ${domain.hostname || domain.id}`);
765
+ }
766
+ async function cmdHostingDomains(apiUrl, argv) {
767
+ const [action, ...rest] = argv;
768
+ if (!action || action === 'list' || action.startsWith('-')) {
769
+ await cmdHostingDomainsList(apiUrl, action?.startsWith('-') ? argv : rest);
770
+ }
771
+ else if (action === 'add')
772
+ await cmdHostingDomainsAdd(apiUrl, rest);
773
+ else if (action === 'verify')
774
+ await cmdHostingDomainsVerify(apiUrl, rest);
775
+ else if (action === 'rm' || action === 'remove' || action === 'delete')
776
+ await cmdHostingDomainsRm(apiUrl, rest);
777
+ else {
778
+ throw new Error('Usage: zuvo hosting domains [list|add|verify|rm] …');
779
+ }
780
+ }
659
781
  async function main() {
660
782
  const argv = process.argv.slice(2);
661
783
  const global = parseGlobal(argv);
@@ -700,6 +822,8 @@ async function main() {
700
822
  await cmdHostingDeployLogs(apiUrl, argvAfterCommand(argv, 'hosting', sub));
701
823
  else if (command === 'hosting' && (sub === 'env' || sub === 'secrets'))
702
824
  await cmdHostingEnv(apiUrl, argvAfterCommand(argv, 'hosting', sub));
825
+ else if (command === 'hosting' && (sub === 'domains' || sub === 'domain'))
826
+ await cmdHostingDomains(apiUrl, argvAfterCommand(argv, 'hosting', sub));
703
827
  else {
704
828
  console.error(usage());
705
829
  process.exit(command ? 1 : 0);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@zuvo/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
- "description": "Zuvo CLI — login, link, functions/hosting deploy, secrets, env, db push, and logs",
5
+ "description": "Zuvo CLI — login, link, functions/hosting deploy, secrets, env, domains, db push, and logs",
6
6
  "license": "MIT",
7
7
  "bin": {
8
8
  "zuvo": "bin/zuvo.js"