11import { info , warning , addPath } from "@actions/core" ;
2- import { exec } from "@actions/exec" ;
2+ import { exec , getExecOutput } from "@actions/exec" ;
3+ import { existsSync , writeFileSync } from "node:fs" ;
34import { delimiter , join } from "node:path" ;
45import { setTimeout as sleep } from "node:timers/promises" ;
56import { getInstallScriptUrls , pkgPrNewCommitSha } from "./ci/install-script-urls.js" ;
@@ -14,6 +15,8 @@ import {
1415import type { Inputs } from "./types.js" ;
1516import { DISPLAY_NAME } from "./types.js" ;
1617import { getVitePlusHome } from "./utils.js" ;
18+ import { withVitePlusInstallLock } from "./install-lock.js" ;
19+ import { parseInstalledVpVersion } from "./ci/version.js" ;
1720
1821// Try each group's URLs in order, for up to N rounds per group (max attempts
1922// per group = rounds * URLs). Two rounds × two URLs = 4 attempts, ~1 minute
@@ -22,6 +25,10 @@ const INSTALL_MAX_ROUNDS = 2;
2225const INSTALL_RETRY_DELAY_MS = 2000 ;
2326
2427export async function installVitePlus ( inputs : Inputs ) : Promise < void > {
28+ await withVitePlusInstallLock ( getVitePlusHome ( ) , ( ) => installVitePlusUnlocked ( inputs ) ) ;
29+ }
30+
31+ async function installVitePlusUnlocked ( inputs : Inputs ) : Promise < void > {
2532 const { version } = inputs ;
2633
2734 info ( `Installing ${ DISPLAY_NAME } @${ version } ...` ) ;
@@ -51,6 +58,13 @@ export async function installVitePlus(inputs: Inputs): Promise<void> {
5158 env . VP_NODE_MANAGER = inputs . nodeManager ? "yes" : "no" ;
5259 }
5360
61+ if ( await canReuseInstalledVersion ( version ) ) {
62+ info ( `Reusing installed ${ DISPLAY_NAME } @${ version } .` ) ;
63+ await restoreReusedInstall ( version , dirsFile , inputs . nodeManager ) ;
64+ ensureVitePlusBinInPath ( version , dirsFile , ! dirsFile ) ;
65+ return ;
66+ }
67+
5468 // For pkg.pr.new preview builds, tell the install script to fetch from
5569 // pkg.pr.new (bypassing the npm registry) instead of resolving VP_VERSION.
5670 const prVersion = pkgPrNewCommitSha ( version ) ;
@@ -124,8 +138,60 @@ async function runInstallCommand(url: string, env: { [key: string]: string }): P
124138 return exec ( command , args , options ) ;
125139}
126140
127- function ensureVitePlusBinInPath ( version : string , dirsFile : string | undefined ) : void {
128- const binDir = resolveVitePlusBinDir ( version , dirsFile , join ( getVitePlusHome ( ) , "bin" ) ) ;
141+ async function canReuseInstalledVersion ( version : string ) : Promise < boolean > {
142+ const requestedVersion = version . replace ( / ^ v / , "" ) ;
143+ if ( ! / ^ \d + \. \d + \. \d + (?: - [ 0 - 9 A - Z a - z . - ] + ) ? $ / . test ( requestedVersion ) ) return false ;
144+
145+ const binary = getCurrentVitePlusBinary ( ) ;
146+ if ( ! existsSync ( binary ) ) return false ;
147+
148+ const result = await getExecOutput ( binary , [ "--version" ] , {
149+ ignoreReturnCode : true ,
150+ silent : true ,
151+ } ) ;
152+ return result . exitCode === 0 && parseInstalledVpVersion ( result . stdout ) === requestedVersion ;
153+ }
154+
155+ async function restoreReusedInstall (
156+ version : string ,
157+ dirsFile : string | undefined ,
158+ nodeManager : boolean | undefined ,
159+ ) : Promise < void > {
160+ const binary = getCurrentVitePlusBinary ( ) ;
161+
162+ if ( dirsFile ) {
163+ const result = await getExecOutput ( binary , [ ] , {
164+ env : { ...process . env , VP_DUMP_DIRS : "1" } ,
165+ ignoreReturnCode : true ,
166+ silent : true ,
167+ } ) ;
168+ if ( result . exitCode !== 0 ) {
169+ throw new Error ( `Could not read VpDirs from reused ${ DISPLAY_NAME } @${ version } .` ) ;
170+ }
171+ writeFileSync ( dirsFile , result . stdout ) ;
172+ }
173+
174+ // The installer refreshes managed Node.js shims by default on CI. Reapply
175+ // that behavior after reuse; node-manager: false is reconciled by runMain's
176+ // subsequent `vp env off` call.
177+ if ( nodeManager !== false ) {
178+ await exec ( binary , [ "env" , "setup" , "--refresh" ] ) ;
179+ }
180+ }
181+
182+ function getCurrentVitePlusBinary ( ) : string {
183+ return join ( getVitePlusHome ( ) , "current" , "bin" , process . platform === "win32" ? "vp.exe" : "vp" ) ;
184+ }
185+
186+ function ensureVitePlusBinInPath (
187+ version : string ,
188+ dirsFile : string | undefined ,
189+ allowLegacyBin = false ,
190+ ) : void {
191+ const legacyBinDir = join ( getVitePlusHome ( ) , "bin" ) ;
192+ const binDir = allowLegacyBin
193+ ? legacyBinDir
194+ : resolveVitePlusBinDir ( version , dirsFile , legacyBinDir ) ;
129195 if ( ! process . env . PATH ?. split ( delimiter ) . includes ( binDir ) ) {
130196 addPath ( binDir ) ;
131197 }
0 commit comments