az2aws 1.4.0 → 1.6.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.0](https://github.com/kuma0128/az2aws/compare/v1.5.0...v1.6.0) (2026-03-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * add incognito mode option ([#97](https://github.com/kuma0128/az2aws/issues/97)) ([01d5f98](https://github.com/kuma0128/az2aws/commit/01d5f98897795eca9cef1b372284ac0a8c24b840))
9
+ * notify new available version ([#137](https://github.com/kuma0128/az2aws/issues/137)) ([c204bae](https://github.com/kuma0128/az2aws/commit/c204bae4ec57ea0b63d01d5d6c963951f8341318))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * update dependencies and add Dependabot config ([#120](https://github.com/kuma0128/az2aws/issues/120)) ([c500dd0](https://github.com/kuma0128/az2aws/commit/c500dd06fd4c799bde467d069ec03ac244967542))
15
+ * update workflows to use pnpm cache and remove standalone option ([#119](https://github.com/kuma0128/az2aws/issues/119)) ([8d759c3](https://github.com/kuma0128/az2aws/commit/8d759c34d8d04211325cfba56692e2cbc55cfe51))
16
+
17
+ ## [1.5.0](https://github.com/kuma0128/az2aws/compare/v1.4.0...v1.5.0) (2026-02-12)
18
+
19
+
20
+ ### Features
21
+
22
+ * optimize Docker image build ([#105](https://github.com/kuma0128/az2aws/issues/105)) ([fc7f491](https://github.com/kuma0128/az2aws/commit/fc7f491a3d3029f3dbca750b984cbb3b5ea4bd3a))
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * handle TargetCloseError from incompatible browser profile ([#117](https://github.com/kuma0128/az2aws/issues/117)) ([d5c1752](https://github.com/kuma0128/az2aws/commit/d5c17521cd179891a13c8285d451ad353b47bc71))
28
+
3
29
  ## [1.4.0](https://github.com/kuma0128/az2aws/compare/v1.3.0...v1.4.0) (2026-02-12)
4
30
 
5
31
 
package/README.md CHANGED
@@ -100,6 +100,7 @@ https://snapcraft.io/az2aws
100
100
  | `--enable-chrome-seamless-sso` | Enable Azure AD Seamless SSO |
101
101
  | `--no-disable-extensions` | Keep browser extensions enabled |
102
102
  | `--disable-gpu` | Disable GPU acceleration |
103
+ | `--incognito` | Open the login flow in an incognito browser context |
103
104
  | `--version (-v)` | Show version number |
104
105
 
105
106
  ## Usage
@@ -132,6 +133,10 @@ Enable "Stay logged in" during configuration to use `--no-prompt` without storin
132
133
  az2aws --no-prompt
133
134
  az2aws --profile foo --no-prompt
134
135
 
136
+ `--incognito` starts the login flow in a fresh incognito browser context. This
137
+ helps avoid reusing an existing browser session, and it overrides any saved
138
+ "Stay logged in" browser state for that run.
139
+
135
140
  #### Environment Variables
136
141
 
137
142
  You can set defaults via environment variables (use with `--no-prompt`):
@@ -176,6 +181,7 @@ Example:
176
181
  az2aws # Default profile
177
182
  az2aws --profile foo # Named profile
178
183
  az2aws --mode gui # Use browser UI (more reliable)
184
+ az2aws --mode gui --incognito # Open a fresh incognito login window
179
185
 
180
186
  You'll be prompted for username, password, and MFA if required. After login, use AWS CLI/SDKs as usual.
181
187
 
@@ -186,6 +192,16 @@ You'll be prompted for username, password, and MFA if required. After login, use
186
192
 
187
193
  #### Troubleshooting
188
194
 
195
+ If you see `TargetCloseError: Protocol error (Target.setAutoAttach): Target closed`,
196
+ the browser profile may be incompatible with the bundled Chromium version
197
+ (e.g., after upgrading or downgrading az2aws). When using the default
198
+ managed profile (`~/.aws/chromium`) with "Stay logged in" enabled, az2aws
199
+ will automatically reset the profile and retry. If you have set
200
+ `BROWSER_USER_DATA_DIR` to point to an existing Chrome profile, az2aws
201
+ will **not** modify that directory — you will need to resolve the
202
+ incompatibility manually (e.g., update az2aws, or use a different
203
+ `BROWSER_USER_DATA_DIR`).
204
+
189
205
  If you see device compliance errors (e.g., "Device UnSecured Or Non-Compliant"),
190
206
  Try:
191
207
  `--mode gui` and use your system Chrome via `BROWSER_CHROME_BIN`.
@@ -0,0 +1,36 @@
1
+ const tseslint = require("@typescript-eslint/eslint-plugin");
2
+ const prettier = require("eslint-config-prettier");
3
+ const sourceFiles = ["src/**/*.ts", "typings/**/*.ts"];
4
+
5
+ module.exports = [
6
+ {
7
+ ignores: [
8
+ "**/*.test.ts",
9
+ "vitest.config.ts",
10
+ "eslint.config.cjs",
11
+ "lib/**",
12
+ "coverage/**",
13
+ ],
14
+ },
15
+ {
16
+ linterOptions: {
17
+ reportUnusedDisableDirectives: "off",
18
+ },
19
+ },
20
+ ...tseslint.configs["flat/recommended-type-checked"].map((config) => ({
21
+ ...config,
22
+ files: sourceFiles,
23
+ })),
24
+ {
25
+ files: sourceFiles,
26
+ languageOptions: {
27
+ parserOptions: {
28
+ project: "./tsconfig.json",
29
+ tsconfigRootDir: __dirname,
30
+ ecmaVersion: 2018,
31
+ sourceType: "module",
32
+ },
33
+ },
34
+ },
35
+ prettier,
36
+ ];
package/lib/awsConfig.js CHANGED
@@ -7,7 +7,7 @@ exports.awsConfig = void 0;
7
7
  const ini_1 = __importDefault(require("ini"));
8
8
  const debug_1 = __importDefault(require("debug"));
9
9
  const paths_1 = require("./paths");
10
- const mkdirp_1 = __importDefault(require("mkdirp"));
10
+ const mkdirp_1 = require("mkdirp");
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const util_1 = __importDefault(require("util"));
13
13
  const debug = (0, debug_1.default)("az2aws");
@@ -101,7 +101,7 @@ exports.awsConfig = {
101
101
  debug(`Stringifying ${type} INI data`);
102
102
  const text = ini_1.default.stringify(data);
103
103
  debug(`Creating AWS config directory '${paths_1.paths.awsDir}' if not exists.`);
104
- await (0, mkdirp_1.default)(paths_1.paths.awsDir);
104
+ await (0, mkdirp_1.mkdirp)(paths_1.paths.awsDir);
105
105
  debug(`Writing '${type}' INI to file '${paths_1.paths[type]}'`);
106
106
  await writeFile(paths_1.paths[type], text);
107
107
  },
@@ -11,23 +11,27 @@ async function configureProfileAsync(profileName) {
11
11
  const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
12
12
  const questions = [
13
13
  {
14
+ type: "input",
14
15
  name: "tenantId",
15
16
  message: "Azure Tenant ID:",
16
- validate: (input) => !!input,
17
+ validate: (input) => input.trim().length > 0,
17
18
  default: profile && profile.azure_tenant_id,
18
19
  },
19
20
  {
21
+ type: "input",
20
22
  name: "appIdUri",
21
23
  message: "Azure App ID URI:",
22
- validate: (input) => !!input,
24
+ validate: (input) => input.trim().length > 0,
23
25
  default: profile && profile.azure_app_id_uri,
24
26
  },
25
27
  {
28
+ type: "input",
26
29
  name: "username",
27
30
  message: "Default Username:",
28
31
  default: profile && profile.azure_default_username,
29
32
  },
30
33
  {
34
+ type: "input",
31
35
  name: "rememberMe",
32
36
  message: "Stay logged in: skip authentication while refreshing aws credentials (true|false)",
33
37
  default: (profile &&
@@ -41,22 +45,25 @@ async function configureProfileAsync(profileName) {
41
45
  },
42
46
  },
43
47
  {
48
+ type: "input",
44
49
  name: "defaultRoleArn",
45
50
  message: "Default Role ARN (if multiple):",
46
51
  default: profile && profile.azure_default_role_arn,
47
52
  },
48
53
  {
54
+ type: "input",
49
55
  name: "defaultDurationHours",
50
56
  message: "Default Session Duration Hours (up to 12):",
51
57
  default: (profile && profile.azure_default_duration_hours) || 1,
52
58
  validate: (input) => {
53
- input = Number(input);
54
- if (input > 0 && input <= 12)
59
+ const num = Number(input);
60
+ if (num > 0 && num <= 12)
55
61
  return true;
56
62
  return "Duration hours must be between 1 and 12";
57
63
  },
58
64
  },
59
65
  {
66
+ type: "input",
60
67
  name: "region",
61
68
  message: "AWS Region:",
62
69
  default: profile && profile.region,
package/lib/index.js CHANGED
@@ -6,7 +6,8 @@ process.on("SIGTERM", () => process.exit(1));
6
6
  const commander_1 = require("commander");
7
7
  const configureProfileAsync_1 = require("./configureProfileAsync");
8
8
  const login_1 = require("./login");
9
- // eslint-disable-next-line @typescript-eslint/no-var-requires
9
+ const updateNotifier_1 = require("./updateNotifier");
10
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
11
  const { version } = require("../package.json");
11
12
  const program = new commander_1.Command();
12
13
  program
@@ -23,6 +24,7 @@ program
23
24
  .option("--enable-chrome-seamless-sso", "Enable Chromium's pass-through authentication with Azure Active Directory Seamless Single Sign-On")
24
25
  .option("--no-disable-extensions", "Tell Puppeteer not to pass the --disable-extensions flag to Chromium")
25
26
  .option("--disable-gpu", "Tell Puppeteer to pass the --disable-gpu flag to Chromium")
27
+ .option("--incognito", "Launch Chromium in incognito mode")
26
28
  .parse(process.argv);
27
29
  const options = program.opts();
28
30
  const profileName = options.profile ||
@@ -37,22 +39,42 @@ const enableChromeSeamlessSso = !!options.enableChromeSeamlessSso;
37
39
  const forceRefresh = !!options.forceRefresh;
38
40
  const noDisableExtensions = !options.disableExtensions;
39
41
  const disableGpu = !!options.disableGpu;
40
- Promise.resolve()
41
- .then(() => {
42
- if (options.allProfiles) {
43
- return login_1.login.loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu);
42
+ const incognito = !!options.incognito;
43
+ // Start the update lookup immediately, but only print after the main flow ends.
44
+ const updateCheckPromise = (0, updateNotifier_1.checkForUpdate)(version, {
45
+ useColor: process.stderr.isTTY && !process.env.NO_COLOR,
46
+ });
47
+ async function runAsync() {
48
+ let exitCode = 0;
49
+ try {
50
+ if (options.allProfiles) {
51
+ await login_1.login.loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito);
52
+ }
53
+ else if (options.configure) {
54
+ await (0, configureProfileAsync_1.configureProfileAsync)(profileName);
55
+ }
56
+ else {
57
+ await login_1.login.loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito);
58
+ }
44
59
  }
45
- if (options.configure)
46
- return (0, configureProfileAsync_1.configureProfileAsync)(profileName);
47
- return login_1.login.loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu);
48
- })
49
- .catch((err) => {
50
- if (err.name === "CLIError") {
51
- console.error(err.message);
52
- process.exit(2);
60
+ catch (err) {
61
+ if (err instanceof Error && err.name === "CLIError") {
62
+ console.error(err.message);
63
+ exitCode = 2;
64
+ }
65
+ else {
66
+ console.error(err);
67
+ exitCode = 1;
68
+ }
53
69
  }
54
- else {
55
- console.error(err);
56
- process.exit(1);
70
+ if (exitCode === 0) {
71
+ const updateMessage = await updateCheckPromise;
72
+ if (updateMessage) {
73
+ process.stderr.write(updateMessage);
74
+ }
57
75
  }
58
- });
76
+ if (exitCode !== 0) {
77
+ process.exit(exitCode);
78
+ }
79
+ }
80
+ void runAsync();
package/lib/login.js CHANGED
@@ -15,9 +15,8 @@ const querystring_1 = __importDefault(require("querystring"));
15
15
  const debug_1 = __importDefault(require("debug"));
16
16
  const CLIError_1 = require("./CLIError");
17
17
  const awsConfig_1 = require("./awsConfig");
18
- const https_proxy_agent_1 = require("https-proxy-agent");
19
18
  const paths_1 = require("./paths");
20
- const mkdirp_1 = __importDefault(require("mkdirp"));
19
+ const mkdirp_1 = require("mkdirp");
21
20
  const promises_1 = __importDefault(require("fs/promises"));
22
21
  const https_1 = require("https");
23
22
  const node_http_handler_1 = require("@smithy/node-http-handler");
@@ -32,12 +31,20 @@ const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
32
31
  const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
33
32
  const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
34
33
  const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
34
+ // Keep the runtime import as native `import()` so CommonJS output can load
35
+ // the ESM-only https-proxy-agent package.
36
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
37
+ const importHttpsProxyAgent = Function('return import("https-proxy-agent")');
35
38
  const getProxyUrl = () => process.env.https_proxy ||
36
39
  process.env.HTTPS_PROXY ||
37
40
  process.env.http_proxy ||
38
41
  process.env.HTTP_PROXY;
39
42
  exports.login = {
40
- async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu) {
43
+ async _createHttpsProxyAgentAsync(proxyUrl, proxyOptions) {
44
+ const { HttpsProxyAgent } = await importHttpsProxyAgent();
45
+ return new HttpsProxyAgent(proxyUrl, proxyOptions);
46
+ },
47
+ async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false) {
41
48
  let headless, cliProxy;
42
49
  if (mode === "cli") {
43
50
  headless = true;
@@ -70,16 +77,13 @@ exports.login = {
70
77
  }
71
78
  console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
72
79
  const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
73
- const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu);
80
+ const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu, incognito);
74
81
  const roles = this._parseRolesFromSamlResponse(samlResponse);
75
82
  const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, noPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours);
76
83
  await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region);
77
84
  },
78
- async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu) {
85
+ async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito = false) {
79
86
  const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
80
- if (!profiles) {
81
- return;
82
- }
83
87
  for (const profile of profiles) {
84
88
  debug(`Check if profile ${profile} is expired or is about to expire`);
85
89
  if (!forceRefresh &&
@@ -88,7 +92,7 @@ exports.login = {
88
92
  continue;
89
93
  }
90
94
  debug(`Run login for profile: ${profile}`);
91
- await this.loginAsync(profile, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu);
95
+ await this.loginAsync(profile, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito);
92
96
  }
93
97
  },
94
98
  // Gather data from environment variables
@@ -182,16 +186,20 @@ exports.login = {
182
186
  * @param {bool} [rememberMe] - Enable remembering the session
183
187
  * @param {bool} [noDisableExtensions] - True to prevent Puppeteer from disabling Chromium extensions
184
188
  * @param {bool} [disableGpu] - Disables GPU Acceleration
189
+ * @param {bool} [incognito] - Launch the login flow in an incognito browser context
185
190
  * @returns {Promise.<string>} The SAML response.
186
191
  * @private
187
192
  */
188
- async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu) {
193
+ async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false) {
189
194
  debug("Loading login page in Chrome");
190
195
  let browser;
196
+ const useRememberMe = rememberMe && !incognito;
191
197
  try {
192
198
  const args = headless
193
199
  ? []
194
- : [`--app=${url}`, `--window-size=${WIDTH},${HEIGHT}`];
200
+ : incognito
201
+ ? [`--window-size=${WIDTH},${HEIGHT}`]
202
+ : [`--app=${url}`, `--window-size=${WIDTH},${HEIGHT}`];
195
203
  if (disableSandbox)
196
204
  args.push("--no-sandbox");
197
205
  if (enableChromeNetworkService)
@@ -199,12 +207,12 @@ exports.login = {
199
207
  if (enableChromeSeamlessSso)
200
208
  args.push(`--auth-server-whitelist=${AZURE_AD_SSO}`, `--auth-negotiate-delegate-whitelist=${AZURE_AD_SSO}`);
201
209
  debug(`rememberMe value: ${rememberMe} (type: ${typeof rememberMe})`);
202
- if (rememberMe) {
210
+ if (useRememberMe) {
203
211
  if (paths_1.paths.userDataDir) {
204
212
  args.push(`--user-data-dir=${paths_1.paths.userDataDir}`);
205
213
  }
206
214
  else {
207
- await (0, mkdirp_1.default)(paths_1.paths.chromium);
215
+ await (0, mkdirp_1.mkdirp)(paths_1.paths.chromium);
208
216
  args.push(`--user-data-dir=${paths_1.paths.chromium}`);
209
217
  }
210
218
  // --profile-directory requires --user-data-dir to work properly
@@ -212,6 +220,9 @@ exports.login = {
212
220
  args.push(`--profile-directory=${paths_1.paths.profileDir}`);
213
221
  }
214
222
  }
223
+ if (incognito && rememberMe) {
224
+ console.warn("WARNING: Incognito mode overrides 'Stay logged in' and ignores saved Chrome profiles.");
225
+ }
215
226
  const proxyUrl = getProxyUrl();
216
227
  if (proxyUrl) {
217
228
  args.push(`--proxy-server=${proxyUrl}`);
@@ -235,13 +246,13 @@ exports.login = {
235
246
  }
236
247
  catch (e) {
237
248
  if (e instanceof Error &&
238
- e.constructor.name === "TargetCloseError" &&
239
- rememberMe) {
240
- const userDataDir = paths_1.paths.userDataDir || paths_1.paths.chromium;
241
- debug(`Browser launch failed with TargetCloseError. Resetting profile at ${userDataDir}`);
249
+ e.name === "TargetCloseError" &&
250
+ useRememberMe &&
251
+ !paths_1.paths.userDataDir) {
252
+ debug(`Browser launch failed with TargetCloseError. Resetting profile at ${paths_1.paths.chromium}`);
242
253
  console.warn("Browser profile appears incompatible. Resetting profile data and retrying...");
243
- await promises_1.default.rm(userDataDir, { recursive: true, force: true });
244
- await (0, mkdirp_1.default)(userDataDir);
254
+ await promises_1.default.rm(paths_1.paths.chromium, { recursive: true, force: true });
255
+ await (0, mkdirp_1.mkdirp)(paths_1.paths.chromium);
245
256
  browser = await puppeteer_1.default.launch(launchParams);
246
257
  }
247
258
  else {
@@ -250,8 +261,20 @@ exports.login = {
250
261
  }
251
262
  // Wait for a bit as sometimes the browser isn't ready.
252
263
  await bluebird_1.default.delay(200);
253
- const pages = await browser.pages();
254
- const page = pages[0];
264
+ let page;
265
+ if (incognito) {
266
+ const existingPages = await browser.pages();
267
+ const context = await browser.createBrowserContext();
268
+ page = await context.newPage();
269
+ await Promise.all(existingPages.map((existingPage) => existingPage.close()));
270
+ if (!headless) {
271
+ await page.bringToFront();
272
+ }
273
+ }
274
+ else {
275
+ const pages = await browser.pages();
276
+ page = pages[0];
277
+ }
255
278
  await page.setExtraHTTPHeaders({
256
279
  "Accept-Language": "en",
257
280
  });
@@ -290,7 +313,7 @@ exports.login = {
290
313
  debug("Enabling request interception");
291
314
  await page.setRequestInterception(true);
292
315
  try {
293
- if (headless || (!headless && cliProxy)) {
316
+ if (incognito || headless || cliProxy) {
294
317
  debug("Going to login page");
295
318
  await page.goto(url, { waitUntil: "domcontentloaded" });
296
319
  }
@@ -332,7 +355,7 @@ exports.login = {
332
355
  debug(`Found state: ${state.name}`);
333
356
  await Promise.race([
334
357
  samlResponsePromise,
335
- state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, rememberMe),
358
+ state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe),
336
359
  ]);
337
360
  debug(`Finished state: ${state.name}`);
338
361
  break;
@@ -422,10 +445,13 @@ exports.login = {
422
445
  let durationHours = 1;
423
446
  if (defaultDurationHours) {
424
447
  const parsedDuration = parseInt(defaultDurationHours, 10);
425
- if (!Number.isNaN(parsedDuration) && parsedDuration > 0) {
448
+ if (!Number.isNaN(parsedDuration) &&
449
+ parsedDuration > 0 &&
450
+ parsedDuration <= 12) {
426
451
  durationHours = parsedDuration;
427
452
  }
428
453
  }
454
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
429
455
  const questions = [];
430
456
  if (roles.length === 0) {
431
457
  throw new CLIError_1.CLIError("No roles found in SAML response.");
@@ -450,7 +476,7 @@ exports.login = {
450
476
  questions.push({
451
477
  name: "role",
452
478
  message: "Role:",
453
- type: "list",
479
+ type: "select",
454
480
  choices: roles.map((r) => r.roleArn).sort(),
455
481
  default: defaultRoleArn,
456
482
  });
@@ -471,8 +497,8 @@ exports.login = {
471
497
  type: "input",
472
498
  default: defaultDurationHours || 1,
473
499
  validate: (input) => {
474
- input = Number(input);
475
- if (input > 0 && input <= 12)
500
+ const num = Number(input);
501
+ if (num > 0 && num <= 12)
476
502
  return true;
477
503
  return "Duration hours must be between 1 and 12";
478
504
  },
@@ -519,7 +545,7 @@ exports.login = {
519
545
  stsOptions = {
520
546
  ...stsOptions,
521
547
  requestHandler: new node_http_handler_1.NodeHttpHandler({
522
- httpsAgent: new https_proxy_agent_1.HttpsProxyAgent(proxyUrl, proxyOptions),
548
+ httpsAgent: await this._createHttpsProxyAgentAsync(proxyUrl, proxyOptions),
523
549
  }),
524
550
  };
525
551
  }
@@ -37,8 +37,10 @@ exports.states = [
37
37
  }
38
38
  else {
39
39
  debug("Prompting user for username");
40
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
40
41
  ({ username } = await inquirer_1.default.prompt([
41
42
  {
43
+ type: "input",
42
44
  name: "username",
43
45
  message: "Username:",
44
46
  default: defaultUsername,
@@ -115,7 +117,7 @@ exports.states = [
115
117
  {
116
118
  name: "account",
117
119
  message: "Account:",
118
- type: "list",
120
+ type: "select",
119
121
  choices: accounts.map((a) => a.message),
120
122
  default: aadTileMessage,
121
123
  },
@@ -187,6 +189,7 @@ exports.states = [
187
189
  }
188
190
  else {
189
191
  debug("Prompting user for password");
192
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
190
193
  ({ password } = await inquirer_1.default.prompt([
191
194
  {
192
195
  name: "password",
@@ -272,8 +275,10 @@ exports.states = [
272
275
  (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
273
276
  console.log(descriptionMessage);
274
277
  }
278
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
275
279
  const { verificationCode } = await inquirer_1.default.prompt([
276
280
  {
281
+ type: "input",
277
282
  name: "verificationCode",
278
283
  message: "Verification Code:",
279
284
  },
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkForUpdate = checkForUpdate;
7
+ const https_1 = __importDefault(require("https"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const PACKAGE_NAME = "az2aws";
12
+ const CACHE_TTL_MS = 1000 * 60 * 60 * 24; // 24 hours
13
+ const FAKE_LATEST_VERSION_ENV = "AZ2AWS_FAKE_LATEST_VERSION";
14
+ const ANSI_YELLOW = "\u001b[33m";
15
+ const ANSI_RESET = "\u001b[0m";
16
+ function getCachePath() {
17
+ return path_1.default.join(os_1.default.homedir(), ".config", "az2aws", "update-check.json");
18
+ }
19
+ function readCache() {
20
+ try {
21
+ const data = fs_1.default.readFileSync(getCachePath(), "utf-8");
22
+ const cache = JSON.parse(data);
23
+ if (Date.now() - cache.checkedAt < CACHE_TTL_MS) {
24
+ return cache;
25
+ }
26
+ }
27
+ catch (_a) {
28
+ // Cache doesn't exist or is invalid
29
+ }
30
+ return null;
31
+ }
32
+ function writeCache(latestVersion) {
33
+ try {
34
+ const cachePath = getCachePath();
35
+ const dir = path_1.default.dirname(cachePath);
36
+ fs_1.default.mkdirSync(dir, { recursive: true });
37
+ fs_1.default.writeFileSync(cachePath, JSON.stringify({ latestVersion, checkedAt: Date.now() }));
38
+ }
39
+ catch (_a) {
40
+ // Ignore cache write failures
41
+ }
42
+ }
43
+ function compareVersions(current, latest) {
44
+ const currentParts = current.split(".").map(Number);
45
+ const latestParts = latest.split(".").map(Number);
46
+ for (let i = 0; i < 3; i++) {
47
+ const c = currentParts[i] || 0;
48
+ const l = latestParts[i] || 0;
49
+ if (l > c)
50
+ return 1;
51
+ if (l < c)
52
+ return -1;
53
+ }
54
+ return 0;
55
+ }
56
+ function fetchLatestVersion() {
57
+ return new Promise((resolve, reject) => {
58
+ const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
59
+ const req = https_1.default.get(url, { timeout: 3000 }, (res) => {
60
+ if (res.statusCode !== 200) {
61
+ res.resume();
62
+ reject(new Error(`Unexpected npm registry status code: ${res.statusCode}`));
63
+ return;
64
+ }
65
+ let body = "";
66
+ res.on("data", (chunk) => {
67
+ body += chunk.toString();
68
+ });
69
+ res.on("end", () => {
70
+ try {
71
+ const data = JSON.parse(body);
72
+ resolve(data.version);
73
+ }
74
+ catch (_a) {
75
+ reject(new Error("Failed to parse npm registry response"));
76
+ }
77
+ });
78
+ });
79
+ req.on("error", reject);
80
+ req.on("timeout", () => {
81
+ req.destroy();
82
+ reject(new Error("Request timed out"));
83
+ });
84
+ });
85
+ }
86
+ function detectInstallMethod(env, executablePath) {
87
+ if (env.SNAP) {
88
+ return "snap";
89
+ }
90
+ if (executablePath === null || executablePath === void 0 ? void 0 : executablePath.includes(`${path_1.default.sep}mise${path_1.default.sep}`)) {
91
+ return "mise";
92
+ }
93
+ return "unknown";
94
+ }
95
+ function getForcedLatestVersion(env) {
96
+ var _a;
97
+ const forcedLatestVersion = (_a = env[FAKE_LATEST_VERSION_ENV]) === null || _a === void 0 ? void 0 : _a.trim();
98
+ return forcedLatestVersion ? forcedLatestVersion : null;
99
+ }
100
+ function getUpdateInstructions(installMethod) {
101
+ switch (installMethod) {
102
+ case "mise":
103
+ return `Run: mise use -g npm:${PACKAGE_NAME}`;
104
+ case "snap":
105
+ return `Run: sudo snap refresh ${PACKAGE_NAME}`;
106
+ default:
107
+ return `Run: npm install -g ${PACKAGE_NAME}`;
108
+ }
109
+ }
110
+ function createUpdateMessage(currentVersion, latestVersion, installMethod, useColor = false) {
111
+ const message = [
112
+ "",
113
+ `Update available! ${currentVersion} -> ${latestVersion}`,
114
+ getUpdateInstructions(installMethod),
115
+ "",
116
+ ].join("\n");
117
+ return useColor ? `${ANSI_YELLOW}${message}${ANSI_RESET}` : message;
118
+ }
119
+ async function checkForUpdate(currentVersion, options = {}) {
120
+ var _a, _b, _c, _d;
121
+ try {
122
+ const env = (_a = options.env) !== null && _a !== void 0 ? _a : process.env;
123
+ const forcedLatestVersion = getForcedLatestVersion(env);
124
+ let latestVersion;
125
+ if (forcedLatestVersion) {
126
+ latestVersion = forcedLatestVersion;
127
+ }
128
+ else {
129
+ const cache = readCache();
130
+ if (cache) {
131
+ latestVersion = cache.latestVersion;
132
+ }
133
+ else {
134
+ latestVersion = await fetchLatestVersion();
135
+ writeCache(latestVersion);
136
+ }
137
+ }
138
+ if (compareVersions(currentVersion, latestVersion) > 0) {
139
+ const installMethod = (_b = options.installMethod) !== null && _b !== void 0 ? _b : detectInstallMethod(env, (_c = options.executablePath) !== null && _c !== void 0 ? _c : process.argv[1]);
140
+ return createUpdateMessage(currentVersion, latestVersion, installMethod, (_d = options.useColor) !== null && _d !== void 0 ? _d : false);
141
+ }
142
+ }
143
+ catch (_e) {
144
+ // Silently ignore update check failures
145
+ }
146
+ return null;
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "az2aws",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Use Azure AD SSO to log into the AWS CLI. A modern, actively maintained alternative to aws-azure-login.",
5
5
  "main": "index.js",
6
6
  "author": {
@@ -42,7 +42,11 @@
42
42
  "onlyBuiltDependencies": [
43
43
  "esbuild",
44
44
  "puppeteer"
45
- ]
45
+ ],
46
+ "overrides": {
47
+ "minimatch@>=9.0.0 <9.0.7": "9.0.7",
48
+ "yauzl@<3.2.1": "3.2.1"
49
+ }
46
50
  },
47
51
  "bin": {
48
52
  "az2aws": "lib/index.js"
@@ -51,7 +55,7 @@
51
55
  "start": "ts-node-dev src/index.ts",
52
56
  "build": "tsc",
53
57
  "watch": "tsc -w",
54
- "eslint": "eslint . --ext .ts",
58
+ "eslint": "eslint .",
55
59
  "prettier:write": "prettier --write \"src/**/*.{ts,json}\"",
56
60
  "prettier:check": "prettier --check \"src/**/*.ts\"",
57
61
  "test": "vitest run",
@@ -60,37 +64,32 @@
60
64
  "lint": "pnpm eslint && pnpm prettier:check"
61
65
  },
62
66
  "dependencies": {
63
- "@aws-sdk/client-sts": "^3.723.0",
64
- "@smithy/node-http-handler": "^2.5.0",
65
- "az2aws": "^1.1.0",
67
+ "@aws-sdk/client-sts": "^3.1009.0",
68
+ "@smithy/node-http-handler": "^4.4.16",
66
69
  "bluebird": "^3.7.2",
67
- "cheerio": "^1.0.0-rc.12",
68
- "commander": "^9.5.0",
69
- "debug": "^4.3.1",
70
- "https-proxy-agent": "^7.0.6",
71
- "ini": "^3.0.1",
72
- "inquirer": "^8.2.6",
73
- "mkdirp": "^2.1.6",
74
- "puppeteer": "^24.34.0",
75
- "uuid": "^9.0.1"
70
+ "cheerio": "^1.2.0",
71
+ "commander": "^14.0.3",
72
+ "debug": "^4.4.3",
73
+ "https-proxy-agent": "^8.0.0",
74
+ "ini": "^6.0.0",
75
+ "inquirer": "^13.3.2",
76
+ "mkdirp": "^3.0.1",
77
+ "puppeteer": "^24.39.1",
78
+ "uuid": "^13.0.0"
76
79
  },
77
80
  "devDependencies": {
78
81
  "@types/bluebird": "^3.5.42",
79
- "@types/cheerio": "^0.22.35",
80
82
  "@types/debug": "^4.1.12",
81
- "@types/ini": "^1.3.34",
82
- "@types/inquirer": "^8.2.10",
83
- "@types/mkdirp": "^1.0.2",
84
- "@types/node": "^24.0.0",
85
- "@types/uuid": "^8.3.4",
86
- "@typescript-eslint/eslint-plugin": "^6.21.0",
87
- "@typescript-eslint/parser": "^6.21.0",
88
- "@vitest/coverage-v8": "^4.0.16",
89
- "eslint": "^8.57.0",
90
- "eslint-config-prettier": "^8.10.0",
91
- "prettier": "^2.8.8",
92
- "ts-node-dev": "^1.1.8",
93
- "typescript": "^5.4.3",
94
- "vitest": "^4.0.16"
83
+ "@types/ini": "^4.1.1",
84
+ "@types/node": "^25.5.0",
85
+ "@typescript-eslint/eslint-plugin": "^8.57.0",
86
+ "@typescript-eslint/parser": "^8.57.0",
87
+ "@vitest/coverage-v8": "^4.1.0",
88
+ "eslint": "^10.0.3",
89
+ "eslint-config-prettier": "^10.1.8",
90
+ "prettier": "^3.8.1",
91
+ "ts-node-dev": "^2.0.0",
92
+ "typescript": "^5.9.3",
93
+ "vitest": "^4.1.0"
95
94
  }
96
95
  }