@xano/cli 0.0.79 → 0.0.80-beta.0

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.
@@ -9,6 +9,7 @@ export default class Push extends BaseCommand {
9
9
  env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  tenant: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ transaction: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
13
  truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
14
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -50,6 +50,12 @@ Truncate all table records before importing
50
50
  description: 'Tenant name to push to',
51
51
  required: true,
52
52
  }),
53
+ transaction: Flags.boolean({
54
+ allowNo: true,
55
+ default: true,
56
+ description: 'Wrap import in a database transaction (use --no-transaction for debugging purposes)',
57
+ required: false,
58
+ }),
53
59
  truncate: Flags.boolean({
54
60
  default: false,
55
61
  description: 'Truncate all table records before importing',
@@ -157,6 +163,7 @@ Truncate all table records before importing
157
163
  const queryParams = new URLSearchParams({
158
164
  env: flags.env.toString(),
159
165
  records: flags.records.toString(),
166
+ transaction: flags.transaction.toString(),
160
167
  truncate: flags.truncate.toString(),
161
168
  });
162
169
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { execSync } from 'node:child_process';
3
3
  import BaseCommand from '../../base-command.js';
4
+ import { clearUpdateCache } from '../../update-check.js';
4
5
  export default class Update extends BaseCommand {
5
6
  static description = 'Update the Xano CLI to the latest version';
6
7
  static examples = [`$ xano update`, `$ xano update --check`, `$ xano update --beta`];
@@ -31,6 +32,7 @@ export default class Update extends BaseCommand {
31
32
  }
32
33
  this.log(`Updating @xano/cli ${currentVersion} → ${latest}...`);
33
34
  execSync(`npm install -g @xano/cli@${tag} --no-fund`, { stdio: 'inherit' });
35
+ clearUpdateCache();
34
36
  this.log(`Updated to ${latest}`);
35
37
  }
36
38
  catch (error) {
@@ -13,6 +13,7 @@ export default class Push extends BaseCommand {
13
13
  partial: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
14
  records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  'sync-guids': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ transaction: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
17
  truncate: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
18
  workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -86,6 +86,12 @@ Truncate all table records before importing
86
86
  description: 'Write server-assigned GUIDs back to local files (use --no-sync-guids to skip)',
87
87
  required: false,
88
88
  }),
89
+ transaction: Flags.boolean({
90
+ allowNo: true,
91
+ default: true,
92
+ description: 'Wrap import in a database transaction (use --no-transaction for debugging purposes)',
93
+ required: false,
94
+ }),
89
95
  truncate: Flags.boolean({
90
96
  default: false,
91
97
  description: 'Truncate all table records before importing',
@@ -179,6 +185,7 @@ Truncate all table records before importing
179
185
  env: flags.env.toString(),
180
186
  partial: flags.partial.toString(),
181
187
  records: flags.records.toString(),
188
+ transaction: flags.transaction.toString(),
182
189
  truncate: flags.truncate.toString(),
183
190
  });
184
191
  // POST the multidoc to the API
@@ -245,7 +252,27 @@ Truncate all table records before importing
245
252
  this.renderPreview(preview, shouldDelete, workspaceId, flags.verbose);
246
253
  // Check if there are any actual changes (exclude deletes when --delete is off)
247
254
  const hasChanges = Object.values(preview.summary).some((c) => c.created > 0 || c.updated > 0 || (shouldDelete && c.deleted > 0) || c.truncated > 0);
248
- if (!hasChanges) {
255
+ // Detect if local files contain records that would be imported
256
+ const tablesWithRecords = flags.records
257
+ ? documentEntries
258
+ .filter((d) => /^table\s+/m.test(d.content) && /\bitems\s*=\s*\[/m.test(d.content))
259
+ .map((d) => {
260
+ const nameMatch = d.content.match(/^table\s+(\S+)/m);
261
+ const itemCount = (d.content.match(/^\s*\{$/gm) || []).length;
262
+ return { name: nameMatch ? nameMatch[1] : 'unknown', records: itemCount };
263
+ })
264
+ : [];
265
+ const hasLocalRecords = tablesWithRecords.length > 0;
266
+ if (hasLocalRecords) {
267
+ this.log('');
268
+ this.log(ux.colorize('bold', '--- Records ---'));
269
+ this.log('');
270
+ for (const t of tablesWithRecords) {
271
+ this.log(` ${ux.colorize('yellow', 'UPSERT'.padEnd(16))} ${'table'.padEnd(18)} ${t.name} (${t.records} records)`);
272
+ }
273
+ this.log('');
274
+ }
275
+ if (!hasChanges && !hasLocalRecords) {
249
276
  this.log('');
250
277
  this.log('No changes to push.');
251
278
  return;
@@ -1,3 +1,4 @@
1
+ export declare function clearUpdateCache(): void;
1
2
  /**
2
3
  * Check if an update is available. Returns an update message string if there
3
4
  * is an update, or null if the CLI is up to date / on a beta / check not due.
@@ -7,6 +7,18 @@ const CHECK_INTERVAL_MS = 8 * 60 * 60 * 1000; // 8 hours
7
7
  function isBeta(version) {
8
8
  return version.includes('beta') || version.includes('alpha') || version.includes('rc');
9
9
  }
10
+ /** Returns true if `latest` is a higher semver than `current`. */
11
+ function isNewer(latest, current) {
12
+ const a = latest.split('.').map(Number);
13
+ const b = current.split('.').map(Number);
14
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
15
+ if ((a[i] ?? 0) > (b[i] ?? 0))
16
+ return true;
17
+ if ((a[i] ?? 0) < (b[i] ?? 0))
18
+ return false;
19
+ }
20
+ return false;
21
+ }
10
22
  function readCache() {
11
23
  try {
12
24
  if (!fs.existsSync(UPDATE_CHECK_FILE))
@@ -21,6 +33,16 @@ function readCache() {
21
33
  return null;
22
34
  }
23
35
  }
36
+ export function clearUpdateCache() {
37
+ try {
38
+ if (fs.existsSync(UPDATE_CHECK_FILE)) {
39
+ fs.unlinkSync(UPDATE_CHECK_FILE);
40
+ }
41
+ }
42
+ catch {
43
+ // Silently fail
44
+ }
45
+ }
24
46
  function writeCache(latestVersion) {
25
47
  try {
26
48
  const dir = path.dirname(UPDATE_CHECK_FILE);
@@ -62,7 +84,7 @@ export function checkForUpdate(currentVersion, forceCheck = false) {
62
84
  writeCache(latestVersion);
63
85
  }
64
86
  }
65
- if (!latestVersion || latestVersion === currentVersion)
87
+ if (!latestVersion || !isNewer(latestVersion, currentVersion))
66
88
  return null;
67
89
  const yellow = '\u001B[33m';
68
90
  const cyan = '\u001B[36m';