switch-profile 1.1.0 → 2.0.1
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 +197 -94
- package/dist/cli.js +7921 -0
- package/index.js +12 -409
- package/package.json +31 -25
- package/.eslintrc.json +0 -39
- package/CHANGELOG.md +0 -120
- package/docs/architecture.md +0 -199
- package/docs/aws-profile-management.md +0 -325
- package/docs/cli-interface.md +0 -304
- package/docs/configuration-files.md +0 -240
- package/docs/development-guide.md +0 -168
- package/docs/gotchas.md +0 -82
- package/skills-lock.json +0 -73
- package/src/aws/index.js +0 -574
- package/src/aws/regions.js +0 -103
- package/src/core.js +0 -64
- package/src/fileHelper.js +0 -433
package/index.js
CHANGED
|
@@ -1,411 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
require('
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const OPTIONS_KEY = '_options_4s2s3a'
|
|
17
|
-
const ABORT_KEY = '_abort_rfewq1'
|
|
18
|
-
|
|
19
|
-
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
|
|
20
|
-
|
|
21
|
-
const chooseProfileName = async (denyList) => {
|
|
22
|
-
let { name } = await inquirer.prompt([
|
|
23
|
-
{
|
|
24
|
-
type: 'input',
|
|
25
|
-
name: 'name',
|
|
26
|
-
message: 'Enter a profile name (alphanumerical lowercase and \'-\' characters only)'
|
|
27
|
-
}
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
if (!name) {
|
|
31
|
-
console.log('Profile name is required. Please try again.'.red)
|
|
32
|
-
name = await chooseProfileName()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (/[^a-z0-9_-]/.test(name)) {
|
|
36
|
-
console.log('Profile name contained invalid characters. Only alphanumerical lowercase and \'-\' characters are allowed. Please try again.'.red)
|
|
37
|
-
name = await chooseProfileName()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (name.length < 2) {
|
|
41
|
-
console.log('Profile name is not long enough. The profile name must be longer or equal to 2 characters. Please try again.'.red)
|
|
42
|
-
name = await chooseProfileName()
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (denyList && denyList.some(n => n == name)) {
|
|
46
|
-
console.log(`Profile name ${name.bold} already exist. Please try again.`.red)
|
|
47
|
-
name = await chooseProfileName()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return name
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const chooseNonEmpty = async (prop, message) => {
|
|
54
|
-
let { value } = await inquirer.prompt([
|
|
55
|
-
{
|
|
56
|
-
type: 'input',
|
|
57
|
-
name: 'value',
|
|
58
|
-
message
|
|
59
|
-
}
|
|
60
|
-
])
|
|
61
|
-
|
|
62
|
-
if (!value) {
|
|
63
|
-
console.log(`${prop} cannot be empty. Please try again.`.red)
|
|
64
|
-
value = await chooseNonEmpty(prop, message)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return value
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const chooseRegions = async () => {
|
|
71
|
-
const { region } = await inquirer.prompt([
|
|
72
|
-
{
|
|
73
|
-
type: 'autocomplete',
|
|
74
|
-
name: 'region',
|
|
75
|
-
message: 'Select a region:',
|
|
76
|
-
pageSize: 20,
|
|
77
|
-
source: function(answersSoFar, input) {
|
|
78
|
-
if (input)
|
|
79
|
-
return regions.filter(r => `${r.code} - ${r.name}`.toLowerCase().indexOf(input.toLowerCase()) >= 0).map(r => ({
|
|
80
|
-
name: `${r.code} - ${r.name}`,
|
|
81
|
-
value:r.code
|
|
82
|
-
}))
|
|
83
|
-
else
|
|
84
|
-
return regions.map(r => ({
|
|
85
|
-
name: `${r.code} - ${r.name}`,
|
|
86
|
-
value:r.code
|
|
87
|
-
}))
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
])
|
|
91
|
-
|
|
92
|
-
return region
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const createNewProfile = async (profiles, makeItDefault) => {
|
|
96
|
-
const name = await chooseProfileName(profiles.map(p => p.name))
|
|
97
|
-
const { type } = await inquirer.prompt([
|
|
98
|
-
{
|
|
99
|
-
type: 'list',
|
|
100
|
-
name: 'type',
|
|
101
|
-
message: 'Choose an AWS profile type: ',
|
|
102
|
-
choices: ['standard','sso']
|
|
103
|
-
}
|
|
104
|
-
])
|
|
105
|
-
|
|
106
|
-
if (type == 'standard') {
|
|
107
|
-
const aws_access_key_id = await chooseNonEmpty('aws_access_key_id', 'Enter the profile\'s access key:')
|
|
108
|
-
const aws_secret_access_key = await chooseNonEmpty('aws_secret_access_key', 'Enter the profile\'s access secret key:')
|
|
109
|
-
const region = await chooseRegions()
|
|
110
|
-
const [profileErrors] = await createProfile({ name, aws_access_key_id, aws_secret_access_key, region })
|
|
111
|
-
if (profileErrors)
|
|
112
|
-
return printAWSerrors([new Error('Fail to create profile'), ...profileErrors])
|
|
113
|
-
} else {
|
|
114
|
-
console.log('')
|
|
115
|
-
console.log('┌─────────────────────────────────────────────────────────────────────┐'.cyan)
|
|
116
|
-
console.log('│ SSO Profile Setup Guide │'.cyan)
|
|
117
|
-
console.log('├─────────────────────────────────────────────────────────────────────┤'.cyan)
|
|
118
|
-
console.log('│ │'.cyan)
|
|
119
|
-
console.log('│ You\'re about to run \'aws configure sso\'. Here\'s what to expect: │'.cyan)
|
|
120
|
-
console.log('│ │'.cyan)
|
|
121
|
-
console.log('│ 1. SSO session name'.bold + ' │'.cyan)
|
|
122
|
-
console.log('│ Provide a name (e.g., "my-company-sso"). │'.cyan)
|
|
123
|
-
console.log('│ This enables token reuse across profiles and automatic refresh. │'.cyan)
|
|
124
|
-
console.log('│ Skipping this uses the legacy format (no auto-refresh). │'.cyan)
|
|
125
|
-
console.log('│ │'.cyan)
|
|
126
|
-
console.log('│ 2. SSO start URL'.bold + ' │'.cyan)
|
|
127
|
-
console.log('│ Your AWS SSO portal URL (e.g., https://my-co.awsapps.com/start) │'.cyan)
|
|
128
|
-
console.log('│ Ask your AWS administrator if you don\'t have it. │'.cyan)
|
|
129
|
-
console.log('│ │'.cyan)
|
|
130
|
-
console.log('│ 3. SSO region'.bold + ' │'.cyan)
|
|
131
|
-
console.log('│ The region where your SSO instance is configured. │'.cyan)
|
|
132
|
-
console.log('│ WARNING: This is NOT your deployment region. │'.yellow)
|
|
133
|
-
console.log('│ Using the wrong region causes "invalid_grant" errors. │'.yellow)
|
|
134
|
-
console.log('│ │'.cyan)
|
|
135
|
-
console.log('│ Docs: https://docs.aws.amazon.com/cli/latest/userguide/ │'.cyan)
|
|
136
|
-
console.log('│ cli-configure-sso.html │'.cyan)
|
|
137
|
-
console.log('│ │'.cyan)
|
|
138
|
-
console.log('└─────────────────────────────────────────────────────────────────────┘'.cyan)
|
|
139
|
-
console.log('')
|
|
140
|
-
|
|
141
|
-
const [ssoErrors] = await createSsoProfile(name)
|
|
142
|
-
if (ssoErrors)
|
|
143
|
-
return printAWSerrors([new Error(`Fail to create SSO profile ${name}`), ...ssoErrors])
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
console.log(`New profile ${name.bold} successfully created 🚀`.green)
|
|
147
|
-
|
|
148
|
-
if (makeItDefault === false)
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
if (makeItDefault === undefined) {
|
|
152
|
-
const { setAsDefault } = await inquirer.prompt([
|
|
153
|
-
{
|
|
154
|
-
type: 'confirm',
|
|
155
|
-
name: 'setAsDefault',
|
|
156
|
-
message: 'Do you wish to set this new profile as the default?',
|
|
157
|
-
}
|
|
158
|
-
])
|
|
159
|
-
|
|
160
|
-
if (!setAsDefault)
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const [listErrors2, profiles2] = await listProfiles()
|
|
165
|
-
if (listErrors2)
|
|
166
|
-
return printAWSerrors([new Error('Fail to list profiles'), ...listErrors2])
|
|
167
|
-
|
|
168
|
-
await setProfileToDefault(name, profiles2)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const switchCmd = async () => {
|
|
172
|
-
const [awsCliErrors, awsCliExists] = await awsCliV2Exists(true)
|
|
173
|
-
if (awsCliErrors)
|
|
174
|
-
return printAWSerrors(awsCliErrors, { noStack:true })
|
|
175
|
-
|
|
176
|
-
if (!awsCliExists) {
|
|
177
|
-
if (process.platform == 'darwin') {
|
|
178
|
-
console.log('AWS CLI seems to not be installed. Try installing it as follow\n'.red)
|
|
179
|
-
console.log('brew install awscli')
|
|
180
|
-
console.log('brew link --overwrite awscli')
|
|
181
|
-
return
|
|
182
|
-
} else if (process.platform == 'win32') {
|
|
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')
|
|
185
|
-
return
|
|
186
|
-
} else {
|
|
187
|
-
console.log('AWS CLI seems to not be installed. Try installing it as follow\n'.red)
|
|
188
|
-
console.log('curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"')
|
|
189
|
-
console.log('unzip awscli-bundle.zip')
|
|
190
|
-
console.log('sudo ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws')
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const [defaultProfileErrors, defaultProfile] = await getDefaultProfile()
|
|
196
|
-
if (defaultProfileErrors)
|
|
197
|
-
return printAWSerrors([new Error('Fail to get default profile'), ...defaultProfileErrors])
|
|
198
|
-
|
|
199
|
-
const defaultProfileName = defaultProfile && defaultProfile.profile ? defaultProfile.profile : ''
|
|
200
|
-
let defaultProfileExpired = false
|
|
201
|
-
if (defaultProfileName) {
|
|
202
|
-
const expiryDate = defaultProfile.expiry_date ? new Date(defaultProfile.expiry_date) : null
|
|
203
|
-
const messages = [EOL,`Current default profile: ${defaultProfileName.bold}`.cyan]
|
|
204
|
-
if (expiryDate) {
|
|
205
|
-
const exp = expiryDate.getTime()
|
|
206
|
-
const now = Date.now()
|
|
207
|
-
if (now > exp) {
|
|
208
|
-
defaultProfileExpired = true
|
|
209
|
-
messages.push(' WARNING: Expired'.yellow)
|
|
210
|
-
} else if (now+2*60*1000 > exp)
|
|
211
|
-
messages.push(' WARNING: Expires in less than 2 minutes'.yellow)
|
|
212
|
-
else {
|
|
213
|
-
const timeLeftInMin = ((exp-now)/60000).toFixed(2)
|
|
214
|
-
messages.push(` INFO: This profile expires in ${timeLeftInMin} minutes`.cyan)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
messages.push(EOL)
|
|
218
|
-
console.log(messages.join(''))
|
|
219
|
-
} else
|
|
220
|
-
console.log(`${EOL}Current default profile: ${'unknown'.bold} (pick one up in the list below and we'll remember next time)${EOL}`.cyan)
|
|
221
|
-
|
|
222
|
-
const [listErrors, profiles] = await listProfiles()
|
|
223
|
-
if (listErrors)
|
|
224
|
-
return printAWSerrors([new Error('Fail to list profiles'), ...listErrors])
|
|
225
|
-
|
|
226
|
-
const profileCount = profiles.length
|
|
227
|
-
|
|
228
|
-
if (!profileCount) {
|
|
229
|
-
const { create } = await inquirer.prompt([
|
|
230
|
-
{
|
|
231
|
-
type: 'confirm',
|
|
232
|
-
name: 'create',
|
|
233
|
-
message: 'There no profiles yet. Do you wish to create one now? ',
|
|
234
|
-
}
|
|
235
|
-
])
|
|
236
|
-
|
|
237
|
-
if (!create)
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
console.log('Ooh yeah ✨! I like your style. Let\'s do this!'.cyan)
|
|
241
|
-
await createNewProfile([], true)
|
|
242
|
-
} else {
|
|
243
|
-
const { friendlyName } = await inquirer.prompt([
|
|
244
|
-
{
|
|
245
|
-
type: 'list',
|
|
246
|
-
name: 'friendlyName',
|
|
247
|
-
message: `Choose one of the following ${profileCount} profiles:`,
|
|
248
|
-
pageSize:20,
|
|
249
|
-
default: 2,
|
|
250
|
-
choices: [
|
|
251
|
-
{ name:'More options', value:OPTIONS_KEY },
|
|
252
|
-
{ name: 'Abort', value:ABORT_KEY },
|
|
253
|
-
new inquirer.Separator(), ...profiles.map((p,i) => {
|
|
254
|
-
return {
|
|
255
|
-
name: `${i+1}. ${p.friendlyName}`,
|
|
256
|
-
value: p.friendlyName
|
|
257
|
-
}
|
|
258
|
-
})]
|
|
259
|
-
}
|
|
260
|
-
])
|
|
261
|
-
|
|
262
|
-
if (!friendlyName || friendlyName == ABORT_KEY)
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
if (friendlyName == OPTIONS_KEY) {
|
|
266
|
-
const { option } = await inquirer.prompt([
|
|
267
|
-
{
|
|
268
|
-
type: 'list',
|
|
269
|
-
name: 'option',
|
|
270
|
-
message: 'Options:',
|
|
271
|
-
pageSize:20,
|
|
272
|
-
choices: [
|
|
273
|
-
...(defaultProfileExpired ? [{ name: `Refresh default profile ${defaultProfileName.bold}` ,value:'refresh' }] : []),
|
|
274
|
-
{ name: 'Create profile', value:'new' },
|
|
275
|
-
{ name: 'Delete profiles', value:'delete' },
|
|
276
|
-
{ name: 'Abort', value:ABORT_KEY }
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
])
|
|
280
|
-
|
|
281
|
-
if (option == 'refresh')
|
|
282
|
-
await setProfileToDefault(defaultProfileName, profiles, `AWS profile ${defaultProfileName.bold} successfully refreshed.`)
|
|
283
|
-
else if (option == 'delete') {
|
|
284
|
-
const { delProfiles } = await inquirer.prompt([
|
|
285
|
-
{
|
|
286
|
-
type: 'checkbox',
|
|
287
|
-
name: 'delProfiles',
|
|
288
|
-
message: 'Select the profiles you wish to delete (SPACE to select, ENTER to confirm):',
|
|
289
|
-
pageSize: 20,
|
|
290
|
-
choices: profiles.map((p,i) => {
|
|
291
|
-
return {
|
|
292
|
-
name: `${i+1}. ${p.friendlyName}`,
|
|
293
|
-
value: p.name
|
|
294
|
-
}
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
])
|
|
298
|
-
|
|
299
|
-
if (!delProfiles.length)
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
const label = delProfiles.length > 1 ? 'profiles' : 'profile'
|
|
303
|
-
const labelText = delProfiles.length > 1 ? `those ${delProfiles.length} profiles` : 'this profile'
|
|
304
|
-
const { delConfirm } = await inquirer.prompt([
|
|
305
|
-
{
|
|
306
|
-
type: 'confirm',
|
|
307
|
-
name: 'delConfirm',
|
|
308
|
-
message: `Are you sure you want to delete ${labelText}? `,
|
|
309
|
-
}
|
|
310
|
-
])
|
|
311
|
-
|
|
312
|
-
if (!delConfirm)
|
|
313
|
-
return
|
|
314
|
-
|
|
315
|
-
if (defaultProfileName && delProfiles.some(p => p == defaultProfileName)) {
|
|
316
|
-
return printAWSerrors([new Error(`Fail to delete ${label}. Profile ${defaultProfileName.bold} is the current default. Set another profile as the default, then try deleting again.`)], { noStack:true })
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const [delErrors] = await deleteProfiles(delProfiles)
|
|
320
|
-
if (delErrors)
|
|
321
|
-
return printAWSerrors([new Error(`Fail to delete ${label}`), ...delErrors])
|
|
322
|
-
|
|
323
|
-
console.log(`AWS profile${delProfiles.length > 1 ? 's' : ''} successfully deleted.`.green)
|
|
324
|
-
} else if (option == 'new')
|
|
325
|
-
await createNewProfile(profiles)
|
|
326
|
-
else if (option == ABORT_KEY)
|
|
327
|
-
return
|
|
328
|
-
|
|
329
|
-
return
|
|
330
|
-
} else
|
|
331
|
-
await setProfileToDefault(friendlyName, profiles)
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const printExportHint = (profileName) => {
|
|
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
|
-
] : [
|
|
344
|
-
' To lock this profile to this terminal session, run: ',
|
|
345
|
-
'',
|
|
346
|
-
` export AWS_PROFILE=${profileName}`,
|
|
347
|
-
'',
|
|
348
|
-
' This prevents other terminals from affecting this one. '
|
|
349
|
-
]
|
|
350
|
-
const width = Math.max(...lines.map(l => l.length))
|
|
351
|
-
const pad = s => s + ' '.repeat(width - s.length)
|
|
352
|
-
console.log('')
|
|
353
|
-
console.log(('┌' + '─'.repeat(width) + '┐').cyan)
|
|
354
|
-
const boldStart = IS_WINDOWS ? 2 : 2
|
|
355
|
-
const boldEnd = IS_WINDOWS ? 3 : 2
|
|
356
|
-
for (let i = 0; i < lines.length; i++) {
|
|
357
|
-
const content = '│' + pad(lines[i]) + '│'
|
|
358
|
-
console.log(i >= boldStart && i <= boldEnd ? content.cyan.bold : content.cyan)
|
|
359
|
-
}
|
|
360
|
-
console.log(('└' + '─'.repeat(width) + '┘').cyan)
|
|
361
|
-
console.log('')
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const setProfileToDefault = async (profileName, profileList, successMsg) => {
|
|
365
|
-
const profile = profileList.find(p => p.friendlyName == profileName || p.name == profileName)
|
|
366
|
-
|
|
367
|
-
if (!profile)
|
|
368
|
-
return printAWSerrors([new Error(`Profile ${profileName.bold} was not found in ~/.aws/config. It may not have been created correctly. Try creating it again.`)], { noStack:true })
|
|
369
|
-
|
|
370
|
-
// Gets the AWS credentials for a specific profile. If that profile is an SSO profile, this function has a series of
|
|
371
|
-
// side-effects:
|
|
372
|
-
// - If the local SSO session stored under ~/.aws/sso/cache has expired, then it will redirect the user to the SSO portal and eventually refresh that ~/.aws/sso/cache.
|
|
373
|
-
// - If the local SSO creds stored under ~/.aws/cli/cache have expired (AWS_KEY, AWS_SECRET, AWS_SESSION), then they will be refreshed using the session stored under the ~/.aws/sso/cache.
|
|
374
|
-
const [credsErrors, creds] = await getCredentials(profile.name, profile.sso_start_url)
|
|
375
|
-
if (credsErrors)
|
|
376
|
-
return printAWSerrors([new Error(`Fail to get credentials for profile ${profile.name}`), ...credsErrors])
|
|
377
|
-
|
|
378
|
-
// console.log(profile)
|
|
379
|
-
// console.log(creds)
|
|
380
|
-
|
|
381
|
-
const [errors] = await updateDefaultProfile({
|
|
382
|
-
...creds,
|
|
383
|
-
profile: profile.name,
|
|
384
|
-
region: profile.region
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
if (errors)
|
|
388
|
-
return printAWSerrors([new Error('Fail to update the default profile'), ...errors])
|
|
389
|
-
|
|
390
|
-
console.log((successMsg || `AWS profile ${profile.name.bold} successfully set up as default.`).green)
|
|
391
|
-
printExportHint(profile.name)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// 1. Creates your first command. This example shows an 'order' command with a required argument
|
|
395
|
-
// called 'product' and an optional argument called 'option'.
|
|
396
|
-
program
|
|
397
|
-
.command('switch')
|
|
398
|
-
.description('Default behavior. List the existing configuration and help select one. Equivalent to `npx switch-cloud`') // Optional description
|
|
399
|
-
.action(switchCmd)
|
|
400
|
-
|
|
401
|
-
// 2. Deals with cases where no command is passed.
|
|
402
|
-
if (process.argv.length == 2)
|
|
403
|
-
process.argv.push('switch')
|
|
404
|
-
|
|
405
|
-
// 3. Starts the commander program
|
|
406
|
-
program.parse(process.argv)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
3
|
+
// Checked before anything else is loaded, so old Node versions get a clear message instead of a syntax error.
|
|
4
|
+
const [major, minor] = process.versions.node.split('.').map(Number)
|
|
5
|
+
if (major < 20 || (major == 20 && minor < 12)) {
|
|
6
|
+
console.error(`switch-profile needs Node.js 20.12 or later (you have ${process.versions.node}).\nUpdate Node.js (e.g. with nvm or fnm) and try again.`)
|
|
7
|
+
process.exit(1)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// A source checkout runs the sources; the published package runs the bundle.
|
|
11
|
+
const { existsSync } = require('fs')
|
|
12
|
+
const { join } = require('path')
|
|
13
|
+
const entry = existsSync(join(__dirname, 'src', 'cli.js')) && !process.env.SWITCH_PROFILE_DIST ? './src/cli.js' : './dist/cli.js'
|
|
14
|
+
require(entry).main(process.argv)
|
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switch-profile",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"main": "index.js",
|
|
5
|
-
"bin":
|
|
6
|
-
|
|
5
|
+
"bin": {
|
|
6
|
+
"switch-profile": "index.js"
|
|
7
|
+
},
|
|
8
|
+
"description": "Switch between AWS profiles from your terminal: fuzzy search, SSO login (device code over SSH), per-terminal AWS_PROFILE and tab-completion.",
|
|
7
9
|
"keywords": [
|
|
8
|
-
"aws-cli",
|
|
9
10
|
"aws",
|
|
11
|
+
"aws-cli",
|
|
12
|
+
"aws-sso",
|
|
13
|
+
"iam-identity-center",
|
|
10
14
|
"profile",
|
|
11
|
-
"sso
|
|
12
|
-
"
|
|
15
|
+
"sso",
|
|
16
|
+
"cli",
|
|
17
|
+
"device-code"
|
|
13
18
|
],
|
|
14
19
|
"repository": {
|
|
15
20
|
"type": "git",
|
|
@@ -20,34 +25,35 @@
|
|
|
20
25
|
},
|
|
21
26
|
"homepage": "https://github.com/nicolasdao/switch-profile#readme",
|
|
22
27
|
"scripts": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"rls": "standard-version --release-as",
|
|
27
|
-
"start": "TZ=UTC NODE_ENV=production node index.js",
|
|
28
|
+
"build": "esbuild src/cli.js --bundle --platform=node --target=node20.12 --format=cjs --minify-syntax --legal-comments=none --outfile=dist/cli.js",
|
|
29
|
+
"dev": "node index.js",
|
|
30
|
+
"lint": "eslint . --fix",
|
|
28
31
|
"test": "mocha --exit",
|
|
32
|
+
"test:dist": "npm run build && SWITCH_PROFILE_DIST=1 mocha --exit test/cli.js",
|
|
33
|
+
"prepublishOnly": "npm run lint && npm test && npm run test:dist",
|
|
29
34
|
"v": "node -e \"console.log(require('./package.json').version)\""
|
|
30
35
|
},
|
|
31
36
|
"author": "Nicolas Dao",
|
|
32
37
|
"license": "BSD-3-Clause",
|
|
33
38
|
"devDependencies": {
|
|
39
|
+
"@clack/prompts": "^1.8.1",
|
|
40
|
+
"@eslint/js": "^10.0.1",
|
|
34
41
|
"chai": "^4.1.2",
|
|
42
|
+
"commander": "^14.0.3",
|
|
43
|
+
"esbuild": "^0.28.2",
|
|
35
44
|
"eslint": "^10.0.0",
|
|
45
|
+
"fuzzysort": "^3.1.0",
|
|
46
|
+
"globals": "^17.13.0",
|
|
36
47
|
"mocha": "^8.2.1",
|
|
37
|
-
"
|
|
48
|
+
"uqr": "^0.1.3"
|
|
38
49
|
},
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"inquirer-autocomplete-prompt": "^1.4.0",
|
|
48
|
-
"mime-types": "^2.1.31",
|
|
49
|
-
"puffy": "^1.6.0",
|
|
50
|
-
"rimraf": "^3.0.2",
|
|
51
|
-
"tar-stream": "^2.2.0"
|
|
50
|
+
"files": [
|
|
51
|
+
"index.js",
|
|
52
|
+
"dist/",
|
|
53
|
+
"LICENSE",
|
|
54
|
+
"README.md"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20.12"
|
|
52
58
|
}
|
|
53
59
|
}
|
package/.eslintrc.json
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"commonjs": true,
|
|
5
|
-
"es6": true
|
|
6
|
-
},
|
|
7
|
-
"extends": "eslint:recommended",
|
|
8
|
-
"globals": {
|
|
9
|
-
"process": false,
|
|
10
|
-
"describe": false,
|
|
11
|
-
"it": false,
|
|
12
|
-
"xit": false,
|
|
13
|
-
"Buffer": false,
|
|
14
|
-
"__dirname": false
|
|
15
|
-
},
|
|
16
|
-
"parserOptions": {
|
|
17
|
-
"sourceType": "module",
|
|
18
|
-
"ecmaVersion": 2018
|
|
19
|
-
},
|
|
20
|
-
"rules": {
|
|
21
|
-
"no-console":0,
|
|
22
|
-
"indent": [
|
|
23
|
-
"error",
|
|
24
|
-
"tab"
|
|
25
|
-
],
|
|
26
|
-
"linebreak-style": [
|
|
27
|
-
"error",
|
|
28
|
-
"unix"
|
|
29
|
-
],
|
|
30
|
-
"quotes": [
|
|
31
|
-
"error",
|
|
32
|
-
"single"
|
|
33
|
-
],
|
|
34
|
-
"semi": [
|
|
35
|
-
"error",
|
|
36
|
-
"never"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
## [1.0.0](https://github.com/nicolasdao/switch-profile/compare/v0.2.3...v1.0.0) (2026-03-21)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
### Features
|
|
16
|
-
|
|
17
|
-
* Display export AWS_PROFILE hint after profile switch for per-terminal isolation ([7dd09c8](https://github.com/nicolasdao/switch-profile/commit/7dd09c882708fcafd66fefc6139cd97a13d9b2fb))
|
|
18
|
-
|
|
19
|
-
### [0.2.3](https://github.com/nicolasdao/switch-profile/compare/v0.2.2...v0.2.3) (2026-02-11)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
### Bug Fixes
|
|
23
|
-
|
|
24
|
-
* Add support for AWS CLI v2 SSO session format and improve profile deletion UX ([4eb7366](https://github.com/nicolasdao/switch-profile/commit/4eb7366d6ed1f50f024128d94da213515a4ff9da))
|
|
25
|
-
|
|
26
|
-
### [0.2.2](https://github.com/nicolasdao/switch-profile/compare/v0.2.1...v0.2.2) (2026-02-10)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### Bug Fixes
|
|
30
|
-
|
|
31
|
-
* SSO profile credentials written as undefined by falling back to aws configure export-credentials ([7067eff](https://github.com/nicolasdao/switch-profile/commit/7067eff15d376a3dd902ee4a03f5db7cfa539918))
|
|
32
|
-
|
|
33
|
-
### [0.2.1](https://github.com/nicolasdao/switch-profile/compare/v0.2.0...v0.2.1) (2026-02-08)
|
|
34
|
-
|
|
35
|
-
## [0.2.0](https://github.com/nicolasdao/switch-profile/compare/v0.1.2...v0.2.0) (2026-02-08)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### Bug Fixes
|
|
39
|
-
|
|
40
|
-
* handle SSO profile creation failure and improve SSO setup UX ([b0ee837](https://github.com/nicolasdao/switch-profile/commit/b0ee83791bfbc1208cc5fb6b6f2850d4cbd160c9))
|
|
41
|
-
* vulnerabilities ([3fa2575](https://github.com/nicolasdao/switch-profile/commit/3fa2575156f3e07a6de9cd077f3f8f75f9bceafa))
|
|
42
|
-
|
|
43
|
-
### [0.1.2](https://github.com/nicolasdao/switch-profile/compare/v0.1.1...v0.1.2) (2022-05-14)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
### Bug Fixes
|
|
47
|
-
|
|
48
|
-
* Multiple SSO profiles are not supported. It only uses one. ([b9d9d03](https://github.com/nicolasdao/switch-profile/commit/b9d9d03b4edfa048cc157376222378513138e257))
|
|
49
|
-
|
|
50
|
-
### [0.1.1](https://github.com/nicolasdao/switch-profile/compare/v0.1.0...v0.1.1) (2021-08-03)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### Bug Fixes
|
|
54
|
-
|
|
55
|
-
* Edge cases where the SSO session expires and a forde login is required ([67f3667](https://github.com/nicolasdao/switch-profile/commit/67f36678f29f2ab5f02c4d4630eb1c7c41ccebbf))
|
|
56
|
-
|
|
57
|
-
## [0.1.0](https://github.com/nicolasdao/switch-profile/compare/v0.0.9...v0.1.0) (2021-08-03)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### Bug Fixes
|
|
61
|
-
|
|
62
|
-
* Switching to an SSO profile adds an incorrect 'sso_' prefix to the default region in ~/.aws/config file ([384c17e](https://github.com/nicolasdao/switch-profile/commit/384c17e26b368754a5bfdb5ca47fd9996181f3b7))
|
|
63
|
-
|
|
64
|
-
### [0.0.9](https://github.com/nicolasdao/switch-profile/compare/v0.0.8...v0.0.9) (2021-07-19)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
### Bug Fixes
|
|
68
|
-
|
|
69
|
-
* Switching does not update the region in the ~/.aws/config ([14d6724](https://github.com/nicolasdao/switch-profile/commit/14d672429d81e3c421273d1e090fa4d444a09b5b))
|
|
70
|
-
|
|
71
|
-
### [0.0.8](https://github.com/nicolasdao/switch-profile/compare/v0.0.7...v0.0.8) (2021-07-19)
|
|
72
|
-
|
|
73
|
-
### [0.0.7](https://github.com/nicolasdao/switch-profile/compare/v0.0.6...v0.0.7) (2021-07-18)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
### Features
|
|
77
|
-
|
|
78
|
-
* Add ability to set up the new profile as default ([c667c3c](https://github.com/nicolasdao/switch-profile/commit/c667c3c29db2b43c86c083da36b67d6c27c737f8))
|
|
79
|
-
|
|
80
|
-
### [0.0.6](https://github.com/nicolasdao/switch-profile/compare/v0.0.5...v0.0.6) (2021-07-18)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### Features
|
|
84
|
-
|
|
85
|
-
* Add support for congrats message ([a8a14dd](https://github.com/nicolasdao/switch-profile/commit/a8a14ddb4536c3fe6a153716f605796e436626bb))
|
|
86
|
-
|
|
87
|
-
### [0.0.5](https://github.com/nicolasdao/switch-profile/compare/v0.0.1...v0.0.5) (2021-07-18)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### Features
|
|
91
|
-
|
|
92
|
-
* Add support for creating and deleting profiles ([cdbb115](https://github.com/nicolasdao/switch-profile/commit/cdbb115386536ce1d38d4de13d71b0e8dc1e4c68))
|
|
93
|
-
|
|
94
|
-
### [0.0.4](https://github.com/nicolasdao/switch-profile/compare/v0.0.1...v0.0.4) (2021-07-18)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### Features
|
|
98
|
-
|
|
99
|
-
* Add support for creating and deleting profiles ([cdbb115](https://github.com/nicolasdao/switch-profile/commit/cdbb115386536ce1d38d4de13d71b0e8dc1e4c68))
|
|
100
|
-
|
|
101
|
-
### [0.0.3](https://github.com/nicolasdao/switch-profile/compare/v0.0.1...v0.0.3) (2021-07-18)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
### Features
|
|
105
|
-
|
|
106
|
-
* Add support for creating and deleting profiles ([cdbb115](https://github.com/nicolasdao/switch-profile/commit/cdbb115386536ce1d38d4de13d71b0e8dc1e4c68))
|
|
107
|
-
|
|
108
|
-
### [0.0.2](https://github.com/nicolasdao/switch-profile/compare/v0.0.1...v0.0.2) (2021-07-18)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
### Features
|
|
112
|
-
|
|
113
|
-
* Add support for creating and deleting profiles ([cdbb115](https://github.com/nicolasdao/switch-profile/commit/cdbb115386536ce1d38d4de13d71b0e8dc1e4c68))
|
|
114
|
-
|
|
115
|
-
### [0.0.1](https://github.com/nicolasdao/switch-profile/compare/v0.0.4...v0.0.1) (2021-07-18)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
### Features
|
|
119
|
-
|
|
120
|
-
* Add support for choosing a default profile ([73d936f](https://github.com/nicolasdao/switch-profile/commit/73d936fa95d5240dbbeb12e3f640e7436f1cc76f))
|