@xano/cli 0.0.2

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 (49) hide show
  1. package/README.md +1200 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +5 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +5 -0
  6. package/dist/base-command.d.ts +11 -0
  7. package/dist/base-command.js +40 -0
  8. package/dist/commands/ephemeral/run/job/index.d.ts +19 -0
  9. package/dist/commands/ephemeral/run/job/index.js +318 -0
  10. package/dist/commands/ephemeral/run/service/index.d.ts +18 -0
  11. package/dist/commands/ephemeral/run/service/index.js +286 -0
  12. package/dist/commands/function/create/index.d.ts +18 -0
  13. package/dist/commands/function/create/index.js +280 -0
  14. package/dist/commands/function/edit/index.d.ts +24 -0
  15. package/dist/commands/function/edit/index.js +482 -0
  16. package/dist/commands/function/get/index.d.ts +18 -0
  17. package/dist/commands/function/get/index.js +279 -0
  18. package/dist/commands/function/list/index.d.ts +19 -0
  19. package/dist/commands/function/list/index.js +208 -0
  20. package/dist/commands/profile/create/index.d.ts +17 -0
  21. package/dist/commands/profile/create/index.js +123 -0
  22. package/dist/commands/profile/delete/index.d.ts +14 -0
  23. package/dist/commands/profile/delete/index.js +124 -0
  24. package/dist/commands/profile/edit/index.d.ts +18 -0
  25. package/dist/commands/profile/edit/index.js +129 -0
  26. package/dist/commands/profile/get-default/index.d.ts +6 -0
  27. package/dist/commands/profile/get-default/index.js +44 -0
  28. package/dist/commands/profile/list/index.d.ts +10 -0
  29. package/dist/commands/profile/list/index.js +115 -0
  30. package/dist/commands/profile/set-default/index.d.ts +9 -0
  31. package/dist/commands/profile/set-default/index.js +63 -0
  32. package/dist/commands/profile/wizard/index.d.ts +15 -0
  33. package/dist/commands/profile/wizard/index.js +350 -0
  34. package/dist/commands/static_host/build/create/index.d.ts +18 -0
  35. package/dist/commands/static_host/build/create/index.js +194 -0
  36. package/dist/commands/static_host/build/get/index.d.ts +16 -0
  37. package/dist/commands/static_host/build/get/index.js +165 -0
  38. package/dist/commands/static_host/build/list/index.d.ts +17 -0
  39. package/dist/commands/static_host/build/list/index.js +192 -0
  40. package/dist/commands/static_host/list/index.d.ts +15 -0
  41. package/dist/commands/static_host/list/index.js +187 -0
  42. package/dist/commands/workspace/list/index.d.ts +11 -0
  43. package/dist/commands/workspace/list/index.js +154 -0
  44. package/dist/help.d.ts +20 -0
  45. package/dist/help.js +26 -0
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.js +1 -0
  48. package/oclif.manifest.json +1370 -0
  49. package/package.json +79 -0
@@ -0,0 +1,279 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import * as yaml from 'js-yaml';
6
+ import inquirer from 'inquirer';
7
+ import BaseCommand from '../../../base-command.js';
8
+ export default class FunctionGet extends BaseCommand {
9
+ static args = {
10
+ function_id: Args.string({
11
+ description: 'Function ID',
12
+ required: false,
13
+ }),
14
+ };
15
+ static flags = {
16
+ ...BaseCommand.baseFlags,
17
+ workspace: Flags.string({
18
+ char: 'w',
19
+ description: 'Workspace ID (optional if set in profile)',
20
+ required: false,
21
+ }),
22
+ output: Flags.string({
23
+ char: 'o',
24
+ description: 'Output format',
25
+ required: false,
26
+ default: 'summary',
27
+ options: ['summary', 'json', 'xs'],
28
+ }),
29
+ include_draft: Flags.boolean({
30
+ description: 'Include draft version',
31
+ required: false,
32
+ default: false,
33
+ }),
34
+ include_xanoscript: Flags.boolean({
35
+ description: 'Include XanoScript in response',
36
+ required: false,
37
+ default: false,
38
+ }),
39
+ };
40
+ static description = 'Get a specific function from a workspace';
41
+ static examples = [
42
+ `$ xano function:get 145 -w 40
43
+ Function: yo (ID: 145)
44
+ Created: 2025-10-10 10:30:00
45
+ Description: Sample function
46
+ `,
47
+ `$ xano function:get 145 --profile production
48
+ Function: yo (ID: 145)
49
+ Created: 2025-10-10 10:30:00
50
+ `,
51
+ `$ xano function:get
52
+ Select a function:
53
+ ❯ yo (ID: 145) - Sample function
54
+ another-func (ID: 146)
55
+ `,
56
+ `$ xano function:get 145 -w 40 --output json
57
+ {
58
+ "id": 145,
59
+ "name": "yo",
60
+ "description": "Sample function"
61
+ }
62
+ `,
63
+ `$ xano function:get 145 -p staging -o json --include_draft
64
+ {
65
+ "id": 145,
66
+ "name": "yo"
67
+ }
68
+ `,
69
+ `$ xano function:get 145 -p staging -o xs
70
+ function yo {
71
+ input {
72
+ }
73
+ stack {
74
+ }
75
+ response = null
76
+ }
77
+ `,
78
+ ];
79
+ async run() {
80
+ const { args, flags } = await this.parse(FunctionGet);
81
+ // Get profile name (default or from flag/env)
82
+ const profileName = flags.profile || this.getDefaultProfile();
83
+ // Load credentials
84
+ const credentials = this.loadCredentials();
85
+ // Get the profile configuration
86
+ if (!(profileName in credentials.profiles)) {
87
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
88
+ `Create a profile using 'xano profile:create'`);
89
+ }
90
+ const profile = credentials.profiles[profileName];
91
+ // Validate required fields
92
+ if (!profile.instance_origin) {
93
+ this.error(`Profile '${profileName}' is missing instance_origin`);
94
+ }
95
+ if (!profile.access_token) {
96
+ this.error(`Profile '${profileName}' is missing access_token`);
97
+ }
98
+ // Determine workspace_id from flag or profile
99
+ let workspaceId;
100
+ if (flags.workspace) {
101
+ workspaceId = flags.workspace;
102
+ }
103
+ else if (profile.workspace) {
104
+ workspaceId = profile.workspace;
105
+ }
106
+ else {
107
+ this.error(`Workspace ID is required. Either:\n` +
108
+ ` 1. Provide it as a flag: xano function:get [function_id] -w <workspace_id>\n` +
109
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
110
+ }
111
+ // If function_id is not provided, prompt user to select from list
112
+ let functionId;
113
+ if (args.function_id) {
114
+ functionId = args.function_id;
115
+ }
116
+ else {
117
+ functionId = await this.promptForFunctionId(profile, workspaceId);
118
+ }
119
+ // Build query parameters
120
+ // Automatically set include_xanoscript to true if output format is xs
121
+ const includeXanoscript = flags.output === 'xs' ? true : flags.include_xanoscript;
122
+ const queryParams = new URLSearchParams({
123
+ include_draft: flags.include_draft.toString(),
124
+ include_xanoscript: includeXanoscript.toString(),
125
+ });
126
+ // Construct the API URL
127
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function/${functionId}?${queryParams.toString()}`;
128
+ // Fetch function from the API
129
+ try {
130
+ const response = await fetch(apiUrl, {
131
+ method: 'GET',
132
+ headers: {
133
+ 'accept': 'application/json',
134
+ 'Authorization': `Bearer ${profile.access_token}`,
135
+ },
136
+ });
137
+ if (!response.ok) {
138
+ const errorText = await response.text();
139
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
140
+ }
141
+ const func = await response.json();
142
+ // Validate response is an object
143
+ if (!func || typeof func !== 'object') {
144
+ this.error('Unexpected API response format: expected a function object');
145
+ }
146
+ // Output results
147
+ if (flags.output === 'json') {
148
+ this.log(JSON.stringify(func, null, 2));
149
+ }
150
+ else if (flags.output === 'xs') {
151
+ // xs (XanoScript) format - output only the xanoscript element
152
+ if (func.xanoscript) {
153
+ // If status is "ok", output only the value, otherwise output the full xanoscript object
154
+ if (func.xanoscript.status === 'ok' && func.xanoscript.value !== undefined) {
155
+ this.log(func.xanoscript.value);
156
+ }
157
+ else {
158
+ this.log(JSON.stringify(func.xanoscript, null, 2));
159
+ }
160
+ }
161
+ else {
162
+ this.log('null');
163
+ }
164
+ }
165
+ else {
166
+ // summary format
167
+ this.log(`Function: ${func.name} (ID: ${func.id})`);
168
+ if (func.created_at) {
169
+ this.log(`Created: ${func.created_at}`);
170
+ }
171
+ if (func.description) {
172
+ this.log(`Description: ${func.description}`);
173
+ }
174
+ if (func.type) {
175
+ this.log(`Type: ${func.type}`);
176
+ }
177
+ // Don't display xanoscript in summary mode as it can be very large
178
+ if (func.xanoscript) {
179
+ this.log(`XanoScript: (available with -o xs or -o json)`);
180
+ }
181
+ }
182
+ }
183
+ catch (error) {
184
+ if (error instanceof Error) {
185
+ this.error(`Failed to fetch function: ${error.message}`);
186
+ }
187
+ else {
188
+ this.error(`Failed to fetch function: ${String(error)}`);
189
+ }
190
+ }
191
+ }
192
+ async promptForFunctionId(profile, workspaceId) {
193
+ try {
194
+ // Fetch list of functions
195
+ const queryParams = new URLSearchParams({
196
+ include_draft: 'false',
197
+ include_xanoscript: 'false',
198
+ page: '1',
199
+ per_page: '50',
200
+ sort: 'created_at',
201
+ order: 'desc',
202
+ });
203
+ const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
204
+ const response = await fetch(listUrl, {
205
+ method: 'GET',
206
+ headers: {
207
+ 'accept': 'application/json',
208
+ 'Authorization': `Bearer ${profile.access_token}`,
209
+ },
210
+ });
211
+ if (!response.ok) {
212
+ const errorText = await response.text();
213
+ this.error(`Failed to fetch function list: ${response.status} ${response.statusText}\n${errorText}`);
214
+ }
215
+ const data = await response.json();
216
+ // Handle different response formats
217
+ let functions;
218
+ if (Array.isArray(data)) {
219
+ functions = data;
220
+ }
221
+ else if (data && typeof data === 'object' && 'functions' in data && Array.isArray(data.functions)) {
222
+ functions = data.functions;
223
+ }
224
+ else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
225
+ functions = data.items;
226
+ }
227
+ else {
228
+ this.error('Unexpected API response format');
229
+ }
230
+ if (functions.length === 0) {
231
+ this.error('No functions found in workspace');
232
+ }
233
+ // Create choices for inquirer
234
+ const choices = functions.map(func => ({
235
+ name: `${func.name} (ID: ${func.id})${func.description ? ` - ${func.description}` : ''}`,
236
+ value: func.id.toString(),
237
+ }));
238
+ // Prompt user to select a function
239
+ const answer = await inquirer.prompt([
240
+ {
241
+ type: 'list',
242
+ name: 'functionId',
243
+ message: 'Select a function:',
244
+ choices,
245
+ },
246
+ ]);
247
+ return answer.functionId;
248
+ }
249
+ catch (error) {
250
+ if (error instanceof Error) {
251
+ this.error(`Failed to prompt for function: ${error.message}`);
252
+ }
253
+ else {
254
+ this.error(`Failed to prompt for function: ${String(error)}`);
255
+ }
256
+ }
257
+ }
258
+ loadCredentials() {
259
+ const configDir = path.join(os.homedir(), '.xano');
260
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
261
+ // Check if credentials file exists
262
+ if (!fs.existsSync(credentialsPath)) {
263
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
264
+ `Create a profile using 'xano profile:create'`);
265
+ }
266
+ // Read credentials file
267
+ try {
268
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
269
+ const parsed = yaml.load(fileContent);
270
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
271
+ this.error('Credentials file has invalid format.');
272
+ }
273
+ return parsed;
274
+ }
275
+ catch (error) {
276
+ this.error(`Failed to parse credentials file: ${error}`);
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class FunctionList extends BaseCommand {
3
+ static args: {};
4
+ static flags: {
5
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ include_draft: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ include_xanoscript: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ per_page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ sort: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ order: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ };
15
+ static description: string;
16
+ static examples: string[];
17
+ run(): Promise<void>;
18
+ private loadCredentials;
19
+ }
@@ -0,0 +1,208 @@
1
+ import { Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import * as yaml from 'js-yaml';
6
+ import BaseCommand from '../../../base-command.js';
7
+ export default class FunctionList extends BaseCommand {
8
+ static args = {};
9
+ static flags = {
10
+ ...BaseCommand.baseFlags,
11
+ workspace: Flags.string({
12
+ char: 'w',
13
+ description: 'Workspace ID (optional if set in profile)',
14
+ required: false,
15
+ }),
16
+ output: Flags.string({
17
+ char: 'o',
18
+ description: 'Output format',
19
+ required: false,
20
+ default: 'summary',
21
+ options: ['summary', 'json'],
22
+ }),
23
+ include_draft: Flags.boolean({
24
+ description: 'Include draft functions',
25
+ required: false,
26
+ default: false,
27
+ }),
28
+ include_xanoscript: Flags.boolean({
29
+ description: 'Include XanoScript in response',
30
+ required: false,
31
+ default: false,
32
+ }),
33
+ page: Flags.integer({
34
+ description: 'Page number for pagination',
35
+ required: false,
36
+ default: 1,
37
+ }),
38
+ per_page: Flags.integer({
39
+ description: 'Number of results per page',
40
+ required: false,
41
+ default: 50,
42
+ }),
43
+ sort: Flags.string({
44
+ description: 'Sort field',
45
+ required: false,
46
+ default: 'created_at',
47
+ }),
48
+ order: Flags.string({
49
+ description: 'Sort order',
50
+ required: false,
51
+ default: 'desc',
52
+ options: ['asc', 'desc'],
53
+ }),
54
+ };
55
+ static description = 'List all functions in a workspace from the Xano Metadata API';
56
+ static examples = [
57
+ `$ xano function:list -w 40
58
+ Available functions:
59
+ - function-1 (ID: 1)
60
+ - function-2 (ID: 2)
61
+ - function-3 (ID: 3)
62
+ `,
63
+ `$ xano function:list --profile production
64
+ Available functions:
65
+ - my-function (ID: 1)
66
+ - another-function (ID: 2)
67
+ `,
68
+ `$ xano function:list -w 40 --output json
69
+ [
70
+ {
71
+ "id": 1,
72
+ "name": "function-1"
73
+ }
74
+ ]
75
+ `,
76
+ `$ xano function:list -p staging -o json --include_draft
77
+ [
78
+ {
79
+ "id": 1,
80
+ "name": "function-1"
81
+ }
82
+ ]
83
+ `,
84
+ ];
85
+ async run() {
86
+ const { flags } = await this.parse(FunctionList);
87
+ // Get profile name (default or from flag/env)
88
+ const profileName = flags.profile || this.getDefaultProfile();
89
+ // Load credentials
90
+ const credentials = this.loadCredentials();
91
+ // Get the profile configuration
92
+ if (!(profileName in credentials.profiles)) {
93
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
94
+ `Create a profile using 'xano profile:create'`);
95
+ }
96
+ const profile = credentials.profiles[profileName];
97
+ // Validate required fields
98
+ if (!profile.instance_origin) {
99
+ this.error(`Profile '${profileName}' is missing instance_origin`);
100
+ }
101
+ if (!profile.access_token) {
102
+ this.error(`Profile '${profileName}' is missing access_token`);
103
+ }
104
+ // Determine workspace_id from flag or profile
105
+ let workspaceId;
106
+ if (flags.workspace) {
107
+ workspaceId = flags.workspace;
108
+ }
109
+ else if (profile.workspace) {
110
+ workspaceId = profile.workspace;
111
+ }
112
+ else {
113
+ this.error(`Workspace ID is required. Either:\n` +
114
+ ` 1. Provide it as a flag: xano function:list -w <workspace_id>\n` +
115
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
116
+ }
117
+ // Build query parameters
118
+ const queryParams = new URLSearchParams({
119
+ include_draft: flags.include_draft.toString(),
120
+ include_xanoscript: flags.include_xanoscript.toString(),
121
+ page: flags.page.toString(),
122
+ per_page: flags.per_page.toString(),
123
+ sort: flags.sort,
124
+ order: flags.order,
125
+ });
126
+ // Construct the API URL
127
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/function?${queryParams.toString()}`;
128
+ // Fetch functions from the API
129
+ try {
130
+ const response = await fetch(apiUrl, {
131
+ method: 'GET',
132
+ headers: {
133
+ 'accept': 'application/json',
134
+ 'Authorization': `Bearer ${profile.access_token}`,
135
+ },
136
+ });
137
+ if (!response.ok) {
138
+ const errorText = await response.text();
139
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
140
+ }
141
+ const data = await response.json();
142
+ // Handle different response formats
143
+ let functions;
144
+ if (Array.isArray(data)) {
145
+ functions = data;
146
+ }
147
+ else if (data && typeof data === 'object' && 'functions' in data && Array.isArray(data.functions)) {
148
+ functions = data.functions;
149
+ }
150
+ else if (data && typeof data === 'object' && 'items' in data && Array.isArray(data.items)) {
151
+ functions = data.items;
152
+ }
153
+ else {
154
+ this.error('Unexpected API response format');
155
+ }
156
+ // Output results
157
+ if (flags.output === 'json') {
158
+ this.log(JSON.stringify(functions, null, 2));
159
+ }
160
+ else {
161
+ // summary format
162
+ if (functions.length === 0) {
163
+ this.log('No functions found');
164
+ }
165
+ else {
166
+ this.log('Available functions:');
167
+ for (const func of functions) {
168
+ if (func.id !== undefined) {
169
+ this.log(` - ${func.name} (ID: ${func.id})`);
170
+ }
171
+ else {
172
+ this.log(` - ${func.name}`);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (error instanceof Error) {
180
+ this.error(`Failed to fetch functions: ${error.message}`);
181
+ }
182
+ else {
183
+ this.error(`Failed to fetch functions: ${String(error)}`);
184
+ }
185
+ }
186
+ }
187
+ loadCredentials() {
188
+ const configDir = path.join(os.homedir(), '.xano');
189
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
190
+ // Check if credentials file exists
191
+ if (!fs.existsSync(credentialsPath)) {
192
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
193
+ `Create a profile using 'xano profile:create'`);
194
+ }
195
+ // Read credentials file
196
+ try {
197
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
198
+ const parsed = yaml.load(fileContent);
199
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
200
+ this.error('Credentials file has invalid format.');
201
+ }
202
+ return parsed;
203
+ }
204
+ catch (error) {
205
+ this.error(`Failed to parse credentials file: ${error}`);
206
+ }
207
+ }
208
+ }
@@ -0,0 +1,17 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ProfileCreate extends Command {
3
+ static args: {
4
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static flags: {
7
+ account_origin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ instance_origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ access_token: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ default: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ };
14
+ static description: string;
15
+ static examples: string[];
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,123 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import * as yaml from 'js-yaml';
6
+ export default class ProfileCreate extends Command {
7
+ static args = {
8
+ name: Args.string({
9
+ description: 'Profile name',
10
+ required: true,
11
+ }),
12
+ };
13
+ static flags = {
14
+ account_origin: Flags.string({
15
+ char: 'a',
16
+ description: 'Account origin URL. Optional for self hosted installs.',
17
+ required: false,
18
+ }),
19
+ instance_origin: Flags.string({
20
+ char: 'i',
21
+ description: 'Instance origin URL',
22
+ required: true,
23
+ }),
24
+ access_token: Flags.string({
25
+ char: 't',
26
+ description: 'Access token for the Xano Metadata API',
27
+ required: true,
28
+ }),
29
+ workspace: Flags.string({
30
+ char: 'w',
31
+ description: 'Workspace name',
32
+ required: false,
33
+ }),
34
+ branch: Flags.string({
35
+ char: 'b',
36
+ description: 'Branch name',
37
+ required: false,
38
+ }),
39
+ default: Flags.boolean({
40
+ description: 'Set this profile as the default',
41
+ required: false,
42
+ default: false,
43
+ }),
44
+ };
45
+ static description = 'Create a new profile configuration';
46
+ static examples = [
47
+ `$ xano profile:create production --account_origin https://account.xano.com --instance_origin https://instance.xano.com --access_token token123
48
+ Profile 'production' created successfully at ~/.xano/credentials.yaml
49
+ `,
50
+ `$ xano profile:create staging -a https://staging-account.xano.com -i https://staging-instance.xano.com -t token456
51
+ Profile 'staging' created successfully at ~/.xano/credentials.yaml
52
+ `,
53
+ `$ xano profile:create dev -i https://dev-instance.xano.com -t token789 -w my-workspace -b feature-branch
54
+ Profile 'dev' created successfully at ~/.xano/credentials.yaml
55
+ `,
56
+ `$ xano profile:create production --account_origin https://account.xano.com --instance_origin https://instance.xano.com --access_token token123 --default
57
+ Profile 'production' created successfully at ~/.xano/credentials.yaml
58
+ Default profile set to 'production'
59
+ `,
60
+ ];
61
+ async run() {
62
+ const { args, flags } = await this.parse(ProfileCreate);
63
+ const configDir = path.join(os.homedir(), '.xano');
64
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
65
+ // Ensure the .xano directory exists
66
+ if (!fs.existsSync(configDir)) {
67
+ fs.mkdirSync(configDir, { recursive: true });
68
+ this.log(`Created directory: ${configDir}`);
69
+ }
70
+ // Read existing credentials file or create new structure
71
+ let credentials = { profiles: {} };
72
+ if (fs.existsSync(credentialsPath)) {
73
+ try {
74
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
75
+ const parsed = yaml.load(fileContent);
76
+ if (parsed && typeof parsed === 'object' && 'profiles' in parsed) {
77
+ credentials = parsed;
78
+ }
79
+ else {
80
+ this.warn('Existing credentials file has invalid format. Creating new structure.');
81
+ }
82
+ }
83
+ catch (error) {
84
+ this.warn(`Failed to parse existing credentials file: ${error}`);
85
+ this.warn('Creating new credentials file.');
86
+ }
87
+ }
88
+ // Add or update the profile
89
+ const profileExists = args.name in credentials.profiles;
90
+ credentials.profiles[args.name] = {
91
+ account_origin: flags.account_origin ?? '',
92
+ instance_origin: flags.instance_origin,
93
+ access_token: flags.access_token,
94
+ ...(flags.workspace && { workspace: flags.workspace }),
95
+ ...(flags.branch && { branch: flags.branch }),
96
+ };
97
+ // Set default if flag is provided
98
+ if (flags.default) {
99
+ credentials.default = args.name;
100
+ }
101
+ // Write the updated credentials back to the file
102
+ try {
103
+ const yamlContent = yaml.dump(credentials, {
104
+ indent: 2,
105
+ lineWidth: -1,
106
+ noRefs: true,
107
+ });
108
+ fs.writeFileSync(credentialsPath, yamlContent, 'utf8');
109
+ if (profileExists) {
110
+ this.log(`Profile '${args.name}' updated successfully at ${credentialsPath}`);
111
+ }
112
+ else {
113
+ this.log(`Profile '${args.name}' created successfully at ${credentialsPath}`);
114
+ }
115
+ if (flags.default) {
116
+ this.log(`Default profile set to '${args.name}'`);
117
+ }
118
+ }
119
+ catch (error) {
120
+ this.error(`Failed to write credentials file: ${error}`);
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ProfileDelete extends Command {
3
+ static args: {
4
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static flags: {
7
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ static description: string;
10
+ static examples: string[];
11
+ run(): Promise<void>;
12
+ private confirm;
13
+ private promptInput;
14
+ }