@xano/cli 1.0.3 → 1.0.4-beta.1

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 (37) hide show
  1. package/README.md +69 -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 +10 -0
  5. package/dist/commands/auth/index.js +146 -9
  6. package/dist/commands/static_host/build/create/index.d.ts +9 -1
  7. package/dist/commands/static_host/build/create/index.js +54 -4
  8. package/dist/commands/static_host/build/delete/index.d.ts +19 -0
  9. package/dist/commands/static_host/build/delete/index.js +114 -0
  10. package/dist/commands/static_host/build/get/index.d.ts +1 -1
  11. package/dist/commands/static_host/build/get/index.js +16 -10
  12. package/dist/commands/static_host/build/pull/index.d.ts +52 -0
  13. package/dist/commands/static_host/build/pull/index.js +300 -0
  14. package/dist/commands/static_host/build/push/index.d.ts +23 -0
  15. package/dist/commands/static_host/build/push/index.js +225 -0
  16. package/dist/commands/static_host/create/index.d.ts +17 -0
  17. package/dist/commands/static_host/create/index.js +86 -0
  18. package/dist/commands/static_host/deploy/index.d.ts +18 -0
  19. package/dist/commands/static_host/deploy/index.js +105 -0
  20. package/dist/commands/static_host/edit/index.d.ts +23 -0
  21. package/dist/commands/static_host/edit/index.js +151 -0
  22. package/dist/commands/static_host/get/index.d.ts +18 -0
  23. package/dist/commands/static_host/get/index.js +94 -0
  24. package/dist/commands/static_host/migrate/index.d.ts +44 -0
  25. package/dist/commands/static_host/migrate/index.js +205 -0
  26. package/dist/commands/tenant/snapshot/create/index.d.ts +17 -0
  27. package/dist/commands/tenant/snapshot/create/index.js +78 -0
  28. package/dist/commands/tenant/snapshot/delete/index.d.ts +19 -0
  29. package/dist/commands/tenant/snapshot/delete/index.js +102 -0
  30. package/dist/commands/tenant/snapshot/list/index.d.ts +16 -0
  31. package/dist/commands/tenant/snapshot/list/index.js +96 -0
  32. package/dist/commands/tenant/snapshot/swap/index.d.ts +19 -0
  33. package/dist/commands/tenant/snapshot/swap/index.js +103 -0
  34. package/dist/utils/multidoc-push.js +21 -17
  35. package/dist/utils/reference-checker.js +2 -2
  36. package/oclif.manifest.json +3831 -2484
  37. package/package.json +3 -1
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantSnapshotSwap extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ snapshot: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ run(): Promise<void>;
18
+ private confirm;
19
+ }
@@ -0,0 +1,103 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as readline from 'node:readline';
3
+ import BaseCommand from '../../../../base-command.js';
4
+ export default class TenantSnapshotSwap extends BaseCommand {
5
+ static args = {
6
+ tenant_name: Args.string({
7
+ description: 'Tenant name to swap',
8
+ required: true,
9
+ }),
10
+ };
11
+ static description = "Swap a tenant's live database to a snapshot. This repoints the tenant; the current live database is left untouched, so you can swap back. To roll back, swap to the original database name.";
12
+ static examples = [
13
+ `$ xano tenant snapshot swap t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1_bk_20260603_203614
14
+ Swapped tenant t1234-abcd-xyz1 from t1234-abcd-xyz1 to t1234-abcd-xyz1_bk_20260603_203614
15
+ `,
16
+ `$ xano tenant snapshot swap t1234-abcd-xyz1 --snapshot t1234-abcd-xyz1 --force`,
17
+ ];
18
+ static flags = {
19
+ ...BaseCommand.baseFlags,
20
+ force: Flags.boolean({
21
+ char: 'f',
22
+ default: false,
23
+ description: 'Skips the confirmation prompt.',
24
+ required: false,
25
+ }),
26
+ output: Flags.string({
27
+ char: 'o',
28
+ default: 'summary',
29
+ description: 'Output format',
30
+ options: ['summary', 'json'],
31
+ required: false,
32
+ }),
33
+ snapshot: Flags.string({
34
+ description: 'Snapshot database name to swap to (use the original tenant name to roll back)',
35
+ required: true,
36
+ }),
37
+ workspace: Flags.string({
38
+ char: 'w',
39
+ description: 'Workspace ID (uses profile workspace if not provided)',
40
+ required: false,
41
+ }),
42
+ };
43
+ async run() {
44
+ const { args, flags } = await this.parse(TenantSnapshotSwap);
45
+ const { profile } = this.resolveProfile(flags);
46
+ const workspaceId = flags.workspace || profile.workspace;
47
+ if (!workspaceId) {
48
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
49
+ }
50
+ const tenantName = args.tenant_name;
51
+ const { snapshot } = flags;
52
+ if (!flags.force) {
53
+ const confirmed = await this.confirm(`Swap tenant ${tenantName} to "${snapshot}"? This repoints the tenant's live database.`);
54
+ if (!confirmed) {
55
+ this.log('Swap cancelled.');
56
+ return;
57
+ }
58
+ }
59
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/snapshot/swap`;
60
+ try {
61
+ const response = await this.verboseFetch(apiUrl, {
62
+ body: JSON.stringify({ snapshot }),
63
+ headers: {
64
+ 'accept': 'application/json',
65
+ 'Authorization': `Bearer ${profile.access_token}`,
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ method: 'POST',
69
+ }, flags.verbose, profile.access_token);
70
+ if (!response.ok) {
71
+ const errorText = await response.text();
72
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
73
+ }
74
+ const result = (await response.json());
75
+ if (flags.output === 'json') {
76
+ this.log(JSON.stringify(result, null, 2));
77
+ }
78
+ else {
79
+ this.log(`Swapped tenant ${tenantName} from ${result.from ?? 'unknown'} to ${result.to ?? snapshot}`);
80
+ }
81
+ }
82
+ catch (error) {
83
+ if (error instanceof Error) {
84
+ this.error(`Failed to swap snapshot: ${error.message}`);
85
+ }
86
+ else {
87
+ this.error(`Failed to swap snapshot: ${String(error)}`);
88
+ }
89
+ }
90
+ }
91
+ async confirm(message) {
92
+ const rl = readline.createInterface({
93
+ input: process.stdin,
94
+ output: process.stdout,
95
+ });
96
+ return new Promise((resolve) => {
97
+ rl.question(`${message} (y/N) `, (answer) => {
98
+ rl.close();
99
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
100
+ });
101
+ });
102
+ }
103
+ }
@@ -476,6 +476,7 @@ export async function executePush(ctx, target, flags) {
476
476
  }
477
477
  // Warn when the sandbox currently holds a different workspace than the one being
478
478
  // pushed and the change set is large enough that stale state is a real risk.
479
+ let mismatchConfirmed = false;
479
480
  if (target.warnOnWorkspaceMismatch && preview.workspace_name) {
480
481
  const localWorkspaceName = findLocalWorkspaceName(documentEntries);
481
482
  const totalChanges = countSummaryChanges(preview.summary, shouldDelete);
@@ -494,29 +495,32 @@ export async function executePush(ctx, target, flags) {
494
495
  log('Push cancelled. Run `xano sandbox reset` then retry.');
495
496
  return;
496
497
  }
498
+ mismatchConfirmed = true;
497
499
  }
498
500
  else {
499
501
  command.error('Workspace mismatch detected in non-interactive mode. Run `xano sandbox reset` first to start clean.');
500
502
  }
501
503
  }
502
504
  }
503
- // Confirm with user
504
- const hasDestructive = preview.operations.some((op) => (shouldDelete && (op.action === 'delete' || op.action === 'cascade_delete')) ||
505
- op.action === 'truncate' ||
506
- op.action === 'drop_field' ||
507
- op.action === 'alter_field');
508
- const message = hasDestructive
509
- ? 'Proceed with push? This includes DESTRUCTIVE operations listed above.'
510
- : 'Proceed with push?';
511
- if (process.stdin.isTTY) {
512
- const confirmed = await confirm(message);
513
- if (!confirmed) {
514
- log('Push cancelled.');
515
- return;
505
+ // Confirm with user (skip if workspace mismatch prompt already obtained confirmation)
506
+ if (!mismatchConfirmed) {
507
+ const hasDestructive = preview.operations.some((op) => (shouldDelete && (op.action === 'delete' || op.action === 'cascade_delete')) ||
508
+ op.action === 'truncate' ||
509
+ op.action === 'drop_field' ||
510
+ op.action === 'alter_field');
511
+ const message = hasDestructive
512
+ ? 'Proceed with push? This includes DESTRUCTIVE operations listed above.'
513
+ : 'Proceed with push?';
514
+ if (process.stdin.isTTY) {
515
+ const confirmed = await confirm(message);
516
+ if (!confirmed) {
517
+ log('Push cancelled.');
518
+ return;
519
+ }
520
+ }
521
+ else {
522
+ command.error('Non-interactive environment detected. Use --force to skip confirmation.');
516
523
  }
517
- }
518
- else {
519
- command.error('Non-interactive environment detected. Use --force to skip confirmation.');
520
524
  }
521
525
  }
522
526
  else {
@@ -589,7 +593,7 @@ export async function executePush(ctx, target, flags) {
589
593
  }
590
594
  catch {
591
595
  if (flags.verbose) {
592
- log('Server response is not JSON; skipping GUID sync');
596
+ log(`Server response is not JSON; skipping GUID sync\n${responseText}`);
593
597
  }
594
598
  }
595
599
  }
@@ -170,8 +170,8 @@ export function checkTableIndexes(documents) {
170
170
  return badIndexes;
171
171
  }
172
172
  function extractSchemaFields(content) {
173
- // id and created_at are auto-added during import
174
- const fields = new Set(['id', 'created_at']);
173
+ // id, created_at, and xdo are system fields not declared in the schema
174
+ const fields = new Set(['id', 'created_at', 'xdo']);
175
175
  // Find the schema block by matching braces
176
176
  const schemaStart = content.match(/\bschema\s*\{/);
177
177
  if (!schemaStart || schemaStart.index === undefined)