@xano/cli 0.0.26 → 0.0.27
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 +5 -4
- package/dist/commands/workspace/pull/index.js +113 -64
- package/dist/commands/workspace/push/index.d.ts +1 -0
- package/dist/commands/workspace/push/index.js +5 -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 +1963 -785
- 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,17 @@ 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
|
-
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
env: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
10
|
records: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
12
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
14
|
};
|
|
12
|
-
static description: string;
|
|
13
|
-
static examples: string[];
|
|
14
15
|
run(): Promise<void>;
|
|
16
|
+
private loadCredentials;
|
|
15
17
|
/**
|
|
16
18
|
* Parse a single document to extract its type, name, and optional verb.
|
|
17
19
|
* Skips leading comment lines (starting with //) to find the first
|
|
@@ -24,5 +26,4 @@ export default class Pull extends BaseCommand {
|
|
|
24
26
|
* characters that are unsafe in filenames.
|
|
25
27
|
*/
|
|
26
28
|
private sanitizeFilename;
|
|
27
|
-
private loadCredentials;
|
|
28
29
|
}
|
|
@@ -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,24 +12,6 @@ export default class Pull extends BaseCommand {
|
|
|
11
12
|
required: true,
|
|
12
13
|
}),
|
|
13
14
|
};
|
|
14
|
-
static flags = {
|
|
15
|
-
...BaseCommand.baseFlags,
|
|
16
|
-
workspace: Flags.string({
|
|
17
|
-
char: 'w',
|
|
18
|
-
description: 'Workspace ID (optional if set in profile)',
|
|
19
|
-
required: false,
|
|
20
|
-
}),
|
|
21
|
-
env: Flags.boolean({
|
|
22
|
-
description: 'Include environment variables',
|
|
23
|
-
required: false,
|
|
24
|
-
default: false,
|
|
25
|
-
}),
|
|
26
|
-
records: Flags.boolean({
|
|
27
|
-
description: 'Include records',
|
|
28
|
-
required: false,
|
|
29
|
-
default: false,
|
|
30
|
-
}),
|
|
31
|
-
};
|
|
32
15
|
static description = 'Pull a workspace multidoc from the Xano Metadata API and split into individual files';
|
|
33
16
|
static examples = [
|
|
34
17
|
`$ xano workspace pull ./my-workspace
|
|
@@ -41,6 +24,24 @@ Pulled 15 documents to ./output
|
|
|
41
24
|
Pulled 58 documents to ./backup
|
|
42
25
|
`,
|
|
43
26
|
];
|
|
27
|
+
static flags = {
|
|
28
|
+
...BaseCommand.baseFlags,
|
|
29
|
+
env: Flags.boolean({
|
|
30
|
+
default: false,
|
|
31
|
+
description: 'Include environment variables',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
records: Flags.boolean({
|
|
35
|
+
default: false,
|
|
36
|
+
description: 'Include records',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
workspace: Flags.string({
|
|
40
|
+
char: 'w',
|
|
41
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
42
|
+
required: false,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
44
45
|
async run() {
|
|
45
46
|
const { args, flags } = await this.parse(Pull);
|
|
46
47
|
// Get profile name (default or from flag/env)
|
|
@@ -79,16 +80,16 @@ Pulled 58 documents to ./backup
|
|
|
79
80
|
records: flags.records.toString(),
|
|
80
81
|
});
|
|
81
82
|
// Construct the API URL
|
|
82
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
83
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
|
|
83
84
|
// Fetch multidoc from the API
|
|
84
85
|
let responseText;
|
|
85
86
|
try {
|
|
86
87
|
const response = await fetch(apiUrl, {
|
|
87
|
-
method: 'GET',
|
|
88
88
|
headers: {
|
|
89
89
|
'accept': 'application/json',
|
|
90
90
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
91
91
|
},
|
|
92
|
+
method: 'GET',
|
|
92
93
|
});
|
|
93
94
|
if (!response.ok) {
|
|
94
95
|
const errorText = await response.text();
|
|
@@ -130,35 +131,101 @@ Pulled 58 documents to ./backup
|
|
|
130
131
|
const filenameCounters = new Map();
|
|
131
132
|
let writtenCount = 0;
|
|
132
133
|
for (const doc of documents) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
let typeDir;
|
|
135
|
+
let baseName;
|
|
136
|
+
if (doc.type === 'workspace') {
|
|
137
|
+
// workspace → workspace/{name}.xs
|
|
138
|
+
typeDir = path.join(outputDir, 'workspace');
|
|
139
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
140
|
+
}
|
|
141
|
+
else if (doc.type === 'workspace_trigger') {
|
|
142
|
+
// workspace_trigger → workspace/trigger/{name}.xs
|
|
143
|
+
typeDir = path.join(outputDir, 'workspace', 'trigger');
|
|
144
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
145
|
+
}
|
|
146
|
+
else if (doc.type === 'agent_trigger') {
|
|
147
|
+
// agent_trigger → agent/trigger/{name}.xs
|
|
148
|
+
typeDir = path.join(outputDir, 'agent', 'trigger');
|
|
149
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
150
|
+
}
|
|
151
|
+
else if (doc.type === 'mcp_server_trigger') {
|
|
152
|
+
// mcp_server_trigger → mcp_server/trigger/{name}.xs
|
|
153
|
+
typeDir = path.join(outputDir, 'mcp_server', 'trigger');
|
|
154
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
155
|
+
}
|
|
156
|
+
else if (doc.type === 'table_trigger') {
|
|
157
|
+
// table_trigger → table/trigger/{name}.xs
|
|
158
|
+
typeDir = path.join(outputDir, 'table', 'trigger');
|
|
159
|
+
baseName = this.sanitizeFilename(doc.name);
|
|
160
|
+
}
|
|
161
|
+
else if (doc.type === 'api_group') {
|
|
162
|
+
// api_group "test" → api/test/api_group.xs
|
|
163
|
+
const groupFolder = snakeCase(doc.name);
|
|
164
|
+
typeDir = path.join(outputDir, 'api', groupFolder);
|
|
165
|
+
baseName = 'api_group';
|
|
166
|
+
}
|
|
167
|
+
else if (doc.type === 'query' && doc.apiGroup) {
|
|
168
|
+
// query in group "test" → api/test/{query_name}.xs
|
|
169
|
+
const groupFolder = snakeCase(doc.apiGroup);
|
|
170
|
+
const nameParts = doc.name.split('/');
|
|
171
|
+
const leafName = nameParts.pop();
|
|
172
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
173
|
+
typeDir = path.join(outputDir, 'api', groupFolder, ...folderParts);
|
|
174
|
+
baseName = this.sanitizeFilename(leafName);
|
|
175
|
+
if (doc.verb) {
|
|
176
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Default: split folder path from name
|
|
181
|
+
const nameParts = doc.name.split('/');
|
|
182
|
+
const leafName = nameParts.pop();
|
|
183
|
+
const folderParts = nameParts.map((part) => snakeCase(part));
|
|
184
|
+
typeDir = path.join(outputDir, doc.type, ...folderParts);
|
|
185
|
+
baseName = this.sanitizeFilename(leafName);
|
|
186
|
+
if (doc.verb) {
|
|
187
|
+
baseName = `${baseName}_${doc.verb}`;
|
|
188
|
+
}
|
|
140
189
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
190
|
+
fs.mkdirSync(typeDir, { recursive: true });
|
|
191
|
+
// Track duplicates per directory
|
|
192
|
+
const dirKey = path.relative(outputDir, typeDir);
|
|
193
|
+
if (!filenameCounters.has(dirKey)) {
|
|
194
|
+
filenameCounters.set(dirKey, new Map());
|
|
144
195
|
}
|
|
145
|
-
const typeCounters = filenameCounters.get(
|
|
196
|
+
const typeCounters = filenameCounters.get(dirKey);
|
|
146
197
|
const count = typeCounters.get(baseName) || 0;
|
|
147
198
|
typeCounters.set(baseName, count + 1);
|
|
148
199
|
// Append numeric suffix for duplicates
|
|
149
200
|
let filename;
|
|
150
|
-
|
|
151
|
-
filename = `${baseName}.xs`;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
filename = `${baseName}_${count + 1}.xs`;
|
|
155
|
-
}
|
|
201
|
+
filename = count === 0 ? `${baseName}.xs` : `${baseName}_${count + 1}.xs`;
|
|
156
202
|
const filePath = path.join(typeDir, filename);
|
|
157
203
|
fs.writeFileSync(filePath, doc.content, 'utf8');
|
|
158
204
|
writtenCount++;
|
|
159
205
|
}
|
|
160
206
|
this.log(`Pulled ${writtenCount} documents to ${args.directory}`);
|
|
161
207
|
}
|
|
208
|
+
loadCredentials() {
|
|
209
|
+
const configDir = path.join(os.homedir(), '.xano');
|
|
210
|
+
const credentialsPath = path.join(configDir, 'credentials.yaml');
|
|
211
|
+
// Check if credentials file exists
|
|
212
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
213
|
+
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
214
|
+
`Create a profile using 'xano profile:create'`);
|
|
215
|
+
}
|
|
216
|
+
// Read credentials file
|
|
217
|
+
try {
|
|
218
|
+
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
219
|
+
const parsed = yaml.load(fileContent);
|
|
220
|
+
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
221
|
+
this.error('Credentials file has invalid format.');
|
|
222
|
+
}
|
|
223
|
+
return parsed;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
this.error(`Failed to parse credentials file: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
162
229
|
/**
|
|
163
230
|
* Parse a single document to extract its type, name, and optional verb.
|
|
164
231
|
* Skips leading comment lines (starting with //) to find the first
|
|
@@ -201,7 +268,13 @@ Pulled 58 documents to ./backup
|
|
|
201
268
|
if (verbMatch) {
|
|
202
269
|
verb = verbMatch[1];
|
|
203
270
|
}
|
|
204
|
-
|
|
271
|
+
// Extract api_group if present (e.g., api_group = "test")
|
|
272
|
+
let apiGroup;
|
|
273
|
+
const apiGroupMatch = content.match(/api_group\s*=\s*"([^"]*)"/);
|
|
274
|
+
if (apiGroupMatch) {
|
|
275
|
+
apiGroup = apiGroupMatch[1];
|
|
276
|
+
}
|
|
277
|
+
return { apiGroup, content, name, type, verb };
|
|
205
278
|
}
|
|
206
279
|
/**
|
|
207
280
|
* Sanitize a document name for use as a filename.
|
|
@@ -209,30 +282,6 @@ Pulled 58 documents to ./backup
|
|
|
209
282
|
* characters that are unsafe in filenames.
|
|
210
283
|
*/
|
|
211
284
|
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
|
-
}
|
|
285
|
+
return snakeCase(name.replaceAll('"', ''));
|
|
237
286
|
}
|
|
238
287
|
}
|
|
@@ -8,6 +8,7 @@ export default class Push extends BaseCommand {
|
|
|
8
8
|
static flags: {
|
|
9
9
|
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<void>;
|
|
13
14
|
/**
|
|
@@ -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 = {
|
|
@@ -89,17 +89,17 @@ Pushed 58 documents from ./backup
|
|
|
89
89
|
}
|
|
90
90
|
const multidoc = documents.join('\n---\n');
|
|
91
91
|
// Construct the API URL
|
|
92
|
-
const apiUrl = `${profile.instance_origin}/api:meta/
|
|
92
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/multidoc`;
|
|
93
93
|
// POST the multidoc to the API
|
|
94
94
|
try {
|
|
95
95
|
const response = await fetch(apiUrl, {
|
|
96
|
-
|
|
96
|
+
body: multidoc,
|
|
97
97
|
headers: {
|
|
98
98
|
'accept': 'application/json',
|
|
99
99
|
'Authorization': `Bearer ${profile.access_token}`,
|
|
100
100
|
'Content-Type': 'text/x-xanoscript',
|
|
101
101
|
},
|
|
102
|
-
|
|
102
|
+
method: 'POST',
|
|
103
103
|
});
|
|
104
104
|
if (!response.ok) {
|
|
105
105
|
const errorText = await response.text();
|
|
@@ -107,7 +107,7 @@ Pushed 58 documents from ./backup
|
|
|
107
107
|
}
|
|
108
108
|
// Log the response if any
|
|
109
109
|
const responseText = await response.text();
|
|
110
|
-
if (responseText) {
|
|
110
|
+
if (responseText && responseText !== 'null') {
|
|
111
111
|
this.log(responseText);
|
|
112
112
|
}
|
|
113
113
|
}
|
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
|
*/
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Base command for all run commands
|
|
3
3
|
*/
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
4
5
|
import * as fs from 'node:fs';
|
|
5
6
|
import * as os from 'node:os';
|
|
6
7
|
import * as path from 'node:path';
|
|
7
|
-
import * as yaml from 'js-yaml';
|
|
8
8
|
import BaseCommand from '../base-command.js';
|
|
9
9
|
import { DEFAULT_RUN_BASE_URL, RunHttpClient } from './run-http-client.js';
|
|
10
10
|
export default class BaseRunCommand extends BaseCommand {
|
|
@@ -14,7 +14,7 @@ export default class BaseRunCommand extends BaseCommand {
|
|
|
14
14
|
/**
|
|
15
15
|
* Initialize the run command with profile and HTTP client
|
|
16
16
|
*/
|
|
17
|
-
async initRunCommand(profileFlag) {
|
|
17
|
+
async initRunCommand(profileFlag, verbose) {
|
|
18
18
|
this.profileName = profileFlag || this.getDefaultProfile();
|
|
19
19
|
const credentials = this.loadCredentials();
|
|
20
20
|
if (!(this.profileName in credentials.profiles)) {
|
|
@@ -27,19 +27,21 @@ export default class BaseRunCommand extends BaseCommand {
|
|
|
27
27
|
}
|
|
28
28
|
const baseUrl = this.profile.run_base_url || DEFAULT_RUN_BASE_URL;
|
|
29
29
|
this.httpClient = new RunHttpClient({
|
|
30
|
-
baseUrl,
|
|
31
30
|
authToken: this.profile.access_token,
|
|
31
|
+
baseUrl,
|
|
32
|
+
logger: (msg) => this.log(msg),
|
|
32
33
|
projectId: this.profile.project,
|
|
34
|
+
verbose,
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Initialize with project required
|
|
37
39
|
*/
|
|
38
|
-
async initRunCommandWithProject(profileFlag) {
|
|
39
|
-
await this.initRunCommand(profileFlag);
|
|
40
|
+
async initRunCommandWithProject(profileFlag, verbose) {
|
|
41
|
+
await this.initRunCommand(profileFlag, verbose);
|
|
40
42
|
if (!this.profile.project) {
|
|
41
43
|
this.error(`Profile '${this.profileName}' is missing project. ` +
|
|
42
|
-
`
|
|
44
|
+
`Run 'xano profile:wizard' to set up your profile or use 'xano profile:edit --project <project-id>'`);
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
/**
|