GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — perf: download bundletool on demand instead of vendoring it by edusperoni · Pull Request #6097 · NativeScript/nativescript-cli Conversation
|
| packed | unpacked | |
|---|---|---|
| before | 34.0 MB | 42.9 MB |
| after | 3.4 MB | 10.3 MB |
A jar is already-compressed, so the saving survives gzip almost intact: -90% on the published tarball.
vendor/ still ships — vendor/gradle-plugin (148KB) remains vendored and unaffected.
The first .aab install on a machine now needs network access. NS_BUNDLETOOL_PATH covers the cases where that is not acceptable. Given the jar is untouched by every other command, taxing every install to pre-stage it was the worse default.
objects.githubusercontent.com followed transparently, progress events delivered with a total, body left undefined on the pipe path, and the resulting sha256 equal to the pinned constant — which is also byte-identical to the jar this PR deletes.ns doctor needs no changes; it has never had any knowledge of bundletool or vendor/.
New Features
Bug Fixes
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
📝 WalkthroughWalkthroughHTTP downloads can now stream into destinations. Android bundletool resolution now supports overrides, cached downloads, checksum validation, locking, progress reporting, and atomic installation, with tests covering these paths. ChangesBundletool acquisition and HTTP streaming
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant AndroidBundleToolService
participant IFileSystem
participant ILockService
participant HttpClient
AndroidBundleToolService->>IFileSystem: check cached bundletool JAR
AndroidBundleToolService->>ILockService: coordinate download
AndroidBundleToolService->>HttpClient: stream bundletool release
HttpClient->>IFileSystem: write temporary download
AndroidBundleToolService->>IFileSystem: verify checksum and rename JAR
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
The 32MB bundletool jar shipped with every install, but it is only used to convert and install .aab artifacts on a device - a path most users never hit, since building an aab is gradle's job. Resolve it lazily instead: on first use fetch the pinned release into <profileDir>/bundletool/, verify its sha256, and move it into place atomically under a lock so concurrent CLIs neither race nor download it twice. NS_BUNDLETOOL_PATH overrides the whole flow for offline and air-gapped setups. This needs a streaming download, so httpRequest grows a pipeTo option - it previously ran every response through JSON.stringify, which would corrupt a binary payload. The same call now also sets httpsAgent, since axios selects the agent by protocol and https requests were silently bypassing the configured proxy tunnel. Release package drops from 34.0MB to 3.4MB (42.9MB to 10.3MB unpacked).
3b9492f to
bae97e9
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
lib/common/http-client.ts (2)
41-59: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftRetrying a
pipeTorequest reuses an already-destroyed destination stream.On a stuck request/response,
httpRequestCoreis retried with the sameoptionsobject (Line 49). Whenoptions.pipeTois set, the first attempt's failure runspipeline(result.data, options.pipeTo)(or the request never even reaches that point), and Node'sstream.pipelinedestroys both source and destination streams on error. Retrying with the same destroyed write stream will either throw (ERR_STREAM_DESTROYED) or produce a corrupted/incomplete file, defeating the purpose of the retry.🔧 Suggested fix: don't retry when a destination stream is involved
) { + if (options.pipeTo) { + // the destination stream from the failed attempt is already + // destroyed or partially written; retrying would corrupt it + throw err; + } // Retry the request immediately because there are at least 10 seconds between the two requests. this.$logger.warn( "%s Retrying request to %s...", err.message, options.url || options, ); const retryResult = await this.httpRequestCore(options, proxySettings);This directly affects the new bundletool download path (
downloadBundleToolinlib/services/android/android-bundle-tool-service.ts), which passes afs.createWriteStreamaspipeTo.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/common/http-client.ts` around lines 41 - 59, Update the retry branch in httpRequestCore to skip the immediate retry whenever options.pipeTo is set, allowing the original error to propagate instead of reusing a destroyed destination stream. Preserve the existing retry behavior for requests without a pipeTo destination, including its logging and response handling.
111-140: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winClose the download stream before
deleteFileruns.When
options.pipeTois used,pipeline()is not called in the.catchhandler, so a failed HTTP stream leavesoptions.pipeToopen.downloadBundleToolthen callsfs.unlinkSync(tempPath)on that open file handle, which can fail on Windows and leak the descriptor on all platforms.Suggested fix: destroy the destination stream on any request failure
}).catch((err) => { this.$logger.trace("An error occurred while sending the request:", err); + if (options.pipeTo && typeof options.pipeTo.destroy === "function") { + options.pipeTo.destroy(); + } if (err.response) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/common/http-client.ts` around lines 111 - 140, Update the request error handler in the axios call to destroy or otherwise close the destination stream referenced by options.pipeTo before rethrowing the error. Ensure this cleanup runs for every failed piped request, while preserving existing error-message handling and behavior for non-stream requests.
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/common/http-client.ts`:
- Around line 44-48: Update the retry warning in the HTTP client’s logger.warn
call to avoid passing the full options object when options.url is falsy. Log
only a safe, bounded request identifier or fallback value that excludes headers,
credentials, and request bodies, while preserving the existing URL logging
behavior when available.
In `@lib/services/android/android-bundle-tool-service.ts`:
- Around line 119-124: Update getBundleToolPath to clear bundleToolPathPromise
when resolveBundleToolPath rejects, while retaining the cached promise after
successful resolution. Ensure subsequent installApks or buildApks calls can
retry resolution after a transient failure.
---
Outside diff comments:
In `@lib/common/http-client.ts`:
- Around line 41-59: Update the retry branch in httpRequestCore to skip the
immediate retry whenever options.pipeTo is set, allowing the original error to
propagate instead of reusing a destroyed destination stream. Preserve the
existing retry behavior for requests without a pipeTo destination, including its
logging and response handling.
- Around line 111-140: Update the request error handler in the axios call to
destroy or otherwise close the destination stream referenced by options.pipeTo
before rethrowing the error. Ensure this cleanup runs for every failed piped
request, while preserving existing error-message handling and behavior for
non-stream requests.
Fix all unresolved CodeRabbit comments on this PR:
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6d982bbf-bd80-4939-af56-08c523f8de76
vendor/aab-tool/bundletool.jar is excluded by !**/*.jarlib/common/http-client.tslib/constants.tslib/services/android/android-bundle-tool-service.tsscripts/copy-assets.jstest/services/android-bundle-tool-service.tsvendor/aab-tool/LICENSEvendor/aab-tool/README.txt
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| this.$logger.warn( | ||
| "%s Retrying request to %s...", | ||
| err.message, | ||
| options.url || options | ||
| options.url || options, | ||
| ); |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Retry-warning fallback can log the entire options object, including headers/credentials.
options.url || options will log the full options object (potentially containing Authorization headers, proxy credentials, or request bodies) whenever url is falsy. Prefer logging a safe, bounded subset.
this.$logger.warn( "%s Retrying request to %s...", err.message, - options.url || options, + options.url || options.method || "unknown request", );
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.$logger.warn( | |
| "%s Retrying request to %s...", | |
| err.message, | |
| options.url || options | |
| options.url || options, | |
| ); | |
| this.$logger.warn( | |
| "%s Retrying request to %s...", | |
| err.message, | |
| options.url || options.method || "unknown request", | |
| ); |
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/common/http-client.ts` around lines 44 - 48, Update the retry warning in
the HTTP client’s logger.warn call to avoid passing the full options object when
options.url is falsy. Log only a safe, bounded request identifier or fallback
value that excludes headers, credentials, and request bodies, while preserving
the existing URL logging behavior when available.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| private getBundleToolPath(): Promise<string> { | ||
| this.bundleToolPathPromise = | ||
| this.bundleToolPathPromise || this.resolveBundleToolPath(); | ||
|
|
||
| return this.bundleToolPathPromise; | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
A failed resolution is cached forever for the life of the process.
bundleToolPathPromise is memoized unconditionally. If resolveBundleToolPath() rejects once (transient network blip, momentary checksum mismatch, etc.), every subsequent installApks/buildApks call in the same process will immediately re-reject with the same stale error — there's no way to retry without restarting the CLI process.
private getBundleToolPath(): Promise<string> {
- this.bundleToolPathPromise =
- this.bundleToolPathPromise || this.resolveBundleToolPath();
+ if (!this.bundleToolPathPromise) {
+ this.bundleToolPathPromise = this.resolveBundleToolPath().catch(
+ (err) => {
+ this.bundleToolPathPromise = null;
+ throw err;
+ },
+ );
+ }
return this.bundleToolPathPromise;
}
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private getBundleToolPath(): Promise<string> { | |
| this.bundleToolPathPromise = | |
| this.bundleToolPathPromise || this.resolveBundleToolPath(); | |
| return this.bundleToolPathPromise; | |
| } | |
| private getBundleToolPath(): Promise<string> { | |
| if (!this.bundleToolPathPromise) { | |
| this.bundleToolPathPromise = this.resolveBundleToolPath().catch( | |
| (err) => { | |
| this.bundleToolPathPromise = null; | |
| throw err; | |
| }, | |
| ); | |
| } | |
| return this.bundleToolPathPromise; | |
| } |
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/services/android/android-bundle-tool-service.ts` around lines 119 - 124,
Update getBundleToolPath to clear bundleToolPathPromise when
resolveBundleToolPath rejects, while retaining the cached promise after
successful resolution. Ensure subsequent installApks or buildApks calls can
retry resolution after a transient failure.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.