@xano/cli 1.0.4-beta.12 → 1.0.4-beta.15
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.
|
@@ -5,7 +5,7 @@ import open from 'open';
|
|
|
5
5
|
import BaseCommand from '../../../base-command.js';
|
|
6
6
|
import { executePush } from '../../../utils/multidoc-push.js';
|
|
7
7
|
export default class SandboxPush extends BaseCommand {
|
|
8
|
-
static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to your sandbox environment via multidoc import. By default, only changed files are pushed (partial mode). Use --sync to push all files. Shows a preview of changes before pushing unless --force is specified. Use --dry-run to preview only. Include/exclude glob filters are intentionally not supported on sandbox push — partial pushes can hide deletions during review and lead to data loss when promoted to the workspace.
|
|
8
|
+
static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to your sandbox environment via multidoc import. By default, only changed files are pushed (partial mode). Use --sync to push all files. Shows a preview of changes before pushing unless --force is specified. Use --dry-run to preview only. Include/exclude glob filters are intentionally not supported on sandbox push — partial pushes can hide deletions during review and lead to data loss when promoted to the workspace. Pushing into a sandbox that currently holds a different workspace will prompt for confirmation; run `xano sandbox reset` first to start clean.';
|
|
9
9
|
static examples = [
|
|
10
10
|
`$ xano sandbox push
|
|
11
11
|
Push from current directory (default partial mode)
|
|
@@ -109,6 +109,7 @@ Push and open sandbox review in the browser
|
|
|
109
109
|
cliVersion: this.config.version,
|
|
110
110
|
instanceOrigin: profile.instance_origin,
|
|
111
111
|
label: 'sandbox environment',
|
|
112
|
+
sourceWorkspaceId: profile.workspace,
|
|
112
113
|
supportsBranches: false,
|
|
113
114
|
supportsPartial: true,
|
|
114
115
|
warnOnWorkspaceMismatch: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
import { pushKnowledge } from '../../../utils/knowledge-sync.js';
|
|
3
4
|
export default class SandboxReset extends BaseCommand {
|
|
4
5
|
static description = '[CRITICAL] NEVER run without explicit user confirmation; this clears all workspace data and drafts. Resets your sandbox environment.';
|
|
5
6
|
static examples = [
|
|
@@ -42,6 +43,17 @@ Sandbox environment has been reset.
|
|
|
42
43
|
const message = await this.parseApiError(response, 'API request failed');
|
|
43
44
|
this.error(message);
|
|
44
45
|
}
|
|
46
|
+
try {
|
|
47
|
+
const knowledgeUrl = `${profile.instance_origin}/api:meta/sandbox/knowledge/sync`;
|
|
48
|
+
await pushKnowledge(knowledgeUrl, profile.access_token, this.verboseFetch.bind(this), flags.verbose, {
|
|
49
|
+
delete: true,
|
|
50
|
+
force: true,
|
|
51
|
+
items: [],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.warn(`Sandbox environment was reset, but clearing knowledge files failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
}
|
|
45
57
|
this.log('Sandbox environment has been reset.');
|
|
46
58
|
}
|
|
47
59
|
catch (error) {
|
|
@@ -25,14 +25,19 @@ export interface PushTarget {
|
|
|
25
25
|
instanceOrigin: string;
|
|
26
26
|
/** Human-readable label for log messages (e.g., "sandbox environment", "workspace 40") */
|
|
27
27
|
label: string;
|
|
28
|
+
/**
|
|
29
|
+
* The id of the source workspace being pushed (from the active profile). Sent to the sandbox
|
|
30
|
+
* so the backend can flag a mismatch when it differs from the workspace the sandbox last held.
|
|
31
|
+
*/
|
|
32
|
+
sourceWorkspaceId?: string;
|
|
28
33
|
/** Does this target support branches? */
|
|
29
34
|
supportsBranches: boolean;
|
|
30
35
|
/** Does this target support the partial query param? */
|
|
31
36
|
supportsPartial: boolean;
|
|
32
37
|
/**
|
|
33
|
-
* Warn when the
|
|
34
|
-
*
|
|
35
|
-
* across workspaces and pushing onto a different
|
|
38
|
+
* Warn when the sandbox currently holds a different source workspace than the one being pushed
|
|
39
|
+
* (per the dry-run's `source_workspace_mismatch` flag). Used by sandbox push because the sandbox
|
|
40
|
+
* is shared across workspaces and pushing onto a different one can leave stale state behind.
|
|
36
41
|
*/
|
|
37
42
|
warnOnWorkspaceMismatch?: boolean;
|
|
38
43
|
}
|
|
@@ -51,17 +56,6 @@ export interface PushContext {
|
|
|
51
56
|
knowledge?: KnowledgeConfig;
|
|
52
57
|
verboseFetch: (url: string, options: RequestInit, verbose: boolean, authToken?: string) => Promise<Response>;
|
|
53
58
|
}
|
|
54
|
-
export declare const WORKSPACE_MISMATCH_THRESHOLD = 10;
|
|
55
|
-
/**
|
|
56
|
-
* Sum all impactful operations in a dry-run summary. `deleted` is only counted when the
|
|
57
|
-
* caller actually intends to apply deletions (sync mode), matching what the user will see.
|
|
58
|
-
*/
|
|
59
|
-
export declare function countSummaryChanges(summary: Record<string, {
|
|
60
|
-
created: number;
|
|
61
|
-
deleted: number;
|
|
62
|
-
truncated: number;
|
|
63
|
-
updated: number;
|
|
64
|
-
}>, shouldDelete: boolean): number;
|
|
65
59
|
/**
|
|
66
60
|
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
67
61
|
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
@@ -105,10 +99,6 @@ export declare function readDocuments(files: string[]): Array<{
|
|
|
105
99
|
}>;
|
|
106
100
|
export declare function renderBadReferences(badRefs: BadReference[], log: (msg: string) => void): void;
|
|
107
101
|
export declare function renderBadIndexes(badIndexes: BadIndex[], log: (msg: string) => void): void;
|
|
108
|
-
export declare function findLocalWorkspaceName(entries: Array<{
|
|
109
|
-
content: string;
|
|
110
|
-
filePath: string;
|
|
111
|
-
}>): null | string;
|
|
112
102
|
export declare function confirm(message: string): Promise<boolean>;
|
|
113
103
|
/**
|
|
114
104
|
* Execute a multidoc push with preview, validation, partial mode, and GUID sync.
|
|
@@ -5,16 +5,6 @@ import { join, relative } from 'node:path';
|
|
|
5
5
|
import { buildDocumentKey, findFilesWithGuid, parseDocument } from './document-parser.js';
|
|
6
6
|
import { collectKnowledgeObjects, fetchKnowledge, knowledgePreview, pushKnowledge, syncGuidToFrontmatter, toPushItems, } from './knowledge-sync.js';
|
|
7
7
|
import { checkReferences, checkTableIndexes } from './reference-checker.js';
|
|
8
|
-
// Minimum total operations before a workspace mismatch is treated as worth interrupting for.
|
|
9
|
-
// Small change sets (e.g., editing a single function) aren't worth a reset prompt.
|
|
10
|
-
export const WORKSPACE_MISMATCH_THRESHOLD = 10;
|
|
11
|
-
/**
|
|
12
|
-
* Sum all impactful operations in a dry-run summary. `deleted` is only counted when the
|
|
13
|
-
* caller actually intends to apply deletions (sync mode), matching what the user will see.
|
|
14
|
-
*/
|
|
15
|
-
export function countSummaryChanges(summary, shouldDelete) {
|
|
16
|
-
return Object.values(summary).reduce((sum, c) => sum + c.created + c.updated + (shouldDelete ? c.deleted : 0) + c.truncated, 0);
|
|
17
|
-
}
|
|
18
8
|
/**
|
|
19
9
|
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
20
10
|
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
@@ -263,14 +253,6 @@ function renderPreview(result, willDelete, target, verbose, partial, log) {
|
|
|
263
253
|
}
|
|
264
254
|
log('');
|
|
265
255
|
}
|
|
266
|
-
export function findLocalWorkspaceName(entries) {
|
|
267
|
-
for (const entry of entries) {
|
|
268
|
-
const parsed = parseDocument(entry.content);
|
|
269
|
-
if (parsed?.type === 'workspace')
|
|
270
|
-
return parsed.name;
|
|
271
|
-
}
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
256
|
// ── Confirmation ────────────────────────────────────────────────────────────
|
|
275
257
|
export async function confirm(message) {
|
|
276
258
|
const readline = await import('node:readline');
|
|
@@ -435,6 +417,9 @@ export async function executePush(ctx, target, flags) {
|
|
|
435
417
|
if (target.supportsPartial) {
|
|
436
418
|
queryParams.set('partial', isPartial.toString());
|
|
437
419
|
}
|
|
420
|
+
if (target.sourceWorkspaceId) {
|
|
421
|
+
queryParams.set('source_workspace_id', target.sourceWorkspaceId);
|
|
422
|
+
}
|
|
438
423
|
// ── Request headers ───────────────────────────────────────────────────
|
|
439
424
|
const requestHeaders = {
|
|
440
425
|
accept: 'application/json',
|
|
@@ -530,32 +515,28 @@ export async function executePush(ctx, target, flags) {
|
|
|
530
515
|
if (flags['dry-run']) {
|
|
531
516
|
return;
|
|
532
517
|
}
|
|
533
|
-
// Warn when the sandbox currently holds a different workspace than the one being
|
|
534
|
-
// pushed
|
|
518
|
+
// Warn when the sandbox currently holds a different source workspace than the one being
|
|
519
|
+
// pushed. The backend compares the source workspace id we sent against the id stored on
|
|
520
|
+
// the sandbox from its last push, so this is reliable regardless of whether the push
|
|
521
|
+
// includes a workspace-settings document (unlike the old name comparison).
|
|
535
522
|
let mismatchConfirmed = false;
|
|
536
|
-
if (target.warnOnWorkspaceMismatch && preview.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (process.stdin.isTTY) {
|
|
549
|
-
const proceed = await confirm('Continue with push anyway?');
|
|
550
|
-
if (!proceed) {
|
|
551
|
-
log('Push cancelled. Run `xano sandbox reset` then retry.');
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
mismatchConfirmed = true;
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
command.error('Workspace mismatch detected in non-interactive mode. Run `xano sandbox reset` first to start clean.');
|
|
523
|
+
if (target.warnOnWorkspaceMismatch && preview.source_workspace_mismatch) {
|
|
524
|
+
log('');
|
|
525
|
+
log(ux.colorize('yellow', ux.colorize('bold', '=== Workspace Mismatch ===')));
|
|
526
|
+
log('');
|
|
527
|
+
log(ux.colorize('yellow', "This sandbox currently holds a different workspace than the one you're pushing."));
|
|
528
|
+
log(ux.colorize('yellow', 'Pushing on top of it can leave stale data behind. Run `xano sandbox reset` first to start clean.'));
|
|
529
|
+
log('');
|
|
530
|
+
if (process.stdin.isTTY) {
|
|
531
|
+
const proceed = await confirm('Continue with push anyway?');
|
|
532
|
+
if (!proceed) {
|
|
533
|
+
log('Push cancelled. Run `xano sandbox reset` then retry.');
|
|
534
|
+
return;
|
|
558
535
|
}
|
|
536
|
+
mismatchConfirmed = true;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
command.error('Workspace mismatch detected in non-interactive mode. Run `xano sandbox reset` first to start clean.');
|
|
559
540
|
}
|
|
560
541
|
}
|
|
561
542
|
// Confirm with user (skip if workspace mismatch prompt already obtained confirmation)
|