@xano/cli 1.0.5 → 1.0.6-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.
@@ -0,0 +1,143 @@
1
+ import { Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import BaseCommand from '../../../base-command.js';
5
+ import { executePush } from '../../../utils/multidoc-push.js';
6
+ export default class EphemeralPush extends BaseCommand {
7
+ static description = '[IMPORTANT] ALWAYS run --dry-run first and show the user the output before pushing. Push local documents to an ephemeral tenant 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.';
8
+ static examples = [
9
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1
10
+ Push from current directory (default partial mode)
11
+ `,
12
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 -d ./my-workspace
13
+ Push from a specific directory
14
+ `,
15
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --sync
16
+ Push all files to the ephemeral tenant
17
+ `,
18
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --sync --delete
19
+ Push all files and delete remote objects not included
20
+ `,
21
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --dry-run
22
+ Preview changes without pushing
23
+ `,
24
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --force
25
+ Skip preview and push immediately
26
+ `,
27
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --records --env`,
28
+ `$ xano ephemeral push -t e4f2-9ab1-xyz1 --truncate`,
29
+ ];
30
+ static flags = {
31
+ ...BaseCommand.baseFlags,
32
+ directory: Flags.string({
33
+ char: 'd',
34
+ default: '.',
35
+ description: 'Directory containing documents to push (defaults to current directory)',
36
+ required: false,
37
+ }),
38
+ delete: Flags.boolean({
39
+ default: false,
40
+ description: 'Delete remote objects not included in the push (requires --sync)',
41
+ required: false,
42
+ }),
43
+ 'dry-run': Flags.boolean({
44
+ default: false,
45
+ description: 'Show preview of changes without pushing (exit after preview)',
46
+ required: false,
47
+ }),
48
+ env: Flags.boolean({
49
+ default: false,
50
+ description: 'Include environment variables in import',
51
+ required: false,
52
+ }),
53
+ force: Flags.boolean({
54
+ default: false,
55
+ description: 'Skip preview and confirmation prompt (for CI/CD pipelines)',
56
+ required: false,
57
+ }),
58
+ guids: Flags.boolean({
59
+ allowNo: true,
60
+ default: true,
61
+ description: 'Write server-assigned GUIDs back to local files (use --no-guids to skip)',
62
+ required: false,
63
+ }),
64
+ records: Flags.boolean({
65
+ default: false,
66
+ description: 'Include records in import',
67
+ required: false,
68
+ }),
69
+ sync: Flags.boolean({
70
+ default: false,
71
+ description: 'Full push — send all files, not just changed ones. Required for --delete.',
72
+ required: false,
73
+ }),
74
+ tenant: Flags.string({
75
+ char: 't',
76
+ description: 'Ephemeral tenant name to push to',
77
+ required: true,
78
+ }),
79
+ transaction: Flags.boolean({
80
+ allowNo: true,
81
+ default: true,
82
+ description: '[CRITICAL] DO NOT run with --no-transaction without explicit user confirmation; this disables rollback. Wraps import in a database transaction (use --no-transaction for debugging purposes).',
83
+ required: false,
84
+ }),
85
+ truncate: Flags.boolean({
86
+ default: false,
87
+ description: '[CRITICAL] STOP and confirm with the user; this truncates live tables before importing.',
88
+ required: false,
89
+ }),
90
+ workspace: Flags.string({
91
+ char: 'w',
92
+ description: 'Workspace ID (uses profile workspace if not provided)',
93
+ required: false,
94
+ }),
95
+ };
96
+ async run() {
97
+ const { flags } = await this.parse(EphemeralPush);
98
+ const { profile } = this.resolveProfile(flags);
99
+ const workspaceId = flags.workspace || profile.workspace;
100
+ if (!workspaceId) {
101
+ this.error('No workspace ID provided. Ephemeral tenants require a workspace — use --workspace or set one in your profile.');
102
+ }
103
+ const inputDir = resolve(flags.directory);
104
+ if (!fs.existsSync(inputDir)) {
105
+ this.error(`Directory not found: ${inputDir}`);
106
+ }
107
+ if (!fs.statSync(inputDir).isDirectory()) {
108
+ this.error(`Not a directory: ${inputDir}`);
109
+ }
110
+ const tenantName = flags.tenant;
111
+ const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
112
+ const target = {
113
+ buildDryRunUrl: (params) => `${baseUrl}/multidoc/dry-run?${params.toString()}`,
114
+ buildPushUrl: (params) => `${baseUrl}/multidoc?${params.toString()}`,
115
+ cliVersion: this.config.version,
116
+ instanceOrigin: profile.instance_origin,
117
+ label: `ephemeral tenant ${tenantName}`,
118
+ sourceWorkspaceId: profile.workspace,
119
+ supportsBranches: false,
120
+ supportsPartial: true,
121
+ warnOnWorkspaceMismatch: false,
122
+ };
123
+ const pushFlags = {
124
+ delete: flags.delete,
125
+ 'dry-run': flags['dry-run'],
126
+ env: flags.env,
127
+ force: flags.force,
128
+ guids: flags.guids,
129
+ records: flags.records,
130
+ sync: flags.sync,
131
+ transaction: flags.transaction,
132
+ truncate: flags.truncate,
133
+ verbose: flags.verbose,
134
+ };
135
+ await executePush({
136
+ accessToken: profile.access_token,
137
+ branch: '',
138
+ command: this,
139
+ inputDir,
140
+ verboseFetch: this.verboseFetch.bind(this),
141
+ }, target, pushFlags);
142
+ }
143
+ }