@xano/cli 1.0.3 → 1.0.4-beta.10

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 (58) hide show
  1. package/README.md +100 -3
  2. package/dist/base-command.d.ts +27 -0
  3. package/dist/base-command.js +124 -1
  4. package/dist/commands/auth/index.d.ts +48 -1
  5. package/dist/commands/auth/index.js +259 -27
  6. package/dist/commands/function/run/index.d.ts +40 -0
  7. package/dist/commands/function/run/index.js +244 -0
  8. package/dist/commands/sandbox/pull/index.js +12 -2
  9. package/dist/commands/sandbox/push/index.d.ts +1 -1
  10. package/dist/commands/sandbox/push/index.js +17 -13
  11. package/dist/commands/static_host/build/create/index.d.ts +9 -1
  12. package/dist/commands/static_host/build/create/index.js +54 -4
  13. package/dist/commands/static_host/build/delete/index.d.ts +19 -0
  14. package/dist/commands/static_host/build/delete/index.js +114 -0
  15. package/dist/commands/static_host/build/get/index.d.ts +1 -1
  16. package/dist/commands/static_host/build/get/index.js +16 -10
  17. package/dist/commands/static_host/build/pull/index.d.ts +52 -0
  18. package/dist/commands/static_host/build/pull/index.js +300 -0
  19. package/dist/commands/static_host/build/push/index.d.ts +23 -0
  20. package/dist/commands/static_host/build/push/index.js +228 -0
  21. package/dist/commands/static_host/create/index.d.ts +17 -0
  22. package/dist/commands/static_host/create/index.js +86 -0
  23. package/dist/commands/static_host/deploy/index.d.ts +18 -0
  24. package/dist/commands/static_host/deploy/index.js +105 -0
  25. package/dist/commands/static_host/edit/index.d.ts +23 -0
  26. package/dist/commands/static_host/edit/index.js +151 -0
  27. package/dist/commands/static_host/get/index.d.ts +18 -0
  28. package/dist/commands/static_host/get/index.js +94 -0
  29. package/dist/commands/static_host/migrate/index.d.ts +44 -0
  30. package/dist/commands/static_host/migrate/index.js +205 -0
  31. package/dist/commands/tenant/snapshot/create/index.d.ts +17 -0
  32. package/dist/commands/tenant/snapshot/create/index.js +78 -0
  33. package/dist/commands/tenant/snapshot/delete/index.d.ts +19 -0
  34. package/dist/commands/tenant/snapshot/delete/index.js +102 -0
  35. package/dist/commands/tenant/snapshot/list/index.d.ts +16 -0
  36. package/dist/commands/tenant/snapshot/list/index.js +96 -0
  37. package/dist/commands/tenant/snapshot/swap/index.d.ts +19 -0
  38. package/dist/commands/tenant/snapshot/swap/index.js +103 -0
  39. package/dist/commands/tenant/unit_test/run/index.d.ts +1 -0
  40. package/dist/commands/tenant/unit_test/run/index.js +7 -0
  41. package/dist/commands/tenant/unit_test/run_all/index.js +7 -3
  42. package/dist/commands/unit_test/run/index.d.ts +1 -0
  43. package/dist/commands/unit_test/run/index.js +8 -0
  44. package/dist/commands/unit_test/run_all/index.js +7 -3
  45. package/dist/commands/workspace/pull/index.js +13 -2
  46. package/dist/commands/workspace/push/index.d.ts +1 -1
  47. package/dist/commands/workspace/push/index.js +15 -5
  48. package/dist/utils/function-input.d.ts +77 -0
  49. package/dist/utils/function-input.js +148 -0
  50. package/dist/utils/knowledge-sync.d.ts +113 -0
  51. package/dist/utils/knowledge-sync.js +404 -0
  52. package/dist/utils/multidoc-push.d.ts +22 -0
  53. package/dist/utils/multidoc-push.js +262 -107
  54. package/dist/utils/reference-checker.js +2 -2
  55. package/dist/utils/static-host-files.d.ts +21 -0
  56. package/dist/utils/static-host-files.js +41 -0
  57. package/oclif.manifest.json +4179 -2636
  58. package/package.json +4 -1
package/README.md CHANGED
@@ -46,8 +46,42 @@ These warnings are layer 1 of broader push-safety work; ephemeral sandbox enviro
46
46
  xano auth
47
47
  xano auth --origin https://custom.xano.com
48
48
  xano auth --insecure # Skip TLS verification (self-signed certs)
49
+ xano auth --no-browser # Headless login (no local callback server)
50
+
51
+ # Pre-select instance/workspace/branch and profile name (skips the pickers)
52
+ xano auth -i my-instance -w 5 -b dev -p staging
53
+ xano auth --instance my-instance --workspace "My Workspace" --branch dev --profile staging
54
+
55
+ # Pass "" to take a picker's default: skip workspace, use live branch, default profile name
56
+ xano auth -i my-instance -w 5 -b "" -p ""
57
+ ```
58
+
59
+ The default flow starts a temporary callback server on `127.0.0.1` and waits
60
+ for the browser to redirect back to it. On remote/SSH sessions, Docker
61
+ containers, or locked-down networks where the browser can't reach the CLI's
62
+ loopback address, use `--no-browser`: the CLI prints a login URL, you open it
63
+ in any browser, and paste back the code it displays. No local server required.
64
+
65
+ Each picker can be pre-answered with a flag: `-i/--instance` (instance name, or numeric instance ID),
66
+ `-w/--workspace` (workspace ID or name), `-b/--branch` (branch label), and
67
+ `-p/--profile` (profile name to save). An empty value (`""`) takes the
68
+ picker's default answer: `-w ""` skips workspace selection, `-b ""` skips and
69
+ uses the live branch, and `-p ""` uses the default profile name. With all four
70
+ set alongside `--no-browser`, the only input is pasting the code from the
71
+ browser — useful for scripted or remote setups.
72
+
73
+ When stdin is piped (not a TTY), `--no-browser` reads the code directly from
74
+ stdin instead of prompting, so scripts and AI agents can complete the flow
75
+ without an interactive terminal:
76
+
77
+ ```bash
78
+ echo "$CODE" | xano auth --no-browser -i my-instance -w 5 -b dev -p staging
49
79
  ```
50
80
 
81
+ If you can't run `xano auth` at all, you can always create a profile manually
82
+ with a Metadata API token from the Xano dashboard — see
83
+ [Profiles](#profiles) below.
84
+
51
85
  ### Profiles
52
86
 
53
87
  Profiles store your Xano credentials and default workspace settings.
@@ -221,8 +255,35 @@ xano function edit <function_id> -f new.xs # Update from file
221
255
  xano function edit <function_id> -f new.xs --edit # Open in $EDITOR before updating
222
256
  cat function.xs | xano function edit <function_id> --stdin # Update from stdin
223
257
  xano function edit <function_id> --no-publish # Edit without publishing
258
+
259
+ # Run (execute) a function by name
260
+ xano function run <name> # Prompts for declared inputs (on a TTY)
261
+ xano function run <name> --data email=jo@x.com --data age:=30 --data active:=true
262
+ xano function run <name> --json @payload.json --data env=staging # base payload + override
263
+ echo '{"email":"jo@x.com"}' | xano function run <name> --stdin -o json | jq .result
264
+ xano function run <name> --branch dev --logs # run on a branch, show execution logs
224
265
  ```
225
266
 
267
+ Input flexibility for `function run` (assembled into one JSON `input` object):
268
+
269
+ | Form | Meaning |
270
+ | --- | --- |
271
+ | `--data key=value` | string field |
272
+ | `--data key:=<json>` | raw JSON field (`age:=30`, `active:=true`, `tags:='["a","b"]'`) |
273
+ | `--data key@file` | field value read from a file |
274
+ | `--json '<inline>'` / `--json @file.json` / `--json -` | a base JSON object (stdin with `-`) |
275
+ | `--stdin` | read the JSON object from stdin (same as `--json -`) |
276
+
277
+ Merge order is JSON base first, then `--data` overrides. Missing required inputs are
278
+ prompted for on an interactive terminal; in a non-TTY (CI) context the command fails
279
+ listing them. Output defaults to the raw `result` (`-o json`); the exit code is non-zero
280
+ when the function returns an error status.
281
+
282
+ > **Permissions:** running a function requires **Function** (read) plus the **run/debug**
283
+ > action on your workspace role. If your access token's role lacks run/debug, the call is
284
+ > denied with an access error — grant run/debug to the role (or use a token whose role has
285
+ > it). `function list`/`get`/`create`/`edit` only need the Function permission.
286
+
226
287
  ### Releases
227
288
 
228
289
  All release commands use **release names** (e.g., `v1.0`), not IDs.
@@ -292,6 +353,7 @@ xano unit_test list --branch dev --obj-type function
292
353
 
293
354
  # Run a single unit test
294
355
  xano unit_test run <unit_test_id>
356
+ xano unit_test run <unit_test_id> --branch dev # run the test from a specific branch
295
357
 
296
358
  # Run all unit tests
297
359
  xano unit_test run_all
@@ -522,14 +584,49 @@ xano sandbox reset --force
522
584
  # List static hosts
523
585
  xano static_host list
524
586
 
525
- # Create a build
526
- xano static_host build create default -f ./build.zip -n "v1.0.0"
587
+ # Create / get / edit a static host
588
+ xano static_host create marketing --description "Marketing site"
589
+ xano static_host get marketing
590
+ xano static_host edit marketing --name marketing-v2 --description "Updated"
527
591
 
528
592
  # List builds
529
593
  xano static_host build list default
530
594
 
531
595
  # Get build details
532
- xano static_host build get default 52
596
+ xano static_host build get default --build_id 52
597
+
598
+ # Pull a build to disk. Defaults to the original uploaded source
599
+ # (including package.json). Use --source built for the compiled/served output.
600
+ xano static_host build pull default --build_id 52 # By build ID (original source)
601
+ xano static_host build pull default --build_id 52 --source built # Compiled output
602
+ xano static_host build pull default --latest # Latest build
603
+ xano static_host build pull default --env dev # Build currently deployed to dev
604
+ xano static_host build pull default --env prod -d ./prod-release
605
+
606
+ # Push a build (name optional — auto-generated from the timestamp if omitted).
607
+ # Accepts a directory (-d) or a zip file (-f). Defaults to the current directory.
608
+ # When pushing a directory, files matched by its .gitignore are skipped by default
609
+ # (the .git/ folder is always excluded); use --no-gitignore to push everything.
610
+ # For package.json builds, the CLI waits for the build to finish (--no-wait to skip).
611
+ xano static_host build push default -d ./dist -n "v1.0.0"
612
+ xano static_host build push default # current dir, auto-name
613
+ xano static_host build push default -f ./build.zip -n "v1.0.0" # from zip file
614
+ xano static_host build push default -n "release" --description "Production build"
615
+ xano static_host build push default -d ./static --no-gitignore # push gitignored files too
616
+
617
+ # Delete a build (prompts for confirmation; --force to skip)
618
+ xano static_host build delete default --build_id 52
619
+ xano static_host build delete default --build_id 52 --force
620
+
621
+ # Deploy a build to an environment
622
+ xano static_host deploy default --build_id 52 --env dev
623
+ xano static_host deploy default --build_id 52 --env prod
624
+
625
+ # Migrate a host to instance-managed (v2) hosting
626
+ xano static_host migrate newsite # one host (both envs)
627
+ xano static_host migrate newsite --env dev # one env
628
+ xano static_host migrate --all # every v1 host in the workspace
629
+ xano static_host migrate --all --dry-run # preview without changing anything
533
630
  ```
534
631
 
535
632
  ## Global Options
@@ -103,4 +103,31 @@ export default abstract class BaseCommand extends Command {
103
103
  * Use this for all Metadata API calls to support the --verbose flag.
104
104
  */
105
105
  protected verboseFetch(url: string, options: RequestInit, verbose: boolean, authToken?: string): Promise<Response>;
106
+ /**
107
+ * Poll a static-host build until it reaches a terminal status (ok | error),
108
+ * showing a live, ticking spinner with the current stage and elapsed time —
109
+ * mirroring the UI's build progress for async (package.json) builds, which
110
+ * keep running after the upload returns.
111
+ *
112
+ * On a TTY it renders an animated spinner via ux.action; when quiet (JSON
113
+ * output) or non-interactive it falls back to plain one-line status updates.
114
+ *
115
+ * Returns the final status. Resolves to the last-seen status on timeout.
116
+ */
117
+ protected logStaticHostUrls(opts: {
118
+ profile: ProfileConfig;
119
+ staticHost: string;
120
+ verbose: boolean;
121
+ workspaceId: string;
122
+ }): Promise<void>;
123
+ protected waitForBuild(opts: {
124
+ buildId: number | string;
125
+ intervalMs?: number;
126
+ profile: ProfileConfig;
127
+ quiet?: boolean;
128
+ staticHost: string;
129
+ timeoutMs?: number;
130
+ verbose: boolean;
131
+ workspaceId: string;
132
+ }): Promise<string>;
106
133
  }
@@ -1,4 +1,4 @@
1
- import { Command, Flags } from '@oclif/core';
1
+ import { Command, Flags, ux } from '@oclif/core';
2
2
  import * as yaml from 'js-yaml';
3
3
  import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
@@ -358,4 +358,127 @@ export default class BaseCommand extends Command {
358
358
  }
359
359
  return response;
360
360
  }
361
+ /**
362
+ * Poll a static-host build until it reaches a terminal status (ok | error),
363
+ * showing a live, ticking spinner with the current stage and elapsed time —
364
+ * mirroring the UI's build progress for async (package.json) builds, which
365
+ * keep running after the upload returns.
366
+ *
367
+ * On a TTY it renders an animated spinner via ux.action; when quiet (JSON
368
+ * output) or non-interactive it falls back to plain one-line status updates.
369
+ *
370
+ * Returns the final status. Resolves to the last-seen status on timeout.
371
+ */
372
+ async logStaticHostUrls(opts) {
373
+ const { profile, staticHost, verbose, workspaceId } = opts;
374
+ const url = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}`;
375
+ try {
376
+ const response = await this.verboseFetch(url, {
377
+ headers: { accept: 'application/json', Authorization: `Bearer ${profile.access_token}` },
378
+ method: 'GET',
379
+ }, verbose, profile.access_token);
380
+ if (!response.ok)
381
+ return;
382
+ const host = (await response.json());
383
+ if (host.dev?.default_url)
384
+ this.log(`Dev URL: ${host.dev.default_url}`);
385
+ if (host.dev?.custom_url)
386
+ this.log(`Dev Custom URL: ${host.dev.custom_url}`);
387
+ if (host.prod?.default_url)
388
+ this.log(`Prod URL: ${host.prod.default_url}`);
389
+ if (host.prod?.custom_url)
390
+ this.log(`Prod Custom URL: ${host.prod.custom_url}`);
391
+ }
392
+ catch {
393
+ // Non-fatal — the build succeeded, we just can't show the URL.
394
+ }
395
+ }
396
+ async waitForBuild(opts) {
397
+ const { buildId, profile, quiet, staticHost, verbose, workspaceId } = opts;
398
+ const intervalMs = opts.intervalMs ?? 2000;
399
+ const timeoutMs = opts.timeoutMs ?? 600_000; // 10 min
400
+ const terminal = new Set(['error', 'ok']);
401
+ const url = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/static_host/${staticHost}/build/${buildId}`;
402
+ // Spinner only on an interactive TTY and when not emitting JSON. Verbose mode
403
+ // also disables it (the spinner would interleave with request/response logs).
404
+ const animate = Boolean(process.stdout.isTTY) && !quiet && !verbose;
405
+ const start = Date.now();
406
+ const elapsed = () => Math.round((Date.now() - start) / 1000);
407
+ let stage = 'pending';
408
+ let ticker;
409
+ // Reflect the current stage: live spinner status on a TTY, else a plain line.
410
+ const render = () => {
411
+ if (animate) {
412
+ ux.action.status = `${stageLabel(stage)} (${elapsed()}s)`;
413
+ }
414
+ else if (!quiet && !terminal.has(stage)) {
415
+ this.log(`Build status: ${stage}`);
416
+ }
417
+ };
418
+ // Stop the spinner/ticker and emit a final line.
419
+ const conclude = (message) => {
420
+ if (ticker)
421
+ clearInterval(ticker);
422
+ if (animate) {
423
+ ux.action.stop(message);
424
+ }
425
+ else if (!quiet) {
426
+ this.log(message);
427
+ }
428
+ };
429
+ if (animate) {
430
+ ux.action.start('Building', stageLabel(stage));
431
+ // Re-render every 120ms so the elapsed counter ticks even between polls.
432
+ ticker = setInterval(render, 120);
433
+ }
434
+ else if (!quiet) {
435
+ this.log(`Build status: ${stage}`);
436
+ }
437
+ /* eslint-disable no-await-in-loop */
438
+ while (Date.now() - start < timeoutMs) {
439
+ const response = await this.verboseFetch(url, {
440
+ headers: { accept: 'application/json', Authorization: `Bearer ${profile.access_token}` },
441
+ method: 'GET',
442
+ }, verbose, profile.access_token);
443
+ if (response.ok) {
444
+ const build = (await response.json());
445
+ const status = build.status ?? 'pending';
446
+ if (status !== stage) {
447
+ stage = status;
448
+ render();
449
+ }
450
+ if (terminal.has(status)) {
451
+ const took = `${elapsed()}s`;
452
+ conclude(status === 'ok' ? `done in ${took}` : `failed after ${took}`);
453
+ return status;
454
+ }
455
+ }
456
+ await new Promise((resolve) => {
457
+ setTimeout(resolve, intervalMs);
458
+ });
459
+ }
460
+ /* eslint-enable no-await-in-loop */
461
+ conclude(`stopped waiting after ${Math.round(timeoutMs / 1000)}s (last status: ${stage || 'unknown'})`);
462
+ return stage;
463
+ }
464
+ }
465
+ /** Human-friendly label for a build status stage. */
466
+ function stageLabel(status) {
467
+ switch (status) {
468
+ case 'building': {
469
+ return 'Installing & building (npm)';
470
+ }
471
+ case 'ok': {
472
+ return 'Finishing';
473
+ }
474
+ case 'pending': {
475
+ return 'Queued';
476
+ }
477
+ case 'publishing': {
478
+ return 'Publishing files';
479
+ }
480
+ default: {
481
+ return status;
482
+ }
483
+ }
361
484
  }
@@ -1,18 +1,65 @@
1
1
  import { Command } from '@oclif/core';
2
+ export interface Instance {
3
+ display: string;
4
+ id: string;
5
+ name: string;
6
+ origin: string;
7
+ }
8
+ export interface AuthResult {
9
+ branch: null | string;
10
+ credentialsPath: string;
11
+ instance: {
12
+ id: string;
13
+ name: string;
14
+ origin: string;
15
+ };
16
+ profile: string;
17
+ user: {
18
+ email: string;
19
+ id: string;
20
+ name: string;
21
+ };
22
+ workspace: null | {
23
+ id: string;
24
+ name: string;
25
+ };
26
+ }
27
+ /**
28
+ * Match a user-supplied --instance value against the instance list:
29
+ * numeric values match by ID, URL/hostname values match by the instance
30
+ * origin's hostname, anything else matches by name. Exported for tests.
31
+ */
32
+ export declare function matchInstance(instances: Instance[], query: string): Instance | undefined;
2
33
  export default class Auth extends Command {
3
34
  static description: string;
35
+ static enableJsonFlag: boolean;
4
36
  static examples: string[];
5
37
  static flags: {
38
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
39
+ code: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
40
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
41
  insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
42
+ instance: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
43
+ 'no-browser': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
44
  origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
45
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
46
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
47
  };
10
- run(): Promise<void>;
48
+ run(): Promise<AuthResult>;
49
+ protected toErrorJson(err: unknown): unknown;
50
+ private acquireToken;
11
51
  private getHeaders;
12
52
  private fetchBranches;
13
53
  private fetchInstances;
14
54
  private fetchWorkspaces;
55
+ private promptForToken;
15
56
  private promptProfileName;
57
+ private readTokenFromStdin;
58
+ private resolveBranch;
59
+ private resolveInstance;
60
+ private resolveProfileName;
61
+ private resolveWorkspace;
62
+ private resolveWorkspaceAndBranch;
16
63
  private saveProfile;
17
64
  private selectBranch;
18
65
  private selectInstance;