[Performance] Memoize isVerbose and isUnitTest in local context#7566
[Performance] Memoize isVerbose and isUnitTest in local context#7566gonzaloriestra wants to merge 1 commit into
Conversation
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.
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
There was a problem hiding this comment.
💬 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.
There was a problem hiding this comment.
💡 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:
isVerbose()/isUnitTest()withprocess.envreturns a consistent cached result on repeated calls- Passing a custom env object bypasses the cache and evaluates fresh
- The memoized and non-memoized paths produce identical results for the same inputs
- 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() | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
🎨 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:
| /** | |
| // 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 |
There was a problem hiding this comment.
📐 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:
- Runtime regression:
configureCLIEnvironmentin the theme package sets verbose via env mutation after the cache is already populated (see bug at line 66). - Test isolation: vitest does NOT re-initialize module-level state between individual
test()blocks within a file. Any test callingisVerbose()orisUnitTest()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:
| 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')) |
There was a problem hiding this comment.
🐛 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:
- Adding a
resetEnvironmentMemoization()export (see design suggestion at line 43) thatconfigureCLIEnvironmentcan call after setting the env var - Moving
configureCLIEnvironmentcalls earlier in the theme service functions (before any logging) - Having
configureCLIEnvironmentset the memoized value directly rather than mutatingprocess.env
WHY are these changes introduced?
Performance-obsessed optimization for hot paths. Environment checks like
isVerboseandisUnitTestare static during the execution of a CLI command but are often checked repeatedly (e.g., in everyoutputDebugcall).WHAT is this pull request doing?
This PR implements lazy-initialized memoization for
isVerboseandisUnitTestinpackages/cli-kit/src/public/node/context/local.ts.memoizedIsVerboseandmemoizedIsUnitTestmodule-level variables.process.env.envobject is passed (crucial for unit tests).How to test your changes?
CI
Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset addPR created automatically by Jules for task 7311632441309857235 started by @gonzaloriestra