diff --git a/lib/services/ios/spm-service.ts b/lib/services/ios/spm-service.ts index 2ec1073e2b..485b924647 100644 --- a/lib/services/ios/spm-service.ts +++ b/lib/services/ios/spm-service.ts @@ -168,7 +168,7 @@ export class SPMService implements ISPMService { const render = () => { const elapsed = Math.round((Date.now() - startedAt) / 1000); - spinner.text = `${activity}… ${color.dim(`(${elapsed}s)`)}`; + spinner.text = `${activity}… ${color.dim(`(${this.formatElapsed(elapsed)})`)}`; }; // keep the elapsed timer ticking even when xcodebuild is silent (e.g. // while a binary artifact downloads) so the user can see it's alive. @@ -256,10 +256,10 @@ export class SPMService implements ISPMService { return "Downloading Swift Package binaries (first build only)"; } if (/^Fetching\b/i.test(trimmed)) { - return `Fetching ${this.shortenPackageRef(trimmed)}`; + return this.describePackageActivity("Fetching", trimmed); } if (/^Cloning\b/i.test(trimmed)) { - return `Cloning ${this.shortenPackageRef(trimmed)}`; + return this.describePackageActivity("Cloning", trimmed); } if (/Computing version for/i.test(trimmed)) { return "Computing package versions"; @@ -273,11 +273,25 @@ export class SPMService implements ISPMService { return null; } - /** Extracts a short, readable name from a SwiftPM repo URL/log line. */ - private shortenPackageRef(line: string): string { + /** + * Builds a stable, self-explanatory activity label with the package being + * worked on in parentheses, e.g. "Fetching Swift Packages (Auth0.swift)". + */ + private describePackageActivity(verb: string, line: string): string { + const packageRef = this.shortenPackageRef(line); + return packageRef + ? `${verb} Swift Packages (${packageRef})` + : `${verb} Swift Packages`; + } + + /** + * Extracts a short, readable name from a SwiftPM repo URL/log line, or null + * when the line contains no URL to name the package by. + */ + private shortenPackageRef(line: string): string | null { const match = line.match(/https?:\/\/\S+/); if (!match) { - return "Swift Packages"; + return null; } return path .basename(match[0]) @@ -285,6 +299,13 @@ export class SPMService implements ISPMService { .replace(/[)\s].*$/, ""); } + /** Formats an elapsed duration in whole seconds as "5m 15s". */ + private formatElapsed(totalSeconds: number): string { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes}m ${seconds}s`; + } + /** True when the Xcode project references any Swift packages. */ private hasSPMReferences( platformData: IPlatformData, diff --git a/test/spm-service.ts b/test/spm-service.ts index 829b23812f..ff1b32bf1d 100644 --- a/test/spm-service.ts +++ b/test/spm-service.ts @@ -205,21 +205,28 @@ describe("SPM Service - resolution log parsing", () => { ); }); - it("summarizes fetching with the package name", () => { + it("summarizes fetching with the package name in parentheses", () => { assert.equal( service.describeSPMActivity( "Fetching from https://github.com/NativeScript/ios-spm.git", ), - "Fetching ios-spm", + "Fetching Swift Packages (ios-spm)", ); }); - it("summarizes cloning with the package name", () => { + it("summarizes cloning with the package name in parentheses", () => { assert.equal( service.describeSPMActivity( "Cloning https://github.com/Alamofire/Alamofire.git", ), - "Cloning Alamofire", + "Cloning Swift Packages (Alamofire)", + ); + }); + + it("omits the parenthesized name when the line has no URL", () => { + assert.equal( + service.describeSPMActivity("Fetching cached package"), + "Fetching Swift Packages", ); }); @@ -251,7 +258,7 @@ describe("SPM Service - resolution log parsing", () => { service.describeSPMActivity( " Fetching https://github.com/NativeScript/ios-spm.git ", ), - "Fetching ios-spm", + "Fetching Swift Packages (ios-spm)", ); }); @@ -286,11 +293,18 @@ describe("SPM Service - resolution log parsing", () => { ); }); - it("falls back to a generic label when there is no URL", () => { - assert.equal( - service.shortenPackageRef("Fetching cached package"), - "Swift Packages", - ); + it("returns null when there is no URL", () => { + assert.isNull(service.shortenPackageRef("Fetching cached package")); + }); + }); + + describe("formatElapsed", () => { + it("always renders minutes and seconds", () => { + assert.equal(service.formatElapsed(0), "0m 0s"); + assert.equal(service.formatElapsed(42), "0m 42s"); + assert.equal(service.formatElapsed(60), "1m 0s"); + assert.equal(service.formatElapsed(315), "5m 15s"); + assert.equal(service.formatElapsed(3725), "62m 5s"); }); }); });