@xano/cli 0.0.84 → 0.0.85
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 +1 -1
- package/dist/commands/workspace/push/index.js +35 -9
- package/oclif.manifest.json +1387 -1387
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,7 +118,7 @@ xano workspace pull ./my-workspace --draft # Include draft changes
|
|
|
118
118
|
xano workspace push ./my-workspace
|
|
119
119
|
xano workspace push ./my-workspace -b dev
|
|
120
120
|
xano workspace push ./my-workspace --dry-run # Preview changes without pushing
|
|
121
|
-
xano workspace push ./my-workspace --partial #
|
|
121
|
+
xano workspace push ./my-workspace --partial # Push only changed files, workspace block not required
|
|
122
122
|
xano workspace push ./my-workspace --delete # Delete objects not in the push
|
|
123
123
|
xano workspace push ./my-workspace --records # Include table records
|
|
124
124
|
xano workspace push ./my-workspace --env # Include environment variables
|
|
@@ -164,7 +164,7 @@ Truncate all table records before importing
|
|
|
164
164
|
if (documentEntries.length === 0) {
|
|
165
165
|
this.error(`All .xs files in ${args.directory} are empty`);
|
|
166
166
|
}
|
|
167
|
-
|
|
167
|
+
let multidoc = documentEntries.map((d) => d.content).join('\n---\n');
|
|
168
168
|
// Build lookup map from document key to file path (for GUID writeback)
|
|
169
169
|
const documentFileMap = new Map();
|
|
170
170
|
for (const entry of documentEntries) {
|
|
@@ -176,7 +176,9 @@ Truncate all table records before importing
|
|
|
176
176
|
}
|
|
177
177
|
// Determine branch from flag or profile
|
|
178
178
|
const branch = flags.branch || profile.branch || '';
|
|
179
|
-
|
|
179
|
+
if (flags.partial && flags.delete) {
|
|
180
|
+
this.error('Cannot use --delete with --partial');
|
|
181
|
+
}
|
|
180
182
|
const shouldDelete = flags.partial ? false : flags.delete;
|
|
181
183
|
// Construct the API URL
|
|
182
184
|
const queryParams = new URLSearchParams({
|
|
@@ -195,10 +197,13 @@ Truncate all table records before importing
|
|
|
195
197
|
'Content-Type': 'text/x-xanoscript',
|
|
196
198
|
};
|
|
197
199
|
// Preview mode: show what would change before pushing
|
|
200
|
+
let dryRunPreview = null;
|
|
198
201
|
if (flags['dry-run'] || !flags.force) {
|
|
199
202
|
const dryRunParams = new URLSearchParams(queryParams);
|
|
200
|
-
//
|
|
201
|
-
|
|
203
|
+
// Request delete info in dry-run so we can show remote-only items (skip for partial)
|
|
204
|
+
if (!flags.partial) {
|
|
205
|
+
dryRunParams.set('delete', 'true');
|
|
206
|
+
}
|
|
202
207
|
const dryRunUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc/dry-run?${dryRunParams.toString()}`;
|
|
203
208
|
try {
|
|
204
209
|
const dryRunResponse = await this.verboseFetch(dryRunUrl, {
|
|
@@ -247,9 +252,10 @@ Truncate all table records before importing
|
|
|
247
252
|
else {
|
|
248
253
|
const dryRunText = await dryRunResponse.text();
|
|
249
254
|
const preview = JSON.parse(dryRunText);
|
|
255
|
+
dryRunPreview = preview;
|
|
250
256
|
// Check if the server returned a valid dry-run response
|
|
251
257
|
if (preview && preview.summary) {
|
|
252
|
-
this.renderPreview(preview, shouldDelete, workspaceId, flags.verbose);
|
|
258
|
+
this.renderPreview(preview, shouldDelete, workspaceId, flags.verbose, flags.partial);
|
|
253
259
|
// Check for critical errors that must block the push
|
|
254
260
|
const criticalOps = preview.operations.filter((op) => op.details?.includes('exception:') || op.details?.includes('mvp:placeholder'));
|
|
255
261
|
if (criticalOps.length > 0) {
|
|
@@ -367,6 +373,25 @@ Truncate all table records before importing
|
|
|
367
373
|
}
|
|
368
374
|
}
|
|
369
375
|
}
|
|
376
|
+
// For partial pushes, filter to only changed documents
|
|
377
|
+
if (flags.partial && dryRunPreview) {
|
|
378
|
+
const changedKeys = new Set(dryRunPreview.operations
|
|
379
|
+
.filter((op) => op.action !== 'unchanged' && op.action !== 'delete' && op.action !== 'cascade_delete')
|
|
380
|
+
.map((op) => `${op.type}:${op.name}`));
|
|
381
|
+
const filteredEntries = documentEntries.filter((entry) => {
|
|
382
|
+
const parsed = parseDocument(entry.content);
|
|
383
|
+
if (!parsed)
|
|
384
|
+
return true;
|
|
385
|
+
// For queries, operation name includes verb (e.g., "path/{id} DELETE")
|
|
386
|
+
const opName = parsed.verb ? `${parsed.name} ${parsed.verb}` : parsed.name;
|
|
387
|
+
return changedKeys.has(`${parsed.type}:${opName}`);
|
|
388
|
+
});
|
|
389
|
+
if (filteredEntries.length === 0) {
|
|
390
|
+
this.log('No changes to push.');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
multidoc = filteredEntries.map((d) => d.content).join('\n---\n');
|
|
394
|
+
}
|
|
370
395
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
|
|
371
396
|
const startTime = Date.now();
|
|
372
397
|
try {
|
|
@@ -474,7 +499,8 @@ Truncate all table records before importing
|
|
|
474
499
|
}
|
|
475
500
|
}
|
|
476
501
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
477
|
-
|
|
502
|
+
const pushedCount = multidoc.split('\n---\n').length;
|
|
503
|
+
this.log(`Pushed ${pushedCount} documents from ${args.directory} in ${elapsed}s`);
|
|
478
504
|
}
|
|
479
505
|
async confirm(message) {
|
|
480
506
|
const readline = await import('node:readline');
|
|
@@ -495,7 +521,7 @@ Truncate all table records before importing
|
|
|
495
521
|
});
|
|
496
522
|
});
|
|
497
523
|
}
|
|
498
|
-
renderPreview(result, willDelete, workspaceId, verbose = false) {
|
|
524
|
+
renderPreview(result, willDelete, workspaceId, verbose = false, partial = false) {
|
|
499
525
|
const typeLabels = {
|
|
500
526
|
addon: 'Addons',
|
|
501
527
|
agent: 'Agents',
|
|
@@ -593,8 +619,8 @@ Truncate all table records before importing
|
|
|
593
619
|
this.log(ux.colorize('yellow', ' If this is intended to be a field rename, use the Xano Admin — renaming is not'));
|
|
594
620
|
this.log(ux.colorize('yellow', ' currently available through the CLI or Metadata API.'));
|
|
595
621
|
}
|
|
596
|
-
// Show remote-only items when not using --delete
|
|
597
|
-
if (!willDelete && deleteOps.length > 0) {
|
|
622
|
+
// Show remote-only items when not using --delete (skip for partial pushes)
|
|
623
|
+
if (!willDelete && !partial && deleteOps.length > 0) {
|
|
598
624
|
this.log('');
|
|
599
625
|
this.log(ux.colorize('dim', '--- Remote Only (not included in push) ---'));
|
|
600
626
|
this.log('');
|