@xano/cli 1.0.2-beta.5 → 1.0.2-beta.6

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 (154) 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/oclif.manifest.json +2399 -2312
  154. package/package.json +1 -1
package/README.md CHANGED
@@ -52,6 +52,11 @@ xano auth --insecure # Skip TLS verification (self-signe
52
52
 
53
53
  Profiles store your Xano credentials and default workspace settings.
54
54
 
55
+ > **Juggling multiple workspaces?** Pin a project to a specific profile with a
56
+ > project-local `profile.yaml` so commands can't accidentally target the wrong
57
+ > workspace when you forget `-p`. See
58
+ > [Project-local profile](#project-local-profile-profileyaml).
59
+
55
60
  ```bash
56
61
  # Create a profile interactively
57
62
  xano profile wizard
@@ -89,6 +94,11 @@ xano profile workspace
89
94
  xano profile workspace set
90
95
  xano profile workspace set -p production
91
96
 
97
+ # Pin a profile for the current project (writes ./profile.yaml)
98
+ xano profile use staging
99
+ xano profile use staging -w 110 # pin and override the workspace
100
+ xano profile use staging --gitignore # also add profile.yaml to .gitignore
101
+
92
102
  # Delete a profile
93
103
  xano profile delete myprofile
94
104
  xano profile delete myprofile --force
@@ -575,6 +585,83 @@ profiles:
575
585
  default: default
576
586
  ```
577
587
 
588
+ ### Project-local profile (`profile.yaml`)
589
+
590
+ To avoid accidentally targeting the wrong workspace, pin a project to a profile
591
+ by adding a `profile.yaml` file at the project root. The CLI searches the
592
+ current directory and walks up parent directories (like `.git`) to find it.
593
+
594
+ `profile.yaml` contains **no secrets** — it references a profile by name; the
595
+ access token always comes from `~/.xano/credentials.yaml`. An `access_token`
596
+ key is rejected.
597
+
598
+ ```yaml
599
+ # ./profile.yaml
600
+ profile: staging # which credentials.yaml profile to use
601
+ workspace: 110 # optional override
602
+ instance_origin: https://your-instance.xano.io # optional override
603
+ account_origin: https://app.xano.com # optional override
604
+ branch: main # optional override
605
+ ```
606
+
607
+ When a `profile.yaml` is in effect, every command prints the active target,
608
+ e.g. `Using profile 'staging' (workspace 110) · profile.yaml` (suppressed for
609
+ `--output json`).
610
+
611
+ Generate one with `xano profile use`. It writes a self-documenting
612
+ `profile.yaml` (every overridable field is included as a commented example, so
613
+ you can edit it without consulting the docs) and offers to add it to
614
+ `.gitignore` — skipping that prompt when it is already ignored:
615
+
616
+ ```bash
617
+ xano profile use staging -w 110 # writes ./profile.yaml; prompts to .gitignore
618
+ xano profile use staging --no-gitignore
619
+ ```
620
+
621
+ The generated file looks like:
622
+
623
+ ```yaml
624
+ # Xano project-local profile — pins this project to a profile in ~/.xano/credentials.yaml.
625
+ # No secrets here: the access token always comes from credentials.yaml.
626
+ # Precedence: an explicit -p/--profile or XANO_PROFILE overrides this file entirely.
627
+
628
+ # Profile to use (a profile name from ~/.xano/credentials.yaml):
629
+ profile: staging
630
+
631
+ # Optional per-project overrides — uncomment and edit any you need:
632
+ workspace: 110
633
+ # instance_origin: https://your-instance.xano.io
634
+ # account_origin: https://app.xano.com
635
+ # branch: main
636
+ ```
637
+
638
+ **Profile selection precedence:**
639
+
640
+ 1. `-p/--profile` flag
641
+ 2. `XANO_PROFILE` environment variable
642
+ 3. `profile.yaml` (`profile:` field, plus field overrides)
643
+ 4. Default profile from the credentials file
644
+
645
+ An explicit `-p/--profile` or `XANO_PROFILE` ignores `profile.yaml` entirely.
646
+
647
+ #### `xano profile use <name>`
648
+
649
+ Pin a profile for the current project by writing a local `profile.yaml`.
650
+
651
+ ```bash
652
+ xano profile use staging # pin profile 'staging' for this project
653
+ xano profile use staging -w 110 # pin and override the workspace
654
+ xano profile use staging --gitignore # also add profile.yaml to .gitignore
655
+ ```
656
+
657
+ | Flag | Description |
658
+ |------|-------------|
659
+ | `-w, --workspace` | Override workspace for this project |
660
+ | `-b, --branch` | Override branch for this project |
661
+ | `-i, --instance_origin` | Override instance origin |
662
+ | `-a, --account_origin` | Override account origin |
663
+ | `--gitignore` / `--no-gitignore` | Add (or skip adding) `profile.yaml` to `.gitignore` without prompting |
664
+
578
665
  ### Self-Signed Certificates
579
666
 
580
667
  For environments using self-signed TLS certificates, use the `--insecure` (`-k`) flag to skip certificate verification:
@@ -1,4 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { type LocalProfileConfig } from './utils/local-config.js';
2
3
  export interface ProfileConfig {
3
4
  access_token: string;
4
5
  account_origin?: string;
@@ -30,6 +31,12 @@ export interface SandboxTenant {
30
31
  * Checks (in order): explicit configPath arg, XANO_CONFIG env var, ~/.xano/credentials.yaml
31
32
  */
32
33
  export declare function resolveCredentialsPath(configPath?: string): string;
34
+ /**
35
+ * Detect whether an explicit profile was requested via -p/--profile or the
36
+ * XANO_PROFILE env var. Used at init() time, before flags are parsed, to decide
37
+ * whether the project-local profile.yaml should be ignored (explicit wins).
38
+ */
39
+ export declare function argvHasProfileFlag(argv: string[], env: NodeJS.ProcessEnv): boolean;
33
40
  export default abstract class BaseCommand extends Command {
34
41
  static baseFlags: {
35
42
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -41,8 +48,19 @@ export default abstract class BaseCommand extends Command {
41
48
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
42
49
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
43
50
  };
51
+ protected localProfile: null | {
52
+ config: LocalProfileConfig;
53
+ path: string;
54
+ };
44
55
  protected updateNotice: string | null;
45
56
  init(): Promise<void>;
57
+ /**
58
+ * Find and parse the nearest project-local profile.yaml, unless an explicit
59
+ * -p/XANO_PROFILE was given (in which case the local file is ignored).
60
+ */
61
+ private loadLocalProfile;
62
+ /** Print the one-line target banner when a local profile.yaml is in effect. */
63
+ private maybePrintLocalProfileBanner;
46
64
  finally(_: Error | undefined): Promise<void>;
47
65
  private isJsonOutput;
48
66
  /**
@@ -65,7 +83,9 @@ export default abstract class BaseCommand extends Command {
65
83
  */
66
84
  protected getOrCreateSandbox(profile: ProfileConfig, verbose: boolean): Promise<SandboxTenant>;
67
85
  /**
68
- * Resolve profile from flags, validating instance_origin and access_token exist.
86
+ * Resolve the profile from flags and any project-local profile.yaml,
87
+ * validating instance_origin and access_token exist.
88
+ * Precedence: -p/XANO_PROFILE > profile.yaml > credentials default.
69
89
  */
70
90
  protected resolveProfile(flags: {
71
91
  profile?: string;
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
4
4
  import * as os from 'node:os';
5
5
  import * as path from 'node:path';
6
6
  import { checkForUpdate } from './update-check.js';
7
+ import { applyLocalOverrides, findLocalProfilePath, formatLocalProfileBanner, parseLocalProfile, resolveProfileSelection, } from './utils/local-config.js';
7
8
  export function buildUserAgent(version) {
8
9
  return `xano-cli/${version} (${process.platform}; ${process.arch}) node/${process.version}`;
9
10
  }
@@ -18,6 +19,27 @@ export function resolveCredentialsPath(configPath) {
18
19
  }
19
20
  return path.join(os.homedir(), '.xano', 'credentials.yaml');
20
21
  }
22
+ /**
23
+ * Detect whether an explicit profile was requested via -p/--profile or the
24
+ * XANO_PROFILE env var. Used at init() time, before flags are parsed, to decide
25
+ * whether the project-local profile.yaml should be ignored (explicit wins).
26
+ */
27
+ export function argvHasProfileFlag(argv, env) {
28
+ // XANO_PROFILE is checked directly (not via oclif's flag env binding) because
29
+ // this runs in init(), before flags are parsed and available on the command.
30
+ if (env.XANO_PROFILE) {
31
+ return true;
32
+ }
33
+ // Scan the raw argv so we catch any token form the user might type
34
+ // (`-p prod`, `--profile prod`, `--profile=prod`, `-p=prod`) regardless of
35
+ // how oclif ultimately parses it.
36
+ for (const arg of argv) {
37
+ if (arg === '-p' || arg === '--profile' || arg.startsWith('--profile=') || arg.startsWith('-p=')) {
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ }
21
43
  export default class BaseCommand extends Command {
22
44
  static baseFlags = {
23
45
  config: Flags.string({
@@ -42,13 +64,62 @@ export default class BaseCommand extends Command {
42
64
  };
43
65
  // Override the flags property to include baseFlags
44
66
  static flags = BaseCommand.baseFlags;
67
+ // Resolved project-local profile.yaml, set once in init() before run().
68
+ // Null when none was found or when an explicit -p/XANO_PROFILE overrides it.
69
+ localProfile = null;
45
70
  updateNotice = null;
46
71
  async init() {
47
72
  await super.init();
73
+ this.localProfile = this.loadLocalProfile();
48
74
  this.applyInsecureFromProfile();
75
+ this.maybePrintLocalProfileBanner();
49
76
  const forceUpdateCheck = process.env.XANO_FORCE_UPDATE_CHECK === '1';
50
77
  this.updateNotice = checkForUpdate(this.config.version, forceUpdateCheck);
51
78
  }
79
+ /**
80
+ * Find and parse the nearest project-local profile.yaml, unless an explicit
81
+ * -p/XANO_PROFILE was given (in which case the local file is ignored).
82
+ */
83
+ loadLocalProfile() {
84
+ if (argvHasProfileFlag(process.argv, process.env)) {
85
+ return null;
86
+ }
87
+ // Walks up to the filesystem root (git-style). parseLocalProfile returns
88
+ // null for a profile.yaml with no recognized keys, so an unrelated file
89
+ // belonging to another tool is ignored rather than hijacked.
90
+ const filePath = findLocalProfilePath(process.cwd());
91
+ if (!filePath) {
92
+ return null;
93
+ }
94
+ let config;
95
+ try {
96
+ config = parseLocalProfile(fs.readFileSync(filePath, 'utf8'));
97
+ }
98
+ catch (error) {
99
+ this.error(`${filePath}: ${error.message}`);
100
+ }
101
+ if (!config) {
102
+ this.warn(`Ignoring ${filePath}: no recognized profile keys found.`);
103
+ return null;
104
+ }
105
+ return { config, path: filePath };
106
+ }
107
+ /** Print the one-line target banner when a local profile.yaml is in effect. */
108
+ maybePrintLocalProfileBanner() {
109
+ if (!this.localProfile || this.isJsonOutput()) {
110
+ return;
111
+ }
112
+ // Credential-management commands (the `profile` topic) operate on the
113
+ // credentials store directly and intentionally ignore the project-local
114
+ // pin, so the banner would be misleading for them.
115
+ if (this.id?.startsWith('profile')) {
116
+ return;
117
+ }
118
+ const { config, path: filePath } = this.localProfile;
119
+ const profileName = config.profile ?? this.getDefaultProfile();
120
+ const relativePath = path.relative(process.cwd(), filePath) || path.basename(filePath);
121
+ this.log(formatLocalProfileBanner(profileName, config.workspace, relativePath));
122
+ }
52
123
  async finally(_) {
53
124
  if (this.updateNotice && !this.isJsonOutput()) {
54
125
  this.log(this.updateNotice);
@@ -73,7 +144,7 @@ export default class BaseCommand extends Command {
73
144
  */
74
145
  applyInsecureFromProfile() {
75
146
  try {
76
- const profileName = this.flags?.profile || this.getDefaultProfile();
147
+ const profileName = this.flags?.profile || this.localProfile?.config.profile || this.getDefaultProfile();
77
148
  const credentials = this.loadCredentialsFile();
78
149
  if (!credentials)
79
150
  return;
@@ -151,15 +222,30 @@ export default class BaseCommand extends Command {
151
222
  return (await response.json());
152
223
  }
153
224
  /**
154
- * Resolve profile from flags, validating instance_origin and access_token exist.
225
+ * Resolve the profile from flags and any project-local profile.yaml,
226
+ * validating instance_origin and access_token exist.
227
+ * Precedence: -p/XANO_PROFILE > profile.yaml > credentials default.
155
228
  */
156
229
  resolveProfile(flags) {
157
- const profileName = flags.profile || this.getDefaultProfile();
158
230
  const credentials = this.loadCredentialsFile();
159
- if (!credentials || !(profileName in credentials.profiles)) {
160
- this.error(`Profile '${profileName}' not found.\n` + `Create a profile using 'xano profile create'`);
231
+ const { applyLocal, profileName } = resolveProfileSelection({
232
+ defaultProfile: this.getDefaultProfile(),
233
+ explicitProfile: flags.profile,
234
+ hasLocal: Boolean(this.localProfile),
235
+ localProfileName: this.localProfile?.config.profile,
236
+ });
237
+ if (!credentials) {
238
+ this.error(`Credentials file not found at ${this.getCredentialsPath()}.\n` +
239
+ `Create a profile using 'xano profile create'`);
240
+ }
241
+ if (!(profileName in credentials.profiles)) {
242
+ const available = Object.keys(credentials.profiles).join(', ') || '(none)';
243
+ this.error(`Profile '${profileName}' not found. Available profiles: ${available}`);
244
+ }
245
+ let profile = credentials.profiles[profileName];
246
+ if (applyLocal && this.localProfile) {
247
+ profile = applyLocalOverrides(profile, this.localProfile.config);
161
248
  }
162
- const profile = credentials.profiles[profileName];
163
249
  if (!profile.instance_origin) {
164
250
  this.error(`Profile '${profileName}' is missing instance_origin`);
165
251
  }
@@ -16,5 +16,4 @@ export default class BranchCreate extends BaseCommand {
16
16
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  };
18
18
  run(): Promise<void>;
19
- private loadCredentials;
20
19
  }
@@ -1,6 +1,4 @@
1
1
  import { Args, 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 BranchCreate extends BaseCommand {
6
4
  static description = 'Create a new branch by cloning from an existing branch';
@@ -62,23 +60,7 @@ Created branch: feature-auth
62
60
  };
63
61
  async run() {
64
62
  const { args, flags } = await this.parse(BranchCreate);
65
- // Get profile name (default or from flag/env)
66
- const profileName = flags.profile || this.getDefaultProfile();
67
- // Load credentials
68
- const credentials = this.loadCredentials();
69
- // Get the profile configuration
70
- if (!(profileName in credentials.profiles)) {
71
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
72
- `Create a profile using 'xano profile create'`);
73
- }
74
- const profile = credentials.profiles[profileName];
75
- // Validate required fields
76
- if (!profile.instance_origin) {
77
- this.error(`Profile '${profileName}' is missing instance_origin`);
78
- }
79
- if (!profile.access_token) {
80
- this.error(`Profile '${profileName}' is missing access_token`);
81
- }
63
+ const { profile } = this.resolveProfile(flags);
82
64
  // Get workspace ID from flag or profile
83
65
  const workspaceId = flags.workspace || profile.workspace;
84
66
  if (!workspaceId) {
@@ -143,23 +125,4 @@ Created branch: feature-auth
143
125
  }
144
126
  }
145
127
  }
146
- loadCredentials() {
147
- const credentialsPath = this.getCredentialsPath();
148
- // Check if credentials file exists
149
- if (!fs.existsSync(credentialsPath)) {
150
- this.error(`Credentials file not found at ${credentialsPath}\n` + `Create a profile using 'xano profile create'`);
151
- }
152
- // Read credentials file
153
- try {
154
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
155
- const parsed = yaml.load(fileContent);
156
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
157
- this.error('Credentials file has invalid format.');
158
- }
159
- return parsed;
160
- }
161
- catch (error) {
162
- this.error(`Failed to parse credentials file: ${error}`);
163
- }
164
- }
165
128
  }
@@ -15,5 +15,4 @@ export default class BranchDelete extends BaseCommand {
15
15
  };
16
16
  run(): Promise<void>;
17
17
  private confirm;
18
- private loadCredentials;
19
18
  }
@@ -1,6 +1,4 @@
1
1
  import { Args, 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 BranchDelete extends BaseCommand {
6
4
  static args = {
@@ -48,23 +46,7 @@ Deleted branch: dev
48
46
  };
49
47
  async run() {
50
48
  const { args, flags } = await this.parse(BranchDelete);
51
- // Get profile name (default or from flag/env)
52
- const profileName = flags.profile || this.getDefaultProfile();
53
- // Load credentials
54
- const credentials = this.loadCredentials();
55
- // Get the profile configuration
56
- if (!(profileName in credentials.profiles)) {
57
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
58
- `Create a profile using 'xano profile create'`);
59
- }
60
- const profile = credentials.profiles[profileName];
61
- // Validate required fields
62
- if (!profile.instance_origin) {
63
- this.error(`Profile '${profileName}' is missing instance_origin`);
64
- }
65
- if (!profile.access_token) {
66
- this.error(`Profile '${profileName}' is missing access_token`);
67
- }
49
+ const { profile } = this.resolveProfile(flags);
68
50
  // Get workspace ID from flag or profile
69
51
  const workspaceId = flags.workspace || profile.workspace;
70
52
  if (!workspaceId) {
@@ -130,24 +112,4 @@ Deleted branch: dev
130
112
  });
131
113
  });
132
114
  }
133
- loadCredentials() {
134
- const credentialsPath = this.getCredentialsPath();
135
- // Check if credentials file exists
136
- if (!fs.existsSync(credentialsPath)) {
137
- this.error(`Credentials file not found at ${credentialsPath}\n` +
138
- `Create a profile using 'xano profile create'`);
139
- }
140
- // Read credentials file
141
- try {
142
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
143
- const parsed = yaml.load(fileContent);
144
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
145
- this.error('Credentials file has invalid format.');
146
- }
147
- return parsed;
148
- }
149
- catch (error) {
150
- this.error(`Failed to parse credentials file: ${error}`);
151
- }
152
- }
153
115
  }
@@ -16,5 +16,4 @@ export default class BranchEdit extends BaseCommand {
16
16
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
17
  };
18
18
  run(): Promise<void>;
19
- private loadCredentials;
20
19
  }
@@ -1,6 +1,4 @@
1
1
  import { Args, 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 BranchEdit extends BaseCommand {
6
4
  static args = {
@@ -58,23 +56,7 @@ Updated branch: feature-authentication
58
56
  };
59
57
  async run() {
60
58
  const { args, flags } = await this.parse(BranchEdit);
61
- // Get profile name (default or from flag/env)
62
- const profileName = flags.profile || this.getDefaultProfile();
63
- // Load credentials
64
- const credentials = this.loadCredentials();
65
- // Get the profile configuration
66
- if (!(profileName in credentials.profiles)) {
67
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
68
- `Create a profile using 'xano profile create'`);
69
- }
70
- const profile = credentials.profiles[profileName];
71
- // Validate required fields
72
- if (!profile.instance_origin) {
73
- this.error(`Profile '${profileName}' is missing instance_origin`);
74
- }
75
- if (!profile.access_token) {
76
- this.error(`Profile '${profileName}' is missing access_token`);
77
- }
59
+ const { profile } = this.resolveProfile(flags);
78
60
  // Get workspace ID from flag or profile
79
61
  const workspaceId = flags.workspace || profile.workspace;
80
62
  if (!workspaceId) {
@@ -140,24 +122,4 @@ Updated branch: feature-authentication
140
122
  }
141
123
  }
142
124
  }
143
- loadCredentials() {
144
- const credentialsPath = this.getCredentialsPath();
145
- // Check if credentials file exists
146
- if (!fs.existsSync(credentialsPath)) {
147
- this.error(`Credentials file not found at ${credentialsPath}\n` +
148
- `Create a profile using 'xano profile create'`);
149
- }
150
- // Read credentials file
151
- try {
152
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
153
- const parsed = yaml.load(fileContent);
154
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
155
- this.error('Credentials file has invalid format.');
156
- }
157
- return parsed;
158
- }
159
- catch (error) {
160
- this.error(`Failed to parse credentials file: ${error}`);
161
- }
162
- }
163
125
  }
@@ -13,5 +13,4 @@ export default class BranchGet extends BaseCommand {
13
13
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
14
  };
15
15
  run(): Promise<void>;
16
- private loadCredentials;
17
16
  }
@@ -1,6 +1,4 @@
1
1
  import { Args, 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 BranchGet extends BaseCommand {
6
4
  static args = {
@@ -45,23 +43,7 @@ Branch: dev
45
43
  };
46
44
  async run() {
47
45
  const { args, flags } = await this.parse(BranchGet);
48
- // Get profile name (default or from flag/env)
49
- const profileName = flags.profile || this.getDefaultProfile();
50
- // Load credentials
51
- const credentials = this.loadCredentials();
52
- // Get the profile configuration
53
- if (!(profileName in credentials.profiles)) {
54
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
55
- `Create a profile using 'xano profile create'`);
56
- }
57
- const profile = credentials.profiles[profileName];
58
- // Validate required fields
59
- if (!profile.instance_origin) {
60
- this.error(`Profile '${profileName}' is missing instance_origin`);
61
- }
62
- if (!profile.access_token) {
63
- this.error(`Profile '${profileName}' is missing access_token`);
64
- }
46
+ const { profile } = this.resolveProfile(flags);
65
47
  // Get workspace ID from flag or profile
66
48
  const workspaceId = flags.workspace || profile.workspace;
67
49
  if (!workspaceId) {
@@ -109,24 +91,4 @@ Branch: dev
109
91
  }
110
92
  }
111
93
  }
112
- loadCredentials() {
113
- const credentialsPath = this.getCredentialsPath();
114
- // Check if credentials file exists
115
- if (!fs.existsSync(credentialsPath)) {
116
- this.error(`Credentials file not found at ${credentialsPath}\n` +
117
- `Create a profile using 'xano profile create'`);
118
- }
119
- // Read credentials file
120
- try {
121
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
122
- const parsed = yaml.load(fileContent);
123
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
124
- this.error('Credentials file has invalid format.');
125
- }
126
- return parsed;
127
- }
128
- catch (error) {
129
- this.error(`Failed to parse credentials file: ${error}`);
130
- }
131
- }
132
94
  }
@@ -18,5 +18,4 @@ export default class BranchList extends BaseCommand {
18
18
  verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
19
  };
20
20
  run(): Promise<void>;
21
- private loadCredentials;
22
21
  }
@@ -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 function filterBackups(branches, includeBackups) {
6
4
  return includeBackups ? branches : branches.filter((b) => !b.backup);
@@ -58,23 +56,7 @@ Available branches:
58
56
  };
59
57
  async run() {
60
58
  const { flags } = await this.parse(BranchList);
61
- // Get profile name (default or from flag/env)
62
- const profileName = flags.profile || this.getDefaultProfile();
63
- // Load credentials
64
- const credentials = this.loadCredentials();
65
- // Get the profile configuration
66
- if (!(profileName in credentials.profiles)) {
67
- this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
68
- `Create a profile using 'xano profile create'`);
69
- }
70
- const profile = credentials.profiles[profileName];
71
- // Validate required fields
72
- if (!profile.instance_origin) {
73
- this.error(`Profile '${profileName}' is missing instance_origin`);
74
- }
75
- if (!profile.access_token) {
76
- this.error(`Profile '${profileName}' is missing access_token`);
77
- }
59
+ const { profile } = this.resolveProfile(flags);
78
60
  // Get workspace ID from flag or profile
79
61
  const workspaceId = flags.workspace || profile.workspace;
80
62
  if (!workspaceId) {
@@ -126,24 +108,4 @@ Available branches:
126
108
  }
127
109
  }
128
110
  }
129
- loadCredentials() {
130
- const credentialsPath = this.getCredentialsPath();
131
- // Check if credentials file exists
132
- if (!fs.existsSync(credentialsPath)) {
133
- this.error(`Credentials file not found at ${credentialsPath}\n` +
134
- `Create a profile using 'xano profile create'`);
135
- }
136
- // Read credentials file
137
- try {
138
- const fileContent = fs.readFileSync(credentialsPath, 'utf8');
139
- const parsed = yaml.load(fileContent);
140
- if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
141
- this.error('Credentials file has invalid format.');
142
- }
143
- return parsed;
144
- }
145
- catch (error) {
146
- this.error(`Failed to parse credentials file: ${error}`);
147
- }
148
- }
149
111
  }
@@ -15,5 +15,4 @@ export default class BranchSetLive extends BaseCommand {
15
15
  };
16
16
  run(): Promise<void>;
17
17
  private confirm;
18
- private loadCredentials;
19
18
  }