@xano/cli 0.0.37 → 0.0.39
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 +325 -102
- package/dist/commands/auth/index.d.ts +0 -2
- package/dist/commands/auth/index.js +2 -55
- package/dist/commands/profile/create/index.d.ts +0 -2
- package/dist/commands/profile/create/index.js +0 -15
- package/dist/commands/profile/edit/index.d.ts +0 -4
- package/dist/commands/profile/edit/index.js +7 -38
- package/dist/commands/profile/wizard/index.d.ts +0 -2
- package/dist/commands/profile/wizard/index.js +0 -106
- package/dist/commands/profile/{project → workspace}/index.d.ts +1 -1
- package/dist/commands/profile/{project → workspace}/index.js +10 -10
- package/dist/commands/release/delete/index.d.ts +2 -4
- package/dist/commands/release/delete/index.js +39 -12
- package/dist/commands/release/edit/index.d.ts +2 -4
- package/dist/commands/release/edit/index.js +31 -5
- package/dist/commands/release/export/index.d.ts +2 -4
- package/dist/commands/release/export/index.js +39 -11
- package/dist/commands/release/get/index.d.ts +2 -4
- package/dist/commands/release/get/index.js +31 -5
- package/dist/commands/release/pull/index.d.ts +31 -0
- package/dist/commands/release/pull/index.js +345 -0
- package/dist/commands/release/push/index.d.ts +26 -0
- package/dist/commands/release/push/index.js +230 -0
- package/dist/commands/tenant/backup/delete/index.d.ts +1 -1
- package/dist/commands/tenant/backup/delete/index.js +8 -9
- package/dist/commands/tenant/backup/export/index.d.ts +1 -1
- package/dist/commands/tenant/backup/export/index.js +9 -10
- package/dist/commands/tenant/backup/restore/index.d.ts +1 -1
- package/dist/commands/tenant/backup/restore/index.js +8 -9
- package/dist/commands/tenant/cluster/create/index.d.ts +18 -0
- package/dist/commands/tenant/cluster/create/index.js +149 -0
- package/dist/commands/{run/sessions/start → tenant/cluster/delete}/index.d.ts +9 -3
- package/dist/commands/tenant/cluster/delete/index.js +125 -0
- package/dist/commands/tenant/cluster/edit/index.d.ts +22 -0
- package/dist/commands/tenant/cluster/edit/index.js +128 -0
- package/dist/commands/{run/sessions → tenant/cluster}/get/index.d.ts +7 -3
- package/dist/commands/tenant/cluster/get/index.js +114 -0
- package/dist/commands/{run/info → tenant/cluster/license/get}/index.d.ts +10 -7
- package/dist/commands/tenant/cluster/license/get/index.js +118 -0
- package/dist/commands/tenant/cluster/license/set/index.d.ts +21 -0
- package/dist/commands/tenant/cluster/license/set/index.js +132 -0
- package/dist/commands/{run/env → tenant/cluster}/list/index.d.ts +3 -3
- package/dist/commands/tenant/cluster/list/index.js +109 -0
- package/dist/commands/tenant/create/index.d.ts +6 -3
- package/dist/commands/tenant/create/index.js +28 -20
- package/dist/commands/tenant/deploy_platform/index.d.ts +1 -1
- package/dist/commands/tenant/deploy_platform/index.js +8 -9
- package/dist/commands/tenant/deploy_release/index.d.ts +1 -1
- package/dist/commands/tenant/deploy_release/index.js +8 -9
- package/dist/commands/tenant/env/delete/index.d.ts +19 -0
- package/dist/commands/tenant/env/delete/index.js +139 -0
- package/dist/commands/{run/projects/create → tenant/env/get}/index.d.ts +7 -4
- package/dist/commands/tenant/env/get/index.js +113 -0
- package/dist/commands/{run/projects/update → tenant/env/get_all}/index.d.ts +7 -5
- package/dist/commands/tenant/env/get_all/index.js +123 -0
- package/dist/commands/{run/secrets/get → tenant/env/list}/index.d.ts +5 -3
- package/dist/commands/tenant/env/list/index.js +116 -0
- package/dist/commands/tenant/env/set/index.d.ts +18 -0
- package/dist/commands/tenant/env/set/index.js +122 -0
- package/dist/commands/tenant/env/set_all/index.d.ts +18 -0
- package/dist/commands/tenant/env/set_all/index.js +131 -0
- package/dist/commands/tenant/get/index.js +6 -5
- package/dist/commands/tenant/impersonate/index.d.ts +19 -0
- package/dist/commands/tenant/impersonate/index.js +146 -0
- package/dist/commands/tenant/license/get/index.d.ts +18 -0
- package/dist/commands/tenant/license/get/index.js +127 -0
- package/dist/commands/tenant/license/set/index.d.ts +19 -0
- package/dist/commands/tenant/license/set/index.js +141 -0
- package/dist/commands/tenant/list/index.js +6 -6
- package/dist/commands/tenant/pull/index.d.ts +31 -0
- package/dist/commands/tenant/pull/index.js +327 -0
- package/dist/commands/tenant/push/index.d.ts +24 -0
- package/dist/commands/tenant/push/index.js +245 -0
- package/oclif.manifest.json +2218 -1813
- package/package.json +1 -19
- package/dist/commands/run/env/delete/index.d.ts +0 -14
- package/dist/commands/run/env/delete/index.js +0 -65
- package/dist/commands/run/env/get/index.d.ts +0 -14
- package/dist/commands/run/env/get/index.js +0 -52
- package/dist/commands/run/env/list/index.js +0 -56
- package/dist/commands/run/env/set/index.d.ts +0 -14
- package/dist/commands/run/env/set/index.js +0 -51
- package/dist/commands/run/exec/index.d.ts +0 -31
- package/dist/commands/run/exec/index.js +0 -431
- package/dist/commands/run/info/index.js +0 -160
- package/dist/commands/run/projects/create/index.js +0 -75
- package/dist/commands/run/projects/delete/index.d.ts +0 -14
- package/dist/commands/run/projects/delete/index.js +0 -65
- package/dist/commands/run/projects/list/index.d.ts +0 -13
- package/dist/commands/run/projects/list/index.js +0 -66
- package/dist/commands/run/projects/update/index.js +0 -86
- package/dist/commands/run/secrets/delete/index.d.ts +0 -14
- package/dist/commands/run/secrets/delete/index.js +0 -65
- package/dist/commands/run/secrets/get/index.js +0 -52
- package/dist/commands/run/secrets/list/index.d.ts +0 -12
- package/dist/commands/run/secrets/list/index.js +0 -60
- package/dist/commands/run/secrets/set/index.d.ts +0 -16
- package/dist/commands/run/secrets/set/index.js +0 -74
- package/dist/commands/run/sessions/delete/index.d.ts +0 -14
- package/dist/commands/run/sessions/delete/index.js +0 -65
- package/dist/commands/run/sessions/get/index.js +0 -72
- package/dist/commands/run/sessions/list/index.d.ts +0 -13
- package/dist/commands/run/sessions/list/index.js +0 -64
- package/dist/commands/run/sessions/start/index.js +0 -56
- package/dist/commands/run/sessions/stop/index.d.ts +0 -14
- package/dist/commands/run/sessions/stop/index.js +0 -56
- package/dist/commands/run/sink/get/index.d.ts +0 -14
- package/dist/commands/run/sink/get/index.js +0 -63
- package/dist/lib/base-run-command.d.ts +0 -41
- package/dist/lib/base-run-command.js +0 -75
- package/dist/lib/run-http-client.d.ts +0 -64
- package/dist/lib/run-http-client.js +0 -171
- package/dist/lib/run-types.d.ts +0 -226
- package/dist/lib/run-types.js +0 -5
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import BaseCommand from '../../../base-command.js';
|
|
7
|
+
export default class Push extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
directory: Args.string({
|
|
10
|
+
description: 'Directory containing documents to push (as produced by tenant pull or workspace pull)',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Push local documents to a tenant via the Xano Metadata API multidoc endpoint';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano tenant push ./my-workspace -t my-tenant
|
|
17
|
+
Pushed 42 documents to tenant my-tenant from ./my-workspace
|
|
18
|
+
`,
|
|
19
|
+
`$ xano tenant push ./output -t my-tenant -w 40
|
|
20
|
+
Pushed 15 documents to tenant my-tenant from ./output
|
|
21
|
+
`,
|
|
22
|
+
`$ xano tenant push ./backup -t my-tenant --profile production
|
|
23
|
+
Pushed 58 documents to tenant my-tenant from ./backup
|
|
24
|
+
`,
|
|
25
|
+
`$ xano tenant push ./my-workspace -t my-tenant --no-records
|
|
26
|
+
Push schema only, skip importing table records
|
|
27
|
+
`,
|
|
28
|
+
`$ xano tenant push ./my-workspace -t my-tenant --no-env
|
|
29
|
+
Push without overwriting environment variables
|
|
30
|
+
`,
|
|
31
|
+
`$ xano tenant push ./my-workspace -t my-tenant --truncate
|
|
32
|
+
Truncate all table records before importing
|
|
33
|
+
`,
|
|
34
|
+
];
|
|
35
|
+
static flags = {
|
|
36
|
+
...BaseCommand.baseFlags,
|
|
37
|
+
env: Flags.boolean({
|
|
38
|
+
allowNo: true,
|
|
39
|
+
default: true,
|
|
40
|
+
description: 'Include environment variables in import (default: true, use --no-env to exclude)',
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
43
|
+
records: Flags.boolean({
|
|
44
|
+
allowNo: true,
|
|
45
|
+
default: true,
|
|
46
|
+
description: 'Include records in import (default: true, use --no-records to exclude)',
|
|
47
|
+
required: false,
|
|
48
|
+
}),
|
|
49
|
+
tenant: Flags.string({
|
|
50
|
+
char: 't',
|
|
51
|
+
description: 'Tenant name to push to',
|
|
52
|
+
required: true,
|
|
53
|
+
}),
|
|
54
|
+
truncate: Flags.boolean({
|
|
55
|
+
default: false,
|
|
56
|
+
description: 'Truncate all table records before importing',
|
|
57
|
+
required: false,
|
|
58
|
+
}),
|
|
59
|
+
workspace: Flags.string({
|
|
60
|
+
char: 'w',
|
|
61
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
62
|
+
required: false,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
async run() {
|
|
66
|
+
const { args, flags } = await this.parse(Push);
|
|
67
|
+
// Get profile name (default or from flag/env)
|
|
68
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
69
|
+
// Load credentials
|
|
70
|
+
const credentials = this.loadCredentials();
|
|
71
|
+
// Get the profile configuration
|
|
72
|
+
if (!(profileName in credentials.profiles)) {
|
|
73
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
74
|
+
`Create a profile using 'xano profile:create'`);
|
|
75
|
+
}
|
|
76
|
+
const profile = credentials.profiles[profileName];
|
|
77
|
+
// Validate required fields
|
|
78
|
+
if (!profile.instance_origin) {
|
|
79
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
80
|
+
}
|
|
81
|
+
if (!profile.access_token) {
|
|
82
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
83
|
+
}
|
|
84
|
+
// Determine workspace_id from flag or profile
|
|
85
|
+
let workspaceId;
|
|
86
|
+
if (flags.workspace) {
|
|
87
|
+
workspaceId = flags.workspace;
|
|
88
|
+
}
|
|
89
|
+
else if (profile.workspace) {
|
|
90
|
+
workspaceId = profile.workspace;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
94
|
+
` 1. Provide it as a flag: xano tenant push <directory> -t <tenant_name> -w <workspace_id>\n` +
|
|
95
|
+
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
96
|
+
}
|
|
97
|
+
const tenantName = flags.tenant;
|
|
98
|
+
// Fetch tenant details and verify it's ephemeral
|
|
99
|
+
const tenantApiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
|
|
100
|
+
try {
|
|
101
|
+
const tenantResponse = await this.verboseFetch(tenantApiUrl, {
|
|
102
|
+
headers: {
|
|
103
|
+
accept: 'application/json',
|
|
104
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
105
|
+
},
|
|
106
|
+
method: 'GET',
|
|
107
|
+
}, flags.verbose, profile.access_token);
|
|
108
|
+
if (!tenantResponse.ok) {
|
|
109
|
+
const errorText = await tenantResponse.text();
|
|
110
|
+
this.error(`Failed to fetch tenant '${tenantName}' (${tenantResponse.status}): ${errorText}`);
|
|
111
|
+
}
|
|
112
|
+
const tenantData = (await tenantResponse.json());
|
|
113
|
+
if (!tenantData.ephemeral) {
|
|
114
|
+
this.error(`Tenant '${tenantName}' is not ephemeral. Push is only allowed for ephemeral tenants.\n` +
|
|
115
|
+
`Create an ephemeral tenant with: xano tenant create "name" --ephemeral`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof Error && error.message.includes('is not ephemeral')) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
if (error instanceof Error && error.message.includes('Failed to fetch tenant')) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
this.error(`Failed to verify tenant: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.error(`Failed to verify tenant: ${String(error)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Resolve the input directory
|
|
133
|
+
const inputDir = path.resolve(args.directory);
|
|
134
|
+
if (!fs.existsSync(inputDir)) {
|
|
135
|
+
this.error(`Directory not found: ${inputDir}`);
|
|
136
|
+
}
|
|
137
|
+
if (!fs.statSync(inputDir).isDirectory()) {
|
|
138
|
+
this.error(`Not a directory: ${inputDir}`);
|
|
139
|
+
}
|
|
140
|
+
// Collect all .xs files from the directory tree
|
|
141
|
+
const files = this.collectFiles(inputDir);
|
|
142
|
+
if (files.length === 0) {
|
|
143
|
+
this.error(`No .xs files found in ${args.directory}`);
|
|
144
|
+
}
|
|
145
|
+
// Read each file and join with --- separator
|
|
146
|
+
const documents = [];
|
|
147
|
+
for (const filePath of files) {
|
|
148
|
+
const content = fs.readFileSync(filePath, 'utf8').trim();
|
|
149
|
+
if (content) {
|
|
150
|
+
documents.push(content);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (documents.length === 0) {
|
|
154
|
+
this.error(`All .xs files in ${args.directory} are empty`);
|
|
155
|
+
}
|
|
156
|
+
const multidoc = documents.join('\n---\n');
|
|
157
|
+
// Construct the API URL
|
|
158
|
+
const queryParams = new URLSearchParams({
|
|
159
|
+
env: flags.env.toString(),
|
|
160
|
+
records: flags.records.toString(),
|
|
161
|
+
truncate: flags.truncate.toString(),
|
|
162
|
+
});
|
|
163
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}/multidoc?${queryParams.toString()}`;
|
|
164
|
+
// POST the multidoc to the API
|
|
165
|
+
const requestHeaders = {
|
|
166
|
+
accept: 'application/json',
|
|
167
|
+
Authorization: `Bearer ${profile.access_token}`,
|
|
168
|
+
'Content-Type': 'text/x-xanoscript',
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
172
|
+
body: multidoc,
|
|
173
|
+
headers: requestHeaders,
|
|
174
|
+
method: 'POST',
|
|
175
|
+
}, flags.verbose, profile.access_token);
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const errorText = await response.text();
|
|
178
|
+
let errorMessage = `Push failed (${response.status})`;
|
|
179
|
+
try {
|
|
180
|
+
const errorJson = JSON.parse(errorText);
|
|
181
|
+
errorMessage += `: ${errorJson.message}`;
|
|
182
|
+
if (errorJson.payload?.param) {
|
|
183
|
+
errorMessage += `\n Parameter: ${errorJson.payload.param}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
errorMessage += `\n${errorText}`;
|
|
188
|
+
}
|
|
189
|
+
this.error(errorMessage);
|
|
190
|
+
}
|
|
191
|
+
// Log the response if any
|
|
192
|
+
const responseText = await response.text();
|
|
193
|
+
if (responseText && responseText !== 'null') {
|
|
194
|
+
this.log(responseText);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof Error) {
|
|
199
|
+
this.error(`Failed to push multidoc: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
this.error(`Failed to push multidoc: ${String(error)}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.log(`Pushed ${documents.length} documents to tenant ${tenantName} from ${args.directory}`);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Recursively collect all .xs files from a directory, sorted by
|
|
209
|
+
* type subdirectory name then filename for deterministic ordering.
|
|
210
|
+
*/
|
|
211
|
+
collectFiles(dir) {
|
|
212
|
+
const files = [];
|
|
213
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const fullPath = path.join(dir, entry.name);
|
|
216
|
+
if (entry.isDirectory()) {
|
|
217
|
+
files.push(...this.collectFiles(fullPath));
|
|
218
|
+
}
|
|
219
|
+
else if (entry.isFile() && entry.name.endsWith('.xs')) {
|
|
220
|
+
files.push(fullPath);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return files.sort();
|
|
224
|
+
}
|
|
225
|
+
loadCredentials() {
|
|
226
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
227
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
228
|
+
// Check if credentials file exists
|
|
229
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
230
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
|
|
231
|
+
}
|
|
232
|
+
// Read credentials file
|
|
233
|
+
try {
|
|
234
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
235
|
+
const parsed = yaml.load(fileContent);
|
|
236
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
237
|
+
this.error('Credentials file has invalid format.');
|
|
238
|
+
}
|
|
239
|
+
return parsed;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|