@xano/cli 1.0.2-beta.5 → 1.0.2-beta.7
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 +87 -0
- package/dist/base-command.d.ts +21 -1
- package/dist/base-command.js +92 -6
- package/dist/commands/branch/create/index.d.ts +0 -1
- package/dist/commands/branch/create/index.js +1 -38
- package/dist/commands/branch/delete/index.d.ts +0 -1
- package/dist/commands/branch/delete/index.js +1 -39
- package/dist/commands/branch/edit/index.d.ts +0 -1
- package/dist/commands/branch/edit/index.js +1 -39
- package/dist/commands/branch/get/index.d.ts +0 -1
- package/dist/commands/branch/get/index.js +1 -39
- package/dist/commands/branch/list/index.d.ts +0 -1
- package/dist/commands/branch/list/index.js +1 -39
- package/dist/commands/branch/set_live/index.d.ts +0 -1
- package/dist/commands/branch/set_live/index.js +1 -39
- package/dist/commands/function/create/index.d.ts +0 -1
- package/dist/commands/function/create/index.js +1 -38
- package/dist/commands/function/edit/index.d.ts +0 -1
- package/dist/commands/function/edit/index.js +1 -37
- package/dist/commands/function/get/index.d.ts +0 -1
- package/dist/commands/function/get/index.js +1 -38
- package/dist/commands/function/list/index.d.ts +0 -1
- package/dist/commands/function/list/index.js +1 -39
- package/dist/commands/platform/get/index.d.ts +0 -1
- package/dist/commands/platform/get/index.js +1 -33
- package/dist/commands/platform/list/index.d.ts +0 -1
- package/dist/commands/platform/list/index.js +1 -33
- package/dist/commands/profile/use/index.d.ts +33 -0
- package/dist/commands/profile/use/index.js +179 -0
- package/dist/commands/release/create/index.d.ts +0 -1
- package/dist/commands/release/create/index.js +1 -33
- package/dist/commands/release/delete/index.d.ts +0 -1
- package/dist/commands/release/delete/index.js +1 -33
- package/dist/commands/release/deploy/index.js +1 -12
- package/dist/commands/release/edit/index.d.ts +0 -1
- package/dist/commands/release/edit/index.js +1 -33
- package/dist/commands/release/export/index.d.ts +0 -1
- package/dist/commands/release/export/index.js +1 -31
- package/dist/commands/release/get/index.d.ts +0 -1
- package/dist/commands/release/get/index.js +1 -33
- package/dist/commands/release/import/index.d.ts +0 -1
- package/dist/commands/release/import/index.js +1 -32
- package/dist/commands/release/list/index.d.ts +0 -1
- package/dist/commands/release/list/index.js +1 -32
- package/dist/commands/release/pull/index.d.ts +0 -1
- package/dist/commands/release/pull/index.js +2 -38
- package/dist/commands/release/push/index.d.ts +0 -1
- package/dist/commands/release/push/index.js +1 -37
- package/dist/commands/static_host/build/create/index.d.ts +0 -1
- package/dist/commands/static_host/build/create/index.js +1 -39
- package/dist/commands/static_host/build/get/index.d.ts +0 -1
- package/dist/commands/static_host/build/get/index.js +1 -39
- package/dist/commands/static_host/build/list/index.d.ts +0 -1
- package/dist/commands/static_host/build/list/index.js +1 -39
- package/dist/commands/static_host/list/index.d.ts +0 -1
- package/dist/commands/static_host/list/index.js +1 -39
- package/dist/commands/tenant/backup/create/index.d.ts +0 -1
- package/dist/commands/tenant/backup/create/index.js +1 -33
- package/dist/commands/tenant/backup/delete/index.d.ts +0 -1
- package/dist/commands/tenant/backup/delete/index.js +1 -32
- package/dist/commands/tenant/backup/export/index.d.ts +0 -1
- package/dist/commands/tenant/backup/export/index.js +1 -31
- package/dist/commands/tenant/backup/import/index.d.ts +0 -1
- package/dist/commands/tenant/backup/import/index.js +1 -32
- package/dist/commands/tenant/backup/list/index.d.ts +0 -1
- package/dist/commands/tenant/backup/list/index.js +1 -33
- package/dist/commands/tenant/backup/restore/index.d.ts +0 -1
- package/dist/commands/tenant/backup/restore/index.js +1 -32
- package/dist/commands/tenant/cluster/create/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/create/index.js +1 -31
- package/dist/commands/tenant/cluster/delete/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/delete/index.js +1 -33
- package/dist/commands/tenant/cluster/edit/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/edit/index.js +1 -33
- package/dist/commands/tenant/cluster/get/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/get/index.js +1 -32
- package/dist/commands/tenant/cluster/license/get/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/license/get/index.js +1 -31
- package/dist/commands/tenant/cluster/license/set/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/license/set/index.js +1 -31
- package/dist/commands/tenant/cluster/list/index.d.ts +0 -1
- package/dist/commands/tenant/cluster/list/index.js +1 -32
- package/dist/commands/tenant/create/index.d.ts +0 -1
- package/dist/commands/tenant/create/index.js +1 -30
- package/dist/commands/tenant/delete/index.d.ts +0 -1
- package/dist/commands/tenant/delete/index.js +1 -33
- package/dist/commands/tenant/deploy_platform/index.d.ts +0 -1
- package/dist/commands/tenant/deploy_platform/index.js +1 -31
- package/dist/commands/tenant/deploy_release/index.d.ts +0 -1
- package/dist/commands/tenant/deploy_release/index.js +1 -32
- package/dist/commands/tenant/edit/index.d.ts +0 -1
- package/dist/commands/tenant/edit/index.js +1 -33
- package/dist/commands/tenant/env/delete/index.d.ts +0 -1
- package/dist/commands/tenant/env/delete/index.js +1 -32
- package/dist/commands/tenant/env/get/index.d.ts +0 -1
- package/dist/commands/tenant/env/get/index.js +1 -32
- package/dist/commands/tenant/env/get_all/index.d.ts +0 -1
- package/dist/commands/tenant/env/get_all/index.js +1 -30
- package/dist/commands/tenant/env/list/index.d.ts +0 -1
- package/dist/commands/tenant/env/list/index.js +1 -32
- package/dist/commands/tenant/env/set/index.d.ts +0 -1
- package/dist/commands/tenant/env/set/index.js +1 -32
- package/dist/commands/tenant/env/set_all/index.d.ts +0 -1
- package/dist/commands/tenant/env/set_all/index.js +1 -30
- package/dist/commands/tenant/get/index.d.ts +0 -1
- package/dist/commands/tenant/get/index.js +1 -32
- package/dist/commands/tenant/impersonate/index.d.ts +0 -1
- package/dist/commands/tenant/impersonate/index.js +1 -32
- package/dist/commands/tenant/license/get/index.d.ts +0 -1
- package/dist/commands/tenant/license/get/index.js +1 -31
- package/dist/commands/tenant/license/set/index.d.ts +0 -1
- package/dist/commands/tenant/license/set/index.js +1 -31
- package/dist/commands/tenant/list/index.d.ts +0 -1
- package/dist/commands/tenant/list/index.js +1 -32
- package/dist/commands/tenant/pull/index.d.ts +0 -1
- package/dist/commands/tenant/pull/index.js +1 -37
- package/dist/commands/tenant/unit_test/list/index.js +1 -12
- package/dist/commands/tenant/unit_test/run/index.js +1 -12
- package/dist/commands/tenant/unit_test/run_all/index.js +1 -12
- package/dist/commands/tenant/workflow_test/list/index.js +1 -12
- package/dist/commands/tenant/workflow_test/run/index.js +1 -12
- package/dist/commands/tenant/workflow_test/run_all/index.js +1 -12
- package/dist/commands/unit_test/list/index.d.ts +0 -1
- package/dist/commands/unit_test/list/index.js +1 -33
- package/dist/commands/unit_test/run/index.d.ts +0 -1
- package/dist/commands/unit_test/run/index.js +1 -33
- package/dist/commands/unit_test/run_all/index.d.ts +0 -1
- package/dist/commands/unit_test/run_all/index.js +1 -32
- package/dist/commands/workflow_test/delete/index.d.ts +0 -1
- package/dist/commands/workflow_test/delete/index.js +1 -33
- package/dist/commands/workflow_test/get/index.d.ts +0 -1
- package/dist/commands/workflow_test/get/index.js +1 -33
- package/dist/commands/workflow_test/list/index.d.ts +0 -1
- package/dist/commands/workflow_test/list/index.js +1 -33
- package/dist/commands/workflow_test/run/index.d.ts +0 -1
- package/dist/commands/workflow_test/run/index.js +1 -33
- package/dist/commands/workflow_test/run_all/index.d.ts +0 -1
- package/dist/commands/workflow_test/run_all/index.js +1 -32
- package/dist/commands/workspace/create/index.d.ts +0 -1
- package/dist/commands/workspace/create/index.js +1 -39
- package/dist/commands/workspace/delete/index.d.ts +0 -1
- package/dist/commands/workspace/delete/index.js +1 -39
- package/dist/commands/workspace/edit/index.d.ts +0 -1
- package/dist/commands/workspace/edit/index.js +1 -38
- package/dist/commands/workspace/get/index.d.ts +0 -1
- package/dist/commands/workspace/get/index.js +1 -38
- package/dist/commands/workspace/list/index.d.ts +0 -1
- package/dist/commands/workspace/list/index.js +1 -38
- package/dist/commands/workspace/pull/index.d.ts +0 -1
- package/dist/commands/workspace/pull/index.js +1 -37
- package/dist/utils/local-config.d.ts +43 -0
- package/dist/utils/local-config.js +88 -0
- package/dist/utils/multidoc-push.d.ts +25 -0
- package/dist/utils/multidoc-push.js +39 -18
- package/oclif.manifest.json +2490 -2403
- package/package.json +1 -1
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
2
|
import BaseCommand from '../../../base-command.js';
|
|
5
3
|
export default class WorkspaceDelete extends BaseCommand {
|
|
6
4
|
static description = 'Delete a workspace via the Xano Metadata API. Cannot delete workspaces with active tenants.';
|
|
@@ -42,23 +40,7 @@ Deleted workspace 123
|
|
|
42
40
|
};
|
|
43
41
|
async run() {
|
|
44
42
|
const { flags } = await this.parse(WorkspaceDelete);
|
|
45
|
-
|
|
46
|
-
const profileName = flags.profile || this.getDefaultProfile();
|
|
47
|
-
// Load credentials
|
|
48
|
-
const credentials = this.loadCredentials();
|
|
49
|
-
// Get the profile configuration
|
|
50
|
-
if (!(profileName in credentials.profiles)) {
|
|
51
|
-
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
52
|
-
`Create a profile using 'xano profile create'`);
|
|
53
|
-
}
|
|
54
|
-
const profile = credentials.profiles[profileName];
|
|
55
|
-
// Validate required fields
|
|
56
|
-
if (!profile.instance_origin) {
|
|
57
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
58
|
-
}
|
|
59
|
-
if (!profile.access_token) {
|
|
60
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
61
|
-
}
|
|
43
|
+
const { profile } = this.resolveProfile(flags);
|
|
62
44
|
// Get workspace ID from flag or profile
|
|
63
45
|
const workspaceId = flags.workspace || profile.workspace;
|
|
64
46
|
if (!workspaceId) {
|
|
@@ -119,24 +101,4 @@ Deleted workspace 123
|
|
|
119
101
|
});
|
|
120
102
|
});
|
|
121
103
|
}
|
|
122
|
-
loadCredentials() {
|
|
123
|
-
const credentialsPath = this.getCredentialsPath();
|
|
124
|
-
// Check if credentials file exists
|
|
125
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
126
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` +
|
|
127
|
-
`Create a profile using 'xano profile create'`);
|
|
128
|
-
}
|
|
129
|
-
// Read credentials file
|
|
130
|
-
try {
|
|
131
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
132
|
-
const parsed = yaml.load(fileContent);
|
|
133
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
134
|
-
this.error('Credentials file has invalid format.');
|
|
135
|
-
}
|
|
136
|
-
return parsed;
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
104
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
2
|
import BaseCommand from '../../../base-command.js';
|
|
5
3
|
export default class WorkspaceEdit extends BaseCommand {
|
|
6
4
|
static description = 'Edit an existing workspace via the Xano Metadata API';
|
|
@@ -67,23 +65,7 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
67
65
|
};
|
|
68
66
|
async run() {
|
|
69
67
|
const { flags } = await this.parse(WorkspaceEdit);
|
|
70
|
-
|
|
71
|
-
const profileName = flags.profile || this.getDefaultProfile();
|
|
72
|
-
// Load credentials
|
|
73
|
-
const credentials = this.loadCredentials();
|
|
74
|
-
// Get the profile configuration
|
|
75
|
-
if (!(profileName in credentials.profiles)) {
|
|
76
|
-
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
77
|
-
`Create a profile using 'xano profile create'`);
|
|
78
|
-
}
|
|
79
|
-
const profile = credentials.profiles[profileName];
|
|
80
|
-
// Validate required fields
|
|
81
|
-
if (!profile.instance_origin) {
|
|
82
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
83
|
-
}
|
|
84
|
-
if (!profile.access_token) {
|
|
85
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
86
|
-
}
|
|
68
|
+
const { profile } = this.resolveProfile(flags);
|
|
87
69
|
// Get workspace ID from flag or profile
|
|
88
70
|
const workspaceId = flags.workspace || profile.workspace;
|
|
89
71
|
if (!workspaceId) {
|
|
@@ -160,23 +142,4 @@ Updated workspace: my-workspace (ID: 123)
|
|
|
160
142
|
}
|
|
161
143
|
}
|
|
162
144
|
}
|
|
163
|
-
loadCredentials() {
|
|
164
|
-
const credentialsPath = this.getCredentialsPath();
|
|
165
|
-
// Check if credentials file exists
|
|
166
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
167
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
168
|
-
}
|
|
169
|
-
// Read credentials file
|
|
170
|
-
try {
|
|
171
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
172
|
-
const parsed = yaml.load(fileContent);
|
|
173
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
174
|
-
this.error('Credentials file has invalid format.');
|
|
175
|
-
}
|
|
176
|
-
return parsed;
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
145
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
2
|
import BaseCommand from '../../../base-command.js';
|
|
5
3
|
export default class WorkspaceGet extends BaseCommand {
|
|
6
4
|
static description = 'Get details of a specific workspace from the Xano Metadata API';
|
|
@@ -41,23 +39,7 @@ Workspace: my-workspace (ID: 123)
|
|
|
41
39
|
};
|
|
42
40
|
async run() {
|
|
43
41
|
const { flags } = await this.parse(WorkspaceGet);
|
|
44
|
-
|
|
45
|
-
const profileName = flags.profile || this.getDefaultProfile();
|
|
46
|
-
// Load credentials
|
|
47
|
-
const credentials = this.loadCredentials();
|
|
48
|
-
// Get the profile configuration
|
|
49
|
-
if (!(profileName in credentials.profiles)) {
|
|
50
|
-
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
51
|
-
`Create a profile using 'xano profile create'`);
|
|
52
|
-
}
|
|
53
|
-
const profile = credentials.profiles[profileName];
|
|
54
|
-
// Validate required fields
|
|
55
|
-
if (!profile.instance_origin) {
|
|
56
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
57
|
-
}
|
|
58
|
-
if (!profile.access_token) {
|
|
59
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
60
|
-
}
|
|
42
|
+
const { profile } = this.resolveProfile(flags);
|
|
61
43
|
// Get workspace ID from flag or profile
|
|
62
44
|
const workspaceId = flags.workspace || profile.workspace;
|
|
63
45
|
if (!workspaceId) {
|
|
@@ -112,23 +94,4 @@ Workspace: my-workspace (ID: 123)
|
|
|
112
94
|
}
|
|
113
95
|
}
|
|
114
96
|
}
|
|
115
|
-
loadCredentials() {
|
|
116
|
-
const credentialsPath = this.getCredentialsPath();
|
|
117
|
-
// Check if credentials file exists
|
|
118
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
119
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
|
|
120
|
-
}
|
|
121
|
-
// Read credentials file
|
|
122
|
-
try {
|
|
123
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
124
|
-
const parsed = yaml.load(fileContent);
|
|
125
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
126
|
-
this.error('Credentials file has invalid format.');
|
|
127
|
-
}
|
|
128
|
-
return parsed;
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
97
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
2
|
import BaseCommand from '../../../base-command.js';
|
|
5
3
|
export default class WorkspaceList extends BaseCommand {
|
|
6
4
|
static description = 'List all workspaces from the Xano Metadata API';
|
|
@@ -57,23 +55,7 @@ Available workspaces:
|
|
|
57
55
|
};
|
|
58
56
|
async run() {
|
|
59
57
|
const { flags } = await this.parse(WorkspaceList);
|
|
60
|
-
|
|
61
|
-
const profileName = flags.profile || this.getDefaultProfile();
|
|
62
|
-
// Load credentials
|
|
63
|
-
const credentials = this.loadCredentials();
|
|
64
|
-
// Get the profile configuration
|
|
65
|
-
if (!(profileName in credentials.profiles)) {
|
|
66
|
-
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
67
|
-
`Create a profile using 'xano profile:create'`);
|
|
68
|
-
}
|
|
69
|
-
const profile = credentials.profiles[profileName];
|
|
70
|
-
// Validate required fields
|
|
71
|
-
if (!profile.instance_origin) {
|
|
72
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
73
|
-
}
|
|
74
|
-
if (!profile.access_token) {
|
|
75
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
76
|
-
}
|
|
58
|
+
const { profile } = this.resolveProfile(flags);
|
|
77
59
|
// Construct the API URL
|
|
78
60
|
const apiUrl = `${profile.instance_origin}/api:meta/workspace`;
|
|
79
61
|
// Fetch workspaces from the API
|
|
@@ -136,23 +118,4 @@ Available workspaces:
|
|
|
136
118
|
}
|
|
137
119
|
}
|
|
138
120
|
}
|
|
139
|
-
loadCredentials() {
|
|
140
|
-
const credentialsPath = this.getCredentialsPath();
|
|
141
|
-
// Check if credentials file exists
|
|
142
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
143
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
|
|
144
|
-
}
|
|
145
|
-
// Read credentials file
|
|
146
|
-
try {
|
|
147
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
148
|
-
const parsed = yaml.load(fileContent);
|
|
149
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
150
|
-
this.error('Credentials file has invalid format.');
|
|
151
|
-
}
|
|
152
|
-
return parsed;
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
121
|
}
|
|
@@ -14,7 +14,6 @@ export default class Pull extends BaseCommand {
|
|
|
14
14
|
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
15
|
};
|
|
16
16
|
run(): Promise<void>;
|
|
17
|
-
private loadCredentials;
|
|
18
17
|
/**
|
|
19
18
|
* Sanitize a document name for use as a filename.
|
|
20
19
|
* Strips quotes, replaces spaces with underscores, and removes
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import * as yaml from 'js-yaml';
|
|
3
2
|
import * as fs from 'node:fs';
|
|
4
3
|
import * as path from 'node:path';
|
|
5
4
|
import snakeCase from 'lodash.snakecase';
|
|
@@ -59,23 +58,7 @@ Pulled 58 documents
|
|
|
59
58
|
};
|
|
60
59
|
async run() {
|
|
61
60
|
const { flags } = await this.parse(Pull);
|
|
62
|
-
|
|
63
|
-
const profileName = flags.profile || this.getDefaultProfile();
|
|
64
|
-
// Load credentials
|
|
65
|
-
const credentials = this.loadCredentials();
|
|
66
|
-
// Get the profile configuration
|
|
67
|
-
if (!(profileName in credentials.profiles)) {
|
|
68
|
-
this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
|
|
69
|
-
`Create a profile using 'xano profile:create'`);
|
|
70
|
-
}
|
|
71
|
-
const profile = credentials.profiles[profileName];
|
|
72
|
-
// Validate required fields
|
|
73
|
-
if (!profile.instance_origin) {
|
|
74
|
-
this.error(`Profile '${profileName}' is missing instance_origin`);
|
|
75
|
-
}
|
|
76
|
-
if (!profile.access_token) {
|
|
77
|
-
this.error(`Profile '${profileName}' is missing access_token`);
|
|
78
|
-
}
|
|
61
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
79
62
|
// Determine workspace_id from flag or profile
|
|
80
63
|
let workspaceId;
|
|
81
64
|
if (flags.workspace) {
|
|
@@ -258,25 +241,6 @@ Pulled 58 documents
|
|
|
258
241
|
}
|
|
259
242
|
this.log(`Pulled ${writtenCount} documents to ${flags.directory}`);
|
|
260
243
|
}
|
|
261
|
-
loadCredentials() {
|
|
262
|
-
const credentialsPath = this.getCredentialsPath();
|
|
263
|
-
// Check if credentials file exists
|
|
264
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
265
|
-
this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile:create'`);
|
|
266
|
-
}
|
|
267
|
-
// Read credentials file
|
|
268
|
-
try {
|
|
269
|
-
const fileContent = fs.readFileSync(credentialsPath, 'utf8');
|
|
270
|
-
const parsed = yaml.load(fileContent);
|
|
271
|
-
if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
|
|
272
|
-
this.error('Credentials file has invalid format.');
|
|
273
|
-
}
|
|
274
|
-
return parsed;
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
this.error(`Failed to parse credentials file: ${error}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
244
|
/**
|
|
281
245
|
* Sanitize a document name for use as a filename.
|
|
282
246
|
* Strips quotes, replaces spaces with underscores, and removes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ProfileConfig } from '../base-command.js';
|
|
2
|
+
export declare const LOCAL_PROFILE_FILENAME = "profile.yaml";
|
|
3
|
+
/** Fields a project-local profile.yaml may set. No secrets allowed. */
|
|
4
|
+
export interface LocalProfileConfig {
|
|
5
|
+
account_origin?: string;
|
|
6
|
+
branch?: string;
|
|
7
|
+
instance_origin?: string;
|
|
8
|
+
profile?: string;
|
|
9
|
+
workspace?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parse the raw contents of a profile.yaml.
|
|
13
|
+
* - Throws if `access_token` is present (secrets belong in credentials.yaml).
|
|
14
|
+
* - Returns null if the content is not an object or has no recognized keys
|
|
15
|
+
* (so an unrelated profile.yaml from another tool is ignored, not hijacked).
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseLocalProfile(raw: string): LocalProfileConfig | null;
|
|
18
|
+
/**
|
|
19
|
+
* Return a new profile with the local override fields applied.
|
|
20
|
+
* The `profile` pointer is never copied; secrets are preserved from the base.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyLocalOverrides(base: ProfileConfig, local: LocalProfileConfig): ProfileConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Decide which profile name to use and whether local overrides apply.
|
|
25
|
+
* Precedence: explicit -p/XANO_PROFILE > local profile.yaml > credentials default.
|
|
26
|
+
* An explicit profile disables the local file entirely (name and overrides).
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveProfileSelection(params: {
|
|
29
|
+
defaultProfile: string;
|
|
30
|
+
explicitProfile?: string;
|
|
31
|
+
hasLocal: boolean;
|
|
32
|
+
localProfileName?: string;
|
|
33
|
+
}): {
|
|
34
|
+
applyLocal: boolean;
|
|
35
|
+
profileName: string;
|
|
36
|
+
};
|
|
37
|
+
/** Format the one-line banner shown when a local profile.yaml is in effect. */
|
|
38
|
+
export declare function formatLocalProfileBanner(profileName: string, workspace: string | undefined, relativePath: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Walk up from `startDir` to the filesystem root, returning the path of the
|
|
41
|
+
* first profile.yaml found, or null if none exists.
|
|
42
|
+
*/
|
|
43
|
+
export declare function findLocalProfilePath(startDir: string): null | string;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as yaml from 'js-yaml';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
export const LOCAL_PROFILE_FILENAME = 'profile.yaml';
|
|
5
|
+
const RECOGNIZED_KEYS = ['profile', 'workspace', 'instance_origin', 'account_origin', 'branch'];
|
|
6
|
+
/**
|
|
7
|
+
* Fields that may be layered onto a resolved profile (everything except the profile pointer).
|
|
8
|
+
* NOTE: `insecure` is intentionally NOT listed here — a project file must never silently
|
|
9
|
+
* disable TLS verification (security boundary).
|
|
10
|
+
*/
|
|
11
|
+
const OVERRIDE_KEYS = ['workspace', 'instance_origin', 'account_origin', 'branch'];
|
|
12
|
+
/**
|
|
13
|
+
* Parse the raw contents of a profile.yaml.
|
|
14
|
+
* - Throws if `access_token` is present (secrets belong in credentials.yaml).
|
|
15
|
+
* - Returns null if the content is not an object or has no recognized keys
|
|
16
|
+
* (so an unrelated profile.yaml from another tool is ignored, not hijacked).
|
|
17
|
+
*/
|
|
18
|
+
export function parseLocalProfile(raw) {
|
|
19
|
+
const parsed = yaml.load(raw);
|
|
20
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const obj = parsed;
|
|
24
|
+
if ('access_token' in obj) {
|
|
25
|
+
throw new Error(`profile.yaml must not contain access_token. ` +
|
|
26
|
+
`Tokens belong in credentials.yaml — reference a profile by name instead.`);
|
|
27
|
+
}
|
|
28
|
+
const config = {};
|
|
29
|
+
for (const key of RECOGNIZED_KEYS) {
|
|
30
|
+
if (obj[key] !== undefined && obj[key] !== null) {
|
|
31
|
+
config[key] = String(obj[key]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Object.keys(config).length === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Return a new profile with the local override fields applied.
|
|
41
|
+
* The `profile` pointer is never copied; secrets are preserved from the base.
|
|
42
|
+
*/
|
|
43
|
+
export function applyLocalOverrides(base, local) {
|
|
44
|
+
const result = { ...base };
|
|
45
|
+
for (const key of OVERRIDE_KEYS) {
|
|
46
|
+
if (local[key] !== undefined) {
|
|
47
|
+
result[key] = local[key];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Decide which profile name to use and whether local overrides apply.
|
|
54
|
+
* Precedence: explicit -p/XANO_PROFILE > local profile.yaml > credentials default.
|
|
55
|
+
* An explicit profile disables the local file entirely (name and overrides).
|
|
56
|
+
*/
|
|
57
|
+
export function resolveProfileSelection(params) {
|
|
58
|
+
if (params.explicitProfile) {
|
|
59
|
+
return { applyLocal: false, profileName: params.explicitProfile };
|
|
60
|
+
}
|
|
61
|
+
if (params.hasLocal) {
|
|
62
|
+
return { applyLocal: true, profileName: params.localProfileName ?? params.defaultProfile };
|
|
63
|
+
}
|
|
64
|
+
return { applyLocal: false, profileName: params.defaultProfile };
|
|
65
|
+
}
|
|
66
|
+
/** Format the one-line banner shown when a local profile.yaml is in effect. */
|
|
67
|
+
export function formatLocalProfileBanner(profileName, workspace, relativePath) {
|
|
68
|
+
const workspaceClause = workspace ? ` (workspace ${workspace})` : '';
|
|
69
|
+
return `Using profile '${profileName}'${workspaceClause} · ${relativePath}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Walk up from `startDir` to the filesystem root, returning the path of the
|
|
73
|
+
* first profile.yaml found, or null if none exists.
|
|
74
|
+
*/
|
|
75
|
+
export function findLocalProfilePath(startDir) {
|
|
76
|
+
let dir = resolve(startDir);
|
|
77
|
+
while (true) {
|
|
78
|
+
const candidate = join(dir, LOCAL_PROFILE_FILENAME);
|
|
79
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
const parent = dirname(dir);
|
|
83
|
+
if (parent === dir) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
dir = parent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -54,6 +54,31 @@ export declare function countSummaryChanges(summary: Record<string, {
|
|
|
54
54
|
truncated: number;
|
|
55
55
|
updated: number;
|
|
56
56
|
}>, shouldDelete: boolean): number;
|
|
57
|
+
/**
|
|
58
|
+
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
59
|
+
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
60
|
+
*
|
|
61
|
+
* The preview keys each operation as `${type}:${name}` (with the verb appended for
|
|
62
|
+
* API endpoints). Local documents are matched against that set.
|
|
63
|
+
*
|
|
64
|
+
* Triggers need special handling: they are authored with specific subtypes
|
|
65
|
+
* (workspace_trigger, error_trigger, table_trigger, agent_trigger,
|
|
66
|
+
* mcp_server_trigger, realtime_trigger) but the server buckets every one under the
|
|
67
|
+
* generic `trigger` type in the preview. We therefore match a local trigger against
|
|
68
|
+
* both its specific type and the generic `trigger` type — otherwise partial pushes
|
|
69
|
+
* silently drop triggers, requiring `--sync --force` to include them (DEV-7084).
|
|
70
|
+
*/
|
|
71
|
+
export declare function filterChangedEntries(entries: Array<{
|
|
72
|
+
content: string;
|
|
73
|
+
filePath: string;
|
|
74
|
+
}>, operations: Array<{
|
|
75
|
+
action: string;
|
|
76
|
+
name: string;
|
|
77
|
+
type: string;
|
|
78
|
+
}>, includeRecords: boolean): Array<{
|
|
79
|
+
content: string;
|
|
80
|
+
filePath: string;
|
|
81
|
+
}>;
|
|
57
82
|
/**
|
|
58
83
|
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
59
84
|
*/
|
|
@@ -14,6 +14,44 @@ export const WORKSPACE_MISMATCH_THRESHOLD = 10;
|
|
|
14
14
|
export function countSummaryChanges(summary, shouldDelete) {
|
|
15
15
|
return Object.values(summary).reduce((sum, c) => sum + c.created + c.updated + (shouldDelete ? c.deleted : 0) + c.truncated, 0);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Filter document entries down to the ones the dry-run preview reports as changed,
|
|
19
|
+
* used to send only the changed documents during a partial (non-`--sync`) push.
|
|
20
|
+
*
|
|
21
|
+
* The preview keys each operation as `${type}:${name}` (with the verb appended for
|
|
22
|
+
* API endpoints). Local documents are matched against that set.
|
|
23
|
+
*
|
|
24
|
+
* Triggers need special handling: they are authored with specific subtypes
|
|
25
|
+
* (workspace_trigger, error_trigger, table_trigger, agent_trigger,
|
|
26
|
+
* mcp_server_trigger, realtime_trigger) but the server buckets every one under the
|
|
27
|
+
* generic `trigger` type in the preview. We therefore match a local trigger against
|
|
28
|
+
* both its specific type and the generic `trigger` type — otherwise partial pushes
|
|
29
|
+
* silently drop triggers, requiring `--sync --force` to include them (DEV-7084).
|
|
30
|
+
*/
|
|
31
|
+
export function filterChangedEntries(entries, operations, includeRecords) {
|
|
32
|
+
const changedKeys = new Set(operations
|
|
33
|
+
.filter((op) => op.action !== 'unchanged' && op.action !== 'delete' && op.action !== 'cascade_delete')
|
|
34
|
+
.map((op) => `${op.type}:${op.name}`));
|
|
35
|
+
return entries.filter((entry) => {
|
|
36
|
+
const parsed = parseDocument(entry.content);
|
|
37
|
+
if (!parsed)
|
|
38
|
+
return true;
|
|
39
|
+
// Workspace settings always use a fixed key in dry-run regardless of the actual name
|
|
40
|
+
if (parsed.type === 'workspace' && changedKeys.has('workspace:workspace'))
|
|
41
|
+
return true;
|
|
42
|
+
const opName = parsed.verb ? `${parsed.name} ${parsed.verb}` : parsed.name;
|
|
43
|
+
if (changedKeys.has(`${parsed.type}:${opName}`))
|
|
44
|
+
return true;
|
|
45
|
+
// The dry-run preview reports all trigger subtypes under the generic `trigger`
|
|
46
|
+
// type, so match triggers against that bucket too (DEV-7084).
|
|
47
|
+
if (parsed.type.endsWith('_trigger') && changedKeys.has(`trigger:${opName}`))
|
|
48
|
+
return true;
|
|
49
|
+
// Keep table documents that contain records when --records is active
|
|
50
|
+
if (includeRecords && parsed.type === 'table' && /\bitems\s*=\s*\[/m.test(entry.content))
|
|
51
|
+
return true;
|
|
52
|
+
return false;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
17
55
|
// ── File Collection ─────────────────────────────────────────────────────────
|
|
18
56
|
/**
|
|
19
57
|
* Recursively collect all .xs files from a directory, sorted for deterministic ordering.
|
|
@@ -520,24 +558,7 @@ export async function executePush(ctx, target, flags) {
|
|
|
520
558
|
}
|
|
521
559
|
// ── Partial push: filter to changed documents only ────────────────────
|
|
522
560
|
if (isPartial && dryRunPreview) {
|
|
523
|
-
const
|
|
524
|
-
.filter((op) => op.action !== 'unchanged' && op.action !== 'delete' && op.action !== 'cascade_delete')
|
|
525
|
-
.map((op) => `${op.type}:${op.name}`));
|
|
526
|
-
const filteredEntries = documentEntries.filter((entry) => {
|
|
527
|
-
const parsed = parseDocument(entry.content);
|
|
528
|
-
if (!parsed)
|
|
529
|
-
return true;
|
|
530
|
-
// Workspace settings always use a fixed key in dry-run regardless of the actual name
|
|
531
|
-
if (parsed.type === 'workspace' && changedKeys.has('workspace:workspace'))
|
|
532
|
-
return true;
|
|
533
|
-
const opName = parsed.verb ? `${parsed.name} ${parsed.verb}` : parsed.name;
|
|
534
|
-
if (changedKeys.has(`${parsed.type}:${opName}`))
|
|
535
|
-
return true;
|
|
536
|
-
// Keep table documents that contain records when --records is active
|
|
537
|
-
if (flags.records && parsed.type === 'table' && /\bitems\s*=\s*\[/m.test(entry.content))
|
|
538
|
-
return true;
|
|
539
|
-
return false;
|
|
540
|
-
});
|
|
561
|
+
const filteredEntries = filterChangedEntries(documentEntries, dryRunPreview.operations, flags.records);
|
|
541
562
|
if (filteredEntries.length === 0) {
|
|
542
563
|
log('No changes to push.');
|
|
543
564
|
return;
|