switch-profile 2.0.1 → 2.2.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.
- package/README.md +22 -8
- package/dist/cli.js +168 -43
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -11,21 +11,23 @@ npx switch-profile
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
┌ switch-profile 2.
|
|
14
|
+
┌ switch-profile 2.1.0
|
|
15
15
|
│
|
|
16
16
|
│ ● acme-prod · Admin · 111111111111 · ap-southeast-2 · PROD · SSO ✓ auto-refresh · logged in 3h ago
|
|
17
17
|
│
|
|
18
18
|
◆ Switch to · type a name, account, role or client
|
|
19
|
-
│ Search: dev
|
|
20
|
-
│ ● acme-dev Admin 222222222222 ap-southeast-2
|
|
19
|
+
│ Search: dev█ (3 matches)
|
|
20
|
+
│ ● acme-dev Admin 222222222222 ap-southeast-2 used 2d ago
|
|
21
21
|
│ ○ globex-dev ReadOnly 333333333333 us-east-1
|
|
22
22
|
│ ○ initech-dev Admin 444444444444 eu-west-1
|
|
23
|
-
|
|
23
|
+
│
|
|
24
|
+
│ [Log in] [Add] [Remove] [Log out] [Settings]
|
|
25
|
+
└ ↑↓ choose · enter switch · tab actions · esc quit
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
## Why it's great
|
|
27
29
|
|
|
28
|
-
- **Seconds, not minutes.** Fuzzy search across profile name, account ID, account name, role, client and region, with your most-used accounts first. `sp acme prod` switches without even opening the
|
|
30
|
+
- **Seconds, not minutes.** Fuzzy search across profile name, account ID, account name, role, client and region, with your most-used accounts first, and every other action one Tab away. `sp acme prod` switches without even opening the home screen.
|
|
29
31
|
- **Logged in when you need it.** If a session has expired, it logs you in on the spot, then never again until the session ends: credentials refresh on their own in the AWS CLI, SDKs and Terraform.
|
|
30
32
|
- **Every terminal on its own account.** With the `sp` shortcut, each terminal keeps its own `AWS_PROFILE`, plus tab-completion of your profile names.
|
|
31
33
|
- **Made for remote machines.** On a server reached over SSH, the login code lands in your laptop's clipboard, or scan a QR code with your phone.
|
|
@@ -51,6 +53,7 @@ npx switch-profile
|
|
|
51
53
|
- [Why it's great](#why-its-great)
|
|
52
54
|
- [Built on AWS best practices](#built-on-aws-best-practices)
|
|
53
55
|
- [Quick start](#quick-start)
|
|
56
|
+
- [The home screen](#the-home-screen)
|
|
54
57
|
- [Commands](#commands)
|
|
55
58
|
- [Working on remote machines (SSH)](#working-on-remote-machines-ssh)
|
|
56
59
|
- [Importing a client's accounts](#importing-a-clients-accounts)
|
|
@@ -90,11 +93,20 @@ sp acme prod # fuzzy match: switches if exactly one profile matches
|
|
|
90
93
|
|
|
91
94
|
No profiles yet? `switch-profile` starts the **Add profiles** flow. To import every account of your company's or client's SSO portal, see [Importing a client's accounts](#importing-a-clients-accounts).
|
|
92
95
|
|
|
96
|
+
## The home screen
|
|
97
|
+
|
|
98
|
+
Running `sp` (or `npx switch-profile`) with no arguments opens the home screen:
|
|
99
|
+
|
|
100
|
+
- **Type and press Enter** to switch. That's the whole path for everyday use.
|
|
101
|
+
- **Tab** moves to the action bar below the list, which is always visible however many profiles you have: **Log in**, **Add**, **Remove**, **Log out**, **Settings**. Use ←→ to choose and Enter to open.
|
|
102
|
+
- **Every action opens as a page and comes back** to the home screen when it's done. **Esc** goes back from a page, and quits from the home screen.
|
|
103
|
+
- A switch ends the session, which is what lets `sp` set `AWS_PROFILE` in your terminal.
|
|
104
|
+
|
|
93
105
|
## Commands
|
|
94
106
|
|
|
95
107
|
| Command | What it does |
|
|
96
108
|
|---------|--------------|
|
|
97
|
-
| `switch-profile [profile...]` | Opens the
|
|
109
|
+
| `switch-profile [profile...]` | Opens the home screen (search and switch, plus the action bar). With words: an exact name or a unique fuzzy match switches directly; otherwise the home screen opens pre-filtered (or exits 3 when non-interactive). |
|
|
98
110
|
| `use <profile>` | Switches to a profile by its exact name (no fuzzy matching). |
|
|
99
111
|
| `status [--json]` | Shows the default profile, its login state, and what this terminal uses. |
|
|
100
112
|
| `login [profile] [--device\|--browser]` | Starts a fresh login now, even if the session is still valid. Defaults to this terminal's `AWS_PROFILE`, then the default profile. |
|
|
@@ -142,7 +154,7 @@ sp add # then choose "Accounts from an SSO portal"
|
|
|
142
154
|
1. Pick an existing `[sso-session]` or create one: start URL (e.g. `https://acme.awsapps.com/start`), SSO region (where Identity Center lives, not where you deploy), and a short name, usually the client.
|
|
143
155
|
2. Log in if needed.
|
|
144
156
|
3. `switch-profile` lists every account and role you can access, then asks for a default region and a name prefix. Profiles are named `<prefix>-<account-name>-<role>`, for example `acme-prod-workloads-admin`.
|
|
145
|
-
4. A preview shows the new profiles, the ones already set up, and the ones that no longer exist.
|
|
157
|
+
4. A preview shows the new profiles, the ones already set up, and the ones that no longer exist. Then pick the new profiles to add: all are selected, so press Enter to add everything, or untick the ones you don't want (type to search by name, account or role).
|
|
146
158
|
|
|
147
159
|
Run it again later to pick up new accounts. Account/role pairs that already have a profile are never duplicated, whatever its name. Generated profiles that no longer exist can be pruned (after a backup); profiles you wrote by hand are never pruned.
|
|
148
160
|
|
|
@@ -176,6 +188,8 @@ AWS_PROFILE=acme-dev terraform plan
|
|
|
176
188
|
|
|
177
189
|
## Settings
|
|
178
190
|
|
|
191
|
+
Open **Settings** from the home screen's action bar (Tab, then → to Settings), or run it directly:
|
|
192
|
+
|
|
179
193
|
```shell
|
|
180
194
|
sp settings
|
|
181
195
|
```
|
|
@@ -243,7 +257,7 @@ The completion registers only if `compinit` ran before the `switch-profile` bloc
|
|
|
243
257
|
|
|
244
258
|
### Commands still use the wrong account
|
|
245
259
|
|
|
246
|
-
`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` in the environment override every profile, and `AWS_REGION` overrides the profile's region. The
|
|
260
|
+
`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` in the environment override every profile, and `AWS_REGION` overrides the profile's region. The home screen warns about them; switching with `sp` clears the credential variables (not the region).
|
|
247
261
|
|
|
248
262
|
### AWS CLI not found
|
|
249
263
|
|
package/dist/cli.js
CHANGED
|
@@ -2836,6 +2836,30 @@ var require_src = __commonJS({
|
|
|
2836
2836
|
});
|
|
2837
2837
|
|
|
2838
2838
|
// node_modules/@clack/core/dist/index.mjs
|
|
2839
|
+
var dist_exports = {};
|
|
2840
|
+
__export(dist_exports, {
|
|
2841
|
+
AutocompletePrompt: () => T$1,
|
|
2842
|
+
CANCEL_SYMBOL: () => CANCEL_SYMBOL,
|
|
2843
|
+
ConfirmPrompt: () => r,
|
|
2844
|
+
DatePrompt: () => U,
|
|
2845
|
+
GroupMultiSelectPrompt: () => u$2,
|
|
2846
|
+
MultiLinePrompt: () => h,
|
|
2847
|
+
MultiSelectPrompt: () => a,
|
|
2848
|
+
PasswordPrompt: () => u$1,
|
|
2849
|
+
Prompt: () => y,
|
|
2850
|
+
SelectKeyPrompt: () => u3,
|
|
2851
|
+
SelectPrompt: () => n$1,
|
|
2852
|
+
TextPrompt: () => n2,
|
|
2853
|
+
block: () => block,
|
|
2854
|
+
getColumns: () => getColumns,
|
|
2855
|
+
getRows: () => getRows,
|
|
2856
|
+
isAccessible: () => isAccessible,
|
|
2857
|
+
isCancel: () => isCancel,
|
|
2858
|
+
runValidation: () => runValidation,
|
|
2859
|
+
settings: () => settings,
|
|
2860
|
+
updateSettings: () => updateSettings,
|
|
2861
|
+
wrapTextWithPrefix: () => wrapTextWithPrefix
|
|
2862
|
+
});
|
|
2839
2863
|
function findCursor(s, o2, l2) {
|
|
2840
2864
|
if (!l2.some((r2) => !r2.disabled))
|
|
2841
2865
|
return s;
|
|
@@ -3805,8 +3829,8 @@ ${i2}` : `${s}${(0, import_node_util.styleText)("inverse", r2)}${i2}`;
|
|
|
3805
3829
|
});
|
|
3806
3830
|
|
|
3807
3831
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
3808
|
-
var
|
|
3809
|
-
__export(
|
|
3832
|
+
var dist_exports2 = {};
|
|
3833
|
+
__export(dist_exports2, {
|
|
3810
3834
|
CANCEL_SYMBOL: () => CANCEL_SYMBOL,
|
|
3811
3835
|
MULTISELECT_INSTRUCTIONS: () => MULTISELECT_INSTRUCTIONS,
|
|
3812
3836
|
SELECT_INSTRUCTIONS: () => SELECT_INSTRUCTIONS,
|
|
@@ -5261,7 +5285,7 @@ var require_ui = __commonJS({
|
|
|
5261
5285
|
let h2 = Math.floor(m3 / 60);
|
|
5262
5286
|
return h2 < 48 ? `${h2}h ago` : `${Math.floor(h2 / 24)}d ago`;
|
|
5263
5287
|
}, spinner2 = (options) => {
|
|
5264
|
-
let p4 = (init_dist4(), __toCommonJS(
|
|
5288
|
+
let p4 = (init_dist4(), __toCommonJS(dist_exports2));
|
|
5265
5289
|
if (process.stdout.isTTY && !env.ACCESSIBLE)
|
|
5266
5290
|
return p4.spinner(options);
|
|
5267
5291
|
let accessible = process.stdout.isTTY;
|
|
@@ -5277,7 +5301,7 @@ var require_ui = __commonJS({
|
|
|
5277
5301
|
isCancelled: !1
|
|
5278
5302
|
};
|
|
5279
5303
|
}, timerSpinner = () => {
|
|
5280
|
-
let p4 = (init_dist4(), __toCommonJS(
|
|
5304
|
+
let p4 = (init_dist4(), __toCommonJS(dist_exports2));
|
|
5281
5305
|
if (!process.stdout.isTTY || env.ACCESSIBLE)
|
|
5282
5306
|
return spinner2();
|
|
5283
5307
|
let frames = unicode2 ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["-", "\\", "|", "/"], timer = null, message = "", started = 0, i2 = 0, clear = () => process.stdout.write("\r\x1B[2K"), render = () => {
|
|
@@ -5327,7 +5351,7 @@ var require_package = __commonJS({
|
|
|
5327
5351
|
"package.json"(exports2, module2) {
|
|
5328
5352
|
module2.exports = {
|
|
5329
5353
|
name: "switch-profile",
|
|
5330
|
-
version: "2.0
|
|
5354
|
+
version: "2.2.0",
|
|
5331
5355
|
main: "index.js",
|
|
5332
5356
|
bin: {
|
|
5333
5357
|
"switch-profile": "index.js"
|
|
@@ -5363,6 +5387,7 @@ var require_package = __commonJS({
|
|
|
5363
5387
|
author: "Nicolas Dao",
|
|
5364
5388
|
license: "BSD-3-Clause",
|
|
5365
5389
|
devDependencies: {
|
|
5390
|
+
"@clack/core": "^1.5.1",
|
|
5366
5391
|
"@clack/prompts": "^1.8.1",
|
|
5367
5392
|
"@eslint/js": "^10.0.1",
|
|
5368
5393
|
chai: "^4.1.2",
|
|
@@ -5371,7 +5396,7 @@ var require_package = __commonJS({
|
|
|
5371
5396
|
eslint: "^10.0.0",
|
|
5372
5397
|
fuzzysort: "^3.1.0",
|
|
5373
5398
|
globals: "^17.13.0",
|
|
5374
|
-
mocha: "^8.
|
|
5399
|
+
mocha: "^11.8.0",
|
|
5375
5400
|
uqr: "^0.1.3"
|
|
5376
5401
|
},
|
|
5377
5402
|
files: [
|
|
@@ -5615,14 +5640,16 @@ var require_transforms = __commonJS({
|
|
|
5615
5640
|
login_session: params.login_session || null
|
|
5616
5641
|
};
|
|
5617
5642
|
return p4.isSso = !!p4.sso_start_url, p4.isLegacySso = p4.isSso && !p4.sso_session, p4.kind = p4.isSso ? "sso" : p4.login_session ? "login" : p4.role_arn ? "role" : params.credential_process ? "process" : "keys", !p4.sso_account_id && p4.role_arn && (p4.sso_account_id = (p4.role_arn.match(/^arn:aws[^:]*:iam::(\d{12}):/) || [])[1] || null), p4;
|
|
5618
|
-
}), slug = (text2) => String(text2 || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), generatedProfileName = (prefix, accountName, accountId, roleName) => [slug(prefix), slug(accountName) || accountId, slug(roleName)].filter(Boolean).join("-"), populateSsoProfiles = (configStr, { ssoSession, entries, region, prefix, prune, version }) => {
|
|
5619
|
-
let config = configStr, profiles = listProfiles(config), session = ini.getSection(config, `sso-session ${ssoSession}`) || {}, samePortal = (p4) => p4.sso_session == ssoSession || p4.sso_start_url && p4.sso_start_url == session.sso_start_url, key =
|
|
5643
|
+
}), slug = (text2) => String(text2 || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""), generatedProfileName = (prefix, accountName, accountId, roleName) => [slug(prefix), slug(accountName) || accountId, slug(roleName)].filter(Boolean).join("-"), ssoEntryKey = (accountId, roleName) => `${accountId}/${roleName}`, populateSsoProfiles = (configStr, { ssoSession, entries, region, prefix, prune, include, version }) => {
|
|
5644
|
+
let config = configStr, profiles = listProfiles(config), session = ini.getSection(config, `sso-session ${ssoSession}`) || {}, samePortal = (p4) => p4.sso_session == ssoSession || p4.sso_start_url && p4.sso_start_url == session.sso_start_url, key = ssoEntryKey, included = include ? new Set(include) : null, byKey = new Map(profiles.filter((p4) => p4.isSso && samePortal(p4)).map((p4) => [key(p4.sso_account_id, p4.sso_role_name), p4])), taken = new Set(profiles.map((p4) => p4.name)), wanted = new Set(entries.map((e) => key(e.accountId, e.roleName))), added = [], addedEntries = [], existing = [];
|
|
5620
5645
|
for (let e of entries) {
|
|
5621
5646
|
let found = byKey.get(key(e.accountId, e.roleName));
|
|
5622
5647
|
if (found) {
|
|
5623
5648
|
existing.push(found.name);
|
|
5624
5649
|
continue;
|
|
5625
5650
|
}
|
|
5651
|
+
if (included && !included.has(key(e.accountId, e.roleName)))
|
|
5652
|
+
continue;
|
|
5626
5653
|
let base = generatedProfileName(prefix || ssoSession, e.accountName, e.accountId, e.roleName), name = base;
|
|
5627
5654
|
for (let i2 = 2; taken.has(name); i2++)
|
|
5628
5655
|
name = `${base}-${i2}`;
|
|
@@ -5635,13 +5662,13 @@ var require_transforms = __commonJS({
|
|
|
5635
5662
|
...e.accountName ? [[ACCOUNT_NAME_KEY, e.accountName]] : [],
|
|
5636
5663
|
[GENERATED_KEY, ssoSession],
|
|
5637
5664
|
[VERSION_KEY, version]
|
|
5638
|
-
]), added.push(name);
|
|
5665
|
+
]), added.push(name), addedEntries.push({ name, accountId: e.accountId, accountName: e.accountName, roleName: e.roleName });
|
|
5639
5666
|
}
|
|
5640
5667
|
let stale = profiles.filter((p4) => p4.generated == ssoSession && !wanted.has(key(p4.sso_account_id, p4.sso_role_name))).map((p4) => p4.name), removed = [];
|
|
5641
5668
|
if (prune)
|
|
5642
5669
|
for (let name of stale)
|
|
5643
5670
|
config = ini.removeSection(config, configSectionName(config, name)), removed.push(name);
|
|
5644
|
-
return { config, added, existing, stale, removed };
|
|
5671
|
+
return { config, added, addedEntries, existing, stale, removed };
|
|
5645
5672
|
}, listSsoSessions = (configStr) => ini.listSections(configStr).filter((s) => s.startsWith("sso-session ")).map((s) => ({ name: s.replace(/^sso-session\s+/, ""), ...ini.getSection(configStr, s) })), addSsoSession = (configStr, { name, startUrl, region, version }) => ini.getSection(configStr, `sso-session ${name}`) ? configStr : ini.setSection(configStr, `sso-session ${name}`, [
|
|
5646
5673
|
["sso_start_url", startUrl],
|
|
5647
5674
|
["sso_region", region],
|
|
@@ -5653,6 +5680,7 @@ var require_transforms = __commonJS({
|
|
|
5653
5680
|
ACCOUNT_NAME_KEY,
|
|
5654
5681
|
generatedProfileName,
|
|
5655
5682
|
populateSsoProfiles,
|
|
5683
|
+
ssoEntryKey,
|
|
5656
5684
|
listSsoSessions,
|
|
5657
5685
|
addSsoSession,
|
|
5658
5686
|
NAME_KEY,
|
|
@@ -6288,7 +6316,7 @@ var require_migrate = __commonJS({
|
|
|
6288
6316
|
// src/commands/common.js
|
|
6289
6317
|
var require_common = __commonJS({
|
|
6290
6318
|
"src/commands/common.js"(exports2, module2) {
|
|
6291
|
-
var p4 = (init_dist4(), __toCommonJS(
|
|
6319
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), settings2 = require_settings(), shell = require_shell(), migrate = require_migrate(), ui2 = require_ui(), CancelError2 = class extends Error {
|
|
6292
6320
|
}, ask = async (promise) => {
|
|
6293
6321
|
let value = await promise;
|
|
6294
6322
|
if (p4.isCancel(value))
|
|
@@ -7364,7 +7392,7 @@ var require_clipboard = __commonJS({
|
|
|
7364
7392
|
// src/commands/login-flow.js
|
|
7365
7393
|
var require_login_flow = __commonJS({
|
|
7366
7394
|
"src/commands/login-flow.js"(exports2, module2) {
|
|
7367
|
-
var { spawn } = require("child_process"), p4 = (init_dist4(), __toCommonJS(
|
|
7395
|
+
var { spawn } = require("child_process"), p4 = (init_dist4(), __toCommonJS(dist_exports2)), { renderUnicodeCompact } = require_dist(), aws = require_aws(), settings2 = require_settings(), { parseLoginOutput, isRemoteSession, atLeast } = require_login(), clipboard = require_clipboard(), ui2 = require_ui(), { CancelError: CancelError2 } = require_common(), IS_WINDOWS = process.platform == "win32", RAW_OUTPUT_AFTER_MS = 1e4, clipboardNote = (result) => result == "local" ? ui2.ok("\u{1F4CB} copied") : result == "terminal" ? ui2.dim("\u{1F4CB} sent to your clipboard") : "", ssoLogin = async (target, { label, loginKey, interactive, loginMode }) => {
|
|
7368
7396
|
let current = await settings2.read(), args = await aws.ssoLoginArgs(target, loginMode || settings2.getLoginMode(current)), legacyCli = !atLeast(await aws.getAwsCliVersion(), [2, 22, 0]), device = args.includes("--use-device-code") || legacyCli, remote = isRemoteSession(process.env, process.platform);
|
|
7369
7397
|
if (!interactive)
|
|
7370
7398
|
return plainLogin(args, label);
|
|
@@ -7445,10 +7473,74 @@ ${detail}` : ""}`, {
|
|
|
7445
7473
|
}
|
|
7446
7474
|
});
|
|
7447
7475
|
|
|
7476
|
+
// src/home.js
|
|
7477
|
+
var require_home = __commonJS({
|
|
7478
|
+
"src/home.js"(exports2, module2) {
|
|
7479
|
+
var { AutocompletePrompt } = (init_dist3(), __toCommonJS(dist_exports)), { S_BAR: S_BAR2, S_BAR_END: S_BAR_END2, S_RADIO_ACTIVE: S_RADIO_ACTIVE2, S_RADIO_INACTIVE: S_RADIO_INACTIVE2, symbol: symbol2, limitOptions: limitOptions2 } = (init_dist4(), __toCommonJS(dist_exports2)), ui2 = require_ui(), ACTION_PREFIX = "__action__:", nextFocus = (state, { name, char }) => {
|
|
7480
|
+
let { area, index, count, hasRows } = state;
|
|
7481
|
+
return name == "tab" ? { ...state, area: area == "list" || !hasRows ? "actions" : "list" } : area != "actions" ? state : name == "left" ? { ...state, index: (index - 1 + count) % count } : name == "right" ? { ...state, index: (index + 1) % count } : name == "return" ? { ...state, submit: !0 } : (name == "up" || name == "down") && hasRows ? { ...state, area: "list" } : char && char.length == 1 && char >= " " && hasRows ? { ...state, area: "list" } : state;
|
|
7482
|
+
}, HomePrompt = class extends AutocompletePrompt {
|
|
7483
|
+
constructor(opts) {
|
|
7484
|
+
super(opts), this.actions = opts.actions, this.area = this.options.length ? "list" : "actions", this.actionIndex = 0, this.on("key", (char, key) => {
|
|
7485
|
+
let next = nextFocus(
|
|
7486
|
+
{ area: this.area, index: this.actionIndex, count: this.actions.length, hasRows: this.filteredOptions.length > 0 },
|
|
7487
|
+
{ name: key && key.name, char }
|
|
7488
|
+
);
|
|
7489
|
+
this.area = next.area, this.actionIndex = next.index, next.submit && (this.value = ACTION_PREFIX + this.actions[this.actionIndex].key);
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
7492
|
+
}, actionBar = (actions, focused) => actions.map((a2, i2) => {
|
|
7493
|
+
let label = ` ${a2.label} `;
|
|
7494
|
+
return i2 === focused ? ui2.style(["inverse", "cyan"], label) : ui2.dim(`[${label.trim()}]`);
|
|
7495
|
+
}).join(" "), home = ({ message, rows, actions, initialQuery, maxItems, input, output }) => new HomePrompt({
|
|
7496
|
+
actions,
|
|
7497
|
+
input,
|
|
7498
|
+
output,
|
|
7499
|
+
initialUserInput: initialQuery || void 0,
|
|
7500
|
+
options() {
|
|
7501
|
+
return rows(this.userInput || "");
|
|
7502
|
+
},
|
|
7503
|
+
validate: (value) => value === void 0 ? "Nothing matches: change the search, or press Tab for actions." : void 0,
|
|
7504
|
+
render() {
|
|
7505
|
+
let gray = (s) => ui2.style("gray", s), title = `${gray(S_BAR2)}
|
|
7506
|
+
${symbol2(this.state)} ${message}`;
|
|
7507
|
+
if (this.state == "submit") {
|
|
7508
|
+
let v = String(this.value || "");
|
|
7509
|
+
if (v.startsWith(ACTION_PREFIX)) {
|
|
7510
|
+
let label = (actions.find((a2) => ACTION_PREFIX + a2.key == v) || {}).label || "";
|
|
7511
|
+
return `${gray(S_BAR2)}
|
|
7512
|
+
${symbol2(this.state)} ${ui2.dim("switch-profile \u203A")} ${ui2.strong(label)}`;
|
|
7513
|
+
}
|
|
7514
|
+
return `${title}
|
|
7515
|
+
${gray(S_BAR2)} ${ui2.dim(v)}`;
|
|
7516
|
+
}
|
|
7517
|
+
if (this.state == "cancel")
|
|
7518
|
+
return `${title}
|
|
7519
|
+
${gray(S_BAR2)}`;
|
|
7520
|
+
let bar = ui2.style(this.state == "error" ? "yellow" : "cyan", S_BAR2), end = ui2.style(this.state == "error" ? "yellow" : "cyan", S_BAR_END2), onList = this.area == "list", count = this.filteredOptions.length, lines = [title];
|
|
7521
|
+
return lines.push(`${bar} ${ui2.dim("Search:")} ${this.userInputWithCursor}${this.userInput ? ui2.dim(` (${count} match${count == 1 ? "" : "es"})`) : ""}`), count ? limitOptions2({
|
|
7522
|
+
cursor: this.cursor,
|
|
7523
|
+
options: this.filteredOptions,
|
|
7524
|
+
maxItems,
|
|
7525
|
+
output: this.output,
|
|
7526
|
+
rowPadding: 8,
|
|
7527
|
+
style: (option, active) => active && onList ? `${ui2.style("green", S_RADIO_ACTIVE2)} ${option.label}${option.hint ? ui2.dim(` ${option.hint}`) : ""}` : `${ui2.dim(S_RADIO_INACTIVE2)} ${ui2.dim(option.label)}`
|
|
7528
|
+
}).forEach((line) => lines.push(`${bar} ${line}`)) : lines.push(`${bar} ${ui2.warn(this.options.length ? "No matches" : "No profiles yet: Tab to Add")}`), lines.push(bar), lines.push(`${bar} ${actionBar(actions, onList ? -1 : this.actionIndex)}`), this.state == "error" && lines.push(`${bar} ${ui2.warn(this.error)}`), lines.push(`${end} ${ui2.dim(onList ? "\u2191\u2193 choose \xB7 enter switch \xB7 tab actions \xB7 esc quit" : "\u2190\u2192 choose \xB7 enter open \xB7 tab back to the list \xB7 esc quit")}`), lines.join(`
|
|
7529
|
+
`);
|
|
7530
|
+
}
|
|
7531
|
+
}).prompt();
|
|
7532
|
+
module2.exports = {
|
|
7533
|
+
ACTION_PREFIX,
|
|
7534
|
+
nextFocus,
|
|
7535
|
+
home
|
|
7536
|
+
};
|
|
7537
|
+
}
|
|
7538
|
+
});
|
|
7539
|
+
|
|
7448
7540
|
// src/commands/switch.js
|
|
7449
7541
|
var require_switch = __commonJS({
|
|
7450
7542
|
"src/commands/switch.js"(exports2, module2) {
|
|
7451
|
-
var p4 = (init_dist4(), __toCommonJS(
|
|
7543
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), settings2 = require_settings(), shell = require_shell(), rank = require_rank(), ui2 = require_ui(), { ask, preflight, loadState, loginKey, describe, loginState, CancelError: CancelError2 } = require_common(), { ssoLogin } = require_login_flow(), { home, ACTION_PREFIX } = require_home(), header = async (state, { warnings } = {}) => {
|
|
7452
7544
|
p4.intro(`${ui2.badge("switch-profile")} ${ui2.dim(settings2.CLI_VERSION)}`);
|
|
7453
7545
|
let lines = [];
|
|
7454
7546
|
if (state.defaultProfile) {
|
|
@@ -7463,7 +7555,7 @@ var require_switch = __commonJS({
|
|
|
7463
7555
|
lines.push(ui2.dim("No default profile yet. Pick one below."));
|
|
7464
7556
|
let terminal = process.env.AWS_PROFILE;
|
|
7465
7557
|
terminal && terminal != state.defaultName && lines.push(`${ui2.dim("This terminal uses")} ${ui2.accent(terminal)} ${ui2.dim("(AWS_PROFILE)")}`), p4.log.message(lines.join(`
|
|
7466
|
-
`)), warnEnv();
|
|
7558
|
+
`)), warnings && warnEnv();
|
|
7467
7559
|
}, warnEnv = () => {
|
|
7468
7560
|
let set = shell.CLEARED_VARS.filter((v) => process.env[v]);
|
|
7469
7561
|
if (!set.length)
|
|
@@ -7472,11 +7564,11 @@ var require_switch = __commonJS({
|
|
|
7472
7564
|
p4.log.warn(`${set.join(", ")} ${set.length > 1 ? "are" : "is"} set in this terminal and override${set.length > 1 ? "" : "s"} profiles.
|
|
7473
7565
|
${ui2.dim(viaShortcut ? "Switching clears them from this terminal." : `Unset ${set.length > 1 ? "them" : "it"}, or switch with ${shell.FUNCTION_NAME} to clear ${set.length > 1 ? "them" : "it"} automatically.`)}`);
|
|
7474
7566
|
}, ACTIONS = [
|
|
7475
|
-
{ key: "
|
|
7476
|
-
{ key: "
|
|
7477
|
-
{ key: "remove",
|
|
7478
|
-
{ key: "logout",
|
|
7479
|
-
{ key: "settings",
|
|
7567
|
+
{ key: "login", label: "Log in" },
|
|
7568
|
+
{ key: "add", label: "Add" },
|
|
7569
|
+
{ key: "remove", label: "Remove" },
|
|
7570
|
+
{ key: "logout", label: "Log out" },
|
|
7571
|
+
{ key: "settings", label: "Settings" }
|
|
7480
7572
|
], buildRows = (profiles, state) => {
|
|
7481
7573
|
let cols = ui2.columns(), showRole = cols >= 80, showRegion = cols >= 100, roleOf = (x2) => x2.sso_role_name || (x2.kind == "keys" ? "access keys" : x2.kind == "login" ? "console sign-in" : x2.kind), all = state.profiles, nameWidth = Math.min(Math.max(...all.map((x2) => x2.name.length), 8), 32), roleWidth = Math.min(Math.max(0, ...all.map((x2) => roleOf(x2).length)), 22), usage = state.settings.usage || {};
|
|
7482
7574
|
return profiles.map((x2) => {
|
|
@@ -7489,20 +7581,13 @@ ${ui2.dim(viaShortcut ? "Switching clears them from this terminal." : `Unset ${s
|
|
|
7489
7581
|
].filter((v) => v !== null).join(" ").trimEnd(), hint = x2.name == state.defaultName ? "current default" : [x2.accountName, usage[x2.name] ? `used ${ui2.ago(usage[x2.name].last)}` : null].filter(Boolean).join(" \xB7 ");
|
|
7490
7582
|
return { value: x2.name, label, hint: hint || void 0 };
|
|
7491
7583
|
});
|
|
7492
|
-
}, pick =
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
filter: () => !0,
|
|
7500
|
-
options() {
|
|
7501
|
-
let query = this.userInput || "", ranked = rank.rankProfiles(state.profiles, { query, usage: state.settings.usage, current: state.defaultName }), q = query.trim().toLowerCase(), matchingActions = actions.filter((a2) => !q || a2.text.includes(q)).map((a2) => ({ value: a2.value, label: a2.label, hint: a2.hint }));
|
|
7502
|
-
return [...buildRows(ranked, state), ...matchingActions];
|
|
7503
|
-
}
|
|
7504
|
-
}));
|
|
7505
|
-
}, connect = async (profile, state, { interactive, quiet, force }) => {
|
|
7584
|
+
}, pick = (state, initialQuery) => home({
|
|
7585
|
+
message: `Switch to ${ui2.dim("\xB7 type a name, account, role or client")}`,
|
|
7586
|
+
maxItems: Math.max(5, Math.min(14, (process.stdout.rows || 24) - 14)),
|
|
7587
|
+
initialQuery,
|
|
7588
|
+
actions: ACTIONS,
|
|
7589
|
+
rows: (query) => buildRows(rank.rankProfiles(state.profiles, { query, usage: state.settings.usage, current: state.defaultName }), state)
|
|
7590
|
+
}), connect = async (profile, state, { interactive, quiet, force }) => {
|
|
7506
7591
|
let spin = quiet ? null : ui2.spinner(), tryIdentity = async () => {
|
|
7507
7592
|
spin && spin.start(`Connecting to ${profile.name}`);
|
|
7508
7593
|
try {
|
|
@@ -7523,7 +7608,7 @@ ${ui2.dim(viaShortcut ? "Switching clears them from this terminal." : `Unset ${s
|
|
|
7523
7608
|
throw new ui2.CliError(`Logged in, but ${profile.name} still has no access.`, { hint: firstLine(result.message) });
|
|
7524
7609
|
return result;
|
|
7525
7610
|
}, firstLine = (text2) => String(text2 || "").trim().split(`
|
|
7526
|
-
`).filter(Boolean).slice(-1)[0], switchTo = async (profile, state, opts) => {
|
|
7611
|
+
`).filter(Boolean).slice(-1)[0], switchTo = async (profile, state, opts) => (await doSwitch(profile, state, opts), { switched: !0 }), doSwitch = async (profile, state, opts) => {
|
|
7527
7612
|
let { interactive, json } = opts, identity = await connect(profile, state, { interactive, quiet: json, force: opts.force }), [defaultErrors] = await aws.setDefaultProfile(profile.name);
|
|
7528
7613
|
if (defaultErrors)
|
|
7529
7614
|
throw new ui2.CliError(ui2.errorsMessage(defaultErrors));
|
|
@@ -7578,6 +7663,35 @@ ${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name
|
|
|
7578
7663
|
}, runAction = async (key, state, opts) => {
|
|
7579
7664
|
let commands2 = require_commands();
|
|
7580
7665
|
return key == "login" ? commands2.login(state.defaultName, { ...opts, fromPicker: !0 }) : commands2[key](opts.args || [], { ...opts, fromPicker: !0 });
|
|
7666
|
+
}, routeChoice = (choice) => {
|
|
7667
|
+
if (p4.isCancel(choice))
|
|
7668
|
+
return { type: "quit" };
|
|
7669
|
+
let value = String(choice);
|
|
7670
|
+
return value.startsWith(ACTION_PREFIX) ? { type: "page", key: value.slice(ACTION_PREFIX.length) } : { type: "switch", name: value };
|
|
7671
|
+
}, afterPage = (result, err) => err ? err instanceof CancelError2 ? "back" : err instanceof ui2.CliError ? "error" : "throw" : result && result.switched ? "exit" : "home", homeLoop = async (state, opts, initialQuery) => {
|
|
7672
|
+
let first = !0;
|
|
7673
|
+
for (; ; ) {
|
|
7674
|
+
await header(state, { warnings: first });
|
|
7675
|
+
let route = routeChoice(await pick(state, first ? initialQuery : void 0));
|
|
7676
|
+
if (first = !1, route.type == "quit") {
|
|
7677
|
+
p4.outro(ui2.dim(`Bye ${ui2.unicode ? "\u{1F44B}" : ""}`));
|
|
7678
|
+
return;
|
|
7679
|
+
}
|
|
7680
|
+
if (route.type == "switch")
|
|
7681
|
+
return switchTo(state.profiles.find((x2) => x2.name == route.name), state, opts);
|
|
7682
|
+
let result, error;
|
|
7683
|
+
try {
|
|
7684
|
+
result = await runAction(route.key, state, opts);
|
|
7685
|
+
} catch (err) {
|
|
7686
|
+
error = err;
|
|
7687
|
+
}
|
|
7688
|
+
let next = afterPage(result, error);
|
|
7689
|
+
if (next == "exit")
|
|
7690
|
+
return result;
|
|
7691
|
+
if (next == "throw")
|
|
7692
|
+
throw error;
|
|
7693
|
+
next == "back" && p4.cancel(ui2.dim(`${ui2.unicode ? "\u2190" : "<-"} Back`)), next == "error" && ui2.printError(error), state = await loadState();
|
|
7694
|
+
}
|
|
7581
7695
|
}, switchCommand = async (query, opts) => {
|
|
7582
7696
|
let interactive = ui2.isInteractive(opts);
|
|
7583
7697
|
await preflight({ interactive, quiet: opts.json });
|
|
@@ -7598,11 +7712,11 @@ ${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name
|
|
|
7598
7712
|
});
|
|
7599
7713
|
} else if (!interactive)
|
|
7600
7714
|
throw new ui2.CliError("Which profile? Pass its name.", { code: 3, hint: `e.g. switch-profile ${state.profiles[0].name}` });
|
|
7601
|
-
|
|
7602
|
-
let choice = await pick(state, query);
|
|
7603
|
-
return choice.startsWith(ACTION) ? runAction(choice.slice(ACTION.length), state, { ...opts, interactive }) : switchTo(state.profiles.find((x2) => x2.name == choice), state, { ...opts, interactive });
|
|
7715
|
+
return homeLoop(state, { ...opts, interactive }, query);
|
|
7604
7716
|
};
|
|
7605
7717
|
module2.exports = {
|
|
7718
|
+
routeChoice,
|
|
7719
|
+
afterPage,
|
|
7606
7720
|
switchCommand,
|
|
7607
7721
|
switchTo,
|
|
7608
7722
|
connect,
|
|
@@ -7615,7 +7729,7 @@ ${ui2.dim(`Open a new terminal or run: ${installed.reload}`)}`), installed.name
|
|
|
7615
7729
|
// src/commands/index.js
|
|
7616
7730
|
var require_commands = __commonJS({
|
|
7617
7731
|
"src/commands/index.js"(exports2, module2) {
|
|
7618
|
-
var p4 = (init_dist4(), __toCommonJS(
|
|
7732
|
+
var p4 = (init_dist4(), __toCommonJS(dist_exports2)), aws = require_aws(), transforms = require_transforms(), settings2 = require_settings(), shell = require_shell(), migrate = require_migrate(), rank = require_rank(), { resolveLoginMode, isRemoteSession } = require_login(), ui2 = require_ui(), { ask, preflight, loadState, loginKey, describe, loginState, upgradeLegacySso } = require_common(), { ssoLogin, rememberLogin } = require_login_flow(), { switchCommand, switchTo, connect, enableShortcut } = require_switch(), intro2 = (opts) => {
|
|
7619
7733
|
!opts.fromPicker && !opts.json && p4.intro(`${ui2.badge("switch-profile")} ${ui2.dim(settings2.CLI_VERSION)}`);
|
|
7620
7734
|
}, begin = async (opts, { needsInput } = {}) => {
|
|
7621
7735
|
let interactive = ui2.isInteractive(opts);
|
|
@@ -7791,14 +7905,25 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
7791
7905
|
].filter(Boolean).join(`
|
|
7792
7906
|
`);
|
|
7793
7907
|
opts.json || p4.log.message(summary);
|
|
7794
|
-
let prune = !!opts.prune;
|
|
7908
|
+
let prune = !!opts.prune, include;
|
|
7795
7909
|
if (interactive && !opts.yes) {
|
|
7796
|
-
if (preview.added.length
|
|
7797
|
-
|
|
7910
|
+
if (preview.added.length) {
|
|
7911
|
+
let all = preview.addedEntries.map((e) => transforms.ssoEntryKey(e.accountId, e.roleName)), picked = await ask(p4.autocompleteMultiselect({
|
|
7912
|
+
message: `Add which profiles? ${ui2.dim("(all selected: space to untick, type to search, enter to confirm)")}`,
|
|
7913
|
+
maxItems: 12,
|
|
7914
|
+
initialValues: all,
|
|
7915
|
+
options: preview.addedEntries.map((e, i2) => ({ value: all[i2], label: e.name, hint: [e.accountName, e.accountId, e.roleName].filter(Boolean).join(" \xB7 ") }))
|
|
7916
|
+
}));
|
|
7917
|
+
if (!picked.length && !preview.stale.length)
|
|
7918
|
+
return p4.outro(ui2.dim("Nothing changed."));
|
|
7919
|
+
picked.length < all.length && (include = picked);
|
|
7920
|
+
}
|
|
7798
7921
|
preview.stale.length && (prune = await ask(p4.confirm({ message: `Remove the ${preview.stale.length} profile${preview.stale.length == 1 ? "" : "s"} that no longer exist${preview.stale.length == 1 ? "s" : ""}?`, initialValue: !1 })));
|
|
7799
7922
|
}
|
|
7923
|
+
if (include && !include.length && !prune)
|
|
7924
|
+
return p4.outro(ui2.dim("Nothing changed."));
|
|
7800
7925
|
prune && await aws.backupAwsFiles();
|
|
7801
|
-
let result = transforms.populateSsoProfiles(configStr, { ssoSession: sessionName, entries, region, prefix, prune, version: settings2.CLI_VERSION });
|
|
7926
|
+
let result = transforms.populateSsoProfiles(configStr, { ssoSession: sessionName, entries, region, prefix, prune, include, version: settings2.CLI_VERSION });
|
|
7802
7927
|
if (await aws.writeAwsFile(aws.AWS_CONFIG_FILE, result.config), await settings2.update({}), opts.json) {
|
|
7803
7928
|
process.stdout.write(JSON.stringify({ added: result.added, existing: result.existing, removed: result.removed, stale: prune ? [] : result.stale }) + `
|
|
7804
7929
|
`);
|
|
@@ -7887,7 +8012,7 @@ AWS recommends SSO or console sign-in instead.`), !await ask(p4.confirm({ messag
|
|
|
7887
8012
|
});
|
|
7888
8013
|
|
|
7889
8014
|
// src/cli.js
|
|
7890
|
-
var { Command } = require_commander(), p3 = (init_dist4(), __toCommonJS(
|
|
8015
|
+
var { Command } = require_commander(), p3 = (init_dist4(), __toCommonJS(dist_exports2)), ui = require_ui(), { CLI_VERSION } = require_settings(), { CancelError } = require_common(), commands = require_commands();
|
|
7891
8016
|
process.env.AWS_CLI_AGENT_TOOLKIT_HINT_DISABLED = "true";
|
|
7892
8017
|
var EXAMPLES = `
|
|
7893
8018
|
Examples:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switch-profile",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"switch-profile": "index.js"
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"author": "Nicolas Dao",
|
|
37
37
|
"license": "BSD-3-Clause",
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@clack/core": "^1.5.1",
|
|
39
40
|
"@clack/prompts": "^1.8.1",
|
|
40
41
|
"@eslint/js": "^10.0.1",
|
|
41
42
|
"chai": "^4.1.2",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"eslint": "^10.0.0",
|
|
45
46
|
"fuzzysort": "^3.1.0",
|
|
46
47
|
"globals": "^17.13.0",
|
|
47
|
-
"mocha": "^8.
|
|
48
|
+
"mocha": "^11.8.0",
|
|
48
49
|
"uqr": "^0.1.3"
|
|
49
50
|
},
|
|
50
51
|
"files": [
|