switch-profile 1.0.0 → 1.1.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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/docs/architecture.md +3 -1
- package/docs/aws-profile-management.md +1 -1
- package/docs/cli-interface.md +21 -3
- package/docs/gotchas.md +6 -2
- package/index.js +14 -3
- package/package.json +1 -1
- package/src/aws/index.js +25 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.1.0](https://github.com/nicolasdao/switch-profile/compare/v1.0.0...v1.1.0) (2026-03-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add Windows compatibility for profile switching, config parsing, and shell hints ([16ce46b](https://github.com/nicolasdao/switch-profile/commit/16ce46bc5cb1d92fc6d882358eaabc718a157f22))
|
|
11
|
+
|
|
5
12
|
## [1.0.0](https://github.com/nicolasdao/switch-profile/compare/v0.2.3...v1.0.0) (2026-03-21)
|
|
6
13
|
|
|
7
14
|
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ When you select a profile, `switch-profile`:
|
|
|
102
102
|
|
|
103
103
|
### Per-terminal profile isolation
|
|
104
104
|
|
|
105
|
-
By default, switching profiles updates the global `[default]` section, which affects all terminals. If you need different AWS profiles in different terminals simultaneously, copy and run the `export AWS_PROFILE=<name>`
|
|
105
|
+
By default, switching profiles updates the global `[default]` section, which affects all terminals. If you need different AWS profiles in different terminals simultaneously, copy and run the command shown after each switch (`export AWS_PROFILE=<name>` on Linux/macOS, or the PowerShell/CMD equivalent on Windows). This sets an environment variable scoped to that terminal only, so other terminals remain unaffected.
|
|
106
106
|
|
|
107
107
|
For SSO profiles specifically, it leverages two additional AWS CLI cache directories:
|
|
108
108
|
- `~/.aws/sso/cache/` - SSO session tokens (long-lived, ~24 hours)
|
package/docs/architecture.md
CHANGED
|
@@ -38,7 +38,7 @@ The main executable file (with `#!/usr/bin/env node` shebang). Registered as the
|
|
|
38
38
|
|----------|---------|
|
|
39
39
|
| `switchCmd()` | Main command handler. Checks AWS CLI, shows current profile status, lists profiles, renders the selection menu. |
|
|
40
40
|
| `setProfileToDefault(name, list, msg)` | Gets credentials for a profile, writes them as the `[default]` profile. Shows a clear error if the profile is not found in the list. Displays the `export AWS_PROFILE` hint on success. |
|
|
41
|
-
| `printExportHint(profileName)` | Displays a cyan box with the `export AWS_PROFILE=<name
|
|
41
|
+
| `printExportHint(profileName)` | Displays a cyan box with the profile isolation command. On Linux/macOS shows `export AWS_PROFILE=<name>`. On Windows shows both PowerShell (`$env:AWS_PROFILE`) and CMD (`set AWS_PROFILE`) variants. Box width adjusts dynamically. |
|
|
42
42
|
| `createNewProfile(profiles, makeItDefault)` | Walks the user through creating a standard or SSO profile. Displays an SSO Setup Guide before SSO creation and checks for errors before reporting success. |
|
|
43
43
|
| `chooseProfileName(denyList)` | Validates profile name input (lowercase alphanumeric, dashes, underscores, min 2 chars, no duplicates). |
|
|
44
44
|
| `chooseRegions()` | Autocomplete region picker from the 24 supported AWS regions. |
|
|
@@ -57,6 +57,8 @@ The core module. Handles all interactions with AWS configuration files and the A
|
|
|
57
57
|
- Creating, deleting, and listing profiles.
|
|
58
58
|
- Refreshing expired SSO sessions (including browser redirect).
|
|
59
59
|
|
|
60
|
+
**Windows compatibility:** Uses `IS_WINDOWS` (`process.platform === 'win32'`) to select platform-appropriate line separator for parsing (`LINE_SEP`: regex `/\r?\n/` on Windows, `os.EOL` otherwise), newline character for writing (`NL`: `'\n'` on Windows, `os.EOL` otherwise), and `shell: true` for `spawn` on Windows (required because `aws` is a `.cmd` batch file).
|
|
61
|
+
|
|
60
62
|
See [AWS Profile Management](aws-profile-management.md) for a detailed breakdown of every function.
|
|
61
63
|
|
|
62
64
|
### `src/aws/regions.js` - Region Definitions
|
|
@@ -202,7 +202,7 @@ If the config or credentials files don't exist, they are created with a default
|
|
|
202
202
|
|
|
203
203
|
Creates an SSO profile by spawning the interactive `aws configure sso --profile <name>` command. This hands control to the AWS CLI, which walks the user through the SSO setup (URL, region, account, role).
|
|
204
204
|
|
|
205
|
-
Uses `spawn` with `stdio: 'inherit'` so the user interacts directly with the AWS CLI process. Checks the exit code of the spawned process and throws an error if it is non-zero, indicating the SSO setup failed.
|
|
205
|
+
Uses `spawn` with `stdio: 'inherit'` so the user interacts directly with the AWS CLI process. On Windows, `shell: true` is also passed because `aws` is typically a `.cmd` batch file that requires shell resolution. Checks the exit code of the spawned process and throws an error if it is non-zero, indicating the SSO setup failed.
|
|
206
206
|
|
|
207
207
|
#### `deleteProfiles(profiles)`
|
|
208
208
|
|
package/docs/cli-interface.md
CHANGED
|
@@ -21,7 +21,7 @@ Before anything else, the tool verifies AWS CLI v2+ is installed. If not found,
|
|
|
21
21
|
|
|
22
22
|
- **macOS:** `brew install awscli && brew link --overwrite awscli`
|
|
23
23
|
- **Linux:** curl + unzip instructions for the AWS CLI v2 installer
|
|
24
|
-
- **Windows:**
|
|
24
|
+
- **Windows:** "Not installed" message with a link to the AWS CLI MSI installer download page
|
|
25
25
|
|
|
26
26
|
### Step 2: Default Profile Status
|
|
27
27
|
|
|
@@ -82,6 +82,7 @@ Selecting a profile from the list sets it as the `default`. For SSO profiles, th
|
|
|
82
82
|
|
|
83
83
|
On success:
|
|
84
84
|
|
|
85
|
+
**Linux / macOS:**
|
|
85
86
|
```
|
|
86
87
|
AWS profile sso-dev successfully set up as default. # Green
|
|
87
88
|
|
|
@@ -94,7 +95,21 @@ AWS profile sso-dev successfully set up as default. # Green
|
|
|
94
95
|
└─────────────────────────────────────────────────────────┘
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
**Windows:**
|
|
99
|
+
```
|
|
100
|
+
AWS profile sso-dev successfully set up as default. # Green
|
|
101
|
+
|
|
102
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
103
|
+
│ To lock this profile to this terminal session, run: │ # Cyan
|
|
104
|
+
│ │
|
|
105
|
+
│ PowerShell: $env:AWS_PROFILE = "sso-dev" │ # Cyan + Bold
|
|
106
|
+
│ CMD: set AWS_PROFILE=sso-dev │ # Cyan + Bold
|
|
107
|
+
│ │
|
|
108
|
+
│ This prevents other terminals from affecting this one. │
|
|
109
|
+
└──────────────────────────────────────────────────────────────┘
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This hint appears after every successful profile switch, refresh, or new profile set as default. Running the displayed command sets the `AWS_PROFILE` environment variable in the current terminal only, enabling per-terminal profile isolation.
|
|
98
113
|
|
|
99
114
|
### Step 4b: More Options
|
|
100
115
|
|
|
@@ -244,6 +259,7 @@ Selecting it:
|
|
|
244
259
|
2. Retrieves fresh credentials (SSO login if needed).
|
|
245
260
|
3. Updates the `[default]` section.
|
|
246
261
|
|
|
262
|
+
**Linux / macOS:**
|
|
247
263
|
```
|
|
248
264
|
AWS profile sso-dev successfully refreshed. # Green
|
|
249
265
|
|
|
@@ -256,6 +272,8 @@ AWS profile sso-dev successfully refreshed. # Green
|
|
|
256
272
|
└─────────────────────────────────────────────────────────┘
|
|
257
273
|
```
|
|
258
274
|
|
|
275
|
+
On Windows, the hint shows PowerShell and CMD equivalents instead (see [Step 4a](#step-4a-select-a-profile)).
|
|
276
|
+
|
|
259
277
|
## Visual Indicators
|
|
260
278
|
|
|
261
279
|
| Color | Meaning |
|
|
@@ -265,7 +283,7 @@ AWS profile sso-dev successfully refreshed. # Green
|
|
|
265
283
|
| **Cyan** | Informational (current profile, time remaining) |
|
|
266
284
|
| **Yellow** | Warnings (expired, expiring soon) |
|
|
267
285
|
| **Bold** | Profile names in messages |
|
|
268
|
-
| **Cyan + Bold** |
|
|
286
|
+
| **Cyan + Bold** | Profile isolation command hint (`export AWS_PROFILE=...` on Unix, PowerShell/CMD equivalents on Windows) |
|
|
269
287
|
|
|
270
288
|
## Error Display
|
|
271
289
|
|
package/docs/gotchas.md
CHANGED
|
@@ -35,11 +35,15 @@ After every successful switch, `switch-profile` displays a box like:
|
|
|
35
35
|
└─────────────────────────────────────────────────────────┘
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Running
|
|
38
|
+
Running the displayed command sets an environment variable **scoped to that terminal session only**. The AWS CLI and all AWS SDKs honor `AWS_PROFILE` and will use the named profile directly — bypassing `[default]` entirely. Other terminals remain unaffected.
|
|
39
|
+
|
|
40
|
+
On **Linux / macOS**: `export AWS_PROFILE=dev`
|
|
41
|
+
On **Windows (PowerShell)**: `$env:AWS_PROFILE = "dev"`
|
|
42
|
+
On **Windows (CMD)**: `set AWS_PROFILE=dev`
|
|
39
43
|
|
|
40
44
|
### The limitation: you must copy-paste it yourself
|
|
41
45
|
|
|
42
|
-
`switch-profile` **cannot** set the environment variable for you. This is a fundamental operating system constraint: a child process (the `npx` command) cannot modify the environment of its parent process (your terminal shell). The `export`
|
|
46
|
+
`switch-profile` **cannot** set the environment variable for you. This is a fundamental operating system constraint: a child process (the `npx` command) cannot modify the environment of its parent process (your terminal shell). The displayed command (`export` on Unix, `$env:` on PowerShell, `set` on CMD) must be run directly in the terminal by the user.
|
|
43
47
|
|
|
44
48
|
There is no workaround for this within the tool itself. The user must manually copy-paste and run the `export AWS_PROFILE=<name>` command shown after each switch.
|
|
45
49
|
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const program = require('commander')
|
|
7
7
|
const inquirer = require('inquirer')
|
|
8
8
|
const { EOL } = require('os')
|
|
9
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
9
10
|
const { listProfiles, getCredentials, getDefaultProfile, updateDefaultProfile, deleteProfiles, createProfile, regions, createSsoProfile, awsCliV2Exists } = require('./src/aws')
|
|
10
11
|
const { printAWSerrors } = require('./src/core')
|
|
11
12
|
require('colors')
|
|
@@ -179,7 +180,8 @@ const switchCmd = async () => {
|
|
|
179
180
|
console.log('brew link --overwrite awscli')
|
|
180
181
|
return
|
|
181
182
|
} else if (process.platform == 'win32') {
|
|
182
|
-
console.log('AWS CLI seems to not be installed.'.red)
|
|
183
|
+
console.log('AWS CLI seems to not be installed. Download the installer from:\n'.red)
|
|
184
|
+
console.log('https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html')
|
|
183
185
|
return
|
|
184
186
|
} else {
|
|
185
187
|
console.log('AWS CLI seems to not be installed. Try installing it as follow\n'.red)
|
|
@@ -331,7 +333,14 @@ const switchCmd = async () => {
|
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
const printExportHint = (profileName) => {
|
|
334
|
-
const lines = [
|
|
336
|
+
const lines = IS_WINDOWS ? [
|
|
337
|
+
' To lock this profile to this terminal session, run: ',
|
|
338
|
+
'',
|
|
339
|
+
` PowerShell: $env:AWS_PROFILE = "${profileName}"`,
|
|
340
|
+
` CMD: set AWS_PROFILE=${profileName}`,
|
|
341
|
+
'',
|
|
342
|
+
' This prevents other terminals from affecting this one. '
|
|
343
|
+
] : [
|
|
335
344
|
' To lock this profile to this terminal session, run: ',
|
|
336
345
|
'',
|
|
337
346
|
` export AWS_PROFILE=${profileName}`,
|
|
@@ -342,9 +351,11 @@ const printExportHint = (profileName) => {
|
|
|
342
351
|
const pad = s => s + ' '.repeat(width - s.length)
|
|
343
352
|
console.log('')
|
|
344
353
|
console.log(('┌' + '─'.repeat(width) + '┐').cyan)
|
|
354
|
+
const boldStart = IS_WINDOWS ? 2 : 2
|
|
355
|
+
const boldEnd = IS_WINDOWS ? 3 : 2
|
|
345
356
|
for (let i = 0; i < lines.length; i++) {
|
|
346
357
|
const content = '│' + pad(lines[i]) + '│'
|
|
347
|
-
console.log(i
|
|
358
|
+
console.log(i >= boldStart && i <= boldEnd ? content.cyan.bold : content.cyan)
|
|
348
359
|
}
|
|
349
360
|
console.log(('└' + '─'.repeat(width) + '┘').cyan)
|
|
350
361
|
console.log('')
|
package/package.json
CHANGED
package/src/aws/index.js
CHANGED
|
@@ -6,6 +6,9 @@ const { spawn } = require('child_process')
|
|
|
6
6
|
const fileHelper = require('../fileHelper')
|
|
7
7
|
const regions = require('./regions')
|
|
8
8
|
|
|
9
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
10
|
+
const LINE_SEP = IS_WINDOWS ? /\r?\n/ : EOL
|
|
11
|
+
const NL = IS_WINDOWS ? '\n' : EOL
|
|
9
12
|
const SSO_GET_CREDS_TIMEOUT = 5*60*1000 // 5 minutes to complete the SSO login
|
|
10
13
|
const AWS_CONFIG_FILE = join(homedir(), '.aws', 'config')
|
|
11
14
|
const AWS_CREDS_FILE = join(homedir(), '.aws', 'credentials')
|
|
@@ -100,7 +103,7 @@ const listProfiles = () => catchErrors((async () => {
|
|
|
100
103
|
return profiles.map(profile => {
|
|
101
104
|
const [,rest=''] = configStr.split(profile)
|
|
102
105
|
const [config=''] = rest.split('[')
|
|
103
|
-
const params = config.split(
|
|
106
|
+
const params = config.split(LINE_SEP)
|
|
104
107
|
const p = {
|
|
105
108
|
name: profile.replace(/(^\[profile\s+|\[|\])/g, ''),
|
|
106
109
|
sso_start_url: getParam(params, 'sso_start_url'),
|
|
@@ -118,7 +121,7 @@ const listProfiles = () => catchErrors((async () => {
|
|
|
118
121
|
const ssoSessionMatch = configStr.split(ssoSessionSection)
|
|
119
122
|
if (ssoSessionMatch.length > 1) {
|
|
120
123
|
const [ssoSessionRest=''] = ssoSessionMatch[1].split('[')
|
|
121
|
-
const ssoSessionParams = ssoSessionRest.split(
|
|
124
|
+
const ssoSessionParams = ssoSessionRest.split(LINE_SEP)
|
|
122
125
|
p.sso_start_url = getParam(ssoSessionParams, 'sso_start_url')
|
|
123
126
|
// If sso_region is not in profile, check the session section
|
|
124
127
|
if (!p.sso_region) {
|
|
@@ -384,7 +387,7 @@ const getCredentials = (profile, ssoUrl) => catchErrors((async () => {
|
|
|
384
387
|
throw new Error(`Profile ${profile} not found in ${AWS_CREDS_FILE}. Standard profiles must have credentials defined in this file.`)
|
|
385
388
|
|
|
386
389
|
const [config=''] = rest.split('[')
|
|
387
|
-
const params = config.split(
|
|
390
|
+
const params = config.split(LINE_SEP)
|
|
388
391
|
const creds = {
|
|
389
392
|
aws_access_key_id: getParam(params, 'aws_access_key_id'),
|
|
390
393
|
aws_secret_access_key: getParam(params, 'aws_secret_access_key'),
|
|
@@ -404,16 +407,16 @@ const updateDefaultProfile = ({ profile, region, expiry_date, aws_access_key_id,
|
|
|
404
407
|
if (credsStrErrors||configStrErrors)
|
|
405
408
|
throw wrapErrors(errMsg, credsStrErrors||configStrErrors)
|
|
406
409
|
|
|
407
|
-
const newDefaultCreds = '[default]'+
|
|
408
|
-
`aws_access_key_id = ${aws_access_key_id}`+
|
|
409
|
-
`aws_secret_access_key = ${aws_secret_access_key}`+
|
|
410
|
-
(aws_session_token ? `aws_session_token = ${aws_session_token}`+
|
|
411
|
-
(expiry_date ? `expiry_date = ${expiry_date.toISOString()}`+
|
|
412
|
-
`profile = ${profile}`+
|
|
410
|
+
const newDefaultCreds = '[default]'+NL+
|
|
411
|
+
`aws_access_key_id = ${aws_access_key_id}`+NL+
|
|
412
|
+
`aws_secret_access_key = ${aws_secret_access_key}`+NL+
|
|
413
|
+
(aws_session_token ? `aws_session_token = ${aws_session_token}`+NL : '') +
|
|
414
|
+
(expiry_date ? `expiry_date = ${expiry_date.toISOString()}`+NL : '') +
|
|
415
|
+
`profile = ${profile}`+NL+NL
|
|
413
416
|
|
|
414
|
-
const newDefaultConfig = '[default]'+
|
|
415
|
-
`region = ${region}`+
|
|
416
|
-
'output = json'+
|
|
417
|
+
const newDefaultConfig = '[default]'+NL+
|
|
418
|
+
`region = ${region}`+NL+
|
|
419
|
+
'output = json'+NL+NL
|
|
417
420
|
|
|
418
421
|
const defaultCredsSection = (credsStr.match(/\[default\]((.|\n|\r)*?)(\[|$)/)||[])[0]
|
|
419
422
|
const defaultConfigSection = (configStr.match(/\[default\]((.|\n|\r)*?)(\[|$)/)||[])[0]
|
|
@@ -442,7 +445,7 @@ const getDefaultProfile = () => catchErrors((async () => {
|
|
|
442
445
|
if (errors)
|
|
443
446
|
throw wrapErrors(errMsg, errors)
|
|
444
447
|
|
|
445
|
-
const params = ((credsStr.match(/\[default\]((.|\n|\r)*?)(\[|$)/)||[])[0]||'').split(
|
|
448
|
+
const params = ((credsStr.match(/\[default\]((.|\n|\r)*?)(\[|$)/)||[])[0]||'').split(LINE_SEP)
|
|
446
449
|
const creds = {
|
|
447
450
|
aws_access_key_id: getParam(params, 'aws_access_key_id'),
|
|
448
451
|
aws_secret_access_key: getParam(params, 'aws_secret_access_key'),
|
|
@@ -526,14 +529,14 @@ const createProfile = ({ name, aws_access_key_id, aws_secret_access_key, region
|
|
|
526
529
|
if (configStrErrors||credsStrErrors)
|
|
527
530
|
throw wrapErrors(errMsg, configStrErrors||credsStrErrors)
|
|
528
531
|
|
|
529
|
-
const configProfiles = [`[profile ${name}]`+
|
|
530
|
-
const credsProfiles = [`[${name}]`+
|
|
532
|
+
const configProfiles = [`[profile ${name}]`+NL]
|
|
533
|
+
const credsProfiles = [`[${name}]`+NL]
|
|
531
534
|
|
|
532
|
-
credsProfiles.push(`aws_access_key_id = ${aws_access_key_id}`+
|
|
533
|
-
credsProfiles.push(`aws_secret_access_key = ${aws_secret_access_key}`+
|
|
535
|
+
credsProfiles.push(`aws_access_key_id = ${aws_access_key_id}`+NL)
|
|
536
|
+
credsProfiles.push(`aws_secret_access_key = ${aws_secret_access_key}`+NL+NL)
|
|
534
537
|
|
|
535
|
-
configProfiles.push(`region = ${region}`+
|
|
536
|
-
configProfiles.push('output = json'+
|
|
538
|
+
configProfiles.push(`region = ${region}`+NL)
|
|
539
|
+
configProfiles.push('output = json'+NL+NL)
|
|
537
540
|
|
|
538
541
|
const newCreds = credsProfiles.join('')
|
|
539
542
|
const newConfig = configProfiles.join('')
|
|
@@ -543,14 +546,14 @@ const createProfile = ({ name, aws_access_key_id, aws_secret_access_key, region
|
|
|
543
546
|
if (!credsStr)
|
|
544
547
|
credsStr = DEFAULT_CREDS
|
|
545
548
|
|
|
546
|
-
await fileHelper.write(AWS_CONFIG_FILE, configStr+
|
|
547
|
-
await fileHelper.write(AWS_CREDS_FILE, credsStr+
|
|
549
|
+
await fileHelper.write(AWS_CONFIG_FILE, configStr+NL+newConfig)
|
|
550
|
+
await fileHelper.write(AWS_CREDS_FILE, credsStr+NL+newCreds)
|
|
548
551
|
})())
|
|
549
552
|
|
|
550
553
|
const createSsoProfile = name => catchErrors((async () => {
|
|
551
554
|
await awsCliV2Exists()
|
|
552
555
|
const exitCode = await new Promise(next => {
|
|
553
|
-
const child = spawn('aws', ['configure', 'sso', '--profile', name],{ stdio: 'inherit' })
|
|
556
|
+
const child = spawn('aws', ['configure', 'sso', '--profile', name], { stdio: 'inherit', ...(IS_WINDOWS ? { shell: true } : {}) })
|
|
554
557
|
child.on('exit', code => next(code))
|
|
555
558
|
})
|
|
556
559
|
if (exitCode !== 0)
|