override suspend fun run()

in sources/amper-cli/src/org/jetbrains/amper/cli/commands/UpdateCommand.kt [91:155]


    override suspend fun run() {
        // We could in theory find the parent dir of the actual script that launched Amper (even without project root
        // discovery, by passing more info from the wrapper to Amper), but the benefit would be marginal, and it would
        // break amper-from-sources.
        // Also, we would still have to respect an explicit --root option to allow users to update other projects.
        val targetDir = commonOptions.explicitProjectRoot ?: Path(".")
        val amperBashPath = targetDir.resolve("amper")
        val amperBatPath = targetDir.resolve("amper.bat")
        checkNotDirectories(amperBashPath, amperBatPath)
        if (!create) {
            confirmCreateIfMissingWrappers(amperBashPath, amperBatPath)
        }

        val version = desiredVersion.resolve()

        terminal.println("Downloading Amper scripts...")
        val newBashPath = downloadWrapper(version = version, extension = "").apply { setReadExecPermissions() }
        val newBatPath = downloadWrapper(version = version, extension = ".bat").apply { setReadExecPermissions() }
        terminal.println("Download complete.")

        if (amperBashPath.exists() && newBashPath.readText() == amperBashPath.readText() &&
            amperBatPath.exists() && newBatPath.readText() == amperBatPath.readText()) {
            terminal.println("Amper is already in version $version, nothing to update")
            return
        }

        // Test the new script and download the Amper distribution and JRE
        val exitCode = spanBuilder("New version first run").use {
            runAmperVersionFirstRun(newBatPath, newBashPath)
        }
        if (exitCode != 0) {
            userReadableError("Couldn't run the new Amper version. Please check the errors above.")
        }

        // Replacing a bash script while it's running is possible. We use move commands to ensure the physical file on
        // disk is not modified, thus we can write a new physical file to the old location. Bash will keep loading the
        // old file incrementally from the old physical file using its old file descriptor, which is good.
        spanBuilder("Replace 'amper' script (bash)").use {
            copyAndReplaceSafely(source = newBashPath, target = amperBashPath)
        }

        // Batch files are different. When running, cmd.exe reloads the file after each command and tries to resume at
        // whatever byte offset it was. We can modify the file while it's running, but when the java command running
        // this code completes, it will resume in the new wrapper code. If the new script is shorter, cmd.exe will just
        // stop and the command completes normally. If the new script is longer, then cmd.exe will likely resume in the
        // middle of a command in the middle of the script, which will fail miserably.
        // Even with atomic moves, the new file is reloaded, so in this case we have to spawn a process that will
        // replace the old wrapper after the update command (and the current wrapper) finished executing.

        // The offset of the exit command is where the script would normally resume (given the characters we use in our
        // scripts, the UTF-8 byte offset should correspond to the character offset).
        val runningWrapperResumeOffset = runningWrapper.readText().lastIndexOf("exit /B %ERRORLEVEL%")
        val batUpdateInPlaceWouldBreak = amperBatPath.exists()
                && amperBatPath.isSameFileAs(runningWrapper)
                && newBatPath.fileSize() > runningWrapperResumeOffset
        spanBuilder("Replace 'amper.bat' script").use { span ->
            if (batUpdateInPlaceWouldBreak) {
                copyAndReplaceLaterWindows(source = newBatPath, target = amperBatPath)
                span.addEvent("amper.bat script copy scheduled for after JVM shutdown")
            } else {
                copyAndReplaceSafely(source = newBatPath, target = amperBatPath)
            }
        }
        terminal.success("Update successful")
    }