[bazel] //cmd/presbmit/presubmit.go: Run "npm ci" step on CI.

In https://skia-review.googlesource.com/c/buildbot/+/787329 I updated Prettier to v3.1.0. Since then, we have seen some (but not all) Housekeeper-OnDemand-Presubmit tasks fail with:

    npm WARN exec The following package was not found and will be installed: prettier
    npm ERR! code E451
    npm ERR! 451 unknown - GET https://npm.skia.org/skia-infra/prettier/-/prettier-3.1.1.tgz

Example: https://task-scheduler.skia.org/job/fybXDSw4BolKQ8FgX6ZP.

The reason for this failure is that:

 - Bazel used to manage the node_modules directory for us, but this has ceased to be true since we upgraded to Bazel 6.0.0. This means we don't get a node_modules directory unless we explicitly create one, e.g. with "npm ci".

 - The "npx prettier" command first looks for a "prettier" package in node_modules, and if it does not find it, it tries to download the latest "prettier" version, apparently ignoring the exact version specified in //package.json and/or //package-lock.json.

 - "npx" fails to download "prettier" because the latest version (3.1.1) is too new, and therefore npm-audit-mirror rejects it, hence the 451 error.

The solution I've found is to run "npm ci" right before "npx prettier", which ensures we have a node_modules directory.

Bug: b/314813928
Change-Id: I2de92c62c82a682afd852f416330766eacfa5e9e
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/789219
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Auto-Submit: Leandro Lovisolo <lovisolo@google.com>
diff --git a/cmd/presubmit/presubmit.go b/cmd/presubmit/presubmit.go
index 579b7ec..418a88f 100644
--- a/cmd/presubmit/presubmit.go
+++ b/cmd/presubmit/presubmit.go
@@ -109,6 +109,16 @@
 	changedFiles, _ = computeDiffFiles(ctx, branchBaseCommit)
 	trackErrors(runGofmt(ctx, changedFiles, branchBaseCommit))
 	changedFiles, _ = computeDiffFiles(ctx, branchBaseCommit)
+	if *commit {
+		// When running the presubmit checks on CI, we must manually ensure that the node_modules
+		// directory exists because Bazel no longer manages that directory for us. Running "npm ci"
+		// accomplishes this. If the node_modules directory does not exist, the prettier step below
+		// will fail.
+		//
+		// When running the presubmit checks as part of the "git cl upload" command, it is the
+		// developer's responsibility to keep their node_modules directory up-to-date.
+		trackErrors(runNpmCi(ctx))
+	}
 	trackErrors(runPrettier(ctx, changedFiles, *repoDir, branchBaseCommit))
 	changedFiles, _ = computeDiffFiles(ctx, branchBaseCommit)
 	trackErrors(runGazelle(ctx, changedFiles, deletedFiles, branchBaseCommit))
@@ -675,6 +685,18 @@
 	return true
 }
 
+// runNpmCi runs "npm ci" to ensure that the "node_modules" directory exists.
+func runNpmCi(ctx context.Context) bool {
+	cmd := exec.CommandContext(ctx, "bazelisk", "run", "--config=mayberemote", "//:npm", "--", "ci")
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		logf(ctx, "Command \"npm ci\" failed. Output:\n")
+		logf(ctx, string(output))
+		return false
+	}
+	return true
+}
+
 // runPrettier runs prettier --write on any changed files. It returns false if
 // prettier returns a non-zero error code.
 func runPrettier(ctx context.Context, files []fileWithChanges, workspaceRoot, branchBaseCommit string) bool {