@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.
Files changed (156) hide show
  1. package/README.md +87 -0
  2. package/dist/base-command.d.ts +21 -1
  3. package/dist/base-command.js +92 -6
  4. package/dist/commands/branch/create/index.d.ts +0 -1
  5. package/dist/commands/branch/create/index.js +1 -38
  6. package/dist/commands/branch/delete/index.d.ts +0 -1
  7. package/dist/commands/branch/delete/index.js +1 -39
  8. package/dist/commands/branch/edit/index.d.ts +0 -1
  9. package/dist/commands/branch/edit/index.js +1 -39
  10. package/dist/commands/branch/get/index.d.ts +0 -1
  11. package/dist/commands/branch/get/index.js +1 -39
  12. package/dist/commands/branch/list/index.d.ts +0 -1
  13. package/dist/commands/branch/list/index.js +1 -39
  14. package/dist/commands/branch/set_live/index.d.ts +0 -1
  15. package/dist/commands/branch/set_live/index.js +1 -39
  16. package/dist/commands/function/create/index.d.ts +0 -1
  17. package/dist/commands/function/create/index.js +1 -38
  18. package/dist/commands/function/edit/index.d.ts +0 -1
  19. package/dist/commands/function/edit/index.js +1 -37
  20. package/dist/commands/function/get/index.d.ts +0 -1
  21. package/dist/commands/function/get/index.js +1 -38
  22. package/dist/commands/function/list/index.d.ts +0 -1
  23. package/dist/commands/function/list/index.js +1 -39
  24. package/dist/commands/platform/get/index.d.ts +0 -1
  25. package/dist/commands/platform/get/index.js +1 -33
  26. package/dist/commands/platform/list/index.d.ts +0 -1
  27. package/dist/commands/platform/list/index.js +1 -33
  28. package/dist/commands/profile/use/index.d.ts +33 -0
  29. package/dist/commands/profile/use/index.js +179 -0
  30. package/dist/commands/release/create/index.d.ts +0 -1
  31. package/dist/commands/release/create/index.js +1 -33
  32. package/dist/commands/release/delete/index.d.ts +0 -1
  33. package/dist/commands/release/delete/index.js +1 -33
  34. package/dist/commands/release/deploy/index.js +1 -12
  35. package/dist/commands/release/edit/index.d.ts +0 -1
  36. package/dist/commands/release/edit/index.js +1 -33
  37. package/dist/commands/release/export/index.d.ts +0 -1
  38. package/dist/commands/release/export/index.js +1 -31
  39. package/dist/commands/release/get/index.d.ts +0 -1
  40. package/dist/commands/release/get/index.js +1 -33
  41. package/dist/commands/release/import/index.d.ts +0 -1
  42. package/dist/commands/release/import/index.js +1 -32
  43. package/dist/commands/release/list/index.d.ts +0 -1
  44. package/dist/commands/release/list/index.js +1 -32
  45. package/dist/commands/release/pull/index.d.ts +0 -1
  46. package/dist/commands/release/pull/index.js +2 -38
  47. package/dist/commands/release/push/index.d.ts +0 -1
  48. package/dist/commands/release/push/index.js +1 -37
  49. package/dist/commands/static_host/build/create/index.d.ts +0 -1
  50. package/dist/commands/static_host/build/create/index.js +1 -39
  51. package/dist/commands/static_host/build/get/index.d.ts +0 -1
  52. package/dist/commands/static_host/build/get/index.js +1 -39
  53. package/dist/commands/static_host/build/list/index.d.ts +0 -1
  54. package/dist/commands/static_host/build/list/index.js +1 -39
  55. package/dist/commands/static_host/list/index.d.ts +0 -1
  56. package/dist/commands/static_host/list/index.js +1 -39
  57. package/dist/commands/tenant/backup/create/index.d.ts +0 -1
  58. package/dist/commands/tenant/backup/create/index.js +1 -33
  59. package/dist/commands/tenant/backup/delete/index.d.ts +0 -1
  60. package/dist/commands/tenant/backup/delete/index.js +1 -32
  61. package/dist/commands/tenant/backup/export/index.d.ts +0 -1
  62. package/dist/commands/tenant/backup/export/index.js +1 -31
  63. package/dist/commands/tenant/backup/import/index.d.ts +0 -1
  64. package/dist/commands/tenant/backup/import/index.js +1 -32
  65. package/dist/commands/tenant/backup/list/index.d.ts +0 -1
  66. package/dist/commands/tenant/backup/list/index.js +1 -33
  67. package/dist/commands/tenant/backup/restore/index.d.ts +0 -1
  68. package/dist/commands/tenant/backup/restore/index.js +1 -32
  69. package/dist/commands/tenant/cluster/create/index.d.ts +0 -1
  70. package/dist/commands/tenant/cluster/create/index.js +1 -31
  71. package/dist/commands/tenant/cluster/delete/index.d.ts +0 -1
  72. package/dist/commands/tenant/cluster/delete/index.js +1 -33
  73. package/dist/commands/tenant/cluster/edit/index.d.ts +0 -1
  74. package/dist/commands/tenant/cluster/edit/index.js +1 -33
  75. package/dist/commands/tenant/cluster/get/index.d.ts +0 -1
  76. package/dist/commands/tenant/cluster/get/index.js +1 -32
  77. package/dist/commands/tenant/cluster/license/get/index.d.ts +0 -1
  78. package/dist/commands/tenant/cluster/license/get/index.js +1 -31
  79. package/dist/commands/tenant/cluster/license/set/index.d.ts +0 -1
  80. package/dist/commands/tenant/cluster/license/set/index.js +1 -31
  81. package/dist/commands/tenant/cluster/list/index.d.ts +0 -1
  82. package/dist/commands/tenant/cluster/list/index.js +1 -32
  83. package/dist/commands/tenant/create/index.d.ts +0 -1
  84. package/dist/commands/tenant/create/index.js +1 -30
  85. package/dist/commands/tenant/delete/index.d.ts +0 -1
  86. package/dist/commands/tenant/delete/index.js +1 -33
  87. package/dist/commands/tenant/deploy_platform/index.d.ts +0 -1
  88. package/dist/commands/tenant/deploy_platform/index.js +1 -31
  89. package/dist/commands/tenant/deploy_release/index.d.ts +0 -1
  90. package/dist/commands/tenant/deploy_release/index.js +1 -32
  91. package/dist/commands/tenant/edit/index.d.ts +0 -1
  92. package/dist/commands/tenant/edit/index.js +1 -33
  93. package/dist/commands/tenant/env/delete/index.d.ts +0 -1
  94. package/dist/commands/tenant/env/delete/index.js +1 -32
  95. package/dist/commands/tenant/env/get/index.d.ts +0 -1
  96. package/dist/commands/tenant/env/get/index.js +1 -32
  97. package/dist/commands/tenant/env/get_all/index.d.ts +0 -1
  98. package/dist/commands/tenant/env/get_all/index.js +1 -30
  99. package/dist/commands/tenant/env/list/index.d.ts +0 -1
  100. package/dist/commands/tenant/env/list/index.js +1 -32
  101. package/dist/commands/tenant/env/set/index.d.ts +0 -1
  102. package/dist/commands/tenant/env/set/index.js +1 -32
  103. package/dist/commands/tenant/env/set_all/index.d.ts +0 -1
  104. package/dist/commands/tenant/env/set_all/index.js +1 -30
  105. package/dist/commands/tenant/get/index.d.ts +0 -1
  106. package/dist/commands/tenant/get/index.js +1 -32
  107. package/dist/commands/tenant/impersonate/index.d.ts +0 -1
  108. package/dist/commands/tenant/impersonate/index.js +1 -32
  109. package/dist/commands/tenant/license/get/index.d.ts +0 -1
  110. package/dist/commands/tenant/license/get/index.js +1 -31
  111. package/dist/commands/tenant/license/set/index.d.ts +0 -1
  112. package/dist/commands/tenant/license/set/index.js +1 -31
  113. package/dist/commands/tenant/list/index.d.ts +0 -1
  114. package/dist/commands/tenant/list/index.js +1 -32
  115. package/dist/commands/tenant/pull/index.d.ts +0 -1
  116. package/dist/commands/tenant/pull/index.js +1 -37
  117. package/dist/commands/tenant/unit_test/list/index.js +1 -12
  118. package/dist/commands/tenant/unit_test/run/index.js +1 -12
  119. package/dist/commands/tenant/unit_test/run_all/index.js +1 -12
  120. package/dist/commands/tenant/workflow_test/list/index.js +1 -12
  121. package/dist/commands/tenant/workflow_test/run/index.js +1 -12
  122. package/dist/commands/tenant/workflow_test/run_all/index.js +1 -12
  123. package/dist/commands/unit_test/list/index.d.ts +0 -1
  124. package/dist/commands/unit_test/list/index.js +1 -33
  125. package/dist/commands/unit_test/run/index.d.ts +0 -1
  126. package/dist/commands/unit_test/run/index.js +1 -33
  127. package/dist/commands/unit_test/run_all/index.d.ts +0 -1
  128. package/dist/commands/unit_test/run_all/index.js +1 -32
  129. package/dist/commands/workflow_test/delete/index.d.ts +0 -1
  130. package/dist/commands/workflow_test/delete/index.js +1 -33
  131. package/dist/commands/workflow_test/get/index.d.ts +0 -1
  132. package/dist/commands/workflow_test/get/index.js +1 -33
  133. package/dist/commands/workflow_test/list/index.d.ts +0 -1
  134. package/dist/commands/workflow_test/list/index.js +1 -33
  135. package/dist/commands/workflow_test/run/index.d.ts +0 -1
  136. package/dist/commands/workflow_test/run/index.js +1 -33
  137. package/dist/commands/workflow_test/run_all/index.d.ts +0 -1
  138. package/dist/commands/workflow_test/run_all/index.js +1 -32
  139. package/dist/commands/workspace/create/index.d.ts +0 -1
  140. package/dist/commands/workspace/create/index.js +1 -39
  141. package/dist/commands/workspace/delete/index.d.ts +0 -1
  142. package/dist/commands/workspace/delete/index.js +1 -39
  143. package/dist/commands/workspace/edit/index.d.ts +0 -1
  144. package/dist/commands/workspace/edit/index.js +1 -38
  145. package/dist/commands/workspace/get/index.d.ts +0 -1
  146. package/dist/commands/workspace/get/index.js +1 -38
  147. package/dist/commands/workspace/list/index.d.ts +0 -1
  148. package/dist/commands/workspace/list/index.js +1 -38
  149. package/dist/commands/workspace/pull/index.d.ts +0 -1
  150. package/dist/commands/workspace/pull/index.js +1 -37
  151. package/dist/utils/local-config.d.ts +43 -0
  152. package/dist/utils/local-config.js +88 -0
  153. package/dist/utils/multidoc-push.d.ts +25 -0
  154. package/dist/utils/multidoc-push.js +39 -18
  155. package/oclif.manifest.json +2490 -2403
  156. 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
- // Get profile name (default or from flag/env)
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
  }
@@ -15,5 +15,4 @@ export default class WorkspaceEdit extends BaseCommand {
15
15
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
16
  };
17
17
  run(): Promise<void>;
18
- private loadCredentials;
19
18
  }
@@ -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
- // Get profile name (default or from flag/env)
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
  }
@@ -10,5 +10,4 @@ export default class WorkspaceGet extends BaseCommand {
10
10
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  run(): Promise<void>;
13
- private loadCredentials;
14
13
  }
@@ -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
- // Get profile name (default or from flag/env)
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
  }
@@ -10,5 +10,4 @@ export default class WorkspaceList extends BaseCommand {
10
10
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  run(): Promise<void>;
13
- private loadCredentials;
14
13
  }
@@ -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
- // Get profile name (default or from flag/env)
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
- // Get profile name (default or from flag/env)
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 changedKeys = new Set(dryRunPreview.operations
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;