@xano/cli 0.0.37 → 0.0.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.
Files changed (114) hide show
  1. package/README.md +325 -102
  2. package/dist/commands/auth/index.d.ts +0 -2
  3. package/dist/commands/auth/index.js +2 -55
  4. package/dist/commands/profile/create/index.d.ts +0 -2
  5. package/dist/commands/profile/create/index.js +0 -15
  6. package/dist/commands/profile/edit/index.d.ts +0 -4
  7. package/dist/commands/profile/edit/index.js +7 -38
  8. package/dist/commands/profile/wizard/index.d.ts +0 -2
  9. package/dist/commands/profile/wizard/index.js +0 -106
  10. package/dist/commands/profile/{project → workspace}/index.d.ts +1 -1
  11. package/dist/commands/profile/{project → workspace}/index.js +10 -10
  12. package/dist/commands/release/delete/index.d.ts +2 -4
  13. package/dist/commands/release/delete/index.js +39 -12
  14. package/dist/commands/release/edit/index.d.ts +2 -4
  15. package/dist/commands/release/edit/index.js +31 -5
  16. package/dist/commands/release/export/index.d.ts +2 -4
  17. package/dist/commands/release/export/index.js +39 -11
  18. package/dist/commands/release/get/index.d.ts +2 -4
  19. package/dist/commands/release/get/index.js +31 -5
  20. package/dist/commands/release/pull/index.d.ts +31 -0
  21. package/dist/commands/release/pull/index.js +345 -0
  22. package/dist/commands/release/push/index.d.ts +26 -0
  23. package/dist/commands/release/push/index.js +230 -0
  24. package/dist/commands/tenant/backup/delete/index.d.ts +1 -1
  25. package/dist/commands/tenant/backup/delete/index.js +8 -9
  26. package/dist/commands/tenant/backup/export/index.d.ts +1 -1
  27. package/dist/commands/tenant/backup/export/index.js +9 -10
  28. package/dist/commands/tenant/backup/restore/index.d.ts +1 -1
  29. package/dist/commands/tenant/backup/restore/index.js +8 -9
  30. package/dist/commands/tenant/cluster/create/index.d.ts +18 -0
  31. package/dist/commands/tenant/cluster/create/index.js +149 -0
  32. package/dist/commands/{run/sessions/start → tenant/cluster/delete}/index.d.ts +9 -3
  33. package/dist/commands/tenant/cluster/delete/index.js +125 -0
  34. package/dist/commands/tenant/cluster/edit/index.d.ts +22 -0
  35. package/dist/commands/tenant/cluster/edit/index.js +128 -0
  36. package/dist/commands/{run/sessions → tenant/cluster}/get/index.d.ts +7 -3
  37. package/dist/commands/tenant/cluster/get/index.js +114 -0
  38. package/dist/commands/{run/info → tenant/cluster/license/get}/index.d.ts +10 -7
  39. package/dist/commands/tenant/cluster/license/get/index.js +118 -0
  40. package/dist/commands/tenant/cluster/license/set/index.d.ts +21 -0
  41. package/dist/commands/tenant/cluster/license/set/index.js +132 -0
  42. package/dist/commands/{run/env → tenant/cluster}/list/index.d.ts +3 -3
  43. package/dist/commands/tenant/cluster/list/index.js +109 -0
  44. package/dist/commands/tenant/create/index.d.ts +6 -3
  45. package/dist/commands/tenant/create/index.js +28 -20
  46. package/dist/commands/tenant/deploy_platform/index.d.ts +1 -1
  47. package/dist/commands/tenant/deploy_platform/index.js +8 -9
  48. package/dist/commands/tenant/deploy_release/index.d.ts +1 -1
  49. package/dist/commands/tenant/deploy_release/index.js +13 -13
  50. package/dist/commands/tenant/env/delete/index.d.ts +19 -0
  51. package/dist/commands/tenant/env/delete/index.js +139 -0
  52. package/dist/commands/{run/projects/create → tenant/env/get}/index.d.ts +7 -4
  53. package/dist/commands/tenant/env/get/index.js +113 -0
  54. package/dist/commands/{run/projects/update → tenant/env/get_all}/index.d.ts +7 -5
  55. package/dist/commands/tenant/env/get_all/index.js +123 -0
  56. package/dist/commands/{run/secrets/get → tenant/env/list}/index.d.ts +5 -3
  57. package/dist/commands/tenant/env/list/index.js +116 -0
  58. package/dist/commands/tenant/env/set/index.d.ts +18 -0
  59. package/dist/commands/tenant/env/set/index.js +122 -0
  60. package/dist/commands/tenant/env/set_all/index.d.ts +18 -0
  61. package/dist/commands/tenant/env/set_all/index.js +131 -0
  62. package/dist/commands/tenant/get/index.js +6 -5
  63. package/dist/commands/tenant/impersonate/index.d.ts +19 -0
  64. package/dist/commands/tenant/impersonate/index.js +146 -0
  65. package/dist/commands/tenant/license/get/index.d.ts +18 -0
  66. package/dist/commands/tenant/license/get/index.js +127 -0
  67. package/dist/commands/tenant/license/set/index.d.ts +19 -0
  68. package/dist/commands/tenant/license/set/index.js +141 -0
  69. package/dist/commands/tenant/list/index.js +6 -6
  70. package/dist/commands/tenant/pull/index.d.ts +31 -0
  71. package/dist/commands/tenant/pull/index.js +327 -0
  72. package/dist/commands/tenant/push/index.d.ts +24 -0
  73. package/dist/commands/tenant/push/index.js +245 -0
  74. package/oclif.manifest.json +2076 -1670
  75. package/package.json +1 -19
  76. package/dist/commands/run/env/delete/index.d.ts +0 -14
  77. package/dist/commands/run/env/delete/index.js +0 -65
  78. package/dist/commands/run/env/get/index.d.ts +0 -14
  79. package/dist/commands/run/env/get/index.js +0 -52
  80. package/dist/commands/run/env/list/index.js +0 -56
  81. package/dist/commands/run/env/set/index.d.ts +0 -14
  82. package/dist/commands/run/env/set/index.js +0 -51
  83. package/dist/commands/run/exec/index.d.ts +0 -31
  84. package/dist/commands/run/exec/index.js +0 -431
  85. package/dist/commands/run/info/index.js +0 -160
  86. package/dist/commands/run/projects/create/index.js +0 -75
  87. package/dist/commands/run/projects/delete/index.d.ts +0 -14
  88. package/dist/commands/run/projects/delete/index.js +0 -65
  89. package/dist/commands/run/projects/list/index.d.ts +0 -13
  90. package/dist/commands/run/projects/list/index.js +0 -66
  91. package/dist/commands/run/projects/update/index.js +0 -86
  92. package/dist/commands/run/secrets/delete/index.d.ts +0 -14
  93. package/dist/commands/run/secrets/delete/index.js +0 -65
  94. package/dist/commands/run/secrets/get/index.js +0 -52
  95. package/dist/commands/run/secrets/list/index.d.ts +0 -12
  96. package/dist/commands/run/secrets/list/index.js +0 -60
  97. package/dist/commands/run/secrets/set/index.d.ts +0 -16
  98. package/dist/commands/run/secrets/set/index.js +0 -74
  99. package/dist/commands/run/sessions/delete/index.d.ts +0 -14
  100. package/dist/commands/run/sessions/delete/index.js +0 -65
  101. package/dist/commands/run/sessions/get/index.js +0 -72
  102. package/dist/commands/run/sessions/list/index.d.ts +0 -13
  103. package/dist/commands/run/sessions/list/index.js +0 -64
  104. package/dist/commands/run/sessions/start/index.js +0 -56
  105. package/dist/commands/run/sessions/stop/index.d.ts +0 -14
  106. package/dist/commands/run/sessions/stop/index.js +0 -56
  107. package/dist/commands/run/sink/get/index.d.ts +0 -14
  108. package/dist/commands/run/sink/get/index.js +0 -63
  109. package/dist/lib/base-run-command.d.ts +0 -41
  110. package/dist/lib/base-run-command.js +0 -75
  111. package/dist/lib/run-http-client.d.ts +0 -64
  112. package/dist/lib/run-http-client.js +0 -171
  113. package/dist/lib/run-types.d.ts +0 -226
  114. package/dist/lib/run-types.js +0 -5
@@ -0,0 +1,122 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import BaseCommand from '../../../../base-command.js';
7
+ export default class TenantEnvSet extends BaseCommand {
8
+ static args = {
9
+ tenant_name: Args.string({
10
+ description: 'Tenant name',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Set (create or update) an environment variable for a tenant';
15
+ static examples = [
16
+ `$ xano tenant env set my-tenant --name DATABASE_URL --value postgres://localhost:5432/mydb
17
+ Environment variable 'DATABASE_URL' set for tenant my-tenant
18
+ `,
19
+ `$ xano tenant env set my-tenant --name DATABASE_URL --value postgres://localhost:5432/mydb -w 5 -o json`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ name: Flags.string({
24
+ char: 'n',
25
+ description: 'Environment variable name',
26
+ required: true,
27
+ }),
28
+ output: Flags.string({
29
+ char: 'o',
30
+ default: 'summary',
31
+ description: 'Output format',
32
+ options: ['summary', 'json'],
33
+ required: false,
34
+ }),
35
+ value: Flags.string({
36
+ description: 'Environment variable value',
37
+ required: true,
38
+ }),
39
+ workspace: Flags.string({
40
+ char: 'w',
41
+ description: 'Workspace ID (uses profile workspace if not provided)',
42
+ required: false,
43
+ }),
44
+ };
45
+ async run() {
46
+ const { args, flags } = await this.parse(TenantEnvSet);
47
+ const profileName = flags.profile || this.getDefaultProfile();
48
+ const credentials = this.loadCredentials();
49
+ if (!(profileName in credentials.profiles)) {
50
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
51
+ `Create a profile using 'xano profile create'`);
52
+ }
53
+ const profile = credentials.profiles[profileName];
54
+ if (!profile.instance_origin) {
55
+ this.error(`Profile '${profileName}' is missing instance_origin`);
56
+ }
57
+ if (!profile.access_token) {
58
+ this.error(`Profile '${profileName}' is missing access_token`);
59
+ }
60
+ const workspaceId = flags.workspace || profile.workspace;
61
+ if (!workspaceId) {
62
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
63
+ }
64
+ const tenantName = args.tenant_name;
65
+ const envName = flags.name;
66
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/env/${envName}`;
67
+ const body = {
68
+ env: {
69
+ name: envName,
70
+ value: flags.value,
71
+ },
72
+ };
73
+ try {
74
+ const response = await this.verboseFetch(apiUrl, {
75
+ body: JSON.stringify(body),
76
+ headers: {
77
+ accept: 'application/json',
78
+ Authorization: `Bearer ${profile.access_token}`,
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ method: 'PATCH',
82
+ }, flags.verbose, profile.access_token);
83
+ if (!response.ok) {
84
+ const errorText = await response.text();
85
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
86
+ }
87
+ if (flags.output === 'json') {
88
+ const result = await response.json();
89
+ this.log(JSON.stringify(result, null, 2));
90
+ }
91
+ else {
92
+ this.log(`Environment variable '${envName}' set for tenant ${tenantName}`);
93
+ }
94
+ }
95
+ catch (error) {
96
+ if (error instanceof Error) {
97
+ this.error(`Failed to set tenant environment variable: ${error.message}`);
98
+ }
99
+ else {
100
+ this.error(`Failed to set tenant environment variable: ${String(error)}`);
101
+ }
102
+ }
103
+ }
104
+ loadCredentials() {
105
+ const configDir = path.join(os.homedir(), '.xano');
106
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
107
+ if (!fs.existsSync(credentialsPath)) {
108
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
109
+ }
110
+ try {
111
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
112
+ const parsed = yaml.load(fileContent);
113
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
114
+ this.error('Credentials file has invalid format.');
115
+ }
116
+ return parsed;
117
+ }
118
+ catch (error) {
119
+ this.error(`Failed to parse credentials file: ${error}`);
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantEnvSetAll 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
+ clean: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: 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
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ private loadCredentials;
18
+ }
@@ -0,0 +1,131 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import BaseCommand from '../../../../base-command.js';
7
+ export default class TenantEnvSetAll extends BaseCommand {
8
+ static args = {
9
+ tenant_name: Args.string({
10
+ description: 'Tenant name',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Set all environment variables for a tenant from a YAML file (replaces all existing)';
15
+ static examples = [
16
+ `$ xano tenant env set_all my-tenant
17
+ Reads from env_my-tenant.yaml
18
+ `,
19
+ `$ xano tenant env set_all my-tenant --file ./my-env.yaml`,
20
+ `$ xano tenant env set_all my-tenant -o json`,
21
+ ];
22
+ static flags = {
23
+ ...BaseCommand.baseFlags,
24
+ clean: Flags.boolean({
25
+ default: false,
26
+ description: 'Remove the source file after successful upload',
27
+ required: false,
28
+ }),
29
+ file: Flags.string({
30
+ char: 'f',
31
+ description: 'Path to env file (default: env_<tenant_name>.yaml)',
32
+ required: false,
33
+ }),
34
+ output: Flags.string({
35
+ char: 'o',
36
+ default: 'summary',
37
+ description: 'Output format',
38
+ options: ['summary', 'json'],
39
+ required: false,
40
+ }),
41
+ workspace: Flags.string({
42
+ char: 'w',
43
+ description: 'Workspace ID (uses profile workspace if not provided)',
44
+ required: false,
45
+ }),
46
+ };
47
+ async run() {
48
+ const { args, flags } = await this.parse(TenantEnvSetAll);
49
+ const tenantName = args.tenant_name;
50
+ const sourceFilePath = path.resolve(flags.file || `env_${tenantName}.yaml`);
51
+ if (!fs.existsSync(sourceFilePath)) {
52
+ this.error(`File not found: ${sourceFilePath}`);
53
+ }
54
+ const fileContent = fs.readFileSync(sourceFilePath, 'utf8');
55
+ const envMap = yaml.load(fileContent);
56
+ if (!envMap || typeof envMap !== 'object') {
57
+ this.error('Invalid env file format. Expected a YAML map of key: value pairs.');
58
+ }
59
+ const envs = Object.entries(envMap).map(([name, value]) => ({ name, value: String(value) }));
60
+ const profileName = flags.profile || this.getDefaultProfile();
61
+ const credentials = this.loadCredentials();
62
+ if (!(profileName in credentials.profiles)) {
63
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
64
+ `Create a profile using 'xano profile create'`);
65
+ }
66
+ const profile = credentials.profiles[profileName];
67
+ if (!profile.instance_origin) {
68
+ this.error(`Profile '${profileName}' is missing instance_origin`);
69
+ }
70
+ if (!profile.access_token) {
71
+ this.error(`Profile '${profileName}' is missing access_token`);
72
+ }
73
+ const workspaceId = flags.workspace || profile.workspace;
74
+ if (!workspaceId) {
75
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
76
+ }
77
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/env_all`;
78
+ try {
79
+ const response = await this.verboseFetch(apiUrl, {
80
+ body: JSON.stringify({ envs }),
81
+ headers: {
82
+ accept: 'application/json',
83
+ Authorization: `Bearer ${profile.access_token}`,
84
+ 'Content-Type': 'application/json',
85
+ },
86
+ method: 'PUT',
87
+ }, flags.verbose, profile.access_token);
88
+ if (!response.ok) {
89
+ const errorText = await response.text();
90
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
91
+ }
92
+ if (flags.output === 'json') {
93
+ const result = await response.json();
94
+ this.log(JSON.stringify(result, null, 2));
95
+ }
96
+ else {
97
+ this.log(`All environment variables updated for tenant ${tenantName} (${envs.length} variables)`);
98
+ }
99
+ if (flags.clean && fs.existsSync(sourceFilePath)) {
100
+ fs.unlinkSync(sourceFilePath);
101
+ this.log(`Removed ${sourceFilePath}`);
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (error instanceof Error) {
106
+ this.error(`Failed to set tenant environment variables: ${error.message}`);
107
+ }
108
+ else {
109
+ this.error(`Failed to set tenant environment variables: ${String(error)}`);
110
+ }
111
+ }
112
+ }
113
+ loadCredentials() {
114
+ const configDir = path.join(os.homedir(), '.xano');
115
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
116
+ if (!fs.existsSync(credentialsPath)) {
117
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
118
+ }
119
+ try {
120
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
121
+ const parsed = yaml.load(fileContent);
122
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
123
+ this.error('Credentials file has invalid format.');
124
+ }
125
+ return parsed;
126
+ }
127
+ catch (error) {
128
+ this.error(`Failed to parse credentials file: ${error}`);
129
+ }
130
+ }
131
+ }
@@ -62,8 +62,8 @@ Tenant: My Tenant (my-tenant)
62
62
  try {
63
63
  const response = await this.verboseFetch(apiUrl, {
64
64
  headers: {
65
- 'accept': 'application/json',
66
- 'Authorization': `Bearer ${profile.access_token}`,
65
+ accept: 'application/json',
66
+ Authorization: `Bearer ${profile.access_token}`,
67
67
  },
68
68
  method: 'GET',
69
69
  }, flags.verbose, profile.access_token);
@@ -71,7 +71,7 @@ Tenant: My Tenant (my-tenant)
71
71
  const errorText = await response.text();
72
72
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
73
73
  }
74
- const tenant = await response.json();
74
+ const tenant = (await response.json());
75
75
  if (flags.output === 'json') {
76
76
  this.log(JSON.stringify(tenant, null, 2));
77
77
  }
@@ -97,6 +97,8 @@ Tenant: My Tenant (my-tenant)
97
97
  this.log(` Tasks: ${tenant.tasks}`);
98
98
  if (tenant.ingress !== undefined)
99
99
  this.log(` Ingress: ${tenant.ingress}`);
100
+ if (tenant.ephemeral)
101
+ this.log(` Ephemeral: ${tenant.ephemeral}`);
100
102
  if (tenant.deployed_at) {
101
103
  const d = new Date(tenant.deployed_at);
102
104
  const deployedDate = Number.isNaN(d.getTime()) ? tenant.deployed_at : d.toISOString().split('T')[0];
@@ -117,8 +119,7 @@ Tenant: My Tenant (my-tenant)
117
119
  const configDir = path.join(os.homedir(), '.xano');
118
120
  const credentialsPath = path.join(configDir, 'credentials.yaml');
119
121
  if (!fs.existsSync(credentialsPath)) {
120
- this.error(`Credentials file not found at ${credentialsPath}\n` +
121
- `Create a profile using 'xano profile create'`);
122
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
122
123
  }
123
124
  try {
124
125
  const fileContent = fs.readFileSync(credentialsPath, 'utf8');
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class TenantImpersonate 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
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'url-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ private getImpersonateResponse;
17
+ private getFrontendUrl;
18
+ private loadCredentials;
19
+ }
@@ -0,0 +1,146 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import open from 'open';
7
+ import BaseCommand from '../../../base-command.js';
8
+ export default class TenantImpersonate extends BaseCommand {
9
+ static args = {
10
+ tenant_name: Args.string({
11
+ description: 'Tenant name to impersonate',
12
+ required: true,
13
+ }),
14
+ };
15
+ static description = 'Impersonate a tenant and open it in the browser';
16
+ static examples = [
17
+ `$ xano tenant impersonate my-tenant
18
+ Opening browser...
19
+ Impersonation successful!
20
+ `,
21
+ `$ xano tenant impersonate my-tenant -o json`,
22
+ ];
23
+ static flags = {
24
+ ...BaseCommand.baseFlags,
25
+ output: Flags.string({
26
+ char: 'o',
27
+ default: 'summary',
28
+ description: 'Output format',
29
+ options: ['summary', 'json'],
30
+ required: false,
31
+ }),
32
+ 'url-only': Flags.boolean({
33
+ char: 'u',
34
+ default: false,
35
+ description: 'Print the URL without opening the browser',
36
+ required: false,
37
+ }),
38
+ workspace: Flags.string({
39
+ char: 'w',
40
+ description: 'Workspace ID (uses profile workspace if not provided)',
41
+ required: false,
42
+ }),
43
+ };
44
+ async run() {
45
+ const { args, flags } = await this.parse(TenantImpersonate);
46
+ const profileName = flags.profile || this.getDefaultProfile();
47
+ const credentials = this.loadCredentials();
48
+ if (!(profileName in credentials.profiles)) {
49
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
50
+ `Create a profile using 'xano auth'`);
51
+ }
52
+ const profile = credentials.profiles[profileName];
53
+ if (!profile.instance_origin) {
54
+ this.error(`Profile '${profileName}' is missing instance_origin`);
55
+ }
56
+ if (!profile.access_token) {
57
+ this.error(`Profile '${profileName}' is missing access_token`);
58
+ }
59
+ const workspaceId = flags.workspace || profile.workspace;
60
+ if (!workspaceId) {
61
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
62
+ }
63
+ const tenantName = args.tenant_name;
64
+ try {
65
+ const response = await this.getImpersonateResponse(profile, workspaceId, tenantName);
66
+ if (flags.output === 'json') {
67
+ this.log(JSON.stringify(response, null, 2));
68
+ }
69
+ else {
70
+ const frontendUrl = this.getFrontendUrl(profile.instance_origin);
71
+ const params = new URLSearchParams({
72
+ _ti: response._ti,
73
+ });
74
+ const impersonateUrl = `${frontendUrl}/tenant-impersonate?${params.toString()}`;
75
+ if (flags['url-only']) {
76
+ this.log(impersonateUrl);
77
+ }
78
+ else {
79
+ this.log('Opening browser...');
80
+ await open(impersonateUrl);
81
+ this.log('Impersonation successful!');
82
+ }
83
+ }
84
+ process.exit(0);
85
+ }
86
+ catch (error) {
87
+ if (error instanceof Error) {
88
+ this.error(`Failed to impersonate tenant: ${error.message}`);
89
+ }
90
+ else {
91
+ this.error(`Failed to impersonate tenant: ${String(error)}`);
92
+ }
93
+ }
94
+ }
95
+ async getImpersonateResponse(profile, workspaceId, tenantName) {
96
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${encodeURIComponent(tenantName)}/impersonate`;
97
+ const { verbose } = await this.parse(TenantImpersonate).then((r) => r.flags);
98
+ const response = await this.verboseFetch(apiUrl, {
99
+ headers: {
100
+ Authorization: `Bearer ${profile.access_token}`,
101
+ accept: 'application/json',
102
+ },
103
+ method: 'GET',
104
+ }, verbose, profile.access_token);
105
+ if (!response.ok) {
106
+ const errorText = await response.text();
107
+ throw new Error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
108
+ }
109
+ const result = (await response.json());
110
+ if (!result._ti) {
111
+ throw new Error('No one-time token returned from impersonate API');
112
+ }
113
+ return result;
114
+ }
115
+ getFrontendUrl(instanceOrigin) {
116
+ try {
117
+ const url = new URL(instanceOrigin);
118
+ if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
119
+ url.port = '4200';
120
+ return url.origin;
121
+ }
122
+ }
123
+ catch {
124
+ // fall through
125
+ }
126
+ return instanceOrigin;
127
+ }
128
+ loadCredentials() {
129
+ const configDir = path.join(os.homedir(), '.xano');
130
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
131
+ if (!fs.existsSync(credentialsPath)) {
132
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano auth'`);
133
+ }
134
+ try {
135
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
136
+ const parsed = yaml.load(fileContent);
137
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
138
+ this.error('Credentials file has invalid format.');
139
+ }
140
+ return parsed;
141
+ }
142
+ catch (error) {
143
+ this.error(`Failed to parse credentials file: ${error}`);
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantLicenseGet 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
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ view: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ private loadCredentials;
18
+ }
@@ -0,0 +1,127 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import BaseCommand from '../../../../base-command.js';
7
+ export default class TenantLicenseGet extends BaseCommand {
8
+ static args = {
9
+ tenant_name: Args.string({
10
+ description: 'Tenant name',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Get the license for a tenant';
15
+ static examples = [
16
+ `$ xano tenant license get my-tenant
17
+ License saved to license_my-tenant.yaml
18
+ `,
19
+ `$ xano tenant license get my-tenant --file ./my-license.yaml
20
+ License saved to my-license.yaml
21
+ `,
22
+ `$ xano tenant license get my-tenant --view`,
23
+ `$ xano tenant license get my-tenant -o json`,
24
+ ];
25
+ static flags = {
26
+ ...BaseCommand.baseFlags,
27
+ file: Flags.string({
28
+ char: 'f',
29
+ description: 'Output file path (default: license_<tenant_name>.yaml)',
30
+ required: false,
31
+ }),
32
+ output: Flags.string({
33
+ char: 'o',
34
+ default: 'summary',
35
+ description: 'Output format',
36
+ options: ['summary', 'json'],
37
+ required: false,
38
+ }),
39
+ view: Flags.boolean({
40
+ default: false,
41
+ description: 'Print license to stdout instead of saving to file',
42
+ required: false,
43
+ }),
44
+ workspace: Flags.string({
45
+ char: 'w',
46
+ description: 'Workspace ID (uses profile workspace if not provided)',
47
+ required: false,
48
+ }),
49
+ };
50
+ async run() {
51
+ const { args, flags } = await this.parse(TenantLicenseGet);
52
+ const profileName = flags.profile || this.getDefaultProfile();
53
+ const credentials = this.loadCredentials();
54
+ if (!(profileName in credentials.profiles)) {
55
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
56
+ `Create a profile using 'xano profile create'`);
57
+ }
58
+ const profile = credentials.profiles[profileName];
59
+ if (!profile.instance_origin) {
60
+ this.error(`Profile '${profileName}' is missing instance_origin`);
61
+ }
62
+ if (!profile.access_token) {
63
+ this.error(`Profile '${profileName}' is missing access_token`);
64
+ }
65
+ const workspaceId = flags.workspace || profile.workspace;
66
+ if (!workspaceId) {
67
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
68
+ }
69
+ const tenantName = args.tenant_name;
70
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/license`;
71
+ try {
72
+ const response = await this.verboseFetch(apiUrl, {
73
+ headers: {
74
+ accept: 'application/json',
75
+ Authorization: `Bearer ${profile.access_token}`,
76
+ },
77
+ method: 'GET',
78
+ }, flags.verbose, profile.access_token);
79
+ if (!response.ok) {
80
+ const errorText = await response.text();
81
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
82
+ }
83
+ const license = await response.json();
84
+ // The license is a raw YAML string — write it directly, not yaml.dump'd
85
+ const licenseContent = typeof license === 'string' ? license : JSON.stringify(license, null, 2);
86
+ if (flags.view || flags.output === 'json') {
87
+ if (flags.output === 'json') {
88
+ this.log(JSON.stringify(license, null, 2));
89
+ }
90
+ else {
91
+ this.log(licenseContent);
92
+ }
93
+ }
94
+ else {
95
+ const filePath = path.resolve(flags.file || `license_${tenantName}.yaml`);
96
+ fs.writeFileSync(filePath, licenseContent, 'utf8');
97
+ this.log(`License saved to ${filePath}`);
98
+ }
99
+ }
100
+ catch (error) {
101
+ if (error instanceof Error) {
102
+ this.error(`Failed to get tenant license: ${error.message}`);
103
+ }
104
+ else {
105
+ this.error(`Failed to get tenant license: ${String(error)}`);
106
+ }
107
+ }
108
+ }
109
+ loadCredentials() {
110
+ const configDir = path.join(os.homedir(), '.xano');
111
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
112
+ if (!fs.existsSync(credentialsPath)) {
113
+ this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
114
+ }
115
+ try {
116
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
117
+ const parsed = yaml.load(fileContent);
118
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
119
+ this.error('Credentials file has invalid format.');
120
+ }
121
+ return parsed;
122
+ }
123
+ catch (error) {
124
+ this.error(`Failed to parse credentials file: ${error}`);
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class TenantLicenseSet 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
+ clean: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ value: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ workspace: 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 loadCredentials;
19
+ }