Skip to content

[Performance] Memoize isVerbose and isUnitTest in local context#7566

Open
gonzaloriestra wants to merge 1 commit into
mainfrom
jules-performance-memoize-env-checks-7311632441309857235
Open

[Performance] Memoize isVerbose and isUnitTest in local context#7566
gonzaloriestra wants to merge 1 commit into
mainfrom
jules-performance-memoize-env-checks-7311632441309857235

Conversation

@gonzaloriestra
Copy link
Copy Markdown
Contributor

@gonzaloriestra gonzaloriestra commented May 17, 2026

WHY are these changes introduced?

Performance-obsessed optimization for hot paths. Environment checks like isVerbose and isUnitTest are static during the execution of a CLI command but are often checked repeatedly (e.g., in every outputDebug call).

WHAT is this pull request doing?

This PR implements lazy-initialized memoization for isVerbose and isUnitTest in packages/cli-kit/src/public/node/context/local.ts.

  • Adds memoizedIsVerbose and memoizedIsUnitTest module-level variables.
  • Implements a check to ensure memoization only occurs when using process.env.
  • Bypasses the cache when a custom env object is passed (crucial for unit tests).
  • Adds comments explaining the rationale and safety of the optimization.

How to test your changes?

CI

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

PR created automatically by Jules for task 7311632441309857235 started by @gonzaloriestra

Memoize the result of `isVerbose` and `isUnitTest` when called with
`process.env`. These checks are performed frequently in hot paths
like logging (`outputDebug`), and memoizing them avoids repeated
`process.argv.includes` scans and redundant environment variable
lookups.

The memoization explicitly bypasses the cache when a custom environment
object is provided, ensuring test isolation remains intact.

Performance impact:
- Reduces overhead in high-frequency logging paths.
- Avoids O(N) array scans for the `--verbose` flag on every debug log.
@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label May 17, 2026
@gonzaloriestra gonzaloriestra added the Jules Created by Jules label May 18, 2026
@gonzaloriestra gonzaloriestra marked this pull request as ready for review May 18, 2026 12:07
@gonzaloriestra gonzaloriestra requested a review from a team as a code owner May 18, 2026 12:07
Comment thread .jules/bolt.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Suggestion: This file was created by the Jules AI agent as part of its task setup but is completely empty. It adds noise to the diff and shouldn't be committed to the repository.

Suggestion: Remove this file from the PR. If Jules requires it for task tracking, consider adding .jules/ to .gitignore.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Improvement: The PR adds memoization logic (the env === process.env guard, the ??= caching, the cache bypass for custom env) without any corresponding tests. isVerbose has zero test coverage even without the memoization. The existing isUnitTest test in local.test.ts only passes a custom env object, which explicitly bypasses the new memoized code path — so the memoization is entirely unexercised in tests.

Key behaviors worth testing:

  1. isVerbose() / isUnitTest() with process.env returns a consistent cached result on repeated calls
  2. Passing a custom env object bypasses the cache and evaluates fresh
  3. The memoized and non-memoized paths produce identical results for the same inputs
  4. If resetEnvironmentMemoization() is added, verify it allows re-evaluation

Suggestion: Add tests in local.test.ts that exercise both the memoized path (calling with process.env) and the bypass path (calling with a custom env), and verify they produce consistent results. Also add basic isVerbose test coverage, which is currently missing entirely.

return homedir()
}

/**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 Code Style: The 5-line JSDoc block on the two let declarations, combined with the 2-line inline comments at lines 64-65 and 106-107, creates redundancy — the module-level block and inline comments say essentially the same thing in three places. The ??= pattern and env === process.env guard are fairly self-explanatory.

Suggestion: A single-line module comment is sufficient; consider removing the inline comments at lines 64-65 and 106-107 since the module-level comment already explains the rationale:

Suggested change
/**
// Lazy-initialized caches for hot-path env checks; bypassed when a custom env is passed.
let memoizedIsVerbose: boolean | undefined
let memoizedIsUnitTest: boolean | undefined

* consistency and performance in hot paths (like logging) while maintaining
* test isolation when custom environment objects are provided.
*/
let memoizedIsVerbose: boolean | undefined
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Design: The memoizedIsVerbose and memoizedIsUnitTest variables are module-level mutable state that can never be reset once set. Unlike clearCache() in cli.ts:159 which provides a user-facing reset for the conf-store cache, these memoized values have no reset path.

This creates two problems:

  1. Runtime regression: configureCLIEnvironment in the theme package sets verbose via env mutation after the cache is already populated (see bug at line 66).
  2. Test isolation: vitest does NOT re-initialize module-level state between individual test() blocks within a file. Any test calling isVerbose() or isUnitTest() without a custom env permanently locks in the value for subsequent tests in that file.

Suggestion: Add an exported reset function for both runtime and test use:

Suggested change
let memoizedIsVerbose: boolean | undefined
let memoizedIsVerbose: boolean | undefined
let memoizedIsUnitTest: boolean | undefined
export function resetEnvironmentMemoization(): void {
memoizedIsVerbose = undefined
memoizedIsUnitTest = undefined
}

if (env === process.env) {
// Memoize the result to avoid repeated scans of process.argv and env lookups
// in high-frequency paths like outputDebug.
return (memoizedIsVerbose ??= isTruthy(env[environmentVariables.verbose]) || process.argv.includes('--verbose'))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 Bug: The theme package's configureCLIEnvironment() in packages/theme/src/cli/utilities/cli-config.ts mutates process.env.SHOPIFY_FLAG_VERBOSE = 'true' at runtime. This is called in push.ts:166 and pull.ts:110 to set up the CLI environment for programmatic theme service invocations (where --verbose is NOT in process.argv).

In push.ts, significant work happens before configureCLIEnvironment is called (authentication, theme checks, etc.), all of which may trigger logging that calls isVerbose(). Once isVerbose() memoizes false during this early work, the subsequent env mutation is invisible — the cache never re-evaluates.

Before this PR: verbose output was lost only for calls before configureCLIEnvironment, but worked correctly afterward.
After this PR: verbose output is permanently suppressed for the entire process execution.

This is a behavioral regression for the programmatic theme service invocation path. Normal CLI usage (where --verbose is in process.argv) is unaffected.

A related concern: tests in packages/store/src/cli/services/store/auth/result.test.ts:79 and packages/store/src/cli/services/store/execute/result.test.ts:73 temporarily set process.env.SHOPIFY_UNIT_TEST = 'false' to test behavior outside unit-test mode. The memoized isUnitTest would silently ignore these mutations, meaning those tests no longer exercise the code path they were designed to test.

Suggestion: Consider either:

  1. Adding a resetEnvironmentMemoization() export (see design suggestion at line 43) that configureCLIEnvironment can call after setting the env var
  2. Moving configureCLIEnvironment calls earlier in the theme service functions (before any logging)
  3. Having configureCLIEnvironment set the memoized value directly rather than mutating process.env

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Jules Created by Jules no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants