@xano/cli 0.0.41 → 0.0.43

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
@@ -106,9 +106,12 @@ xano workspace pull ./my-workspace --draft # Include draft changes
106
106
  # Push local files to workspace
107
107
  xano workspace push ./my-workspace
108
108
  xano workspace push ./my-workspace -b dev
109
- xano workspace push ./my-workspace --no-records # Schema only
110
- xano workspace push ./my-workspace --no-env # Skip env vars
111
- xano workspace push ./my-workspace --truncate # Truncate tables before import
109
+ xano workspace push ./my-workspace --partial # No workspace block required
110
+ xano workspace push ./my-workspace --delete # Delete objects not in the push
111
+ xano workspace push ./my-workspace --no-records # Schema only
112
+ xano workspace push ./my-workspace --no-env # Skip env vars
113
+ xano workspace push ./my-workspace --truncate # Truncate tables before import
114
+ xano workspace push ./my-workspace --no-sync-guids # Skip writing GUIDs back to local files
112
115
  ```
113
116
 
114
117
  ### Branches
@@ -1,3 +1,4 @@
1
+ import { ExitPromptError } from '@inquirer/core';
1
2
  import { Command, Flags } from '@oclif/core';
2
3
  import inquirer from 'inquirer';
3
4
  import * as yaml from 'js-yaml';
@@ -82,7 +83,7 @@ Opening browser for Xano login at https://custom.xano.com...`,
82
83
  process.exit(0);
83
84
  }
84
85
  catch (error) {
85
- if (error instanceof Error && error.message.includes('User force closed the prompt')) {
86
+ if (error instanceof ExitPromptError) {
86
87
  this.log('Authentication cancelled.');
87
88
  return;
88
89
  }
@@ -233,7 +234,7 @@ Opening browser for Xano login at https://custom.xano.com...`,
233
234
  ],
234
235
  message: 'Select a branch',
235
236
  name: 'selectedBranch',
236
- type: 'list',
237
+ type: 'select',
237
238
  },
238
239
  ]);
239
240
  return selectedBranch || undefined;
@@ -247,7 +248,7 @@ Opening browser for Xano login at https://custom.xano.com...`,
247
248
  })),
248
249
  message: 'Select an instance',
249
250
  name: 'instanceId',
250
- type: 'list',
251
+ type: 'select',
251
252
  },
252
253
  ]);
253
254
  return instances.find((inst) => inst.id === instanceId);
@@ -264,7 +265,7 @@ Opening browser for Xano login at https://custom.xano.com...`,
264
265
  ],
265
266
  message: 'Select a workspace',
266
267
  name: 'selectedWorkspace',
267
- type: 'list',
268
+ type: 'select',
268
269
  },
269
270
  ]);
270
271
  if (!selectedWorkspace) {
@@ -442,7 +442,7 @@ Name: my_function
442
442
  choices,
443
443
  message: 'Select a function to edit:',
444
444
  name: 'functionId',
445
- type: 'list',
445
+ type: 'select',
446
446
  },
447
447
  ]);
448
448
  return answer.functionId;
@@ -257,7 +257,7 @@ function yo {
257
257
  choices,
258
258
  message: 'Select a function:',
259
259
  name: 'functionId',
260
- type: 'list',
260
+ type: 'select',
261
261
  },
262
262
  ]);
263
263
  return answer.functionId;
@@ -1,3 +1,4 @@
1
+ import { ExitPromptError } from '@inquirer/core';
1
2
  import { Command, Flags } from '@oclif/core';
2
3
  import inquirer from 'inquirer';
3
4
  import * as yaml from 'js-yaml';
@@ -73,7 +74,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
73
74
  })),
74
75
  message: 'Select an instance',
75
76
  name: 'instanceId',
76
- type: 'list',
77
+ type: 'select',
77
78
  },
78
79
  ]);
79
80
  const selectedInstance = instances.find((inst) => inst.id === instanceId);
@@ -120,7 +121,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
120
121
  ],
121
122
  message: 'Select a workspace (or skip to use default)',
122
123
  name: 'selectedWorkspace',
123
- type: 'list',
124
+ type: 'select',
124
125
  },
125
126
  ]);
126
127
  workspace = selectedWorkspace || undefined;
@@ -150,7 +151,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
150
151
  ],
151
152
  message: 'Select a branch',
152
153
  name: 'selectedBranch',
153
- type: 'list',
154
+ type: 'select',
154
155
  },
155
156
  ]);
156
157
  branch = selectedBranch || undefined;
@@ -170,7 +171,7 @@ Profile 'production' created successfully at ~/.xano/credentials.yaml
170
171
  this.log(`✓ Profile '${profileName}' created successfully!`);
171
172
  }
172
173
  catch (error) {
173
- if (error instanceof Error && error.message.includes('User force closed the prompt')) {
174
+ if (error instanceof ExitPromptError) {
174
175
  this.log('Wizard cancelled.');
175
176
  process.exit(0);
176
177
  }
@@ -7,7 +7,9 @@ export default class Push extends BaseCommand {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ partial: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
13
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
14
  'sync-guids': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
15
  truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -25,6 +25,12 @@ Pushed 58 documents from ./backup
25
25
  `,
26
26
  `$ xano workspace push ./my-workspace -b dev
27
27
  Pushed 42 documents from ./my-workspace
28
+ `,
29
+ `$ xano workspace push ./my-functions --partial
30
+ Push some files without a workspace block (implies --no-delete)
31
+ `,
32
+ `$ xano workspace push ./my-workspace --no-delete
33
+ Patch files without deleting existing workspace objects
28
34
  `,
29
35
  `$ xano workspace push ./my-workspace --no-records
30
36
  Push schema only, skip importing table records
@@ -49,12 +55,23 @@ Push schema only, skip records and environment variables
49
55
  description: 'Branch name (optional if set in profile, defaults to live)',
50
56
  required: false,
51
57
  }),
58
+ delete: Flags.boolean({
59
+ allowNo: true,
60
+ default: false,
61
+ description: 'Delete workspace objects not included in the push (default: false)',
62
+ required: false,
63
+ }),
52
64
  env: Flags.boolean({
53
65
  allowNo: true,
54
66
  default: true,
55
67
  description: 'Include environment variables in import (default: true, use --no-env to exclude)',
56
68
  required: false,
57
69
  }),
70
+ partial: Flags.boolean({
71
+ default: false,
72
+ description: 'Partial push — workspace block is not required, existing objects are kept (implies --no-delete)',
73
+ required: false,
74
+ }),
58
75
  records: Flags.boolean({
59
76
  allowNo: true,
60
77
  default: true,
@@ -146,10 +163,14 @@ Push schema only, skip records and environment variables
146
163
  }
147
164
  // Determine branch from flag or profile
148
165
  const branch = flags.branch || profile.branch || '';
166
+ // --partial implies --no-delete
167
+ const shouldDelete = flags.partial ? false : flags.delete;
149
168
  // Construct the API URL
150
169
  const queryParams = new URLSearchParams({
151
170
  branch,
171
+ delete: shouldDelete.toString(),
152
172
  env: flags.env.toString(),
173
+ partial: flags.partial.toString(),
153
174
  records: flags.records.toString(),
154
175
  truncate: flags.truncate.toString(),
155
176
  });
@@ -200,12 +221,33 @@ Push schema only, skip records and environment variables
200
221
  }
201
222
  // Write GUIDs back to local files
202
223
  if (flags['sync-guids'] && guidMap.length > 0) {
224
+ // Build a secondary lookup by type:name only (without verb/api_group)
225
+ // for cases where the server omits those fields
226
+ const baseKeyMap = new Map();
227
+ for (const [key, fp] of documentFileMap) {
228
+ const baseKey = key.split(':').slice(0, 2).join(':');
229
+ // Only use base key if there's no ambiguity (single entry per base key)
230
+ if (baseKeyMap.has(baseKey)) {
231
+ baseKeyMap.set(baseKey, ''); // Mark as ambiguous
232
+ }
233
+ else {
234
+ baseKeyMap.set(baseKey, fp);
235
+ }
236
+ }
203
237
  let updatedCount = 0;
204
238
  for (const entry of guidMap) {
205
239
  if (!entry.guid)
206
240
  continue;
207
241
  const key = buildDocumentKey(entry.type, entry.name, entry.verb, entry.api_group);
208
- const filePath = documentFileMap.get(key);
242
+ let filePath = documentFileMap.get(key);
243
+ // Fallback: try type:name only if full key didn't match
244
+ if (!filePath) {
245
+ const baseKey = `${entry.type}:${entry.name}`;
246
+ const basePath = baseKeyMap.get(baseKey);
247
+ if (basePath) {
248
+ filePath = basePath;
249
+ }
250
+ }
209
251
  if (!filePath) {
210
252
  if (flags.verbose) {
211
253
  this.log(` No local file found for ${entry.type} "${entry.name}", skipping GUID sync`);