@xano/cli 0.0.16 → 0.0.18

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.
package/README.md CHANGED
@@ -25,7 +25,7 @@ npm install -g @xano/cli
25
25
 
26
26
  3. Execute XanoScript code:
27
27
  ```bash
28
- xano run exec -f script.xs
28
+ xano run exec script.xs
29
29
  ```
30
30
 
31
31
  ## Commands
@@ -54,8 +54,6 @@ xano profile:set-default myprofile
54
54
  # Edit a profile
55
55
  xano profile:edit myprofile -w 123 # Set default workspace
56
56
  xano profile:edit myprofile -j my-project # Set default project
57
- xano profile:edit myprofile --run-project <id> # Set run project for xano run commands
58
- xano profile:edit myprofile --remove-run-project # Remove run project
59
57
 
60
58
  # Delete a profile
61
59
  xano profile:delete myprofile
@@ -100,18 +98,21 @@ Execute XanoScript code and manage projects, sessions, environment variables, an
100
98
 
101
99
  ```bash
102
100
  # Execute XanoScript (job or service)
103
- xano run exec -f script.xs
104
- xano run exec -f https://example.com/script.xs # From URL
105
- xano run exec -f script.xs -a args.json # With input arguments (file)
106
- xano run exec -f script.xs -a https://ex.com/args.json # With input arguments (URL)
107
- xano run exec -f script.xs --edit # Edit in $EDITOR first
108
- xano run exec -f script.xs --env API_KEY=secret # With env overrides
101
+ xano run exec script.xs # Single file
102
+ xano run exec ./my-workspace # Directory (multidoc from .xs files)
103
+ xano run exec https://example.com/script.xs # From URL
104
+ xano run exec script.xs -a args.json # With input arguments (file)
105
+ xano run exec script.xs -a https://ex.com/args.json # With input arguments (URL)
106
+ xano run exec script.xs --edit # Edit in $EDITOR first
107
+ xano run exec script.xs --env API_KEY=secret # With env overrides
109
108
  cat script.xs | xano run exec --stdin # From stdin
110
109
 
111
110
  # Get document info (type, inputs, env vars)
112
111
  xano run info -f script.xs
113
112
  ```
114
113
 
114
+ When a directory is provided, all `.xs` files are collected recursively and combined into a multidoc (joined with `---` separators), similar to `xano workspace push`.
115
+
115
116
  #### Projects
116
117
 
117
118
  ```bash
@@ -225,7 +226,7 @@ profiles:
225
226
  access_token: <token>
226
227
  workspace: <workspace_id>
227
228
  branch: <branch_id>
228
- run_project: <run_project_id> # Used by xano run commands
229
+ project: <project_id>
229
230
  default: default
230
231
  ```
231
232
 
@@ -10,11 +10,9 @@ export default class ProfileEdit extends BaseCommand {
10
10
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- 'run-project': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
13
  'remove-workspace': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
14
  'remove-branch': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
15
  'remove-project': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
- 'remove-run-project': import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
16
  run_base_url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
17
  'remove-run-base-url': import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
18
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -40,11 +40,7 @@ export default class ProfileEdit extends BaseCommand {
40
40
  }),
41
41
  project: Flags.string({
42
42
  char: 'j',
43
- description: 'Update project name',
44
- required: false,
45
- }),
46
- 'run-project': Flags.string({
47
- description: 'Update run project ID (for xano run commands)',
43
+ description: 'Update project ID',
48
44
  required: false,
49
45
  }),
50
46
  'remove-workspace': Flags.boolean({
@@ -62,11 +58,6 @@ export default class ProfileEdit extends BaseCommand {
62
58
  required: false,
63
59
  default: false,
64
60
  }),
65
- 'remove-run-project': Flags.boolean({
66
- description: 'Remove run project from profile',
67
- required: false,
68
- default: false,
69
- }),
70
61
  run_base_url: Flags.string({
71
62
  char: 'r',
72
63
  description: 'Update Xano Run API base URL',
@@ -133,9 +124,9 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
133
124
  const existingProfile = credentials.profiles[profileName];
134
125
  // Check if any flags were provided
135
126
  const hasFlags = flags.account_origin || flags.instance_origin || flags.access_token ||
136
- flags.workspace || flags.branch || flags.project || flags['run-project'] || flags.run_base_url ||
127
+ flags.workspace || flags.branch || flags.project || flags.run_base_url ||
137
128
  flags['remove-workspace'] || flags['remove-branch'] || flags['remove-project'] ||
138
- flags['remove-run-project'] || flags['remove-run-base-url'];
129
+ flags['remove-run-base-url'];
139
130
  if (!hasFlags) {
140
131
  this.error('No fields specified to update. Use at least one flag to edit the profile.');
141
132
  }
@@ -148,7 +139,6 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
148
139
  ...(flags.workspace !== undefined && { workspace: flags.workspace }),
149
140
  ...(flags.branch !== undefined && { branch: flags.branch }),
150
141
  ...(flags.project !== undefined && { project: flags.project }),
151
- ...(flags['run-project'] !== undefined && { run_project: flags['run-project'] }),
152
142
  ...(flags.run_base_url !== undefined && { run_base_url: flags.run_base_url }),
153
143
  };
154
144
  // Handle removal flags
@@ -161,9 +151,6 @@ Profile 'default' updated successfully at ~/.xano/credentials.yaml
161
151
  if (flags['remove-project']) {
162
152
  delete updatedProfile.project;
163
153
  }
164
- if (flags['remove-run-project']) {
165
- delete updatedProfile.run_project;
166
- }
167
154
  if (flags['remove-run-base-url']) {
168
155
  delete updatedProfile.run_base_url;
169
156
  }
@@ -192,14 +192,16 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
192
192
  }
193
193
  }
194
194
  }
195
- // Step 7: Fetch run projects and auto-select the first one
196
- let runProject;
195
+ // Step 7: Fetch run projects and auto-select the first one if no project was selected
197
196
  this.log('');
198
197
  this.log('Fetching available run projects...');
199
198
  try {
200
199
  const runProjects = await this.fetchRunProjects(accessToken);
201
200
  if (runProjects.length > 0) {
202
- runProject = runProjects[0].id;
201
+ // Use run project if no metadata project was selected
202
+ if (!project) {
203
+ project = runProjects[0].id;
204
+ }
203
205
  this.log(`✓ Found ${runProjects.length} run project(s). Using "${runProjects[0].name}" as default.`);
204
206
  }
205
207
  else {
@@ -207,7 +209,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
207
209
  }
208
210
  }
209
211
  catch {
210
- // Silently ignore - run_project will remain undefined
212
+ // Silently ignore - project will remain undefined
211
213
  }
212
214
  // Save profile
213
215
  await this.saveProfile({
@@ -218,7 +220,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
218
220
  workspace,
219
221
  branch,
220
222
  project,
221
- run_project: runProject,
222
223
  }, true);
223
224
  this.log('');
224
225
  this.log(`✓ Profile '${profileName}' created successfully!`);
@@ -441,7 +442,6 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
441
442
  ...(profile.workspace && { workspace: profile.workspace }),
442
443
  ...(profile.branch && { branch: profile.branch }),
443
444
  ...(profile.project && { project: profile.project }),
444
- ...(profile.run_project && { run_project: profile.run_project }),
445
445
  };
446
446
  // Set as default if requested
447
447
  if (setAsDefault) {
@@ -1,6 +1,8 @@
1
1
  import BaseRunCommand from '../../../lib/base-run-command.js';
2
2
  export default class RunExec extends BaseRunCommand {
3
- static args: {};
3
+ static args: {
4
+ path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
4
6
  static flags: {
5
7
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
8
  stdin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -13,6 +15,14 @@ export default class RunExec extends BaseRunCommand {
13
15
  static description: string;
14
16
  static examples: string[];
15
17
  run(): Promise<void>;
18
+ /**
19
+ * Load all .xs files from a directory and combine them into a multidoc.
20
+ */
21
+ private loadMultidocFromDirectory;
22
+ /**
23
+ * Recursively collect all .xs files from a directory, sorted for deterministic ordering.
24
+ */
25
+ private collectFiles;
16
26
  private outputSummary;
17
27
  private editFile;
18
28
  private isUrl;
@@ -1,16 +1,21 @@
1
- import { Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
2
  import { execSync } from 'node:child_process';
3
3
  import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
6
  import BaseRunCommand from '../../../lib/base-run-command.js';
7
7
  export default class RunExec extends BaseRunCommand {
8
- static args = {};
8
+ static args = {
9
+ path: Args.string({
10
+ description: 'Path to file or directory containing XanoScript code (directory creates multidoc from .xs files)',
11
+ required: false,
12
+ }),
13
+ };
9
14
  static flags = {
10
15
  ...BaseRunCommand.baseFlags,
11
16
  file: Flags.string({
12
17
  char: 'f',
13
- description: 'Path or URL to file containing XanoScript code',
18
+ description: 'Path or URL to file containing XanoScript code (deprecated: use path argument instead)',
14
19
  required: false,
15
20
  exclusive: ['stdin'],
16
21
  }),
@@ -48,11 +53,16 @@ export default class RunExec extends BaseRunCommand {
48
53
  };
49
54
  static description = 'Execute XanoScript code (job or service)';
50
55
  static examples = [
51
- `$ xano run exec -f script.xs
56
+ `$ xano run exec script.xs
52
57
  Executed successfully!
53
58
  ...
54
59
  `,
55
- `$ xano run exec -f script.xs --edit
60
+ `$ xano run exec ./my-workspace
61
+ # Executes all .xs files in directory as multidoc
62
+ Executed successfully!
63
+ ...
64
+ `,
65
+ `$ xano run exec script.xs --edit
56
66
  # Opens script.xs in $EDITOR, then executes
57
67
  Executed successfully!
58
68
  ...
@@ -61,45 +71,51 @@ Executed successfully!
61
71
  Executed successfully!
62
72
  ...
63
73
  `,
64
- `$ xano run exec -f script.xs -o json
74
+ `$ xano run exec script.xs -o json
65
75
  {
66
76
  "run": { ... }
67
77
  }
68
78
  `,
69
- `$ xano run exec -f script.xs -a args.json
79
+ `$ xano run exec script.xs -a args.json
70
80
  # Executes with input arguments from args.json
71
81
  Executed successfully!
72
82
  ...
73
83
  `,
74
- `$ xano run exec -f script.xs --env API_KEY=secret --env DEBUG=true
84
+ `$ xano run exec script.xs --env API_KEY=secret --env DEBUG=true
75
85
  # Executes with environment variable overrides
76
86
  Executed successfully!
77
87
  ...
78
88
  `,
79
89
  ];
80
90
  async run() {
81
- const { flags } = await this.parse(RunExec);
91
+ const { args, flags } = await this.parse(RunExec);
82
92
  // Initialize with project required
83
93
  await this.initRunCommandWithProject(flags.profile);
84
94
  // Read XanoScript content
85
95
  let xanoscript;
86
- if (flags.file) {
87
- if (this.isUrl(flags.file)) {
96
+ // Determine input source: path argument, --file flag, or --stdin
97
+ const inputPath = args.path || flags.file;
98
+ if (inputPath) {
99
+ if (this.isUrl(inputPath)) {
88
100
  // Fetch URL content
89
101
  try {
90
- const response = await fetch(flags.file);
102
+ const response = await fetch(inputPath);
91
103
  if (!response.ok) {
92
104
  this.error(`Failed to fetch URL: ${response.status} ${response.statusText}`);
93
105
  }
94
106
  xanoscript = await response.text();
95
107
  }
96
108
  catch (error) {
97
- this.error(`Failed to fetch URL '${flags.file}': ${error}`);
109
+ this.error(`Failed to fetch URL '${inputPath}': ${error}`);
98
110
  }
99
111
  }
112
+ else if (fs.existsSync(inputPath) && fs.statSync(inputPath).isDirectory()) {
113
+ // Handle directory - collect .xs files and create multidoc
114
+ xanoscript = this.loadMultidocFromDirectory(inputPath);
115
+ }
100
116
  else if (flags.edit) {
101
117
  // If edit flag is set, copy to temp file and open in editor
102
- const fileToRead = await this.editFile(flags.file);
118
+ const fileToRead = await this.editFile(inputPath);
103
119
  xanoscript = fs.readFileSync(fileToRead, 'utf8');
104
120
  // Clean up temp file
105
121
  try {
@@ -111,10 +127,10 @@ Executed successfully!
111
127
  }
112
128
  else {
113
129
  try {
114
- xanoscript = fs.readFileSync(flags.file, 'utf8');
130
+ xanoscript = fs.readFileSync(inputPath, 'utf8');
115
131
  }
116
132
  catch (error) {
117
- this.error(`Failed to read file '${flags.file}': ${error}`);
133
+ this.error(`Failed to read file '${inputPath}': ${error}`);
118
134
  }
119
135
  }
120
136
  }
@@ -127,7 +143,7 @@ Executed successfully!
127
143
  }
128
144
  }
129
145
  else {
130
- this.error('Either --file or --stdin must be specified to provide XanoScript code');
146
+ this.error('Either a path argument, --file, or --stdin must be specified to provide XanoScript code');
131
147
  }
132
148
  // Validate xanoscript is not empty
133
149
  if (!xanoscript || xanoscript.trim().length === 0) {
@@ -202,6 +218,48 @@ Executed successfully!
202
218
  }
203
219
  }
204
220
  }
221
+ /**
222
+ * Load all .xs files from a directory and combine them into a multidoc.
223
+ */
224
+ loadMultidocFromDirectory(dir) {
225
+ const resolvedDir = path.resolve(dir);
226
+ if (!fs.existsSync(resolvedDir)) {
227
+ this.error(`Directory not found: ${resolvedDir}`);
228
+ }
229
+ const files = this.collectFiles(resolvedDir);
230
+ if (files.length === 0) {
231
+ this.error(`No .xs files found in ${dir}`);
232
+ }
233
+ // Read each file and join with --- separator
234
+ const documents = [];
235
+ for (const filePath of files) {
236
+ const content = fs.readFileSync(filePath, 'utf8').trim();
237
+ if (content) {
238
+ documents.push(content);
239
+ }
240
+ }
241
+ if (documents.length === 0) {
242
+ this.error(`All .xs files in ${dir} are empty`);
243
+ }
244
+ return documents.join('\n---\n');
245
+ }
246
+ /**
247
+ * Recursively collect all .xs files from a directory, sorted for deterministic ordering.
248
+ */
249
+ collectFiles(dir) {
250
+ const files = [];
251
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
252
+ for (const entry of entries) {
253
+ const fullPath = path.join(dir, entry.name);
254
+ if (entry.isDirectory()) {
255
+ files.push(...this.collectFiles(fullPath));
256
+ }
257
+ else if (entry.isFile() && entry.name.endsWith('.xs')) {
258
+ files.push(fullPath);
259
+ }
260
+ }
261
+ return files.sort();
262
+ }
205
263
  outputSummary(result) {
206
264
  this.log('Executed successfully!');
207
265
  this.log('');
@@ -10,7 +10,6 @@ export interface ProfileConfig {
10
10
  workspace?: string;
11
11
  branch?: string;
12
12
  project?: string;
13
- run_project?: string;
14
13
  run_base_url?: string;
15
14
  }
16
15
  export interface CredentialsFile {
@@ -26,12 +26,10 @@ export default class BaseRunCommand extends BaseCommand {
26
26
  this.error(`Profile '${this.profileName}' is missing access_token`);
27
27
  }
28
28
  const baseUrl = this.profile.run_base_url || DEFAULT_RUN_BASE_URL;
29
- // Use run_project if available, fall back to project for backward compatibility
30
- const projectId = this.profile.run_project || this.profile.project;
31
29
  this.httpClient = new RunHttpClient({
32
30
  baseUrl,
33
31
  authToken: this.profile.access_token,
34
- projectId,
32
+ projectId: this.profile.project,
35
33
  });
36
34
  }
37
35
  /**
@@ -39,9 +37,9 @@ export default class BaseRunCommand extends BaseCommand {
39
37
  */
40
38
  async initRunCommandWithProject(profileFlag) {
41
39
  await this.initRunCommand(profileFlag);
42
- if (!this.profile.run_project && !this.profile.project) {
43
- this.error(`Profile '${this.profileName}' is missing run_project. ` +
44
- `Run 'xano profile:wizard' to set up your profile or use 'xano profile:edit --run-project <project-id>'`);
40
+ if (!this.profile.project) {
41
+ this.error(`Profile '${this.profileName}' is missing project. ` +
42
+ `Run 'xano profile:wizard' to set up your profile or use 'xano profile:edit --project <project-id>'`);
45
43
  }
46
44
  }
47
45
  /**