@xano/cli 0.0.26 → 0.0.28
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/dist/base-command.d.ts +3 -1
- package/dist/base-command.js +12 -5
- package/dist/commands/auth/index.d.ts +21 -0
- package/dist/commands/auth/index.js +533 -0
- package/dist/commands/branch/create/index.d.ts +17 -0
- package/dist/commands/branch/create/index.js +164 -0
- package/dist/commands/branch/delete/index.d.ts +18 -0
- package/dist/commands/branch/delete/index.js +156 -0
- package/dist/commands/branch/edit/index.d.ts +19 -0
- package/dist/commands/branch/edit/index.js +166 -0
- package/dist/commands/branch/get/index.d.ts +16 -0
- package/dist/commands/branch/get/index.js +135 -0
- package/dist/commands/branch/list/index.d.ts +18 -0
- package/dist/commands/branch/list/index.js +138 -0
- package/dist/commands/branch/set-live/index.d.ts +18 -0
- package/dist/commands/branch/set-live/index.js +155 -0
- package/dist/commands/function/create/index.d.ts +7 -6
- package/dist/commands/function/create/index.js +55 -55
- package/dist/commands/function/edit/index.d.ts +11 -10
- package/dist/commands/function/edit/index.js +155 -162
- package/dist/commands/function/get/index.d.ts +6 -5
- package/dist/commands/function/get/index.js +55 -60
- package/dist/commands/function/list/index.d.ts +6 -5
- package/dist/commands/function/list/index.js +52 -52
- package/dist/commands/profile/create/index.d.ts +6 -6
- package/dist/commands/profile/create/index.js +37 -37
- package/dist/commands/profile/delete/index.d.ts +2 -2
- package/dist/commands/profile/delete/index.js +9 -9
- package/dist/commands/profile/edit/index.d.ts +8 -7
- package/dist/commands/profile/edit/index.js +48 -48
- package/dist/commands/profile/get-default/index.js +1 -1
- package/dist/commands/profile/list/index.d.ts +2 -2
- package/dist/commands/profile/list/index.js +9 -9
- package/dist/commands/profile/me/index.d.ts +4 -3
- package/dist/commands/profile/me/index.js +21 -21
- package/dist/commands/profile/project/index.js +1 -1
- package/dist/commands/profile/set-default/index.js +1 -1
- package/dist/commands/profile/token/index.js +1 -1
- package/dist/commands/profile/wizard/index.d.ts +5 -4
- package/dist/commands/profile/wizard/index.js +142 -108
- package/dist/commands/run/env/delete/index.d.ts +3 -2
- package/dist/commands/run/env/delete/index.js +10 -10
- package/dist/commands/run/env/get/index.d.ts +3 -2
- package/dist/commands/run/env/get/index.js +11 -11
- package/dist/commands/run/env/list/index.d.ts +3 -2
- package/dist/commands/run/env/list/index.js +17 -19
- package/dist/commands/run/env/set/index.d.ts +3 -2
- package/dist/commands/run/env/set/index.js +5 -5
- package/dist/commands/run/exec/index.d.ts +19 -8
- package/dist/commands/run/exec/index.js +186 -108
- package/dist/commands/run/info/index.d.ts +5 -4
- package/dist/commands/run/info/index.js +27 -27
- package/dist/commands/run/projects/create/index.d.ts +4 -3
- package/dist/commands/run/projects/create/index.js +23 -23
- package/dist/commands/run/projects/delete/index.d.ts +3 -2
- package/dist/commands/run/projects/delete/index.js +10 -10
- package/dist/commands/run/projects/list/index.d.ts +3 -2
- package/dist/commands/run/projects/list/index.js +12 -12
- package/dist/commands/run/projects/update/index.d.ts +4 -3
- package/dist/commands/run/projects/update/index.js +21 -21
- package/dist/commands/run/secrets/delete/index.d.ts +3 -2
- package/dist/commands/run/secrets/delete/index.js +10 -10
- package/dist/commands/run/secrets/get/index.d.ts +3 -2
- package/dist/commands/run/secrets/get/index.js +11 -11
- package/dist/commands/run/secrets/list/index.d.ts +3 -2
- package/dist/commands/run/secrets/list/index.js +22 -24
- package/dist/commands/run/secrets/set/index.d.ts +4 -3
- package/dist/commands/run/secrets/set/index.js +16 -16
- package/dist/commands/run/sessions/delete/index.d.ts +3 -2
- package/dist/commands/run/sessions/delete/index.js +10 -10
- package/dist/commands/run/sessions/get/index.d.ts +3 -2
- package/dist/commands/run/sessions/get/index.js +11 -11
- package/dist/commands/run/sessions/list/index.d.ts +3 -2
- package/dist/commands/run/sessions/list/index.js +11 -11
- package/dist/commands/run/sessions/start/index.d.ts +3 -2
- package/dist/commands/run/sessions/start/index.js +11 -11
- package/dist/commands/run/sessions/stop/index.d.ts +3 -2
- package/dist/commands/run/sessions/stop/index.js +11 -11
- package/dist/commands/run/sink/get/index.d.ts +3 -2
- package/dist/commands/run/sink/get/index.js +11 -11
- package/dist/commands/static_host/build/create/index.d.ts +5 -4
- package/dist/commands/static_host/build/create/index.js +33 -33
- package/dist/commands/static_host/build/get/index.d.ts +5 -4
- package/dist/commands/static_host/build/get/index.js +20 -20
- package/dist/commands/static_host/build/list/index.d.ts +4 -3
- package/dist/commands/static_host/build/list/index.js +31 -31
- package/dist/commands/static_host/list/index.d.ts +4 -3
- package/dist/commands/static_host/list/index.js +31 -31
- package/dist/commands/workspace/create/index.d.ts +14 -0
- package/dist/commands/workspace/create/index.js +131 -0
- package/dist/commands/workspace/delete/index.d.ts +20 -0
- package/dist/commands/workspace/delete/index.js +141 -0
- package/dist/commands/workspace/edit/index.d.ts +22 -0
- package/dist/commands/workspace/edit/index.js +176 -0
- package/dist/commands/workspace/get/index.d.ts +18 -0
- package/dist/commands/workspace/get/index.js +136 -0
- package/dist/commands/workspace/list/index.d.ts +3 -2
- package/dist/commands/workspace/list/index.js +15 -15
- package/dist/commands/workspace/pull/index.d.ts +6 -4
- package/dist/commands/workspace/pull/index.js +123 -63
- package/dist/commands/workspace/push/index.d.ts +2 -0
- package/dist/commands/workspace/push/index.js +16 -5
- package/dist/help.d.ts +1 -1
- package/dist/lib/base-run-command.d.ts +6 -6
- package/dist/lib/base-run-command.js +8 -6
- package/dist/lib/run-http-client.d.ts +24 -18
- package/dist/lib/run-http-client.js +96 -61
- package/dist/lib/run-types.d.ts +80 -80
- package/oclif.manifest.json +2041 -843
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
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 WorkspaceGet extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
workspace_id: Args.integer({
|
|
10
|
+
description: 'Workspace ID to get details for (uses profile workspace if not provided)',
|
|
11
|
+
required: false,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Get details of a specific workspace from the Xano Metadata API';
|
|
15
|
+
static examples = [
|
|
16
|
+
`$ xano workspace get 123
|
|
17
|
+
Workspace: my-workspace (ID: 123)
|
|
18
|
+
Description: My workspace description
|
|
19
|
+
Created: 2024-01-15
|
|
20
|
+
`,
|
|
21
|
+
`$ xano workspace get --output json
|
|
22
|
+
{
|
|
23
|
+
"id": 123,
|
|
24
|
+
"name": "my-workspace",
|
|
25
|
+
"description": "My workspace description"
|
|
26
|
+
}
|
|
27
|
+
`,
|
|
28
|
+
`$ xano workspace get 456 -p production -o json
|
|
29
|
+
{
|
|
30
|
+
"id": 456,
|
|
31
|
+
"name": "production-workspace"
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
];
|
|
35
|
+
static flags = {
|
|
36
|
+
...BaseCommand.baseFlags,
|
|
37
|
+
output: Flags.string({
|
|
38
|
+
char: 'o',
|
|
39
|
+
default: 'summary',
|
|
40
|
+
description: 'Output format',
|
|
41
|
+
options: ['summary', 'json'],
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(WorkspaceGet);
|
|
47
|
+
// Get profile name (default or from flag/env)
|
|
48
|
+
const profileName = flags.profile || this.getDefaultProfile();
|
|
49
|
+
// Load credentials
|
|
50
|
+
const credentials = this.loadCredentials();
|
|
51
|
+
// Get the profile configuration
|
|
52
|
+
if (!(profileName in credentials.profiles)) {
|
|
53
|
+
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
54
|
+
`Create a profile using 'xano profile create'`);
|
|
55
|
+
}
|
|
56
|
+
const profile = credentials.profiles[profileName];
|
|
57
|
+
// Validate required fields
|
|
58
|
+
if (!profile.instance_origin) {
|
|
59
|
+
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
60
|
+
}
|
|
61
|
+
if (!profile.access_token) {
|
|
62
|
+
this.error(`Profile '${profileName}' is missing access_token`);
|
|
63
|
+
}
|
|
64
|
+
// Get workspace ID from args or profile
|
|
65
|
+
const workspaceId = args.workspace_id || profile.workspace;
|
|
66
|
+
if (!workspaceId) {
|
|
67
|
+
this.error('No workspace ID provided. Either pass a workspace ID as an argument or set one in your profile.\n' +
|
|
68
|
+
'Usage: xano workspace get <workspace_id>');
|
|
69
|
+
}
|
|
70
|
+
// Construct the API URL
|
|
71
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}`;
|
|
72
|
+
// Fetch workspace from the API
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(apiUrl, {
|
|
75
|
+
headers: {
|
|
76
|
+
'accept': 'application/json',
|
|
77
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
78
|
+
},
|
|
79
|
+
method: 'GET',
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const errorText = await response.text();
|
|
83
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
84
|
+
}
|
|
85
|
+
const workspace = await response.json();
|
|
86
|
+
// Output results
|
|
87
|
+
if (flags.output === 'json') {
|
|
88
|
+
this.log(JSON.stringify(workspace, null, 2));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// summary format
|
|
92
|
+
this.log(`Workspace: ${workspace.name} (ID: ${workspace.id})`);
|
|
93
|
+
if (workspace.description) {
|
|
94
|
+
this.log(` Description: ${workspace.description}`);
|
|
95
|
+
}
|
|
96
|
+
if (workspace.created_at) {
|
|
97
|
+
const createdDate = new Date(workspace.created_at * 1000).toISOString().split('T')[0];
|
|
98
|
+
this.log(` Created: ${createdDate}`);
|
|
99
|
+
}
|
|
100
|
+
if (workspace.updated_at) {
|
|
101
|
+
const updatedDate = new Date(workspace.updated_at * 1000).toISOString().split('T')[0];
|
|
102
|
+
this.log(` Updated: ${updatedDate}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
this.error(`Failed to fetch workspace: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.error(`Failed to fetch workspace: ${String(error)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
loadCredentials() {
|
|
116
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
117
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
118
|
+
// Check if credentials file exists
|
|
119
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
120
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
121
|
+
`Create a profile using 'xano profile create'`);
|
|
122
|
+
}
|
|
123
|
+
// Read credentials file
|
|
124
|
+
try {
|
|
125
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
126
|
+
const parsed = yaml.load(fileContent);
|
|
127
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
128
|
+
this.error('Credentials file has invalid format.');
|
|
129
|
+
}
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import BaseCommand from '../../../base-command.js';
|
|
2
2
|
export default class WorkspaceList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
3
5
|
static flags: {
|
|
4
6
|
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
5
7
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
9
|
};
|
|
7
|
-
static description: string;
|
|
8
|
-
static examples: string[];
|
|
9
10
|
run(): Promise<void>;
|
|
10
11
|
private loadCredentials;
|
|
11
12
|
}
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
|
-
import * as yaml from 'js-yaml';
|
|
6
6
|
import BaseCommand from '../../../base-command.js';
|
|
7
7
|
export default class WorkspaceList extends BaseCommand {
|
|
8
|
-
static flags = {
|
|
9
|
-
...BaseCommand.baseFlags,
|
|
10
|
-
output: Flags.string({
|
|
11
|
-
char: 'o',
|
|
12
|
-
description: 'Output format',
|
|
13
|
-
required: false,
|
|
14
|
-
default: 'summary',
|
|
15
|
-
options: ['summary', 'json'],
|
|
16
|
-
}),
|
|
17
|
-
};
|
|
18
8
|
static description = 'List all workspaces from the Xano Metadata API';
|
|
19
9
|
static examples = [
|
|
20
10
|
`$ xano workspace:list
|
|
@@ -53,6 +43,16 @@ Available workspaces:
|
|
|
53
43
|
}
|
|
54
44
|
`,
|
|
55
45
|
];
|
|
46
|
+
static flags = {
|
|
47
|
+
...BaseCommand.baseFlags,
|
|
48
|
+
output: Flags.string({
|
|
49
|
+
char: 'o',
|
|
50
|
+
default: 'summary',
|
|
51
|
+
description: 'Output format',
|
|
52
|
+
options: ['summary', 'json'],
|
|
53
|
+
required: false,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
56
|
async run() {
|
|
57
57
|
const { flags } = await this.parse(WorkspaceList);
|
|
58
58
|
// Get profile name (default or from flag/env)
|
|
@@ -77,11 +77,11 @@ Available workspaces:
|
|
|
77
77
|
// Fetch workspaces from the API
|
|
78
78
|
try {
|
|
79
79
|
const response = await fetch(apiUrl, {
|
|
80
|
-
method: 'GET',
|
|
81
80
|
headers: {
|
|
82
81
|
'accept': 'application/json',
|
|
83
82
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
84
83
|
},
|
|
84
|
+
method: 'GET',
|
|
85
85
|
});
|
|
86
86
|
if (!response.ok) {
|
|
87
87
|
const errorText = await response.text();
|
|
@@ -111,11 +111,11 @@ Available workspaces:
|
|
|
111
111
|
else {
|
|
112
112
|
this.log('Available workspaces:');
|
|
113
113
|
for (const workspace of workspaces) {
|
|
114
|
-
if (workspace.id
|
|
115
|
-
this.log(` - ${workspace.name}
|
|
114
|
+
if (workspace.id === undefined) {
|
|
115
|
+
this.log(` - ${workspace.name}`);
|
|
116
116
|
}
|
|
117
117
|
else {
|
|
118
|
-
this.log(` - ${workspace.name}`);
|
|
118
|
+
this.log(` - ${workspace.name} (ID: ${workspace.id})`);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -3,15 +3,18 @@ export default class Pull extends BaseCommand {
|
|
|
3
3
|
static args: {
|
|
4
4
|
directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
5
|
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
6
8
|
static flags: {
|
|
7
|
-
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
11
|
records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
13
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
15
|
};
|
|
12
|
-
static description: string;
|
|
13
|
-
static examples: string[];
|
|
14
16
|
run(): Promise<void>;
|
|
17
|
+
private loadCredentials;
|
|
15
18
|
/**
|
|
16
19
|
* Parse a single document to extract its type, name, and optional verb.
|
|
17
20
|
* Skips leading comment lines (starting with //) to find the first
|
|
@@ -24,5 +27,4 @@ export default class Pull extends BaseCommand {
|
|
|
24
27
|
* characters that are unsafe in filenames.
|
|
25
28
|
*/
|
|
26
29
|
private sanitizeFilename;
|
|
27
|
-
private loadCredentials;
|
|
28
30
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
|
-
import
|
|
6
|
+
import snakeCase from 'lodash.snakecase';
|
|
6
7
|
import BaseCommand from '../../../base-command.js';
|
|
7
8
|
export default class Pull extends BaseCommand {
|
|
8
9
|
static args = {
|
|
@@ -11,36 +12,44 @@ export default class Pull extends BaseCommand {
|
|
|
11
12
|
required: true,
|
|
12
13
|
}),
|
|
13
14
|
};
|
|
15
|
+
static description = 'Pull a workspace multidoc from the Xano Metadata API and split into individual files';
|
|
16
|
+
static examples = [
|
|
17
|
+
`$ xano workspace pull ./my-workspace
|
|
18
|
+
Pulled 42 documents to ./my-workspace
|
|
19
|
+
`,
|
|
20
|
+
`$ xano workspace pull ./output -w 40
|
|
21
|
+
Pulled 15 documents to ./output
|
|
22
|
+
`,
|
|
23
|
+
`$ xano workspace pull ./backup --profile production --env --records
|
|
24
|
+
Pulled 58 documents to ./backup
|
|
25
|
+
`,
|
|
26
|
+
`$ xano workspace pull ./my-workspace -b dev
|
|
27
|
+
Pulled 42 documents to ./my-workspace
|
|
28
|
+
`,
|
|
29
|
+
];
|
|
14
30
|
static flags = {
|
|
15
31
|
...BaseCommand.baseFlags,
|
|
16
|
-
|
|
17
|
-
char: '
|
|
18
|
-
description: '
|
|
32
|
+
branch: Flags.string({
|
|
33
|
+
char: 'b',
|
|
34
|
+
description: 'Branch name (optional if set in profile, defaults to live)',
|
|
19
35
|
required: false,
|
|
20
36
|
}),
|
|
21
37
|
env: Flags.boolean({
|
|
38
|
+
default: false,
|
|
22
39
|
description: 'Include environment variables',
|
|
23
40
|
required: false,
|
|
24
|
-
default: false,
|
|
25
41
|
}),
|
|
26
42
|
records: Flags.boolean({
|
|
43
|
+
default: false,
|
|
27
44
|
description: 'Include records',
|
|
28
45
|
required: false,
|
|
29
|
-
|
|
46
|
+
}),
|
|
47
|
+
workspace: Flags.string({
|
|
48
|
+
char: 'w',
|
|
49
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
50
|
+
required: false,
|
|
30
51
|
}),
|
|
31
52
|
};
|
|
32
|
-
static description = 'Pull a workspace multidoc from the Xano Metadata API and split into individual files';
|
|
33
|
-
static examples = [
|
|
34
|
-
`$ xano workspace pull ./my-workspace
|
|
35
|
-
Pulled 42 documents to ./my-workspace
|
|
36
|
-
`,
|
|
37
|
-
`$ xano workspace pull ./output -w 40
|
|
38
|
-
Pulled 15 documents to ./output
|
|
39
|
-
`,
|
|
40
|
-
`$ xano workspace pull ./backup --profile production --env --records
|
|
41
|
-
Pulled 58 documents to ./backup
|
|
42
|
-
`,
|
|
43
|
-
];
|
|
44
53
|
async run() {
|
|
45
54
|
const { args, flags } = await this.parse(Pull);
|
|
46
55
|
// Get profile name (default or from flag/env)
|
|
@@ -73,22 +82,25 @@ Pulled 58 documents to ./backup
|
|
|
73
82
|
` 1. Provide it as a flag: xano workspace pull <directory> -w <workspace_id>\n` +
|
|
74
83
|
` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
|
|
75
84
|
}
|
|
85
|
+
// Determine branch from flag or profile
|
|
86
|
+
const branch = flags.branch || profile.branch || '';
|
|
76
87
|
// Build query parameters
|
|
77
88
|
const queryParams = new URLSearchParams({
|
|
89
|
+
branch,
|
|
78
90
|
env: flags.env.toString(),
|
|
79
91
|
records: flags.records.toString(),
|
|
80
92
|
});
|
|
81
93
|
// Construct the API URL
|
|
82
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
94
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
|
|
83
95
|
// Fetch multidoc from the API
|
|
84
96
|
let responseText;
|
|
85
97
|
try {
|
|
86
98
|
const response = await fetch(apiUrl, {
|
|
87
|
-
method: 'GET',
|
|
88
99
|
headers: {
|
|
89
100
|
'accept': 'application/json',
|
|
90
101
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
91
102
|
},
|
|
103
|
+
method: 'GET',
|
|
92
104
|
});
|
|
93
105
|
if (!response.ok) {
|
|
94
106
|
const errorText = await response.text();
|
|
@@ -130,35 +142,101 @@ Pulled 58 documents to ./backup
|
|
|
130
142
|
const filenameCounters = new Map();
|
|
131
143
|
let writtenCount = 0;
|
|
132
144
|
for (const doc of documents) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
let typeDir;
|
|
146
|
+
let baseName;
|
|
147
|
+
if (doc.type === 'workspace') {
|
|
148
|
+
// workspace → workspace/{name}.xs
|
|
149
|
+
typeDir = path.join(outputDir, 'workspace');
|
|
150
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
151
|
+
}
|
|
152
|
+
else if (doc.type === 'workspace_trigger') {
|
|
153
|
+
// workspace_trigger → workspace/trigger/{name}.xs
|
|
154
|
+
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
155
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
156
|
+
}
|
|
157
|
+
else if (doc.type === 'agent_trigger') {
|
|
158
|
+
// agent_trigger → agent/trigger/{name}.xs
|
|
159
|
+
typeDir = path.join(outputDir, 'agent', 'trigger');
|
|
160
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
161
|
+
}
|
|
162
|
+
else if (doc.type === 'mcp_server_trigger') {
|
|
163
|
+
// mcp_server_trigger → mcp_server/trigger/{name}.xs
|
|
164
|
+
typeDir = path.join(outputDir, 'mcp_server', 'trigger');
|
|
165
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
166
|
+
}
|
|
167
|
+
else if (doc.type === 'table_trigger') {
|
|
168
|
+
// table_trigger → table/trigger/{name}.xs
|
|
169
|
+
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
170
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
171
|
+
}
|
|
172
|
+
else if (doc.type === 'api_group') {
|
|
173
|
+
// api_group "test" → api/test/api_group.xs
|
|
174
|
+
const groupFolder = snakeCase(doc.name);
|
|
175
|
+
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
176
|
+
baseName = 'api_group';
|
|
177
|
+
}
|
|
178
|
+
else if (doc.type === 'query' && doc.apiGroup) {
|
|
179
|
+
// query in group "test" → api/test/{query_name}.xs
|
|
180
|
+
const groupFolder = snakeCase(doc.apiGroup);
|
|
181
|
+
const nameParts = doc.name.split('/');
|
|
182
|
+
const leafName = nameParts.pop();
|
|
183
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
184
|
+
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
185
|
+
baseName = this.sanitizeFilename(leafName);
|
|
186
|
+
if (doc.verb) {
|
|
187
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
188
|
+
}
|
|
140
189
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
190
|
+
else {
|
|
191
|
+
// Default: split folder path from name
|
|
192
|
+
const nameParts = doc.name.split('/');
|
|
193
|
+
const leafName = nameParts.pop();
|
|
194
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
195
|
+
typeDir = path.join(outputDir, doc.type, ...folderParts);
|
|
196
|
+
baseName = this.sanitizeFilename(leafName);
|
|
197
|
+
if (doc.verb) {
|
|
198
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
202
|
+
// Track duplicates per directory
|
|
203
|
+
const dirKey = path.relative(outputDir, typeDir);
|
|
204
|
+
if (!filenameCounters.has(dirKey)) {
|
|
205
|
+
filenameCounters.set(dirKey, new Map());
|
|
144
206
|
}
|
|
145
|
-
const typeCounters = filenameCounters.get(
|
|
207
|
+
const typeCounters = filenameCounters.get(dirKey);
|
|
146
208
|
const count = typeCounters.get(baseName) || 0;
|
|
147
209
|
typeCounters.set(baseName, count + 1);
|
|
148
210
|
// Append numeric suffix for duplicates
|
|
149
211
|
let filename;
|
|
150
|
-
|
|
151
|
-
filename = `${baseName}.xs`;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
filename = `${baseName}_${count + 1}.xs`;
|
|
155
|
-
}
|
|
212
|
+
filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
156
213
|
const filePath = path.join(typeDir, filename);
|
|
157
214
|
fs.writeFileSync(filePath, doc.content, 'utf8');
|
|
158
215
|
writtenCount++;
|
|
159
216
|
}
|
|
160
217
|
this.log(`Pulled ${writtenCount} documents to ${args.directory}`);
|
|
161
218
|
}
|
|
219
|
+
loadCredentials() {
|
|
220
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
221
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
222
|
+
// Check if credentials file exists
|
|
223
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
224
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
225
|
+
`Create a profile using 'xano profile:create'`);
|
|
226
|
+
}
|
|
227
|
+
// Read credentials file
|
|
228
|
+
try {
|
|
229
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
230
|
+
const parsed = yaml.load(fileContent);
|
|
231
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
232
|
+
this.error('Credentials file has invalid format.');
|
|
233
|
+
}
|
|
234
|
+
return parsed;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
162
240
|
/**
|
|
163
241
|
* Parse a single document to extract its type, name, and optional verb.
|
|
164
242
|
* Skips leading comment lines (starting with //) to find the first
|
|
@@ -201,7 +279,13 @@ Pulled 58 documents to ./backup
|
|
|
201
279
|
if (verbMatch) {
|
|
202
280
|
verb = verbMatch[1];
|
|
203
281
|
}
|
|
204
|
-
|
|
282
|
+
// Extract api_group if present (e.g., api_group = "test")
|
|
283
|
+
let apiGroup;
|
|
284
|
+
const apiGroupMatch = content.match(/api_group\s*=\s*"([^"]*)"/);
|
|
285
|
+
if (apiGroupMatch) {
|
|
286
|
+
apiGroup = apiGroupMatch[1];
|
|
287
|
+
}
|
|
288
|
+
return { apiGroup, content, name, type, verb };
|
|
205
289
|
}
|
|
206
290
|
/**
|
|
207
291
|
* Sanitize a document name for use as a filename.
|
|
@@ -209,30 +293,6 @@ Pulled 58 documents to ./backup
|
|
|
209
293
|
* characters that are unsafe in filenames.
|
|
210
294
|
*/
|
|
211
295
|
sanitizeFilename(name) {
|
|
212
|
-
return name
|
|
213
|
-
.replace(/"/g, '')
|
|
214
|
-
.replace(/\s+/g, '_')
|
|
215
|
-
.replace(/[<>:"/\\|?*]/g, '_');
|
|
216
|
-
}
|
|
217
|
-
loadCredentials() {
|
|
218
|
-
const configDir = path.join(os.homedir(), '.xano');
|
|
219
|
-
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
220
|
-
// Check if credentials file exists
|
|
221
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
222
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
223
|
-
`Create a profile using 'xano profile:create'`);
|
|
224
|
-
}
|
|
225
|
-
// Read credentials file
|
|
226
|
-
try {
|
|
227
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
228
|
-
const parsed = yaml.load(fileContent);
|
|
229
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
230
|
-
this.error('Credentials file has invalid format.');
|
|
231
|
-
}
|
|
232
|
-
return parsed;
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
236
|
-
}
|
|
296
|
+
return snakeCase(name.replaceAll('"', ''));
|
|
237
297
|
}
|
|
238
298
|
}
|
|
@@ -6,8 +6,10 @@ export default class Push extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
13
|
};
|
|
12
14
|
run(): Promise<void>;
|
|
13
15
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
|
-
import * as yaml from 'js-yaml';
|
|
6
6
|
import BaseCommand from '../../../base-command.js';
|
|
7
7
|
export default class Push extends BaseCommand {
|
|
8
8
|
static args = {
|
|
@@ -21,10 +21,18 @@ Pushed 15 documents from ./output
|
|
|
21
21
|
`,
|
|
22
22
|
`$ xano workspace push ./backup --profile production
|
|
23
23
|
Pushed 58 documents from ./backup
|
|
24
|
+
`,
|
|
25
|
+
`$ xano workspace push ./my-workspace -b dev
|
|
26
|
+
Pushed 42 documents from ./my-workspace
|
|
24
27
|
`,
|
|
25
28
|
];
|
|
26
29
|
static flags = {
|
|
27
30
|
...BaseCommand.baseFlags,
|
|
31
|
+
branch: Flags.string({
|
|
32
|
+
char: 'b',
|
|
33
|
+
description: 'Branch name (optional if set in profile, defaults to live)',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
28
36
|
workspace: Flags.string({
|
|
29
37
|
char: 'w',
|
|
30
38
|
description: 'Workspace ID (optional if set in profile)',
|
|
@@ -88,18 +96,21 @@ Pushed 58 documents from ./backup
|
|
|
88
96
|
this.error(`All .xs files in ${args.directory} are empty`);
|
|
89
97
|
}
|
|
90
98
|
const multidoc = documents.join('\n---\n');
|
|
99
|
+
// Determine branch from flag or profile
|
|
100
|
+
const branch = flags.branch || profile.branch || '';
|
|
91
101
|
// Construct the API URL
|
|
92
|
-
const
|
|
102
|
+
const queryParams = new URLSearchParams({ branch });
|
|
103
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
|
|
93
104
|
// POST the multidoc to the API
|
|
94
105
|
try {
|
|
95
106
|
const response = await fetch(apiUrl, {
|
|
96
|
-
|
|
107
|
+
body: multidoc,
|
|
97
108
|
headers: {
|
|
98
109
|
'accept': 'application/json',
|
|
99
110
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
100
111
|
'Content-Type': 'text/x-xanoscript',
|
|
101
112
|
},
|
|
102
|
-
|
|
113
|
+
method: 'POST',
|
|
103
114
|
});
|
|
104
115
|
if (!response.ok) {
|
|
105
116
|
const errorText = await response.text();
|
|
@@ -107,7 +118,7 @@ Pushed 58 documents from ./backup
|
|
|
107
118
|
}
|
|
108
119
|
// Log the response if any
|
|
109
120
|
const responseText = await response.text();
|
|
110
|
-
if (responseText) {
|
|
121
|
+
if (responseText && responseText !== 'null') {
|
|
111
122
|
this.log(responseText);
|
|
112
123
|
}
|
|
113
124
|
}
|
package/dist/help.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Help as BaseHelp, Command } from '@oclif/core';
|
|
2
2
|
import { CommandHelp as BaseCommandHelp } from '@oclif/core/help';
|
|
3
3
|
/**
|
|
4
4
|
* Custom CommandHelp class that extends the default to display environment variables
|
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
import BaseCommand from '../base-command.js';
|
|
5
5
|
import { RunHttpClient } from './run-http-client.js';
|
|
6
6
|
export interface ProfileConfig {
|
|
7
|
-
account_origin?: string;
|
|
8
|
-
instance_origin: string;
|
|
9
7
|
access_token: string;
|
|
10
|
-
|
|
8
|
+
account_origin?: string;
|
|
11
9
|
branch?: string;
|
|
10
|
+
instance_origin: string;
|
|
12
11
|
project?: string;
|
|
13
12
|
run_base_url?: string;
|
|
13
|
+
workspace?: string;
|
|
14
14
|
}
|
|
15
15
|
export interface CredentialsFile {
|
|
16
|
+
default?: string;
|
|
16
17
|
profiles: {
|
|
17
18
|
[key: string]: ProfileConfig;
|
|
18
19
|
};
|
|
19
|
-
default?: string;
|
|
20
20
|
}
|
|
21
21
|
export default abstract class BaseRunCommand extends BaseCommand {
|
|
22
22
|
protected httpClient: RunHttpClient;
|
|
@@ -25,11 +25,11 @@ export default abstract class BaseRunCommand extends BaseCommand {
|
|
|
25
25
|
/**
|
|
26
26
|
* Initialize the run command with profile and HTTP client
|
|
27
27
|
*/
|
|
28
|
-
protected initRunCommand(profileFlag?: string): Promise<void>;
|
|
28
|
+
protected initRunCommand(profileFlag?: string, verbose?: boolean): Promise<void>;
|
|
29
29
|
/**
|
|
30
30
|
* Initialize with project required
|
|
31
31
|
*/
|
|
32
|
-
protected initRunCommandWithProject(profileFlag?: string): Promise<void>;
|
|
32
|
+
protected initRunCommandWithProject(profileFlag?: string, verbose?: boolean): Promise<void>;
|
|
33
33
|
/**
|
|
34
34
|
* Load credentials from file
|
|
35
35
|
*/
|