@xano/cli 0.0.26 → 0.0.27

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 (110) hide show
  1. package/dist/base-command.d.ts +3 -1
  2. package/dist/base-command.js +12 -5
  3. package/dist/commands/auth/index.d.ts +21 -0
  4. package/dist/commands/auth/index.js +533 -0
  5. package/dist/commands/branch/create/index.d.ts +17 -0
  6. package/dist/commands/branch/create/index.js +164 -0
  7. package/dist/commands/branch/delete/index.d.ts +18 -0
  8. package/dist/commands/branch/delete/index.js +156 -0
  9. package/dist/commands/branch/edit/index.d.ts +19 -0
  10. package/dist/commands/branch/edit/index.js +166 -0
  11. package/dist/commands/branch/get/index.d.ts +16 -0
  12. package/dist/commands/branch/get/index.js +135 -0
  13. package/dist/commands/branch/list/index.d.ts +18 -0
  14. package/dist/commands/branch/list/index.js +138 -0
  15. package/dist/commands/branch/set-live/index.d.ts +18 -0
  16. package/dist/commands/branch/set-live/index.js +155 -0
  17. package/dist/commands/function/create/index.d.ts +7 -6
  18. package/dist/commands/function/create/index.js +55 -55
  19. package/dist/commands/function/edit/index.d.ts +11 -10
  20. package/dist/commands/function/edit/index.js +155 -162
  21. package/dist/commands/function/get/index.d.ts +6 -5
  22. package/dist/commands/function/get/index.js +55 -60
  23. package/dist/commands/function/list/index.d.ts +6 -5
  24. package/dist/commands/function/list/index.js +52 -52
  25. package/dist/commands/profile/create/index.d.ts +6 -6
  26. package/dist/commands/profile/create/index.js +37 -37
  27. package/dist/commands/profile/delete/index.d.ts +2 -2
  28. package/dist/commands/profile/delete/index.js +9 -9
  29. package/dist/commands/profile/edit/index.d.ts +8 -7
  30. package/dist/commands/profile/edit/index.js +48 -48
  31. package/dist/commands/profile/get-default/index.js +1 -1
  32. package/dist/commands/profile/list/index.d.ts +2 -2
  33. package/dist/commands/profile/list/index.js +9 -9
  34. package/dist/commands/profile/me/index.d.ts +4 -3
  35. package/dist/commands/profile/me/index.js +21 -21
  36. package/dist/commands/profile/project/index.js +1 -1
  37. package/dist/commands/profile/set-default/index.js +1 -1
  38. package/dist/commands/profile/token/index.js +1 -1
  39. package/dist/commands/profile/wizard/index.d.ts +5 -4
  40. package/dist/commands/profile/wizard/index.js +142 -108
  41. package/dist/commands/run/env/delete/index.d.ts +3 -2
  42. package/dist/commands/run/env/delete/index.js +10 -10
  43. package/dist/commands/run/env/get/index.d.ts +3 -2
  44. package/dist/commands/run/env/get/index.js +11 -11
  45. package/dist/commands/run/env/list/index.d.ts +3 -2
  46. package/dist/commands/run/env/list/index.js +17 -19
  47. package/dist/commands/run/env/set/index.d.ts +3 -2
  48. package/dist/commands/run/env/set/index.js +5 -5
  49. package/dist/commands/run/exec/index.d.ts +19 -8
  50. package/dist/commands/run/exec/index.js +186 -108
  51. package/dist/commands/run/info/index.d.ts +5 -4
  52. package/dist/commands/run/info/index.js +27 -27
  53. package/dist/commands/run/projects/create/index.d.ts +4 -3
  54. package/dist/commands/run/projects/create/index.js +23 -23
  55. package/dist/commands/run/projects/delete/index.d.ts +3 -2
  56. package/dist/commands/run/projects/delete/index.js +10 -10
  57. package/dist/commands/run/projects/list/index.d.ts +3 -2
  58. package/dist/commands/run/projects/list/index.js +12 -12
  59. package/dist/commands/run/projects/update/index.d.ts +4 -3
  60. package/dist/commands/run/projects/update/index.js +21 -21
  61. package/dist/commands/run/secrets/delete/index.d.ts +3 -2
  62. package/dist/commands/run/secrets/delete/index.js +10 -10
  63. package/dist/commands/run/secrets/get/index.d.ts +3 -2
  64. package/dist/commands/run/secrets/get/index.js +11 -11
  65. package/dist/commands/run/secrets/list/index.d.ts +3 -2
  66. package/dist/commands/run/secrets/list/index.js +22 -24
  67. package/dist/commands/run/secrets/set/index.d.ts +4 -3
  68. package/dist/commands/run/secrets/set/index.js +16 -16
  69. package/dist/commands/run/sessions/delete/index.d.ts +3 -2
  70. package/dist/commands/run/sessions/delete/index.js +10 -10
  71. package/dist/commands/run/sessions/get/index.d.ts +3 -2
  72. package/dist/commands/run/sessions/get/index.js +11 -11
  73. package/dist/commands/run/sessions/list/index.d.ts +3 -2
  74. package/dist/commands/run/sessions/list/index.js +11 -11
  75. package/dist/commands/run/sessions/start/index.d.ts +3 -2
  76. package/dist/commands/run/sessions/start/index.js +11 -11
  77. package/dist/commands/run/sessions/stop/index.d.ts +3 -2
  78. package/dist/commands/run/sessions/stop/index.js +11 -11
  79. package/dist/commands/run/sink/get/index.d.ts +3 -2
  80. package/dist/commands/run/sink/get/index.js +11 -11
  81. package/dist/commands/static_host/build/create/index.d.ts +5 -4
  82. package/dist/commands/static_host/build/create/index.js +33 -33
  83. package/dist/commands/static_host/build/get/index.d.ts +5 -4
  84. package/dist/commands/static_host/build/get/index.js +20 -20
  85. package/dist/commands/static_host/build/list/index.d.ts +4 -3
  86. package/dist/commands/static_host/build/list/index.js +31 -31
  87. package/dist/commands/static_host/list/index.d.ts +4 -3
  88. package/dist/commands/static_host/list/index.js +31 -31
  89. package/dist/commands/workspace/create/index.d.ts +14 -0
  90. package/dist/commands/workspace/create/index.js +131 -0
  91. package/dist/commands/workspace/delete/index.d.ts +20 -0
  92. package/dist/commands/workspace/delete/index.js +141 -0
  93. package/dist/commands/workspace/edit/index.d.ts +22 -0
  94. package/dist/commands/workspace/edit/index.js +176 -0
  95. package/dist/commands/workspace/get/index.d.ts +18 -0
  96. package/dist/commands/workspace/get/index.js +136 -0
  97. package/dist/commands/workspace/list/index.d.ts +3 -2
  98. package/dist/commands/workspace/list/index.js +15 -15
  99. package/dist/commands/workspace/pull/index.d.ts +5 -4
  100. package/dist/commands/workspace/pull/index.js +113 -64
  101. package/dist/commands/workspace/push/index.d.ts +1 -0
  102. package/dist/commands/workspace/push/index.js +5 -5
  103. package/dist/help.d.ts +1 -1
  104. package/dist/lib/base-run-command.d.ts +6 -6
  105. package/dist/lib/base-run-command.js +8 -6
  106. package/dist/lib/run-http-client.d.ts +24 -18
  107. package/dist/lib/run-http-client.js +96 -61
  108. package/dist/lib/run-types.d.ts +80 -80
  109. package/oclif.manifest.json +1963 -785
  110. package/package.json +1 -1
@@ -0,0 +1,136 @@
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 WorkspaceGet extends BaseCommand {
8
+ static args = {
9
+ workspace_id: Args.integer({
10
+ description: 'Workspace ID to get details for (uses profile workspace if not provided)',
11
+ required: false,
12
+ }),
13
+ };
14
+ static description = 'Get details of a specific workspace from the Xano Metadata API';
15
+ static examples = [
16
+ `$ xano workspace get 123
17
+ Workspace: my-workspace (ID: 123)
18
+ Description: My workspace description
19
+ Created: 2024-01-15
20
+ `,
21
+ `$ xano workspace get --output json
22
+ {
23
+ "id": 123,
24
+ "name": "my-workspace",
25
+ "description": "My workspace description"
26
+ }
27
+ `,
28
+ `$ xano workspace get 456 -p production -o json
29
+ {
30
+ "id": 456,
31
+ "name": "production-workspace"
32
+ }
33
+ `,
34
+ ];
35
+ static flags = {
36
+ ...BaseCommand.baseFlags,
37
+ output: Flags.string({
38
+ char: 'o',
39
+ default: 'summary',
40
+ description: 'Output format',
41
+ options: ['summary', 'json'],
42
+ required: false,
43
+ }),
44
+ };
45
+ async run() {
46
+ const { args, flags } = await this.parse(WorkspaceGet);
47
+ // Get profile name (default or from flag/env)
48
+ const profileName = flags.profile || this.getDefaultProfile();
49
+ // Load credentials
50
+ const credentials = this.loadCredentials();
51
+ // Get the profile configuration
52
+ if (!(profileName in credentials.profiles)) {
53
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
54
+ `Create a profile using 'xano profile create'`);
55
+ }
56
+ const profile = credentials.profiles[profileName];
57
+ // Validate required fields
58
+ if (!profile.instance_origin) {
59
+ this.error(`Profile '${profileName}' is missing instance_origin`);
60
+ }
61
+ if (!profile.access_token) {
62
+ this.error(`Profile '${profileName}' is missing access_token`);
63
+ }
64
+ // Get workspace ID from args or profile
65
+ const workspaceId = args.workspace_id || profile.workspace;
66
+ if (!workspaceId) {
67
+ this.error('No workspace ID provided. Either pass a workspace ID as an argument or set one in your profile.\n' +
68
+ 'Usage: xano workspace get <workspace_id>');
69
+ }
70
+ // Construct the API URL
71
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}`;
72
+ // Fetch workspace from the API
73
+ try {
74
+ const response = await fetch(apiUrl, {
75
+ headers: {
76
+ 'accept': 'application/json',
77
+ 'Authorization': `Bearer ${profile.access_token}`,
78
+ },
79
+ method: 'GET',
80
+ });
81
+ if (!response.ok) {
82
+ const errorText = await response.text();
83
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
84
+ }
85
+ const workspace = await response.json();
86
+ // Output results
87
+ if (flags.output === 'json') {
88
+ this.log(JSON.stringify(workspace, null, 2));
89
+ }
90
+ else {
91
+ // summary format
92
+ this.log(`Workspace: ${workspace.name} (ID: ${workspace.id})`);
93
+ if (workspace.description) {
94
+ this.log(` Description: ${workspace.description}`);
95
+ }
96
+ if (workspace.created_at) {
97
+ const createdDate = new Date(workspace.created_at * 1000).toISOString().split('T')[0];
98
+ this.log(` Created: ${createdDate}`);
99
+ }
100
+ if (workspace.updated_at) {
101
+ const updatedDate = new Date(workspace.updated_at * 1000).toISOString().split('T')[0];
102
+ this.log(` Updated: ${updatedDate}`);
103
+ }
104
+ }
105
+ }
106
+ catch (error) {
107
+ if (error instanceof Error) {
108
+ this.error(`Failed to fetch workspace: ${error.message}`);
109
+ }
110
+ else {
111
+ this.error(`Failed to fetch workspace: ${String(error)}`);
112
+ }
113
+ }
114
+ }
115
+ loadCredentials() {
116
+ const configDir = path.join(os.homedir(), '.xano');
117
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
118
+ // Check if credentials file exists
119
+ if (!fs.existsSync(credentialsPath)) {
120
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
121
+ `Create a profile using 'xano profile create'`);
122
+ }
123
+ // Read credentials file
124
+ try {
125
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
126
+ const parsed = yaml.load(fileContent);
127
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
128
+ this.error('Credentials file has invalid format.');
129
+ }
130
+ return parsed;
131
+ }
132
+ catch (error) {
133
+ this.error(`Failed to parse credentials file: ${error}`);
134
+ }
135
+ }
136
+ }
@@ -1,11 +1,12 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
2
  export default class WorkspaceList extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
3
5
  static flags: {
4
6
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
5
7
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
9
  };
7
- static description: string;
8
- static examples: string[];
9
10
  run(): Promise<void>;
10
11
  private loadCredentials;
11
12
  }
@@ -1,20 +1,10 @@
1
1
  import { Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
2
3
  import * as fs from 'node:fs';
3
4
  import * as os from 'node:os';
4
5
  import * as path from 'node:path';
5
- import * as yaml from 'js-yaml';
6
6
  import BaseCommand from '../../../base-command.js';
7
7
  export default class WorkspaceList extends BaseCommand {
8
- static flags = {
9
- ...BaseCommand.baseFlags,
10
- output: Flags.string({
11
- char: 'o',
12
- description: 'Output format',
13
- required: false,
14
- default: 'summary',
15
- options: ['summary', 'json'],
16
- }),
17
- };
18
8
  static description = 'List all workspaces from the Xano Metadata API';
19
9
  static examples = [
20
10
  `$ xano workspace:list
@@ -53,6 +43,16 @@ Available workspaces:
53
43
  }
54
44
  `,
55
45
  ];
46
+ static flags = {
47
+ ...BaseCommand.baseFlags,
48
+ output: Flags.string({
49
+ char: 'o',
50
+ default: 'summary',
51
+ description: 'Output format',
52
+ options: ['summary', 'json'],
53
+ required: false,
54
+ }),
55
+ };
56
56
  async run() {
57
57
  const { flags } = await this.parse(WorkspaceList);
58
58
  // Get profile name (default or from flag/env)
@@ -77,11 +77,11 @@ Available workspaces:
77
77
  // Fetch workspaces from the API
78
78
  try {
79
79
  const response = await fetch(apiUrl, {
80
- method: 'GET',
81
80
  headers: {
82
81
  'accept': 'application/json',
83
82
  'Authorization': `Bearer ${profile.access_token}`,
84
83
  },
84
+ method: 'GET',
85
85
  });
86
86
  if (!response.ok) {
87
87
  const errorText = await response.text();
@@ -111,11 +111,11 @@ Available workspaces:
111
111
  else {
112
112
  this.log('Available workspaces:');
113
113
  for (const workspace of workspaces) {
114
- if (workspace.id !== undefined) {
115
- this.log(` - ${workspace.name} (ID: ${workspace.id})`);
114
+ if (workspace.id === undefined) {
115
+ this.log(` - ${workspace.name}`);
116
116
  }
117
117
  else {
118
- this.log(` - ${workspace.name}`);
118
+ this.log(` - ${workspace.name} (ID: ${workspace.id})`);
119
119
  }
120
120
  }
121
121
  }
@@ -3,15 +3,17 @@ export default class Pull extends BaseCommand {
3
3
  static args: {
4
4
  directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
6
+ static description: string;
7
+ static examples: string[];
6
8
  static flags: {
7
- workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
12
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
14
  };
12
- static description: string;
13
- static examples: string[];
14
15
  run(): Promise<void>;
16
+ private loadCredentials;
15
17
  /**
16
18
  * Parse a single document to extract its type, name, and optional verb.
17
19
  * Skips leading comment lines (starting with //) to find the first
@@ -24,5 +26,4 @@ export default class Pull extends BaseCommand {
24
26
  * characters that are unsafe in filenames.
25
27
  */
26
28
  private sanitizeFilename;
27
- private loadCredentials;
28
29
  }
@@ -1,8 +1,9 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
2
3
  import * as fs from 'node:fs';
3
4
  import * as os from 'node:os';
4
5
  import * as path from 'node:path';
5
- import * as yaml from 'js-yaml';
6
+ import snakeCase from 'lodash.snakecase';
6
7
  import BaseCommand from '../../../base-command.js';
7
8
  export default class Pull extends BaseCommand {
8
9
  static args = {
@@ -11,24 +12,6 @@ export default class Pull extends BaseCommand {
11
12
  required: true,
12
13
  }),
13
14
  };
14
- static flags = {
15
- ...BaseCommand.baseFlags,
16
- workspace: Flags.string({
17
- char: 'w',
18
- description: 'Workspace ID (optional if set in profile)',
19
- required: false,
20
- }),
21
- env: Flags.boolean({
22
- description: 'Include environment variables',
23
- required: false,
24
- default: false,
25
- }),
26
- records: Flags.boolean({
27
- description: 'Include records',
28
- required: false,
29
- default: false,
30
- }),
31
- };
32
15
  static description = 'Pull a workspace multidoc from the Xano Metadata API and split into individual files';
33
16
  static examples = [
34
17
  `$ xano workspace pull ./my-workspace
@@ -41,6 +24,24 @@ Pulled 15 documents to ./output
41
24
  Pulled 58 documents to ./backup
42
25
  `,
43
26
  ];
27
+ static flags = {
28
+ ...BaseCommand.baseFlags,
29
+ env: Flags.boolean({
30
+ default: false,
31
+ description: 'Include environment variables',
32
+ required: false,
33
+ }),
34
+ records: Flags.boolean({
35
+ default: false,
36
+ description: 'Include records',
37
+ required: false,
38
+ }),
39
+ workspace: Flags.string({
40
+ char: 'w',
41
+ description: 'Workspace ID (optional if set in profile)',
42
+ required: false,
43
+ }),
44
+ };
44
45
  async run() {
45
46
  const { args, flags } = await this.parse(Pull);
46
47
  // Get profile name (default or from flag/env)
@@ -79,16 +80,16 @@ Pulled 58 documents to ./backup
79
80
  records: flags.records.toString(),
80
81
  });
81
82
  // Construct the API URL
82
- const apiUrl = `${profile.instance_origin}/api:meta/beta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
83
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
83
84
  // Fetch multidoc from the API
84
85
  let responseText;
85
86
  try {
86
87
  const response = await fetch(apiUrl, {
87
- method: 'GET',
88
88
  headers: {
89
89
  'accept': 'application/json',
90
90
  'Authorization': `Bearer ${profile.access_token}`,
91
91
  },
92
+ method: 'GET',
92
93
  });
93
94
  if (!response.ok) {
94
95
  const errorText = await response.text();
@@ -130,35 +131,101 @@ Pulled 58 documents to ./backup
130
131
  const filenameCounters = new Map();
131
132
  let writtenCount = 0;
132
133
  for (const doc of documents) {
133
- // Create the type subdirectory
134
- const typeDir = path.join(outputDir, doc.type);
135
- fs.mkdirSync(typeDir, { recursive: true });
136
- // Build the base filename
137
- let baseName = this.sanitizeFilename(doc.name);
138
- if (doc.verb) {
139
- baseName = `${baseName}_${doc.verb}`;
134
+ let typeDir;
135
+ let baseName;
136
+ if (doc.type === 'workspace') {
137
+ // workspace workspace/{name}.xs
138
+ typeDir = path.join(outputDir, 'workspace');
139
+ baseName = this.sanitizeFilename(doc.name);
140
+ }
141
+ else if (doc.type === 'workspace_trigger') {
142
+ // workspace_trigger → workspace/trigger/{name}.xs
143
+ typeDir = path.join(outputDir, 'workspace', 'trigger');
144
+ baseName = this.sanitizeFilename(doc.name);
145
+ }
146
+ else if (doc.type === 'agent_trigger') {
147
+ // agent_trigger → agent/trigger/{name}.xs
148
+ typeDir = path.join(outputDir, 'agent', 'trigger');
149
+ baseName = this.sanitizeFilename(doc.name);
150
+ }
151
+ else if (doc.type === 'mcp_server_trigger') {
152
+ // mcp_server_trigger → mcp_server/trigger/{name}.xs
153
+ typeDir = path.join(outputDir, 'mcp_server', 'trigger');
154
+ baseName = this.sanitizeFilename(doc.name);
155
+ }
156
+ else if (doc.type === 'table_trigger') {
157
+ // table_trigger → table/trigger/{name}.xs
158
+ typeDir = path.join(outputDir, 'table', 'trigger');
159
+ baseName = this.sanitizeFilename(doc.name);
160
+ }
161
+ else if (doc.type === 'api_group') {
162
+ // api_group "test" → api/test/api_group.xs
163
+ const groupFolder = snakeCase(doc.name);
164
+ typeDir = path.join(outputDir, 'api', groupFolder);
165
+ baseName = 'api_group';
166
+ }
167
+ else if (doc.type === 'query' && doc.apiGroup) {
168
+ // query in group "test" → api/test/{query_name}.xs
169
+ const groupFolder = snakeCase(doc.apiGroup);
170
+ const nameParts = doc.name.split('/');
171
+ const leafName = nameParts.pop();
172
+ const folderParts = nameParts.map((part) => snakeCase(part));
173
+ typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
174
+ baseName = this.sanitizeFilename(leafName);
175
+ if (doc.verb) {
176
+ baseName = `${baseName}_${doc.verb}`;
177
+ }
178
+ }
179
+ else {
180
+ // Default: split folder path from name
181
+ const nameParts = doc.name.split('/');
182
+ const leafName = nameParts.pop();
183
+ const folderParts = nameParts.map((part) => snakeCase(part));
184
+ typeDir = path.join(outputDir, doc.type, ...folderParts);
185
+ baseName = this.sanitizeFilename(leafName);
186
+ if (doc.verb) {
187
+ baseName = `${baseName}_${doc.verb}`;
188
+ }
140
189
  }
141
- // Track duplicates per type directory
142
- if (!filenameCounters.has(doc.type)) {
143
- filenameCounters.set(doc.type, new Map());
190
+ fs.mkdirSync(typeDir, { recursive: true });
191
+ // Track duplicates per directory
192
+ const dirKey = path.relative(outputDir, typeDir);
193
+ if (!filenameCounters.has(dirKey)) {
194
+ filenameCounters.set(dirKey, new Map());
144
195
  }
145
- const typeCounters = filenameCounters.get(doc.type);
196
+ const typeCounters = filenameCounters.get(dirKey);
146
197
  const count = typeCounters.get(baseName) || 0;
147
198
  typeCounters.set(baseName, count + 1);
148
199
  // Append numeric suffix for duplicates
149
200
  let filename;
150
- if (count === 0) {
151
- filename = `${baseName}.xs`;
152
- }
153
- else {
154
- filename = `${baseName}_${count + 1}.xs`;
155
- }
201
+ filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
156
202
  const filePath = path.join(typeDir, filename);
157
203
  fs.writeFileSync(filePath, doc.content, 'utf8');
158
204
  writtenCount++;
159
205
  }
160
206
  this.log(`Pulled ${writtenCount} documents to ${args.directory}`);
161
207
  }
208
+ loadCredentials() {
209
+ const configDir = path.join(os.homedir(), '.xano');
210
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
211
+ // Check if credentials file exists
212
+ if (!fs.existsSync(credentialsPath)) {
213
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
214
+ `Create a profile using 'xano profile:create'`);
215
+ }
216
+ // Read credentials file
217
+ try {
218
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
219
+ const parsed = yaml.load(fileContent);
220
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
221
+ this.error('Credentials file has invalid format.');
222
+ }
223
+ return parsed;
224
+ }
225
+ catch (error) {
226
+ this.error(`Failed to parse credentials file: ${error}`);
227
+ }
228
+ }
162
229
  /**
163
230
  * Parse a single document to extract its type, name, and optional verb.
164
231
  * Skips leading comment lines (starting with //) to find the first
@@ -201,7 +268,13 @@ Pulled 58 documents to ./backup
201
268
  if (verbMatch) {
202
269
  verb = verbMatch[1];
203
270
  }
204
- return { type, name, verb, content };
271
+ // Extract api_group if present (e.g., api_group = "test")
272
+ let apiGroup;
273
+ const apiGroupMatch = content.match(/api_group\s*=\s*"([^"]*)"/);
274
+ if (apiGroupMatch) {
275
+ apiGroup = apiGroupMatch[1];
276
+ }
277
+ return { apiGroup, content, name, type, verb };
205
278
  }
206
279
  /**
207
280
  * Sanitize a document name for use as a filename.
@@ -209,30 +282,6 @@ Pulled 58 documents to ./backup
209
282
  * characters that are unsafe in filenames.
210
283
  */
211
284
  sanitizeFilename(name) {
212
- return name
213
- .replace(/"/g, '')
214
- .replace(/\s+/g, '_')
215
- .replace(/[<>:"/\\|?*]/g, '_');
216
- }
217
- loadCredentials() {
218
- const configDir = path.join(os.homedir(), '.xano');
219
- const credentialsPath = path.join(configDir, 'credentials.yaml');
220
- // Check if credentials file exists
221
- if (!fs.existsSync(credentialsPath)) {
222
- this.error(`Credentials file not found at ${credentialsPath}\n` +
223
- `Create a profile using 'xano profile:create'`);
224
- }
225
- // Read credentials file
226
- try {
227
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
228
- const parsed = yaml.load(fileContent);
229
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
230
- this.error('Credentials file has invalid format.');
231
- }
232
- return parsed;
233
- }
234
- catch (error) {
235
- this.error(`Failed to parse credentials file: ${error}`);
236
- }
285
+ return snakeCase(name.replaceAll('"', ''));
237
286
  }
238
287
  }
@@ -8,6 +8,7 @@ export default class Push extends BaseCommand {
8
8
  static flags: {
9
9
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  run(): Promise<void>;
13
14
  /**
@@ -1,8 +1,8 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
+ import * as yaml from 'js-yaml';
2
3
  import * as fs from 'node:fs';
3
4
  import * as os from 'node:os';
4
5
  import * as path from 'node:path';
5
- import * as yaml from 'js-yaml';
6
6
  import BaseCommand from '../../../base-command.js';
7
7
  export default class Push extends BaseCommand {
8
8
  static args = {
@@ -89,17 +89,17 @@ Pushed 58 documents from ./backup
89
89
  }
90
90
  const multidoc = documents.join('\n---\n');
91
91
  // Construct the API URL
92
- const apiUrl = `${profile.instance_origin}/api:meta/beta/workspace/${workspaceId}/multidoc`;
92
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc`;
93
93
  // POST the multidoc to the API
94
94
  try {
95
95
  const response = await fetch(apiUrl, {
96
- method: 'POST',
96
+ body: multidoc,
97
97
  headers: {
98
98
  'accept': 'application/json',
99
99
  'Authorization': `Bearer ${profile.access_token}`,
100
100
  'Content-Type': 'text/x-xanoscript',
101
101
  },
102
- body: multidoc,
102
+ method: 'POST',
103
103
  });
104
104
  if (!response.ok) {
105
105
  const errorText = await response.text();
@@ -107,7 +107,7 @@ Pushed 58 documents from ./backup
107
107
  }
108
108
  // Log the response if any
109
109
  const responseText = await response.text();
110
- if (responseText) {
110
+ if (responseText && responseText !== 'null') {
111
111
  this.log(responseText);
112
112
  }
113
113
  }
package/dist/help.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Command, Help as BaseHelp } from '@oclif/core';
1
+ import { Help as BaseHelp, Command } from '@oclif/core';
2
2
  import { CommandHelp as BaseCommandHelp } from '@oclif/core/help';
3
3
  /**
4
4
  * Custom CommandHelp class that extends the default to display environment variables
@@ -4,19 +4,19 @@
4
4
  import BaseCommand from '../base-command.js';
5
5
  import { RunHttpClient } from './run-http-client.js';
6
6
  export interface ProfileConfig {
7
- account_origin?: string;
8
- instance_origin: string;
9
7
  access_token: string;
10
- workspace?: string;
8
+ account_origin?: string;
11
9
  branch?: string;
10
+ instance_origin: string;
12
11
  project?: string;
13
12
  run_base_url?: string;
13
+ workspace?: string;
14
14
  }
15
15
  export interface CredentialsFile {
16
+ default?: string;
16
17
  profiles: {
17
18
  [key: string]: ProfileConfig;
18
19
  };
19
- default?: string;
20
20
  }
21
21
  export default abstract class BaseRunCommand extends BaseCommand {
22
22
  protected httpClient: RunHttpClient;
@@ -25,11 +25,11 @@ export default abstract class BaseRunCommand extends BaseCommand {
25
25
  /**
26
26
  * Initialize the run command with profile and HTTP client
27
27
  */
28
- protected initRunCommand(profileFlag?: string): Promise<void>;
28
+ protected initRunCommand(profileFlag?: string, verbose?: boolean): Promise<void>;
29
29
  /**
30
30
  * Initialize with project required
31
31
  */
32
- protected initRunCommandWithProject(profileFlag?: string): Promise<void>;
32
+ protected initRunCommandWithProject(profileFlag?: string, verbose?: boolean): Promise<void>;
33
33
  /**
34
34
  * Load credentials from file
35
35
  */
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Base command for all run commands
3
3
  */
4
+ import * as yaml from 'js-yaml';
4
5
  import * as fs from 'node:fs';
5
6
  import * as os from 'node:os';
6
7
  import * as path from 'node:path';
7
- import * as yaml from 'js-yaml';
8
8
  import BaseCommand from '../base-command.js';
9
9
  import { DEFAULT_RUN_BASE_URL, RunHttpClient } from './run-http-client.js';
10
10
  export default class BaseRunCommand extends BaseCommand {
@@ -14,7 +14,7 @@ export default class BaseRunCommand extends BaseCommand {
14
14
  /**
15
15
  * Initialize the run command with profile and HTTP client
16
16
  */
17
- async initRunCommand(profileFlag) {
17
+ async initRunCommand(profileFlag, verbose) {
18
18
  this.profileName = profileFlag || this.getDefaultProfile();
19
19
  const credentials = this.loadCredentials();
20
20
  if (!(this.profileName in credentials.profiles)) {
@@ -27,19 +27,21 @@ export default class BaseRunCommand extends BaseCommand {
27
27
  }
28
28
  const baseUrl = this.profile.run_base_url || DEFAULT_RUN_BASE_URL;
29
29
  this.httpClient = new RunHttpClient({
30
- baseUrl,
31
30
  authToken: this.profile.access_token,
31
+ baseUrl,
32
+ logger: (msg) => this.log(msg),
32
33
  projectId: this.profile.project,
34
+ verbose,
33
35
  });
34
36
  }
35
37
  /**
36
38
  * Initialize with project required
37
39
  */
38
- async initRunCommandWithProject(profileFlag) {
39
- await this.initRunCommand(profileFlag);
40
+ async initRunCommandWithProject(profileFlag, verbose) {
41
+ await this.initRunCommand(profileFlag, verbose);
40
42
  if (!this.profile.project) {
41
43
  this.error(`Profile '${this.profileName}' is missing project. ` +
42
- `Update your profile with 'xano profile:edit --project <project-id>'`);
44
+ `Run 'xano profile:wizard' to set up your profile or use 'xano profile:edit --project <project-id>'`);
43
45
  }
44
46
  }
45
47
  /**