terramend 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +145 -0
- package/dist/agents/claude.d.ts +73 -0
- package/dist/agents/claudePretoolGate.d.ts +99 -0
- package/dist/agents/gateServer.d.ts +7 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/nativeFsDenies.d.ts +28 -0
- package/dist/agents/opencode.d.ts +231 -0
- package/dist/agents/opencodePlugin.d.ts +85 -0
- package/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +132 -0
- package/dist/agents/reviewer.d.ts +38 -0
- package/dist/agents/sessionLabeler.d.ts +97 -0
- package/dist/agents/shared.d.ts +189 -0
- package/dist/agents/subagentModels.d.ts +19 -0
- package/dist/agents/subagentToolGates.d.ts +55 -0
- package/dist/cli.mjs +197426 -0
- package/dist/external.d.ts +227 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +196783 -0
- package/dist/internal/index.d.ts +18 -0
- package/dist/internal.js +1714 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/main.d.ts +8 -0
- package/dist/mcp/arkConfig.d.ts +1 -0
- package/dist/mcp/checkSuite.d.ts +25 -0
- package/dist/mcp/checkout.d.ts +77 -0
- package/dist/mcp/comment.d.ts +119 -0
- package/dist/mcp/commitInfo.d.ts +9 -0
- package/dist/mcp/crosswalk.d.ts +105 -0
- package/dist/mcp/dependencies.d.ts +8 -0
- package/dist/mcp/geminiSanitizer.d.ts +28 -0
- package/dist/mcp/git.d.ts +46 -0
- package/dist/mcp/guardrails.d.ts +104 -0
- package/dist/mcp/issue.d.ts +18 -0
- package/dist/mcp/issueComments.d.ts +9 -0
- package/dist/mcp/issueEvents.d.ts +9 -0
- package/dist/mcp/issueInfo.d.ts +9 -0
- package/dist/mcp/labels.d.ts +12 -0
- package/dist/mcp/localContext.d.ts +19 -0
- package/dist/mcp/moduleExtraction.d.ts +71 -0
- package/dist/mcp/moduleTests.d.ts +104 -0
- package/dist/mcp/modules.d.ts +179 -0
- package/dist/mcp/output.d.ts +12 -0
- package/dist/mcp/pathSafety.d.ts +14 -0
- package/dist/mcp/policy.d.ts +48 -0
- package/dist/mcp/pr.d.ts +49 -0
- package/dist/mcp/prInfo.d.ts +9 -0
- package/dist/mcp/providerSchema.d.ts +50 -0
- package/dist/mcp/review.d.ts +199 -0
- package/dist/mcp/reviewComments.d.ts +178 -0
- package/dist/mcp/roots.d.ts +58 -0
- package/dist/mcp/scope.d.ts +15 -0
- package/dist/mcp/selectMode.d.ts +18 -0
- package/dist/mcp/server.d.ts +48 -0
- package/dist/mcp/shared.d.ts +47 -0
- package/dist/mcp/shell.d.ts +37 -0
- package/dist/mcp/staleFix.d.ts +51 -0
- package/dist/mcp/terraform/cost.d.ts +55 -0
- package/dist/mcp/terraform/currency.d.ts +94 -0
- package/dist/mcp/terraform/decisions.d.ts +178 -0
- package/dist/mcp/terraform/findings.d.ts +75 -0
- package/dist/mcp/terraform/plan.d.ts +157 -0
- package/dist/mcp/terraform/scanners.d.ts +131 -0
- package/dist/mcp/terraform/tools.d.ts +63 -0
- package/dist/mcp/terraform/types.d.ts +172 -0
- package/dist/mcp/terraform.d.ts +22 -0
- package/dist/mcp/terratest.d.ts +83 -0
- package/dist/mcp/upload.d.ts +6 -0
- package/dist/models.d.ts +171 -0
- package/dist/modes.d.ts +26 -0
- package/dist/prep/index.d.ts +7 -0
- package/dist/prep/installNodeDependencies.d.ts +2 -0
- package/dist/prep/installPythonDependencies.d.ts +2 -0
- package/dist/prep/types.d.ts +31 -0
- package/dist/reviewQuality.d.ts +64 -0
- package/dist/skills/terraform-best-practices/SKILL.md +369 -0
- package/dist/toolState.d.ts +135 -0
- package/dist/utils/activity.d.ts +40 -0
- package/dist/utils/agent.d.ts +20 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/apiFetch.d.ts +19 -0
- package/dist/utils/apiKeys.d.ts +41 -0
- package/dist/utils/apiUrl.d.ts +20 -0
- package/dist/utils/assets.d.ts +8 -0
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/body.d.ts +34 -0
- package/dist/utils/buildTerramendFooter.d.ts +25 -0
- package/dist/utils/byokFallback.d.ts +85 -0
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/cli.d.ts +10 -0
- package/dist/utils/codexHome.d.ts +29 -0
- package/dist/utils/codexOAuth.d.ts +60 -0
- package/dist/utils/diffCoverage.d.ts +63 -0
- package/dist/utils/errorReport.d.ts +17 -0
- package/dist/utils/exitHandler.d.ts +8 -0
- package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
- package/dist/utils/gitAuth.d.ts +84 -0
- package/dist/utils/gitAuthServer.d.ts +24 -0
- package/dist/utils/github.d.ts +78 -0
- package/dist/utils/globals.d.ts +3 -0
- package/dist/utils/install.d.ts +60 -0
- package/dist/utils/instructions.d.ts +48 -0
- package/dist/utils/leapingComment.d.ts +11 -0
- package/dist/utils/learnings.d.ts +62 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +57 -0
- package/dist/utils/log.d.ts +111 -0
- package/dist/utils/normalizeEnv.d.ts +30 -0
- package/dist/utils/openCodeModels.d.ts +11 -0
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/packageManager.d.ts +49 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
- package/dist/utils/payload.d.ts +105 -0
- package/dist/utils/prSummary.d.ts +61 -0
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/providerErrors.d.ts +31 -0
- package/dist/utils/rangeDiff.d.ts +51 -0
- package/dist/utils/remediationCommand.d.ts +55 -0
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/reviewCleanup.d.ts +14 -0
- package/dist/utils/run.d.ts +9 -0
- package/dist/utils/runContext.d.ts +60 -0
- package/dist/utils/runContextData.d.ts +23 -0
- package/dist/utils/runErrorRenderer.d.ts +64 -0
- package/dist/utils/runLifecycle.d.ts +86 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/secrets.d.ts +22 -0
- package/dist/utils/setup.d.ts +90 -0
- package/dist/utils/shell.d.ts +32 -0
- package/dist/utils/skills.d.ts +10 -0
- package/dist/utils/subprocess.d.ts +80 -0
- package/dist/utils/terraformMcp.d.ts +42 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/timer.d.ts +23 -0
- package/dist/utils/todoTracking.d.ts +16 -0
- package/dist/utils/token.d.ts +39 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/versioning.d.ts +7 -0
- package/dist/utils/vertex.d.ts +16 -0
- package/dist/utils/workflow.d.ts +13 -0
- package/package.json +119 -0
- package/src/agents/claude.test.ts +1016 -0
- package/src/agents/claude.ts +1246 -0
- package/src/agents/claudePretoolGate.test.ts +28 -0
- package/src/agents/claudePretoolGate.ts +173 -0
- package/src/agents/gateServer.test.ts +204 -0
- package/src/agents/gateServer.ts +124 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/nativeFsDenies.ts +82 -0
- package/src/agents/opencode.test.ts +1440 -0
- package/src/agents/opencode.ts +1312 -0
- package/src/agents/opencodePlugin.ts +222 -0
- package/src/agents/opencodeShared.test.ts +34 -0
- package/src/agents/opencodeShared.ts +121 -0
- package/src/agents/postRun.test.ts +549 -0
- package/src/agents/postRun.ts +535 -0
- package/src/agents/reviewer.ts +104 -0
- package/src/agents/sessionLabeler.test.ts +247 -0
- package/src/agents/sessionLabeler.ts +178 -0
- package/src/agents/shared.test.ts +76 -0
- package/src/agents/shared.ts +292 -0
- package/src/agents/subagentModels.test.ts +113 -0
- package/src/agents/subagentModels.ts +40 -0
- package/src/agents/subagentRegistration.test.ts +41 -0
- package/src/agents/subagentToolGates.ts +114 -0
- package/src/cli.test.ts +129 -0
- package/src/cli.ts +105 -0
- package/src/commands/gha.test.ts +192 -0
- package/src/commands/gha.ts +188 -0
- package/src/commands/mcp.ts +122 -0
- package/src/config.ts +1 -0
- package/src/entry.ts +7 -0
- package/src/entryPost.stdlibOnly.test.ts +109 -0
- package/src/entryPost.ts +99 -0
- package/src/external.test.ts +16 -0
- package/src/external.ts +302 -0
- package/src/index.ts +11 -0
- package/src/internal/index.ts +71 -0
- package/src/lifecycle.ts +2 -0
- package/src/main.test.ts +873 -0
- package/src/main.ts +712 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
- package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
- package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
- package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
- package/src/mcp/arkConfig.ts +7 -0
- package/src/mcp/checkSuite.test.ts +245 -0
- package/src/mcp/checkSuite.ts +255 -0
- package/src/mcp/checkout.test.ts +752 -0
- package/src/mcp/checkout.ts +886 -0
- package/src/mcp/comment.test.ts +772 -0
- package/src/mcp/comment.ts +582 -0
- package/src/mcp/commitInfo.test.ts +127 -0
- package/src/mcp/commitInfo.ts +61 -0
- package/src/mcp/crosswalk.test.ts +106 -0
- package/src/mcp/crosswalk.ts +339 -0
- package/src/mcp/dependencies.test.ts +309 -0
- package/src/mcp/dependencies.ts +189 -0
- package/src/mcp/geminiSanitizer.test.ts +287 -0
- package/src/mcp/geminiSanitizer.ts +207 -0
- package/src/mcp/git.test.ts +1083 -0
- package/src/mcp/git.ts +890 -0
- package/src/mcp/guardrails.test.ts +705 -0
- package/src/mcp/guardrails.ts +465 -0
- package/src/mcp/issue.test.ts +113 -0
- package/src/mcp/issue.ts +73 -0
- package/src/mcp/issueComments.test.ts +69 -0
- package/src/mcp/issueComments.ts +48 -0
- package/src/mcp/issueEvents.test.ts +134 -0
- package/src/mcp/issueEvents.ts +100 -0
- package/src/mcp/issueInfo.test.ts +104 -0
- package/src/mcp/issueInfo.ts +72 -0
- package/src/mcp/labels.test.ts +52 -0
- package/src/mcp/labels.ts +34 -0
- package/src/mcp/localContext.ts +28 -0
- package/src/mcp/localServer.test.ts +75 -0
- package/src/mcp/localServer.ts +131 -0
- package/src/mcp/moduleExtraction.test.ts +261 -0
- package/src/mcp/moduleExtraction.ts +313 -0
- package/src/mcp/moduleTests.test.ts +269 -0
- package/src/mcp/moduleTests.ts +421 -0
- package/src/mcp/modules.test.ts +640 -0
- package/src/mcp/modules.ts +696 -0
- package/src/mcp/output.test.ts +96 -0
- package/src/mcp/output.ts +70 -0
- package/src/mcp/pathSafety.test.ts +44 -0
- package/src/mcp/pathSafety.ts +28 -0
- package/src/mcp/policy.test.ts +282 -0
- package/src/mcp/policy.ts +199 -0
- package/src/mcp/pr.test.ts +387 -0
- package/src/mcp/pr.ts +194 -0
- package/src/mcp/prInfo.test.ts +96 -0
- package/src/mcp/prInfo.ts +91 -0
- package/src/mcp/providerSchema.test.ts +85 -0
- package/src/mcp/providerSchema.ts +175 -0
- package/src/mcp/review.test.ts +936 -0
- package/src/mcp/review.ts +923 -0
- package/src/mcp/reviewComments.test.ts +549 -0
- package/src/mcp/reviewComments.ts +896 -0
- package/src/mcp/roots.test.ts +175 -0
- package/src/mcp/roots.ts +217 -0
- package/src/mcp/scope.test.ts +59 -0
- package/src/mcp/scope.ts +65 -0
- package/src/mcp/security.test.ts +720 -0
- package/src/mcp/selectMode.test.ts +210 -0
- package/src/mcp/selectMode.ts +181 -0
- package/src/mcp/server.test.ts +292 -0
- package/src/mcp/server.ts +403 -0
- package/src/mcp/shared.ts +100 -0
- package/src/mcp/shell.test.ts +520 -0
- package/src/mcp/shell.ts +505 -0
- package/src/mcp/staleFix.test.ts +237 -0
- package/src/mcp/staleFix.ts +277 -0
- package/src/mcp/terraform/cost.ts +163 -0
- package/src/mcp/terraform/currency.test.ts +338 -0
- package/src/mcp/terraform/currency.ts +336 -0
- package/src/mcp/terraform/decisions.ts +527 -0
- package/src/mcp/terraform/findings.ts +333 -0
- package/src/mcp/terraform/plan.ts +348 -0
- package/src/mcp/terraform/scanners.ts +809 -0
- package/src/mcp/terraform/tools.test.ts +1071 -0
- package/src/mcp/terraform/tools.ts +908 -0
- package/src/mcp/terraform/types.ts +305 -0
- package/src/mcp/terraform.test.ts +1957 -0
- package/src/mcp/terraform.ts +23 -0
- package/src/mcp/terratest.test.ts +105 -0
- package/src/mcp/terratest.ts +196 -0
- package/src/mcp/toolFiltering.test.ts +85 -0
- package/src/mcp/upload.test.ts +180 -0
- package/src/mcp/upload.ts +112 -0
- package/src/models.test.ts +300 -0
- package/src/models.ts +708 -0
- package/src/modes.test.ts +107 -0
- package/src/modes.ts +880 -0
- package/src/prep/index.ts +43 -0
- package/src/prep/installNodeDependencies.test.ts +298 -0
- package/src/prep/installNodeDependencies.ts +196 -0
- package/src/prep/installPythonDependencies.test.ts +268 -0
- package/src/prep/installPythonDependencies.ts +199 -0
- package/src/prep/types.ts +38 -0
- package/src/reviewQuality.test.ts +63 -0
- package/src/reviewQuality.ts +134 -0
- package/src/runCli.test.ts +214 -0
- package/src/runCli.ts +282 -0
- package/src/skills/terraform-best-practices/SKILL.md +369 -0
- package/src/toolState.test.ts +45 -0
- package/src/toolState.ts +252 -0
- package/src/utils/activity.test.ts +188 -0
- package/src/utils/activity.ts +210 -0
- package/src/utils/agent.test.ts +251 -0
- package/src/utils/agent.ts +139 -0
- package/src/utils/agentHangReport.test.ts +203 -0
- package/src/utils/agentHangReport.ts +170 -0
- package/src/utils/apiFetch.test.ts +115 -0
- package/src/utils/apiFetch.ts +62 -0
- package/src/utils/apiKeys.test.ts +344 -0
- package/src/utils/apiKeys.ts +206 -0
- package/src/utils/apiUrl.test.ts +30 -0
- package/src/utils/apiUrl.ts +59 -0
- package/src/utils/assets.test.ts +153 -0
- package/src/utils/assets.ts +107 -0
- package/src/utils/billingErrors.test.ts +121 -0
- package/src/utils/billingErrors.ts +189 -0
- package/src/utils/body.test.ts +217 -0
- package/src/utils/body.ts +168 -0
- package/src/utils/buildTerramendFooter.test.ts +38 -0
- package/src/utils/buildTerramendFooter.ts +82 -0
- package/src/utils/byokFallback.test.ts +205 -0
- package/src/utils/byokFallback.ts +128 -0
- package/src/utils/claudeSubscription.test.ts +179 -0
- package/src/utils/claudeSubscription.ts +93 -0
- package/src/utils/cli.ts +31 -0
- package/src/utils/codexHome.test.ts +190 -0
- package/src/utils/codexHome.ts +191 -0
- package/src/utils/codexOAuth.ts +147 -0
- package/src/utils/codexRefreshDetect.test.ts +85 -0
- package/src/utils/codexRefreshDetect.ts +35 -0
- package/src/utils/diffCoverage.test.ts +468 -0
- package/src/utils/diffCoverage.ts +404 -0
- package/src/utils/errorReport.test.ts +135 -0
- package/src/utils/errorReport.ts +83 -0
- package/src/utils/exitHandler.ts +35 -0
- package/src/utils/fixDoubleEscapedString.ts +9 -0
- package/src/utils/ghaCore.ts +13 -0
- package/src/utils/gitAuth.test.ts +322 -0
- package/src/utils/gitAuth.ts +263 -0
- package/src/utils/gitAuthServer.test.ts +260 -0
- package/src/utils/gitAuthServer.ts +182 -0
- package/src/utils/github.test.ts +615 -0
- package/src/utils/github.ts +538 -0
- package/src/utils/globals.ts +9 -0
- package/src/utils/humanEditCapture.test.ts +100 -0
- package/src/utils/humanEditCapture.ts +193 -0
- package/src/utils/install.test.ts +768 -0
- package/src/utils/install.ts +492 -0
- package/src/utils/instructions.test.ts +240 -0
- package/src/utils/instructions.ts +543 -0
- package/src/utils/leapingComment.test.ts +51 -0
- package/src/utils/leapingComment.ts +18 -0
- package/src/utils/learnings.test.ts +87 -0
- package/src/utils/learnings.ts +138 -0
- package/src/utils/learningsTocRender.test.ts +116 -0
- package/src/utils/learningsTruncate.test.ts +39 -0
- package/src/utils/learningsTruncate.ts +42 -0
- package/src/utils/lifecycle.test.ts +195 -0
- package/src/utils/lifecycle.ts +198 -0
- package/src/utils/log.test.ts +402 -0
- package/src/utils/log.ts +432 -0
- package/src/utils/normalizeEnv.test.ts +91 -0
- package/src/utils/normalizeEnv.ts +106 -0
- package/src/utils/openCodeModels.ts +82 -0
- package/src/utils/overrides.test.ts +89 -0
- package/src/utils/overrides.ts +98 -0
- package/src/utils/packageManager.test.ts +321 -0
- package/src/utils/packageManager.ts +257 -0
- package/src/utils/patchWorkflowRunFields.test.ts +92 -0
- package/src/utils/patchWorkflowRunFields.ts +150 -0
- package/src/utils/payload.test.ts +497 -0
- package/src/utils/payload.ts +371 -0
- package/src/utils/postApiFetch.ts +51 -0
- package/src/utils/prSummary.test.ts +224 -0
- package/src/utils/prSummary.ts +147 -0
- package/src/utils/progressComment.ts +261 -0
- package/src/utils/providerErrors.test.ts +315 -0
- package/src/utils/providerErrors.ts +172 -0
- package/src/utils/rangeDiff.test.ts +236 -0
- package/src/utils/rangeDiff.ts +182 -0
- package/src/utils/remediationCommand.test.ts +163 -0
- package/src/utils/remediationCommand.ts +119 -0
- package/src/utils/retry.test.ts +153 -0
- package/src/utils/retry.ts +58 -0
- package/src/utils/reviewCleanup.ts +106 -0
- package/src/utils/run.ts +99 -0
- package/src/utils/runContext.ts +145 -0
- package/src/utils/runContextData.ts +58 -0
- package/src/utils/runErrorRenderer.test.ts +95 -0
- package/src/utils/runErrorRenderer.ts +259 -0
- package/src/utils/runFixture.ts +76 -0
- package/src/utils/runLifecycle.ts +237 -0
- package/src/utils/runStartupLog.ts +60 -0
- package/src/utils/secrets.test.ts +103 -0
- package/src/utils/secrets.ts +177 -0
- package/src/utils/setup.test.ts +509 -0
- package/src/utils/setup.ts +352 -0
- package/src/utils/shell.ts +103 -0
- package/src/utils/skills.test.ts +46 -0
- package/src/utils/skills.ts +67 -0
- package/src/utils/subprocess.test.ts +170 -0
- package/src/utils/subprocess.ts +438 -0
- package/src/utils/terraformMcp.test.ts +63 -0
- package/src/utils/terraformMcp.ts +83 -0
- package/src/utils/time.test.ts +105 -0
- package/src/utils/time.ts +59 -0
- package/src/utils/timer.test.ts +91 -0
- package/src/utils/timer.ts +72 -0
- package/src/utils/todoTracking.test.ts +223 -0
- package/src/utils/todoTracking.ts +167 -0
- package/src/utils/token.test.ts +239 -0
- package/src/utils/token.ts +186 -0
- package/src/utils/version.ts +10 -0
- package/src/utils/versioning.test.ts +34 -0
- package/src/utils/versioning.ts +44 -0
- package/src/utils/vertex.ts +85 -0
- package/src/utils/workflow.ts +25 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { installNodeDependencies } from "#app/prep/installNodeDependencies";
|
|
3
|
+
import { installPythonDependencies } from "#app/prep/installPythonDependencies";
|
|
4
|
+
import type { PrepDefinition, PrepOptions, PrepResult } from "#app/prep/types";
|
|
5
|
+
import { log } from "#app/utils/cli";
|
|
6
|
+
|
|
7
|
+
export type { PrepOptions, PrepResult } from "#app/prep/types";
|
|
8
|
+
|
|
9
|
+
// register all prep steps here
|
|
10
|
+
const prepSteps: PrepDefinition[] = [installNodeDependencies, installPythonDependencies];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* run all prep steps sequentially.
|
|
14
|
+
* failures are logged as warnings but don't stop the run.
|
|
15
|
+
*/
|
|
16
|
+
export async function runPrepPhase(options: PrepOptions): Promise<PrepResult[]> {
|
|
17
|
+
log.debug("» starting prep phase...");
|
|
18
|
+
const startTime = performance.now();
|
|
19
|
+
const results: PrepResult[] = [];
|
|
20
|
+
|
|
21
|
+
for (const step of prepSteps) {
|
|
22
|
+
const shouldRun = await step.shouldRun();
|
|
23
|
+
if (!shouldRun) {
|
|
24
|
+
log.debug(`» skipping ${step.name} (not applicable)`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log.debug(`» running ${step.name}...`);
|
|
29
|
+
const result = await step.run(options);
|
|
30
|
+
results.push(result);
|
|
31
|
+
|
|
32
|
+
if (result.dependenciesInstalled) {
|
|
33
|
+
log.debug(`» ${step.name}: dependencies installed`);
|
|
34
|
+
} else if (result.issues.length > 0) {
|
|
35
|
+
log.warning(`» ${step.name}: ${result.issues[0]}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const totalDurationMs = performance.now() - startTime;
|
|
40
|
+
log.debug(`» prep phase completed (${Math.round(totalDurationMs)}ms)`);
|
|
41
|
+
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { installNodeDependencies } from "#app/prep/installNodeDependencies";
|
|
4
|
+
import type { PrepOptions } from "#app/prep/types";
|
|
5
|
+
|
|
6
|
+
vi.mock("node:fs", () => ({
|
|
7
|
+
existsSync: vi.fn(() => false),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("package-manager-detector", () => ({
|
|
11
|
+
detect: vi.fn(async () => null),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock("package-manager-detector/commands", () => ({
|
|
15
|
+
resolveCommand: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock("#app/utils/cli", () => ({
|
|
19
|
+
log: {
|
|
20
|
+
info: vi.fn(),
|
|
21
|
+
debug: vi.fn(),
|
|
22
|
+
warning: vi.fn(),
|
|
23
|
+
error: vi.fn(),
|
|
24
|
+
success: vi.fn(),
|
|
25
|
+
startGroup: vi.fn(),
|
|
26
|
+
endGroup: vi.fn(),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("#app/utils/packageManager", () => ({
|
|
31
|
+
ensurePackageManager: vi.fn(async () => false),
|
|
32
|
+
resolvePackageManagerSpec: vi.fn(async () => null),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock("#app/utils/subprocess", () => ({
|
|
36
|
+
spawn: vi.fn(),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
import { existsSync } from "node:fs";
|
|
40
|
+
import { detect } from "package-manager-detector";
|
|
41
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
42
|
+
import { ensurePackageManager, resolvePackageManagerSpec } from "#app/utils/packageManager";
|
|
43
|
+
import { spawn } from "#app/utils/subprocess";
|
|
44
|
+
|
|
45
|
+
const existsSyncMock = vi.mocked(existsSync);
|
|
46
|
+
const detectMock = vi.mocked(detect);
|
|
47
|
+
const resolveCommandMock = vi.mocked(resolveCommand);
|
|
48
|
+
const ensurePackageManagerMock = vi.mocked(ensurePackageManager);
|
|
49
|
+
const resolveSpecMock = vi.mocked(resolvePackageManagerSpec);
|
|
50
|
+
const spawnMock = vi.mocked(spawn);
|
|
51
|
+
|
|
52
|
+
const options: PrepOptions = { ignoreScripts: false, binDir: "/tmp/run/pm-bin" };
|
|
53
|
+
|
|
54
|
+
type SpawnArg = Parameters<typeof spawn>[0];
|
|
55
|
+
|
|
56
|
+
function spawnResult(init: { exitCode?: number; stdout?: string; stderr?: string } = {}) {
|
|
57
|
+
return {
|
|
58
|
+
exitCode: init.exitCode ?? 0,
|
|
59
|
+
stdout: init.stdout ?? "",
|
|
60
|
+
stderr: init.stderr ?? "",
|
|
61
|
+
durationMs: 1,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** route spawn calls by command so test setup reads as a behavior table */
|
|
66
|
+
function routeSpawn(handlers: Record<string, (call: SpawnArg) => ReturnType<typeof spawnResult>>) {
|
|
67
|
+
spawnMock.mockImplementation(async (call) => {
|
|
68
|
+
const handler = handlers[call.cmd];
|
|
69
|
+
if (!handler) throw new Error(`unexpected spawn: ${call.cmd} ${call.args.join(" ")}`);
|
|
70
|
+
return handler(call);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function whichAvailable(available: boolean) {
|
|
75
|
+
return () => spawnResult({ exitCode: available ? 0 : 1 });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function declaredSpec(overrides: Partial<{ name: string; version: string }> = {}) {
|
|
79
|
+
return {
|
|
80
|
+
name: "pnpm",
|
|
81
|
+
version: "11.1.1",
|
|
82
|
+
concrete: true,
|
|
83
|
+
source: "packageManager",
|
|
84
|
+
...overrides,
|
|
85
|
+
} as NonNullable<Awaited<ReturnType<typeof resolvePackageManagerSpec>>>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
vi.clearAllMocks();
|
|
90
|
+
detectMock.mockResolvedValue(null);
|
|
91
|
+
resolveSpecMock.mockResolvedValue(null);
|
|
92
|
+
ensurePackageManagerMock.mockResolvedValue(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("installNodeDependencies.shouldRun", () => {
|
|
96
|
+
it("runs only when package.json exists in cwd", () => {
|
|
97
|
+
existsSyncMock.mockReturnValueOnce(true);
|
|
98
|
+
expect(installNodeDependencies.shouldRun()).toBe(true);
|
|
99
|
+
existsSyncMock.mockReturnValueOnce(false);
|
|
100
|
+
expect(installNodeDependencies.shouldRun()).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("installNodeDependencies.run", () => {
|
|
105
|
+
it("skips install when no lockfile is detected (default npm path)", async () => {
|
|
106
|
+
routeSpawn({ which: whichAvailable(true) });
|
|
107
|
+
|
|
108
|
+
const result = await installNodeDependencies.run(options);
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
language: "node",
|
|
112
|
+
packageManager: "npm",
|
|
113
|
+
dependenciesInstalled: false,
|
|
114
|
+
issues: [],
|
|
115
|
+
});
|
|
116
|
+
// only the availability probe ran — no installer
|
|
117
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("fails fast when the manager is missing and shell is disabled", async () => {
|
|
121
|
+
detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
|
|
122
|
+
routeSpawn({ which: whichAvailable(false) });
|
|
123
|
+
|
|
124
|
+
const result = await installNodeDependencies.run({ ...options, ignoreScripts: true });
|
|
125
|
+
|
|
126
|
+
expect(result.dependenciesInstalled).toBe(false);
|
|
127
|
+
expect(result.issues[0]).toContain("cannot be installed when shell is disabled");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("provisions a declared manager via corepack and runs the frozen install", async () => {
|
|
131
|
+
resolveSpecMock.mockResolvedValue(declaredSpec());
|
|
132
|
+
detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
|
|
133
|
+
ensurePackageManagerMock.mockResolvedValue(true);
|
|
134
|
+
resolveCommandMock.mockReturnValue({ command: "pnpm", args: ["install", "--frozen-lockfile"] });
|
|
135
|
+
routeSpawn({
|
|
136
|
+
which: whichAvailable(false),
|
|
137
|
+
pnpm: () => spawnResult({ stdout: "done" }),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const result = await installNodeDependencies.run(options);
|
|
141
|
+
|
|
142
|
+
expect(ensurePackageManagerMock).toHaveBeenCalledWith({
|
|
143
|
+
spec: declaredSpec(),
|
|
144
|
+
binDir: options.binDir,
|
|
145
|
+
});
|
|
146
|
+
expect(result).toEqual({
|
|
147
|
+
language: "node",
|
|
148
|
+
packageManager: "pnpm",
|
|
149
|
+
dependenciesInstalled: true,
|
|
150
|
+
issues: [],
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("re-pins an already-available declared manager before installing", async () => {
|
|
155
|
+
resolveSpecMock.mockResolvedValue(declaredSpec());
|
|
156
|
+
detectMock.mockResolvedValue({ name: "pnpm", agent: "pnpm" });
|
|
157
|
+
resolveCommandMock.mockReturnValue({ command: "pnpm", args: ["install", "--frozen-lockfile"] });
|
|
158
|
+
routeSpawn({
|
|
159
|
+
which: whichAvailable(true),
|
|
160
|
+
pnpm: () => spawnResult(),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = await installNodeDependencies.run(options);
|
|
164
|
+
|
|
165
|
+
expect(ensurePackageManagerMock).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(result.dependenciesInstalled).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("falls back to npm install -g when corepack cannot provision", async () => {
|
|
170
|
+
resolveSpecMock.mockResolvedValue(declaredSpec({ name: "bun", version: "1.3.0" }));
|
|
171
|
+
detectMock.mockResolvedValue({ name: "bun", agent: "bun" });
|
|
172
|
+
ensurePackageManagerMock.mockResolvedValue(false);
|
|
173
|
+
resolveCommandMock.mockReturnValue({ command: "bun", args: ["install", "--frozen-lockfile"] });
|
|
174
|
+
const npmCalls: SpawnArg[] = [];
|
|
175
|
+
routeSpawn({
|
|
176
|
+
which: whichAvailable(false),
|
|
177
|
+
npm: (call) => {
|
|
178
|
+
npmCalls.push(call);
|
|
179
|
+
return spawnResult();
|
|
180
|
+
},
|
|
181
|
+
bun: () => spawnResult(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const result = await installNodeDependencies.run(options);
|
|
185
|
+
|
|
186
|
+
expect(npmCalls[0]?.args).toEqual(["install", "-g", "bun@1.3.0"]);
|
|
187
|
+
expect(result.dependenciesInstalled).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("surfaces fallback installer failures as issues", async () => {
|
|
191
|
+
detectMock.mockResolvedValue({ name: "yarn", agent: "yarn" });
|
|
192
|
+
routeSpawn({
|
|
193
|
+
which: whichAvailable(false),
|
|
194
|
+
npm: (call) => {
|
|
195
|
+
// exercise the stderr-forwarding callback
|
|
196
|
+
call.onStderr?.("");
|
|
197
|
+
return spawnResult({ exitCode: 1, stderr: "registry down" });
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const result = await installNodeDependencies.run(options);
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
language: "node",
|
|
205
|
+
packageManager: "yarn",
|
|
206
|
+
dependenciesInstalled: false,
|
|
207
|
+
issues: ["registry down"],
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("installs deno via curl and prepends its bin dir to PATH", async () => {
|
|
212
|
+
const originalPath = process.env.PATH;
|
|
213
|
+
const originalHome = process.env.HOME;
|
|
214
|
+
vi.stubEnv("HOME", "/home/runner");
|
|
215
|
+
vi.stubEnv("PATH", "/usr/bin");
|
|
216
|
+
try {
|
|
217
|
+
detectMock.mockResolvedValue({ name: "deno", agent: "deno" });
|
|
218
|
+
resolveCommandMock.mockReturnValue({ command: "deno", args: ["install", "--frozen"] });
|
|
219
|
+
const shCalls: SpawnArg[] = [];
|
|
220
|
+
routeSpawn({
|
|
221
|
+
which: whichAvailable(false),
|
|
222
|
+
sh: (call) => {
|
|
223
|
+
shCalls.push(call);
|
|
224
|
+
return spawnResult();
|
|
225
|
+
},
|
|
226
|
+
deno: () => spawnResult(),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const result = await installNodeDependencies.run(options);
|
|
230
|
+
|
|
231
|
+
expect(shCalls[0]?.args).toEqual(["-c", "curl -fsSL https://deno.land/install.sh | sh"]);
|
|
232
|
+
const denoBin = join("/home/runner", ".deno", "bin");
|
|
233
|
+
expect(process.env.PATH?.startsWith(`${denoBin}:`)).toBe(true);
|
|
234
|
+
expect(result.dependenciesInstalled).toBe(true);
|
|
235
|
+
} finally {
|
|
236
|
+
vi.unstubAllEnvs();
|
|
237
|
+
process.env.PATH = originalPath;
|
|
238
|
+
process.env.HOME = originalHome;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("reports an issue when no frozen-install command exists for the agent", async () => {
|
|
243
|
+
detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
|
|
244
|
+
resolveCommandMock.mockReturnValue(null);
|
|
245
|
+
routeSpawn({ which: whichAvailable(true) });
|
|
246
|
+
|
|
247
|
+
const result = await installNodeDependencies.run(options);
|
|
248
|
+
|
|
249
|
+
expect(result.issues).toEqual(["no frozen-install command available for npm"]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("appends --ignore-scripts when shell is disabled", async () => {
|
|
253
|
+
detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
|
|
254
|
+
resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
|
|
255
|
+
const npmCalls: SpawnArg[] = [];
|
|
256
|
+
routeSpawn({
|
|
257
|
+
which: whichAvailable(true),
|
|
258
|
+
npm: (call) => {
|
|
259
|
+
npmCalls.push(call);
|
|
260
|
+
return spawnResult();
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const result = await installNodeDependencies.run({ ...options, ignoreScripts: true });
|
|
265
|
+
|
|
266
|
+
expect(npmCalls[0]?.args).toEqual(["ci", "--ignore-scripts"]);
|
|
267
|
+
expect(result.dependenciesInstalled).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("returns the command output as an issue when the install fails", async () => {
|
|
271
|
+
detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
|
|
272
|
+
resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
|
|
273
|
+
routeSpawn({
|
|
274
|
+
which: whichAvailable(true),
|
|
275
|
+
npm: () => spawnResult({ exitCode: 1, stdout: "npm ERR!", stderr: "lockfile out of sync" }),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const result = await installNodeDependencies.run(options);
|
|
279
|
+
|
|
280
|
+
expect(result.dependenciesInstalled).toBe(false);
|
|
281
|
+
expect(result.issues[0]).toContain("`npm ci` failed:");
|
|
282
|
+
expect(result.issues[0]).toContain("npm ERR!");
|
|
283
|
+
expect(result.issues[0]).toContain("lockfile out of sync");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("reports the exit code when a failing install produces no output", async () => {
|
|
287
|
+
detectMock.mockResolvedValue({ name: "npm", agent: "npm" });
|
|
288
|
+
resolveCommandMock.mockReturnValue({ command: "npm", args: ["ci"] });
|
|
289
|
+
routeSpawn({
|
|
290
|
+
which: whichAvailable(true),
|
|
291
|
+
npm: () => spawnResult({ exitCode: 7 }),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result = await installNodeDependencies.run(options);
|
|
295
|
+
|
|
296
|
+
expect(result.issues[0]).toContain("exited with code 7");
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { detect } from "package-manager-detector";
|
|
4
|
+
import { resolveCommand } from "package-manager-detector/commands";
|
|
5
|
+
import type {
|
|
6
|
+
NodePackageManager,
|
|
7
|
+
NodePrepResult,
|
|
8
|
+
PrepDefinition,
|
|
9
|
+
PrepOptions,
|
|
10
|
+
} from "#app/prep/types";
|
|
11
|
+
import { log } from "#app/utils/cli";
|
|
12
|
+
import { ensurePackageManager, resolvePackageManagerSpec } from "#app/utils/packageManager";
|
|
13
|
+
import { spawn } from "#app/utils/subprocess";
|
|
14
|
+
|
|
15
|
+
async function isCommandAvailable(command: string): Promise<boolean> {
|
|
16
|
+
const result = await spawn({
|
|
17
|
+
cmd: "which",
|
|
18
|
+
args: [command],
|
|
19
|
+
env: { PATH: process.env.PATH || "" },
|
|
20
|
+
});
|
|
21
|
+
return result.exitCode === 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// fallback installers for managers corepack doesn't ship shims for.
|
|
25
|
+
// pnpm and yarn are handled by `ensurePackageManager` (corepack); bun and
|
|
26
|
+
// deno fall through to here because corepack ignores them.
|
|
27
|
+
async function installFallback(
|
|
28
|
+
name: NodePackageManager,
|
|
29
|
+
installSpec: string,
|
|
30
|
+
): Promise<string | null> {
|
|
31
|
+
if (name === "npm") return null;
|
|
32
|
+
log.info(`» installing ${installSpec} via npm install -g (corepack does not manage ${name})`);
|
|
33
|
+
const args =
|
|
34
|
+
name === "deno"
|
|
35
|
+
? ["-c", "curl -fsSL https://deno.land/install.sh | sh"]
|
|
36
|
+
: ["install", "-g", installSpec];
|
|
37
|
+
const cmd = name === "deno" ? "sh" : "npm";
|
|
38
|
+
const result = await spawn({
|
|
39
|
+
cmd,
|
|
40
|
+
args,
|
|
41
|
+
env: { PATH: process.env.PATH || "", HOME: process.env.HOME || "" },
|
|
42
|
+
onStderr: (chunk) => process.stderr.write(chunk),
|
|
43
|
+
});
|
|
44
|
+
if (result.exitCode !== 0) {
|
|
45
|
+
return result.stderr || `failed to install ${name}`;
|
|
46
|
+
}
|
|
47
|
+
if (name === "deno") {
|
|
48
|
+
const denoPath = join(process.env.HOME || "", ".deno", "bin");
|
|
49
|
+
process.env.PATH = `${denoPath}:${process.env.PATH}`;
|
|
50
|
+
}
|
|
51
|
+
log.info(`» installed ${name}`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const installNodeDependencies: PrepDefinition = {
|
|
56
|
+
name: "installNodeDependencies",
|
|
57
|
+
|
|
58
|
+
shouldRun: () => {
|
|
59
|
+
const packageJsonPath = join(process.cwd(), "package.json");
|
|
60
|
+
return existsSync(packageJsonPath);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
run: async (options: PrepOptions): Promise<NodePrepResult> => {
|
|
64
|
+
// prefer the project's declared spec (devEngines.packageManager wins over
|
|
65
|
+
// packageManager). fall back to lockfile detection when nothing is declared.
|
|
66
|
+
// restrict detect() to the lockfile strategy: `detected` here doubles as
|
|
67
|
+
// the lockfile-presence gate below, and the default strategy set also
|
|
68
|
+
// returns positives off `packageManager`/`devEngines` fields (which would
|
|
69
|
+
// mask the very case we're trying to detect — declared manager but no
|
|
70
|
+
// lockfile committed).
|
|
71
|
+
const declared = await resolvePackageManagerSpec(process.cwd());
|
|
72
|
+
const detected = await detect({ cwd: process.cwd(), strategies: ["lockfile"] });
|
|
73
|
+
|
|
74
|
+
const packageManager: NodePackageManager =
|
|
75
|
+
declared?.name ?? (detected?.name as NodePackageManager) ?? "npm";
|
|
76
|
+
const agent = detected?.agent ?? packageManager;
|
|
77
|
+
|
|
78
|
+
if (declared) {
|
|
79
|
+
log.info(
|
|
80
|
+
`» using ${packageManager}@${declared.version} from package.json (${declared.source})`,
|
|
81
|
+
);
|
|
82
|
+
} else if (detected) {
|
|
83
|
+
log.info(`» detected package manager: ${packageManager} (${agent})`);
|
|
84
|
+
} else {
|
|
85
|
+
log.info(`» no package manager declared, defaulting to npm`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// provisioning: corepack for pnpm/yarn, legacy npm-install-g for bun/deno.
|
|
89
|
+
// when shell is disabled we can't run installers (they execute code), so
|
|
90
|
+
// we require the binary to already be on PATH.
|
|
91
|
+
if (!(await isCommandAvailable(packageManager))) {
|
|
92
|
+
if (options.ignoreScripts) {
|
|
93
|
+
return {
|
|
94
|
+
language: "node",
|
|
95
|
+
packageManager,
|
|
96
|
+
dependenciesInstalled: false,
|
|
97
|
+
issues: [
|
|
98
|
+
`${packageManager} is not available and cannot be installed when shell is disabled (would execute code)`,
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let provisioned = false;
|
|
104
|
+
if (declared)
|
|
105
|
+
provisioned = await ensurePackageManager({ spec: declared, binDir: options.binDir });
|
|
106
|
+
if (!provisioned) {
|
|
107
|
+
const fallbackSpec = declared ? `${declared.name}@${declared.version}` : packageManager;
|
|
108
|
+
const installError = await installFallback(packageManager, fallbackSpec);
|
|
109
|
+
if (installError) {
|
|
110
|
+
return {
|
|
111
|
+
language: "node",
|
|
112
|
+
packageManager,
|
|
113
|
+
dependenciesInstalled: false,
|
|
114
|
+
issues: [installError],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else if (declared) {
|
|
119
|
+
// PATH already has the binary — but it may be the wrong version.
|
|
120
|
+
// ensurePackageManager is idempotent (caches on `--version` match) so
|
|
121
|
+
// this is cheap when main.ts already activated it.
|
|
122
|
+
await ensurePackageManager({ spec: declared, binDir: options.binDir });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// frozen-lockfile install only. eager prep is non-mutating by contract:
|
|
126
|
+
// we run it before the agent starts and any artifact it leaves in the
|
|
127
|
+
// tree (e.g. a generated `package-lock.json`) trips the dirty-tree
|
|
128
|
+
// post-run gate and produces a spurious PR. `frozen` commands
|
|
129
|
+
// (`npm ci`, `pnpm install --frozen-lockfile`, etc.) were assumed to
|
|
130
|
+
// fail cleanly without a lockfile — that assumption is false for
|
|
131
|
+
// pnpm 11.1.1 against a no-deps `package.json` (it silently writes an
|
|
132
|
+
// empty `pnpm-lock.yaml` despite the flag). gate on `detect()` having
|
|
133
|
+
// found a lockfile; it walks up the tree (so monorepo subpackages
|
|
134
|
+
// resolve to the workspace-root lockfile) and recognizes every
|
|
135
|
+
// manager's accepted lockfile variants (`bun.lockb` + `bun.lock`,
|
|
136
|
+
// `npm-shrinkwrap.json` + `package-lock.json`, etc.). when none is
|
|
137
|
+
// present, the project either has no installable dependencies or
|
|
138
|
+
// opts into install via a `setup` lifecycle hook
|
|
139
|
+
// (`action/utils/lifecycle.ts`); either way, eager prep should skip.
|
|
140
|
+
if (!detected) {
|
|
141
|
+
log.info(
|
|
142
|
+
`» skipping ${packageManager} install: no lockfile found (would otherwise risk lockfile drift)`,
|
|
143
|
+
);
|
|
144
|
+
return { language: "node", packageManager, dependenciesInstalled: false, issues: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const resolved = resolveCommand(agent, "frozen", []);
|
|
148
|
+
if (!resolved) {
|
|
149
|
+
return {
|
|
150
|
+
language: "node",
|
|
151
|
+
packageManager,
|
|
152
|
+
dependenciesInstalled: false,
|
|
153
|
+
issues: [`no frozen-install command available for ${agent}`],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// SECURITY: when shell is disabled, suppress lifecycle scripts to prevent
|
|
158
|
+
// agents from injecting arbitrary code execution via package.json scripts
|
|
159
|
+
if (options.ignoreScripts) {
|
|
160
|
+
resolved.args.push("--ignore-scripts");
|
|
161
|
+
log.info("» --ignore-scripts enabled (shell disabled)");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const fullCommand = `${resolved.command} ${resolved.args.join(" ")}`;
|
|
165
|
+
log.info(`» running: ${fullCommand}`);
|
|
166
|
+
const result = await spawn({
|
|
167
|
+
cmd: resolved.command,
|
|
168
|
+
args: resolved.args,
|
|
169
|
+
env: { PATH: process.env.PATH || "", HOME: process.env.HOME || "" },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
173
|
+
if (output) {
|
|
174
|
+
log.startGroup(`${fullCommand} output`);
|
|
175
|
+
log.info(output);
|
|
176
|
+
log.endGroup();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (result.exitCode !== 0) {
|
|
180
|
+
const errorMessage = output || `exited with code ${result.exitCode}`;
|
|
181
|
+
return {
|
|
182
|
+
language: "node",
|
|
183
|
+
packageManager,
|
|
184
|
+
dependenciesInstalled: false,
|
|
185
|
+
issues: [`\`${fullCommand}\` failed:\n${errorMessage}`],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
language: "node",
|
|
191
|
+
packageManager,
|
|
192
|
+
dependenciesInstalled: true,
|
|
193
|
+
issues: [],
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
};
|