@xano/cli 1.2.0 → 1.3.1-beta.0

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 (192) hide show
  1. package/README.md +381 -4
  2. package/dist/base-command.d.ts +54 -36
  3. package/dist/base-command.js +246 -169
  4. package/dist/commands/auth/index.d.ts +1 -1
  5. package/dist/commands/auth/index.js +7 -7
  6. package/dist/commands/branch/create/index.d.ts +1 -1
  7. package/dist/commands/branch/create/index.js +1 -1
  8. package/dist/commands/branch/set_live/index.d.ts +22 -0
  9. package/dist/commands/branch/set_live/index.js +60 -1
  10. package/dist/commands/ephemeral/create/index.d.ts +18 -0
  11. package/dist/commands/ephemeral/create/index.js +103 -0
  12. package/dist/commands/ephemeral/delete/index.d.ts +18 -0
  13. package/dist/commands/ephemeral/delete/index.js +99 -0
  14. package/dist/commands/ephemeral/edit/index.d.ts +18 -0
  15. package/dist/commands/ephemeral/edit/index.js +109 -0
  16. package/dist/commands/ephemeral/get/index.d.ts +21 -0
  17. package/dist/commands/ephemeral/get/index.js +125 -0
  18. package/dist/commands/ephemeral/impersonate/index.d.ts +20 -0
  19. package/dist/commands/ephemeral/impersonate/index.js +120 -0
  20. package/dist/commands/ephemeral/list/index.d.ts +19 -0
  21. package/dist/commands/ephemeral/list/index.js +123 -0
  22. package/dist/commands/ephemeral/pull/index.d.ts +19 -0
  23. package/dist/commands/ephemeral/pull/index.js +163 -0
  24. package/dist/commands/ephemeral/push/index.d.ts +35 -0
  25. package/dist/commands/ephemeral/push/index.js +228 -0
  26. package/dist/commands/ephemeral/static_host/build/delete/index.d.ts +20 -0
  27. package/dist/commands/ephemeral/static_host/build/delete/index.js +121 -0
  28. package/dist/commands/ephemeral/static_host/build/get/index.d.ts +18 -0
  29. package/dist/commands/ephemeral/static_host/build/get/index.js +137 -0
  30. package/dist/commands/ephemeral/static_host/build/list/index.d.ts +18 -0
  31. package/dist/commands/ephemeral/static_host/build/list/index.js +137 -0
  32. package/dist/commands/ephemeral/static_host/build/pull/index.d.ts +53 -0
  33. package/dist/commands/ephemeral/static_host/build/pull/index.js +308 -0
  34. package/dist/commands/ephemeral/static_host/build/push/index.d.ts +24 -0
  35. package/dist/commands/ephemeral/static_host/build/push/index.js +250 -0
  36. package/dist/commands/ephemeral/static_host/create/index.d.ts +18 -0
  37. package/dist/commands/ephemeral/static_host/create/index.js +92 -0
  38. package/dist/commands/ephemeral/static_host/deploy/index.d.ts +19 -0
  39. package/dist/commands/ephemeral/static_host/deploy/index.js +112 -0
  40. package/dist/commands/ephemeral/static_host/edit/index.d.ts +24 -0
  41. package/dist/commands/ephemeral/static_host/edit/index.js +158 -0
  42. package/dist/commands/ephemeral/static_host/get/index.d.ts +19 -0
  43. package/dist/commands/ephemeral/static_host/get/index.js +101 -0
  44. package/dist/commands/ephemeral/static_host/list/index.d.ts +17 -0
  45. package/dist/commands/ephemeral/static_host/list/index.js +132 -0
  46. package/dist/commands/flatten/index.d.ts +34 -0
  47. package/dist/commands/flatten/index.js +140 -0
  48. package/dist/commands/function/create/index.d.ts +15 -0
  49. package/dist/commands/function/create/index.js +41 -1
  50. package/dist/commands/function/edit/index.d.ts +15 -0
  51. package/dist/commands/function/edit/index.js +66 -32
  52. package/dist/commands/function/get/index.js +2 -3
  53. package/dist/commands/function/run/index.d.ts +1 -0
  54. package/dist/commands/function/run/index.js +19 -6
  55. package/dist/commands/policy/catalogue/index.d.ts +15 -0
  56. package/dist/commands/policy/catalogue/index.js +29 -0
  57. package/dist/commands/policy/create/index.d.ts +18 -0
  58. package/dist/commands/policy/create/index.js +39 -0
  59. package/dist/commands/policy/delete/index.d.ts +19 -0
  60. package/dist/commands/policy/delete/index.js +61 -0
  61. package/dist/commands/policy/evaluate/index.d.ts +32 -0
  62. package/dist/commands/policy/evaluate/index.js +71 -0
  63. package/dist/commands/policy/list/index.d.ts +14 -0
  64. package/dist/commands/policy/list/index.js +21 -0
  65. package/dist/commands/policy/parse/index.d.ts +19 -0
  66. package/dist/commands/policy/parse/index.js +18 -0
  67. package/dist/commands/policy/publish/index.d.ts +20 -0
  68. package/dist/commands/policy/publish/index.js +38 -0
  69. package/dist/commands/policy/runs/index.d.ts +54 -0
  70. package/dist/commands/policy/runs/index.js +273 -0
  71. package/dist/commands/policy/status/index.d.ts +21 -0
  72. package/dist/commands/policy/status/index.js +54 -0
  73. package/dist/commands/profile/create/index.d.ts +1 -1
  74. package/dist/commands/profile/create/index.js +7 -7
  75. package/dist/commands/profile/me/index.js +1 -1
  76. package/dist/commands/profile/use/index.js +0 -4
  77. package/dist/commands/profile/wizard/index.d.ts +1 -1
  78. package/dist/commands/profile/wizard/index.js +12 -10
  79. package/dist/commands/profile/workspace/set/index.js +1 -1
  80. package/dist/commands/release/create/index.js +7 -0
  81. package/dist/commands/release/deploy/index.d.ts +28 -0
  82. package/dist/commands/release/deploy/index.js +90 -6
  83. package/dist/commands/release/edit/index.js +2 -2
  84. package/dist/commands/release/export/index.js +1 -2
  85. package/dist/commands/release/import/index.js +6 -2
  86. package/dist/commands/release/list/index.d.ts +7 -0
  87. package/dist/commands/release/list/index.js +54 -10
  88. package/dist/commands/release/pull/index.d.ts +0 -6
  89. package/dist/commands/release/pull/index.js +13 -99
  90. package/dist/commands/release/push/index.js +21 -3
  91. package/dist/commands/sandbox/env/get_all/index.js +1 -1
  92. package/dist/commands/sandbox/env/set_all/index.js +1 -1
  93. package/dist/commands/sandbox/get/index.js +1 -1
  94. package/dist/commands/sandbox/license/get/index.js +1 -1
  95. package/dist/commands/sandbox/license/set/index.js +1 -1
  96. package/dist/commands/sandbox/pull/index.d.ts +0 -1
  97. package/dist/commands/sandbox/pull/index.js +13 -81
  98. package/dist/commands/sandbox/push/index.d.ts +1 -1
  99. package/dist/commands/sandbox/push/index.js +11 -6
  100. package/dist/commands/skills/pull/index.d.ts +30 -0
  101. package/dist/commands/skills/pull/index.js +118 -0
  102. package/dist/commands/static_host/build/create/index.d.ts +1 -1
  103. package/dist/commands/static_host/build/create/index.js +2 -2
  104. package/dist/commands/static_host/build/pull/index.js +1 -1
  105. package/dist/commands/static_host/build/push/index.js +1 -1
  106. package/dist/commands/static_host/edit/index.js +1 -1
  107. package/dist/commands/tenant/backup/export/index.js +1 -2
  108. package/dist/commands/tenant/backup/import/index.js +1 -1
  109. package/dist/commands/tenant/cluster/create/index.js +1 -1
  110. package/dist/commands/tenant/cluster/license/get/index.js +1 -1
  111. package/dist/commands/tenant/cluster/license/set/index.js +1 -1
  112. package/dist/commands/tenant/create/index.d.ts +4 -1
  113. package/dist/commands/tenant/create/index.js +26 -5
  114. package/dist/commands/tenant/deploy_platform/index.js +1 -1
  115. package/dist/commands/tenant/deploy_release/index.d.ts +40 -0
  116. package/dist/commands/tenant/deploy_release/index.js +186 -4
  117. package/dist/commands/tenant/edit/index.d.ts +2 -0
  118. package/dist/commands/tenant/edit/index.js +46 -11
  119. package/dist/commands/tenant/env/get_all/index.js +1 -1
  120. package/dist/commands/tenant/env/set_all/index.js +1 -1
  121. package/dist/commands/tenant/impersonate/index.d.ts +1 -1
  122. package/dist/commands/tenant/impersonate/index.js +14 -14
  123. package/dist/commands/tenant/license/get/index.js +1 -1
  124. package/dist/commands/tenant/license/set/index.js +1 -1
  125. package/dist/commands/tenant/pull/index.d.ts +0 -6
  126. package/dist/commands/tenant/pull/index.js +14 -100
  127. package/dist/commands/tenant_deploy_request/bypass/index.d.ts +31 -0
  128. package/dist/commands/tenant_deploy_request/bypass/index.js +109 -0
  129. package/dist/commands/tenant_deploy_request/create/index.d.ts +21 -0
  130. package/dist/commands/tenant_deploy_request/create/index.js +108 -0
  131. package/dist/commands/tenant_deploy_request/edit/index.d.ts +22 -0
  132. package/dist/commands/tenant_deploy_request/edit/index.js +103 -0
  133. package/dist/commands/tenant_deploy_request/get/index.d.ts +25 -0
  134. package/dist/commands/tenant_deploy_request/get/index.js +116 -0
  135. package/dist/commands/tenant_deploy_request/list/index.d.ts +20 -0
  136. package/dist/commands/tenant_deploy_request/list/index.js +122 -0
  137. package/dist/commands/tenant_deploy_request/revision/index.d.ts +21 -0
  138. package/dist/commands/tenant_deploy_request/revision/index.js +83 -0
  139. package/dist/commands/tenant_deploy_request/set_status/index.d.ts +38 -0
  140. package/dist/commands/tenant_deploy_request/set_status/index.js +159 -0
  141. package/dist/commands/workspace/git/pull/index.d.ts +4 -4
  142. package/dist/commands/workspace/git/pull/index.js +158 -97
  143. package/dist/commands/workspace/pull/index.d.ts +9 -5
  144. package/dist/commands/workspace/pull/index.js +83 -106
  145. package/dist/commands/workspace/push/index.d.ts +19 -0
  146. package/dist/commands/workspace/push/index.js +124 -5
  147. package/dist/help.js +4 -2
  148. package/dist/policy-command.d.ts +90 -0
  149. package/dist/policy-command.js +94 -0
  150. package/dist/update-check.d.ts +1 -1
  151. package/dist/update-check.js +1 -1
  152. package/dist/utils/document-parser.d.ts +126 -0
  153. package/dist/utils/document-parser.js +332 -4
  154. package/dist/utils/flatten.d.ts +36 -0
  155. package/dist/utils/flatten.js +68 -0
  156. package/dist/utils/knowledge-sync.d.ts +1 -0
  157. package/dist/utils/knowledge-sync.js +11 -6
  158. package/dist/utils/microservice-wait.d.ts +53 -0
  159. package/dist/utils/microservice-wait.js +84 -0
  160. package/dist/utils/multidoc-push.d.ts +143 -2
  161. package/dist/utils/multidoc-push.js +479 -40
  162. package/dist/utils/paging.d.ts +2 -1
  163. package/dist/utils/paging.js +0 -1
  164. package/dist/utils/policy/catalogue.d.ts +7 -0
  165. package/dist/utils/policy/catalogue.js +72 -0
  166. package/dist/utils/policy/errors.d.ts +12 -0
  167. package/dist/utils/policy/errors.js +61 -0
  168. package/dist/utils/policy/feedback.d.ts +68 -0
  169. package/dist/utils/policy/feedback.js +165 -0
  170. package/dist/utils/policy/findings.d.ts +23 -0
  171. package/dist/utils/policy/findings.js +65 -0
  172. package/dist/utils/policy/gate.d.ts +161 -0
  173. package/dist/utils/policy/gate.js +201 -0
  174. package/dist/utils/policy/goal.d.ts +46 -0
  175. package/dist/utils/policy/goal.js +95 -0
  176. package/dist/utils/policy/permission.d.ts +23 -0
  177. package/dist/utils/policy/permission.js +84 -0
  178. package/dist/utils/policy/push-gate.d.ts +5 -0
  179. package/dist/utils/policy/push-gate.js +29 -0
  180. package/dist/utils/policy/release.d.ts +85 -0
  181. package/dist/utils/policy/release.js +115 -0
  182. package/dist/utils/policy/request.d.ts +55 -0
  183. package/dist/utils/policy/request.js +115 -0
  184. package/dist/utils/policy/runs.d.ts +30 -0
  185. package/dist/utils/policy/runs.js +101 -0
  186. package/dist/utils/policy/status.d.ts +47 -0
  187. package/dist/utils/policy/status.js +117 -0
  188. package/dist/utils/policy/types.d.ts +185 -0
  189. package/dist/utils/policy/types.js +1 -0
  190. package/dist/utils/reference-checker.js +14 -3
  191. package/oclif.manifest.json +8538 -3445
  192. package/package.json +16 -1
@@ -212,13 +212,6 @@ To authenticate, open the following URL in any browser:
212
212
  }
213
213
  return noBrowser ? this.promptForToken(origin) : this.startAuthServer(origin);
214
214
  }
215
- getHeaders(accessToken) {
216
- return {
217
- 'User-Agent': buildUserAgent(this.config.version),
218
- accept: 'application/json',
219
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
220
- };
221
- }
222
215
  async fetchBranches(accessToken, origin, workspaceId) {
223
216
  try {
224
217
  const response = await fetch(`${origin}/api:meta/workspace/${workspaceId}/branch`, {
@@ -288,6 +281,13 @@ To authenticate, open the following URL in any browser:
288
281
  return [];
289
282
  }
290
283
  }
284
+ getHeaders(accessToken) {
285
+ return {
286
+ accept: 'application/json',
287
+ 'User-Agent': buildUserAgent(this.config.version),
288
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
289
+ };
290
+ }
291
291
  async promptForToken(origin) {
292
292
  // Headless flow: no local callback server. The login page, when opened
293
293
  // without a `callback` param, renders the access token on screen for the
@@ -1,9 +1,9 @@
1
1
  import BaseCommand from '../../../base-command.js';
2
2
  export default class BranchCreate extends BaseCommand {
3
- static description: string;
4
3
  static args: {
5
4
  label: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
5
  };
6
+ static description: string;
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  color: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,13 +1,13 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
3
  export default class BranchCreate extends BaseCommand {
4
- static description = 'Create a new branch by cloning from an existing branch';
5
4
  static args = {
6
5
  label: Args.string({
7
6
  description: 'Label for the new branch',
8
7
  required: true,
9
8
  }),
10
9
  };
10
+ static description = 'Create a new branch by cloning from an existing branch';
11
11
  static examples = [
12
12
  `$ xano branch create dev
13
13
  Created branch: dev
@@ -8,11 +8,33 @@ export default class BranchSetLive extends BaseCommand {
8
8
  static flags: {
9
9
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'policy-override': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  workspace: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
14
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
16
  };
17
+ /** Set once the platform refuses for a weakened mandatory policy: already explained, it exits 1 as it is. */
18
+ private refusedByPermission;
19
+ /** Set once the set-live policy gate refuses, the one failure that exits 2. */
20
+ private refusedByPolicyGate;
21
+ /** Exit 2 is the set-live gate's refusal; every other failure, flag errors included, exits 1. */
22
+ protected catch(error: Error & {
23
+ oclif?: {
24
+ exit?: number;
25
+ };
26
+ }): Promise<void>;
16
27
  run(): Promise<void>;
17
28
  private confirm;
29
+ /**
30
+ * The set-live gate refused: the verdict on stdout (under `-o json`, `{set_live: false, message,
31
+ * policy_gate}`), then the platform's message and how to proceed, exiting 2.
32
+ */
33
+ private refuseSetLive;
34
+ /**
35
+ * The branch weakens a mandatory policy of the live branch (or lacks one), and the credential lacks
36
+ * `workspace:policy` update. That is a permission refusal, not a blocking finding: it exits 1 (under
37
+ * `-o json` as `{error}`) with the platform's sentence and how to go on. The live branch is unchanged.
38
+ */
39
+ private refuseWeakening;
18
40
  }
@@ -1,5 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import BaseCommand from '../../../base-command.js';
3
+ import { BLANK_POLICY_OVERRIDE, blankPolicyOverride, gateOverrideHint, gateRefusal, liveGateLines, quoted, weakeningRefusal, } from '../../../utils/policy/gate.js';
4
+ import { policyWeakeningGuidance } from '../../../utils/policy/permission.js';
3
5
  export default class BranchSetLive extends BaseCommand {
4
6
  static args = {
5
7
  branch_label: Args.string({
@@ -7,7 +9,7 @@ export default class BranchSetLive extends BaseCommand {
7
9
  required: true,
8
10
  }),
9
11
  };
10
- static description = '[IMPORTANT] ALWAYS confirm with the user before changing the live branch. Sets a branch as the live (active) branch for API requests.';
12
+ static description = '[IMPORTANT] ALWAYS confirm with the user before changing the live branch. Sets a branch as the live (active) branch for API requests. The set-live policy gate checks the branch against its own policies, which live will have once it is set live: when blocking findings refuse it, the command exits 2 unless --policy-override gives a reason. A branch that weakens a mandatory policy of the live branch needs workspace:policy update; without it the command exits 1.';
11
13
  static examples = [
12
14
  `$ xano branch set-live staging
13
15
  Are you sure you want to set 'staging' as the live branch? (y/N) y
@@ -24,6 +26,7 @@ Branch 'v1' is now live
24
26
  "live": true
25
27
  }
26
28
  `,
29
+ `$ xano branch set_live rollback --force --policy-override "Rollback approved; finding tracked in JIRA-12"`,
27
30
  ];
28
31
  static flags = {
29
32
  ...BaseCommand.baseFlags,
@@ -40,14 +43,27 @@ Branch 'v1' is now live
40
43
  options: ['summary', 'json'],
41
44
  required: false,
42
45
  }),
46
+ 'policy-override': Flags.string({ description: 'Set the branch live past a blocking set-live policy gate with an audited reason (requires workspace:policy update)' }),
43
47
  workspace: Flags.integer({
44
48
  char: 'w',
45
49
  description: 'Workspace ID (uses profile workspace if not provided)',
46
50
  required: false,
47
51
  }),
48
52
  };
53
+ /** Set once the platform refuses for a weakened mandatory policy: already explained, it exits 1 as it is. */
54
+ refusedByPermission = false;
55
+ /** Set once the set-live policy gate refuses, the one failure that exits 2. */
56
+ refusedByPolicyGate = false;
57
+ /** Exit 2 is the set-live gate's refusal; every other failure, flag errors included, exits 1. */
58
+ async catch(error) {
59
+ if (this.refusedByPolicyGate)
60
+ return super.catch(error);
61
+ return this.catchAsOperational(error);
62
+ }
49
63
  async run() {
50
64
  const { args, flags } = await this.parse(BranchSetLive);
65
+ if (blankPolicyOverride(flags['policy-override']))
66
+ this.error(BLANK_POLICY_OVERRIDE);
51
67
  const { profile } = this.resolveProfile(flags);
52
68
  // Get workspace ID from flag or profile
53
69
  const workspaceId = flags.workspace || profile.workspace;
@@ -66,16 +82,26 @@ Branch 'v1' is now live
66
82
  }
67
83
  // Construct the API URL
68
84
  const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/branch/${encodeURIComponent(branchLabel)}/live`;
85
+ // A reason from someone with workspace:policy update sets the branch live past a blocking gate.
86
+ const overrideReason = flags['policy-override']?.trim();
69
87
  // Set branch as live via the API
70
88
  try {
71
89
  const response = await this.verboseFetch(apiUrl, {
90
+ ...(overrideReason ? { body: JSON.stringify({ override_reason: overrideReason }) } : {}),
72
91
  headers: {
73
92
  'accept': 'application/json',
74
93
  'Authorization': `Bearer ${profile.access_token}`,
94
+ ...(overrideReason ? { 'Content-Type': 'application/json' } : {}),
75
95
  },
76
96
  method: 'POST',
77
97
  }, flags.verbose, profile.access_token);
78
98
  if (!response.ok) {
99
+ const refused = await gateRefusal(response);
100
+ if (refused)
101
+ this.refuseSetLive(refused, flags, branchLabel);
102
+ const weakening = await weakeningRefusal(response);
103
+ if (weakening)
104
+ this.refuseWeakening(weakening, flags, branchLabel);
79
105
  const errorText = await response.text();
80
106
  this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
81
107
  }
@@ -89,6 +115,8 @@ Branch 'v1' is now live
89
115
  }
90
116
  }
91
117
  catch (error) {
118
+ if (this.refusedByPolicyGate || this.refusedByPermission)
119
+ throw error;
92
120
  if (error instanceof Error) {
93
121
  this.error(`Failed to set branch as live: ${error.message}`);
94
122
  }
@@ -111,4 +139,35 @@ Branch 'v1' is now live
111
139
  });
112
140
  });
113
141
  }
142
+ /**
143
+ * The set-live gate refused: the verdict on stdout (under `-o json`, `{set_live: false, message,
144
+ * policy_gate}`), then the platform's message and how to proceed, exiting 2.
145
+ */
146
+ refuseSetLive(refused, flags, branchLabel) {
147
+ if (flags.output === 'json') {
148
+ this.log(JSON.stringify({ message: refused.message, policy_gate: refused.answer, set_live: false }, null, 2));
149
+ }
150
+ else {
151
+ for (const line of liveGateLines(refused.answer, 'branch'))
152
+ this.log(line);
153
+ }
154
+ const workspace = flags.workspace ? ` -w ${flags.workspace}` : '';
155
+ const hint = gateOverrideHint(refused.answer, `xano branch set_live ${quoted(branchLabel)}${workspace} --policy-override "<why>"`);
156
+ this.refusedByPolicyGate = true;
157
+ this.error(`${refused.message}${hint}`, { exit: 2 });
158
+ }
159
+ /**
160
+ * The branch weakens a mandatory policy of the live branch (or lacks one), and the credential lacks
161
+ * `workspace:policy` update. That is a permission refusal, not a blocking finding: it exits 1 (under
162
+ * `-o json` as `{error}`) with the platform's sentence and how to go on. The live branch is unchanged.
163
+ */
164
+ refuseWeakening(refused, flags, branchLabel) {
165
+ const workspace = flags.workspace ? ` -w ${flags.workspace}` : '';
166
+ this.refusedByPermission = true;
167
+ const again = `xano branch set_live ${quoted(branchLabel)}${workspace}`;
168
+ const way = refused.payload.unavailable === true
169
+ ? `The check may read them next time: ${again}`
170
+ : `To set it live yourself, give ${quoted(branchLabel)} the live branch's version of those policies, then run: ${again}`;
171
+ this.error(`${refused.message}${policyWeakeningGuidance(refused.payload, [way], true)}`);
172
+ }
114
173
  }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralCreate extends BaseCommand {
3
+ static args: {
4
+ display: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'expires-hours': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ run(): Promise<void>;
18
+ }
@@ -0,0 +1,103 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class EphemeralCreate extends BaseCommand {
4
+ static args = {
5
+ display: Args.string({
6
+ description: 'Display name for the ephemeral tenant',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Create a new ephemeral (short-lived, auto-expiring) tenant in a workspace';
11
+ static examples = [
12
+ `$ xano ephemeral create "PR preview"
13
+ Created ephemeral tenant: PR preview (e4f2-9ab1-...) - ID: 42
14
+ Expires: in 1 hour
15
+ `,
16
+ `$ xano ephemeral create "Demo" --expires-hours 24 -w 114`,
17
+ `$ xano ephemeral create "Load test" --description "overnight soak" --expires-hours 24 -o json`,
18
+ ];
19
+ static flags = {
20
+ ...BaseCommand.baseFlags,
21
+ description: Flags.string({
22
+ char: 'd',
23
+ description: 'Ephemeral tenant description',
24
+ required: false,
25
+ }),
26
+ 'expires-hours': Flags.integer({
27
+ default: 1,
28
+ description: 'Hours until the tenant auto-expires (1-24)',
29
+ max: 24,
30
+ min: 1,
31
+ required: false,
32
+ }),
33
+ output: Flags.string({
34
+ char: 'o',
35
+ default: 'summary',
36
+ description: 'Output format',
37
+ options: ['summary', 'json'],
38
+ required: false,
39
+ }),
40
+ workspace: Flags.string({
41
+ char: 'w',
42
+ description: 'Workspace ID (uses profile workspace if not provided)',
43
+ required: false,
44
+ }),
45
+ };
46
+ async run() {
47
+ const { args, flags } = await this.parse(EphemeralCreate);
48
+ const { profile } = this.resolveProfile(flags);
49
+ // Ephemeral tenants REQUIRE a workspace (unlike sandbox, which is workspace-agnostic).
50
+ const workspaceId = flags.workspace || profile.workspace;
51
+ if (!workspaceId) {
52
+ this.error('No workspace ID provided. Ephemeral tenants require a workspace — use --workspace or set one in your profile.');
53
+ }
54
+ const body = {
55
+ display: args.display,
56
+ expires_hours: flags['expires-hours'],
57
+ tag: [],
58
+ };
59
+ if (flags.description)
60
+ body.description = flags.description;
61
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/ephemeral`;
62
+ try {
63
+ const response = await this.verboseFetch(apiUrl, {
64
+ body: JSON.stringify(body),
65
+ headers: {
66
+ accept: 'application/json',
67
+ Authorization: `Bearer ${profile.access_token}`,
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ method: 'POST',
71
+ }, flags.verbose, profile.access_token);
72
+ if (!response.ok) {
73
+ const errorText = await response.text();
74
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
75
+ }
76
+ const tenant = (await response.json());
77
+ if (flags.output === 'json') {
78
+ this.log(JSON.stringify(tenant, null, 2));
79
+ }
80
+ else {
81
+ this.log(`Created ephemeral tenant: ${tenant.display || tenant.name} (${tenant.name}) - ID: ${tenant.id}`);
82
+ if (tenant.state) {
83
+ this.log(` State: ${tenant.state}`);
84
+ }
85
+ if (tenant.ephemeral_expires_at) {
86
+ // The API serializes the timestamp field as a date string
87
+ // ("2026-07-24 19:49:15+0000"); tolerate a raw unix-epoch number too.
88
+ const raw = tenant.ephemeral_expires_at;
89
+ const expiresMs = typeof raw === 'number' ? raw * 1000 : Date.parse(String(raw).replace(' ', 'T'));
90
+ this.log(` Expires: ${Number.isNaN(expiresMs) ? String(raw) : new Date(expiresMs).toISOString()}`);
91
+ }
92
+ }
93
+ }
94
+ catch (error) {
95
+ if (error instanceof Error) {
96
+ this.error(`Failed to create ephemeral tenant: ${error.message}`);
97
+ }
98
+ else {
99
+ this.error(`Failed to create ephemeral tenant: ${String(error)}`);
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralDelete extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ private confirm;
18
+ }
@@ -0,0 +1,99 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class EphemeralDelete extends BaseCommand {
4
+ static args = {
5
+ tenant_name: Args.string({
6
+ description: 'Ephemeral tenant name to delete',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Delete an ephemeral tenant permanently. This destroys all associated infrastructure and cannot be undone.';
11
+ static examples = [
12
+ `$ xano ephemeral delete e4f2-9ab1-xyz1
13
+ Are you sure you want to delete tenant e4f2-9ab1-xyz1? This action cannot be undone. (y/N) y
14
+ Deleted tenant e4f2-9ab1-xyz1
15
+ `,
16
+ `$ xano ephemeral delete e4f2-9ab1-xyz1 --force
17
+ Deleted tenant e4f2-9ab1-xyz1
18
+ `,
19
+ `$ xano ephemeral delete e4f2-9ab1-xyz1 -f -o json`,
20
+ ];
21
+ static flags = {
22
+ ...BaseCommand.baseFlags,
23
+ force: Flags.boolean({
24
+ char: 'f',
25
+ default: false,
26
+ description: '[CRITICAL] NEVER run without explicit user confirmation. Skips the confirmation prompt.',
27
+ required: false,
28
+ }),
29
+ output: Flags.string({
30
+ char: 'o',
31
+ default: 'summary',
32
+ description: 'Output format',
33
+ options: ['summary', 'json'],
34
+ required: false,
35
+ }),
36
+ workspace: Flags.string({
37
+ char: 'w',
38
+ description: 'Workspace ID (uses profile workspace if not provided)',
39
+ required: false,
40
+ }),
41
+ };
42
+ async run() {
43
+ const { args, flags } = await this.parse(EphemeralDelete);
44
+ const { profile } = this.resolveProfile(flags);
45
+ const workspaceId = flags.workspace || profile.workspace;
46
+ if (!workspaceId) {
47
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
48
+ }
49
+ const tenantName = args.tenant_name;
50
+ if (!flags.force) {
51
+ const confirmed = await this.confirm(`Are you sure you want to delete tenant ${tenantName}? This action cannot be undone.`);
52
+ if (!confirmed) {
53
+ this.log('Deletion cancelled.');
54
+ return;
55
+ }
56
+ }
57
+ const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
58
+ try {
59
+ const response = await this.verboseFetch(apiUrl, {
60
+ headers: {
61
+ 'accept': 'application/json',
62
+ 'Authorization': `Bearer ${profile.access_token}`,
63
+ },
64
+ method: 'DELETE',
65
+ }, flags.verbose, profile.access_token);
66
+ if (!response.ok) {
67
+ const errorText = await response.text();
68
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
69
+ }
70
+ if (flags.output === 'json') {
71
+ this.log(JSON.stringify({ deleted: true, tenant_name: tenantName }, null, 2));
72
+ }
73
+ else {
74
+ this.log(`Deleted tenant ${tenantName}`);
75
+ }
76
+ }
77
+ catch (error) {
78
+ if (error instanceof Error) {
79
+ this.error(`Failed to delete ephemeral tenant: ${error.message}`);
80
+ }
81
+ else {
82
+ this.error(`Failed to delete ephemeral tenant: ${String(error)}`);
83
+ }
84
+ }
85
+ }
86
+ async confirm(message) {
87
+ const readline = await import('node:readline');
88
+ const rl = readline.createInterface({
89
+ input: process.stdin,
90
+ output: process.stdout,
91
+ });
92
+ return new Promise((resolve) => {
93
+ rl.question(`${message} (y/N) `, (answer) => {
94
+ rl.close();
95
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
96
+ });
97
+ });
98
+ }
99
+ }
@@ -0,0 +1,18 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralEdit extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ display: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ };
17
+ run(): Promise<void>;
18
+ }
@@ -0,0 +1,109 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import BaseCommand from '../../../base-command.js';
3
+ export default class EphemeralEdit extends BaseCommand {
4
+ static args = {
5
+ tenant_name: Args.string({
6
+ description: 'Ephemeral tenant name to edit',
7
+ required: true,
8
+ }),
9
+ };
10
+ static description = 'Edit an existing ephemeral tenant';
11
+ static examples = [
12
+ `$ xano ephemeral edit e4f2-9ab1-xyz1 --display "New Name" --description "Updated description"
13
+ Updated tenant: New Name (e4f2-9ab1-xyz1) - ID: 42
14
+ `,
15
+ `$ xano ephemeral edit e4f2-9ab1-xyz1 -o json`,
16
+ ];
17
+ static flags = {
18
+ ...BaseCommand.baseFlags,
19
+ description: Flags.string({
20
+ char: 'd',
21
+ description: 'New description',
22
+ required: false,
23
+ }),
24
+ display: Flags.string({
25
+ description: 'New display name',
26
+ required: false,
27
+ }),
28
+ output: Flags.string({
29
+ char: 'o',
30
+ default: 'summary',
31
+ description: 'Output format',
32
+ options: ['summary', 'json'],
33
+ required: false,
34
+ }),
35
+ workspace: Flags.string({
36
+ char: 'w',
37
+ description: 'Workspace ID (uses profile workspace if not provided)',
38
+ required: false,
39
+ }),
40
+ };
41
+ async run() {
42
+ const { args, flags } = await this.parse(EphemeralEdit);
43
+ const { profile } = this.resolveProfile(flags);
44
+ const workspaceId = flags.workspace || profile.workspace;
45
+ if (!workspaceId) {
46
+ this.error('No workspace ID provided. Use --workspace flag or set one in your profile.');
47
+ }
48
+ const tenantName = args.tenant_name;
49
+ const baseUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/tenant/${tenantName}`;
50
+ const headers = {
51
+ 'accept': 'application/json',
52
+ 'Authorization': `Bearer ${profile.access_token}`,
53
+ 'Content-Type': 'application/json',
54
+ };
55
+ try {
56
+ // Fetch current tenant state (PUT requires all fields)
57
+ const getResponse = await this.verboseFetch(baseUrl, {
58
+ headers: {
59
+ 'accept': 'application/json',
60
+ 'Authorization': `Bearer ${profile.access_token}`,
61
+ },
62
+ method: 'GET',
63
+ }, flags.verbose, profile.access_token);
64
+ if (!getResponse.ok) {
65
+ const errorText = await getResponse.text();
66
+ this.error(`Failed to fetch tenant: ${getResponse.status} ${getResponse.statusText}\n${errorText}`);
67
+ }
68
+ const current = await getResponse.json();
69
+ // Merge in user-provided values
70
+ const body = {
71
+ description: flags.description === undefined ? (current.description ?? '') : flags.description,
72
+ display: flags.display === undefined ? (current.display ?? current.name) : flags.display,
73
+ domain: current.domain ?? '',
74
+ ingress: current.ingress ?? false,
75
+ proxy: current.proxy ?? '',
76
+ rbac: {
77
+ enabled: current.rbac?.enabled ?? false,
78
+ },
79
+ tag: current.tag ?? [],
80
+ tasks: current.tasks ?? false,
81
+ };
82
+ // Update tenant
83
+ const putResponse = await this.verboseFetch(baseUrl, {
84
+ body: JSON.stringify(body),
85
+ headers,
86
+ method: 'PUT',
87
+ }, flags.verbose, profile.access_token);
88
+ if (!putResponse.ok) {
89
+ const errorText = await putResponse.text();
90
+ this.error(`API request failed with status ${putResponse.status}: ${putResponse.statusText}\n${errorText}`);
91
+ }
92
+ const tenant = await putResponse.json();
93
+ if (flags.output === 'json') {
94
+ this.log(JSON.stringify(tenant, null, 2));
95
+ }
96
+ else {
97
+ this.log(`Updated tenant: ${tenant.display || tenant.name} (${tenant.name}) - ID: ${tenant.id}`);
98
+ }
99
+ }
100
+ catch (error) {
101
+ if (error instanceof Error) {
102
+ this.error(`Failed to edit ephemeral tenant: ${error.message}`);
103
+ }
104
+ else {
105
+ this.error(`Failed to edit ephemeral tenant: ${String(error)}`);
106
+ }
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,21 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class EphemeralGet extends BaseCommand {
3
+ static args: {
4
+ tenant_name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ /**
17
+ * Render a unix-epoch-seconds expiration as a human-readable "in Xh Ym" string,
18
+ * falling back to an ISO timestamp if the value has already passed or is absent.
19
+ */
20
+ private formatExpiration;
21
+ }