From 07828fb1599d1520213cf9673c1168fe979822f4 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Sat, 6 Jun 2026 00:18:36 +0000 Subject: [PATCH] [Performance] Memoize username The `username` function performs environment lookups, `os.userInfo()` calls, and potential `execa` calls which are expensive. Since the username is static during the CLI execution, memoizing the result improves efficiency. This PR implements memoization for the `username` function in `packages/cli-kit/src/public/node/os.ts` by caching the resulting Promise in a module-level variable. Expected performance impact: Reduces redundant system calls and environment lookups. While the absolute time saved per call is small (milliseconds), it adds up in high-frequency paths or when called multiple times during command initialization and analytics reporting. --- packages/cli-kit/src/public/node/os.test.ts | 42 ++++++++++++++++++++- packages/cli-kit/src/public/node/os.ts | 15 +++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/cli-kit/src/public/node/os.test.ts b/packages/cli-kit/src/public/node/os.test.ts index 0e10a277d7..be967dd46d 100644 --- a/packages/cli-kit/src/public/node/os.test.ts +++ b/packages/cli-kit/src/public/node/os.test.ts @@ -1,7 +1,45 @@ -import {platformAndArch} from './os.js' -import {describe, test, expect, vi} from 'vitest' +import {platformAndArch, username} from './os.js' +import {describe, test, expect, vi, beforeEach, afterEach} from 'vitest' +import {userInfo} from 'os' vi.mock('node:process') +vi.mock('os', async (importOriginal) => { + const original: any = await importOriginal() + return { + ...original, + userInfo: vi.fn(), + } +}) + +describe('username', () => { + beforeEach(() => { + vi.mocked(userInfo).mockReturnValue({ + username: 'test-user', + uid: 1, + gid: 1, + shell: 'sh', + homedir: '/home/test-user', + }) + }) + + afterEach(() => { + vi.unstubAllEnvs() + }) + + test('returns the username and memoizes it', async () => { + // Given + vi.stubEnv('SUDO_USER', 'test-user') + + // When + const result1 = await username() + const result2 = await username() + + // Then + expect(result1).toBe('test-user') + expect(result2).toBe('test-user') + expect(username()).toBe(username()) + }) +}) describe('platformAndArch', () => { test("returns the right architecture when it's x64", () => { diff --git a/packages/cli-kit/src/public/node/os.ts b/packages/cli-kit/src/public/node/os.ts index 139f798cbb..d543bfd1df 100644 --- a/packages/cli-kit/src/public/node/os.ts +++ b/packages/cli-kit/src/public/node/os.ts @@ -5,11 +5,24 @@ import {userInfo as osUserInfo} from 'os' // This code has been vendored from https://github.com/sindresorhus/username // because adding it as a transtive dependency causes conflicts with other // packages that haven't been yet migrated to the latest version. + +/** + * Memoized value for the username. + */ +let memoizedUsername: Promise | undefined + /** * @param platform - The platform to get the username for. Defaults to the current platform. * @returns The username of the current user. */ -export async function username(platform: typeof process.platform = process.platform): Promise { +export function username(platform: typeof process.platform = process.platform): Promise { + if (platform === process.platform) { + return (memoizedUsername ??= getUsername(platform)) + } + return getUsername(platform) +} + +async function getUsername(platform: typeof process.platform): Promise { outputDebug(outputContent`Obtaining user name...`) const environmentVariable = getEnvironmentVariable() if (environmentVariable) {