@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
package/bin/dev.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
package/bin/dev.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning --disable-warning=DEP0180
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({development: true, dir: import.meta.url})
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
package/bin/run.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({dir: import.meta.url})
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default abstract class BaseCommand extends Command {
3
+ static baseFlags: {
4
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
+ };
6
+ static flags: {
7
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ protected getProfile(): string | undefined;
10
+ protected getDefaultProfile(): string;
11
+ }
@@ -0,0 +1,40 @@
1
+ import { 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 BaseCommand extends Command {
7
+ static baseFlags = {
8
+ profile: Flags.string({
9
+ char: 'p',
10
+ description: 'Profile to use for this command',
11
+ env: 'XANO_PROFILE',
12
+ required: false,
13
+ }),
14
+ };
15
+ // Override the flags property to include baseFlags
16
+ static flags = BaseCommand.baseFlags;
17
+ // Helper method to get the profile flag value
18
+ getProfile() {
19
+ return this.flags?.profile;
20
+ }
21
+ // Helper method to get the default profile from credentials file
22
+ getDefaultProfile() {
23
+ try {
24
+ const configDir = path.join(os.homedir(), '.xano');
25
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
26
+ if (!fs.existsSync(credentialsPath)) {
27
+ return 'default';
28
+ }
29
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
30
+ const parsed = yaml.load(fileContent);
31
+ if (parsed && typeof parsed === 'object' && 'default' in parsed && parsed.default) {
32
+ return parsed.default;
33
+ }
34
+ return 'default';
35
+ }
36
+ catch {
37
+ return 'default';
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralRunJob extends BaseCommand {
3
+ static args: {};
4
+ static flags: {
5
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ args: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ static description: string;
14
+ static examples: string[];
15
+ run(): Promise<void>;
16
+ private editFile;
17
+ private readStdin;
18
+ private loadCredentials;
19
+ }
@@ -0,0 +1,318 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { execSync } from 'node:child_process';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import * as yaml from 'js-yaml';
7
+ import BaseCommand from '../../../../base-command.js';
8
+ export default class EphemeralRunJob extends BaseCommand {
9
+ static args = {};
10
+ static flags = {
11
+ ...BaseCommand.baseFlags,
12
+ workspace: Flags.string({
13
+ char: 'w',
14
+ description: 'Workspace ID (optional if set in profile)',
15
+ required: false,
16
+ }),
17
+ file: Flags.string({
18
+ char: 'f',
19
+ description: 'Path to file containing XanoScript code',
20
+ required: false,
21
+ exclusive: ['stdin'],
22
+ }),
23
+ stdin: Flags.boolean({
24
+ char: 's',
25
+ description: 'Read XanoScript code from stdin',
26
+ required: false,
27
+ default: false,
28
+ exclusive: ['file'],
29
+ }),
30
+ edit: Flags.boolean({
31
+ char: 'e',
32
+ description: 'Open file in editor before running job (requires --file)',
33
+ required: false,
34
+ default: false,
35
+ dependsOn: ['file'],
36
+ }),
37
+ output: Flags.string({
38
+ char: 'o',
39
+ description: 'Output format',
40
+ required: false,
41
+ default: 'summary',
42
+ options: ['summary', 'json'],
43
+ }),
44
+ args: Flags.string({
45
+ char: 'a',
46
+ description: 'Path to JSON file containing input arguments',
47
+ required: false,
48
+ }),
49
+ };
50
+ static description = 'Run an ephemeral job in a workspace';
51
+ static examples = [
52
+ `$ xano ephemeral:run:job -w 1 -f script.xs
53
+ Job executed successfully!
54
+ ...
55
+ `,
56
+ `$ xano ephemeral:run:job -f script.xs
57
+ Job executed successfully!
58
+ ...
59
+ `,
60
+ `$ xano ephemeral:run:job -w 1 -f script.xs --edit
61
+ # Opens script.xs in $EDITOR, then runs job with edited content
62
+ Job executed successfully!
63
+ ...
64
+ `,
65
+ `$ cat script.xs | xano ephemeral:run:job -w 1 --stdin
66
+ Job executed successfully!
67
+ ...
68
+ `,
69
+ `$ xano ephemeral:run:job -w 1 -f script.xs -o json
70
+ {
71
+ "job": { "id": 1, "run": { "id": 1 } },
72
+ "result": { ... }
73
+ }
74
+ `,
75
+ `$ xano ephemeral:run:job -w 1 -f script.xs -a args.json
76
+ # Runs job with input arguments from args.json
77
+ Job executed successfully!
78
+ ...
79
+ `,
80
+ ];
81
+ async run() {
82
+ const { flags } = await this.parse(EphemeralRunJob);
83
+ // Get profile name (default or from flag/env)
84
+ const profileName = flags.profile || this.getDefaultProfile();
85
+ // Load credentials
86
+ const credentials = this.loadCredentials();
87
+ // Get the profile configuration
88
+ if (!(profileName in credentials.profiles)) {
89
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
90
+ `Create a profile using 'xano profile:create'`);
91
+ }
92
+ const profile = credentials.profiles[profileName];
93
+ // Validate required fields
94
+ if (!profile.instance_origin) {
95
+ this.error(`Profile '${profileName}' is missing instance_origin`);
96
+ }
97
+ if (!profile.access_token) {
98
+ this.error(`Profile '${profileName}' is missing access_token`);
99
+ }
100
+ // Determine workspace_id from flag or profile
101
+ let workspaceId;
102
+ if (flags.workspace) {
103
+ workspaceId = flags.workspace;
104
+ }
105
+ else if (profile.workspace) {
106
+ workspaceId = profile.workspace;
107
+ }
108
+ else {
109
+ this.error(`Workspace ID is required. Either:\n` +
110
+ ` 1. Provide it as a flag: xano ephemeral:run:job -w <workspace_id>\n` +
111
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
112
+ }
113
+ // Read XanoScript content
114
+ let xanoscript;
115
+ if (flags.file) {
116
+ // Read from file
117
+ let fileToRead = flags.file;
118
+ // If edit flag is set, copy to temp file and open in editor
119
+ if (flags.edit) {
120
+ fileToRead = await this.editFile(flags.file);
121
+ }
122
+ try {
123
+ xanoscript = fs.readFileSync(fileToRead, 'utf8');
124
+ // Clean up temp file if it was created
125
+ if (flags.edit && fileToRead !== flags.file) {
126
+ try {
127
+ fs.unlinkSync(fileToRead);
128
+ }
129
+ catch {
130
+ // Ignore cleanup errors
131
+ }
132
+ }
133
+ }
134
+ catch (error) {
135
+ this.error(`Failed to read file '${fileToRead}': ${error}`);
136
+ }
137
+ }
138
+ else if (flags.stdin) {
139
+ // Read from stdin
140
+ try {
141
+ xanoscript = await this.readStdin();
142
+ }
143
+ catch (error) {
144
+ this.error(`Failed to read from stdin: ${error}`);
145
+ }
146
+ }
147
+ else {
148
+ this.error('Either --file or --stdin must be specified to provide XanoScript code');
149
+ }
150
+ // Validate xanoscript is not empty
151
+ if (!xanoscript || xanoscript.trim().length === 0) {
152
+ this.error('XanoScript content is empty');
153
+ }
154
+ // Load args from JSON file if provided
155
+ let inputArgs;
156
+ if (flags.args) {
157
+ try {
158
+ const argsContent = fs.readFileSync(flags.args, 'utf8');
159
+ inputArgs = JSON.parse(argsContent);
160
+ }
161
+ catch (error) {
162
+ this.error(`Failed to read or parse args file '${flags.args}': ${error}`);
163
+ }
164
+ }
165
+ // Construct the API URL
166
+ const apiUrl = `${profile.instance_origin}/api:meta/beta/workspace/${workspaceId}/ephemeral/job`;
167
+ // Build request body - multipart if args provided, plain xanoscript otherwise
168
+ let requestBody;
169
+ let contentType;
170
+ const formData = new FormData();
171
+ formData.append('doc', xanoscript);
172
+ if (inputArgs) {
173
+ formData.append('args', JSON.stringify(inputArgs));
174
+ }
175
+ requestBody = formData;
176
+ contentType = ''; // Let fetch set the boundary
177
+ // Run ephemeral job via API
178
+ try {
179
+ const headers = {
180
+ 'accept': 'application/json',
181
+ 'Authorization': `Bearer ${profile.access_token}`,
182
+ };
183
+ if (contentType) {
184
+ headers['Content-Type'] = contentType;
185
+ }
186
+ const response = await fetch(apiUrl, {
187
+ method: 'POST',
188
+ headers,
189
+ body: requestBody,
190
+ });
191
+ if (!response.ok) {
192
+ const errorText = await response.text();
193
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
194
+ }
195
+ const result = await response.json();
196
+ // Output results
197
+ if (flags.output === 'json') {
198
+ this.log(JSON.stringify(result, null, 2));
199
+ }
200
+ else {
201
+ // summary format
202
+ this.log('Job executed successfully!');
203
+ this.log('');
204
+ this.log(` Job ID: ${result.job.id}`);
205
+ this.log(` Run ID: ${result.job.run.id}`);
206
+ this.log('');
207
+ this.log(' Timing:');
208
+ this.log(` Total: ${(result.result.total_time * 1000).toFixed(2)}ms`);
209
+ this.log(` Boot: ${(result.result.boot_time * 1000).toFixed(2)}ms`);
210
+ this.log(` Main: ${(result.result.main_time * 1000).toFixed(2)}ms`);
211
+ this.log(` Pre: ${(result.result.pre_time * 1000).toFixed(2)}ms`);
212
+ this.log(` Post: ${(result.result.post_time * 1000).toFixed(2)}ms`);
213
+ this.log('');
214
+ this.log(' Response:');
215
+ const responseStr = typeof result.result.response === 'string'
216
+ ? result.result.response
217
+ : JSON.stringify(result.result.response, null, 2);
218
+ // Indent multiline response
219
+ const indentedResponse = responseStr.split('\n').map((line) => ` ${line}`).join('\n');
220
+ this.log(indentedResponse);
221
+ }
222
+ }
223
+ catch (error) {
224
+ if (error instanceof Error) {
225
+ this.error(`Failed to run ephemeral job: ${error.message}`);
226
+ }
227
+ else {
228
+ this.error(`Failed to run ephemeral job: ${String(error)}`);
229
+ }
230
+ }
231
+ }
232
+ async editFile(filePath) {
233
+ // Get the EDITOR environment variable
234
+ const editor = process.env.EDITOR || process.env.VISUAL;
235
+ if (!editor) {
236
+ this.error('No editor configured. Please set the EDITOR or VISUAL environment variable.\n' +
237
+ 'Example: export EDITOR=vim');
238
+ }
239
+ // Validate editor executable exists
240
+ try {
241
+ execSync(`which ${editor.split(' ')[0]}`, { stdio: 'ignore' });
242
+ }
243
+ catch {
244
+ this.error(`Editor '${editor}' not found. Please set EDITOR to a valid editor.\n` +
245
+ 'Example: export EDITOR=vim');
246
+ }
247
+ // Read the original file
248
+ let originalContent;
249
+ try {
250
+ originalContent = fs.readFileSync(filePath, 'utf8');
251
+ }
252
+ catch (error) {
253
+ this.error(`Failed to read file '${filePath}': ${error}`);
254
+ }
255
+ // Create a temporary file with the same extension
256
+ const ext = path.extname(filePath);
257
+ const tmpFile = path.join(os.tmpdir(), `xano-edit-${Date.now()}${ext}`);
258
+ // Copy content to temp file
259
+ try {
260
+ fs.writeFileSync(tmpFile, originalContent, 'utf8');
261
+ }
262
+ catch (error) {
263
+ this.error(`Failed to create temporary file: ${error}`);
264
+ }
265
+ // Open the editor
266
+ try {
267
+ execSync(`${editor} ${tmpFile}`, { stdio: 'inherit' });
268
+ }
269
+ catch (error) {
270
+ // Clean up temp file
271
+ try {
272
+ fs.unlinkSync(tmpFile);
273
+ }
274
+ catch {
275
+ // Ignore cleanup errors
276
+ }
277
+ this.error(`Editor exited with an error: ${error}`);
278
+ }
279
+ return tmpFile;
280
+ }
281
+ async readStdin() {
282
+ return new Promise((resolve, reject) => {
283
+ const chunks = [];
284
+ process.stdin.on('data', (chunk) => {
285
+ chunks.push(chunk);
286
+ });
287
+ process.stdin.on('end', () => {
288
+ resolve(Buffer.concat(chunks).toString('utf8'));
289
+ });
290
+ process.stdin.on('error', (error) => {
291
+ reject(error);
292
+ });
293
+ // Resume stdin if it was paused
294
+ process.stdin.resume();
295
+ });
296
+ }
297
+ loadCredentials() {
298
+ const configDir = path.join(os.homedir(), '.xano');
299
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
300
+ // Check if credentials file exists
301
+ if (!fs.existsSync(credentialsPath)) {
302
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
303
+ `Create a profile using 'xano profile:create'`);
304
+ }
305
+ // Read credentials file
306
+ try {
307
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
308
+ const parsed = yaml.load(fileContent);
309
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
310
+ this.error('Credentials file has invalid format.');
311
+ }
312
+ return parsed;
313
+ }
314
+ catch (error) {
315
+ this.error(`Failed to parse credentials file: ${error}`);
316
+ }
317
+ }
318
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../../base-command.js';
2
+ export default class EphemeralRunService extends BaseCommand {
3
+ static args: {};
4
+ static flags: {
5
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ edit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ static description: string;
13
+ static examples: string[];
14
+ run(): Promise<void>;
15
+ private editFile;
16
+ private readStdin;
17
+ private loadCredentials;
18
+ }