@superblocksteam/vite-plugin-file-sync 2.0.167-next.0 → 2.0.168-next.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.
Files changed (251) hide show
  1. package/dist/ai-service/agent/middleware.d.ts.map +1 -1
  2. package/dist/ai-service/agent/middleware.js +23 -0
  3. package/dist/ai-service/agent/middleware.js.map +1 -1
  4. package/dist/ai-service/agent/tool-message-utils.d.ts +1 -1
  5. package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
  6. package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
  7. package/dist/ai-service/agent/tool-permission-delivery.d.ts +5 -4
  8. package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
  9. package/dist/ai-service/agent/tool-permission-delivery.js +22 -28
  10. package/dist/ai-service/agent/tool-permission-delivery.js.map +1 -1
  11. package/dist/ai-service/agent/tool-permission-outcome-metrics.d.ts +9 -0
  12. package/dist/ai-service/agent/tool-permission-outcome-metrics.d.ts.map +1 -0
  13. package/dist/ai-service/agent/tool-permission-outcome-metrics.js +23 -0
  14. package/dist/ai-service/agent/tool-permission-outcome-metrics.js.map +1 -0
  15. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts +44 -0
  16. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  17. package/dist/ai-service/agent/tools/build-capture-screenshot.js +198 -18
  18. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  19. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +2 -0
  20. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts.map +1 -1
  21. package/dist/ai-service/agent/tools/build-manage-checklist.js.map +1 -1
  22. package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
  23. package/dist/ai-service/agent/tools/build-reload-file.js +32 -19
  24. package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
  25. package/dist/ai-service/agent/tools/get-security-findings.d.ts +45 -0
  26. package/dist/ai-service/agent/tools/get-security-findings.d.ts.map +1 -0
  27. package/dist/ai-service/agent/tools/get-security-findings.js +74 -0
  28. package/dist/ai-service/agent/tools/get-security-findings.js.map +1 -0
  29. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts +2 -2
  30. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
  31. package/dist/ai-service/agent/tools/integrations/integration-setup.js +24 -2
  32. package/dist/ai-service/agent/tools/integrations/integration-setup.js.map +1 -1
  33. package/dist/ai-service/agent/tools/report-security-findings.d.ts +20 -20
  34. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  35. package/dist/ai-service/agent/tools.js +13 -2
  36. package/dist/ai-service/agent/tools.js.map +1 -1
  37. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
  38. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +14 -0
  39. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
  40. package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -1
  41. package/dist/ai-service/agent/tools2/tools/git.d.ts.map +1 -1
  42. package/dist/ai-service/agent/tools2/tools/git.js +1 -1
  43. package/dist/ai-service/agent/tools2/tools/git.js.map +1 -1
  44. package/dist/ai-service/agent/utils.d.ts.map +1 -1
  45. package/dist/ai-service/agent/utils.js +5 -0
  46. package/dist/ai-service/agent/utils.js.map +1 -1
  47. package/dist/ai-service/app-interface/app-package-manager.d.ts +43 -0
  48. package/dist/ai-service/app-interface/app-package-manager.d.ts.map +1 -0
  49. package/dist/ai-service/app-interface/app-package-manager.js +82 -0
  50. package/dist/ai-service/app-interface/app-package-manager.js.map +1 -0
  51. package/dist/ai-service/app-interface/constants.d.ts +2 -2
  52. package/dist/ai-service/app-interface/constants.d.ts.map +1 -1
  53. package/dist/ai-service/app-interface/constants.js +5 -0
  54. package/dist/ai-service/app-interface/constants.js.map +1 -1
  55. package/dist/ai-service/app-interface/file-system-interface.d.ts.map +1 -1
  56. package/dist/ai-service/app-interface/file-system-interface.js +0 -3
  57. package/dist/ai-service/app-interface/file-system-interface.js.map +1 -1
  58. package/dist/ai-service/app-interface/npm-error-parser.d.ts +1 -1
  59. package/dist/ai-service/app-interface/npm-error-parser.d.ts.map +1 -1
  60. package/dist/ai-service/app-interface/npm-error-parser.js +42 -8
  61. package/dist/ai-service/app-interface/npm-error-parser.js.map +1 -1
  62. package/dist/ai-service/app-interface/npm-registry.d.ts +30 -0
  63. package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -1
  64. package/dist/ai-service/app-interface/npm-registry.js +108 -1
  65. package/dist/ai-service/app-interface/npm-registry.js.map +1 -1
  66. package/dist/ai-service/app-interface/pnpm-lockfile.d.ts +28 -0
  67. package/dist/ai-service/app-interface/pnpm-lockfile.d.ts.map +1 -0
  68. package/dist/ai-service/app-interface/pnpm-lockfile.js +89 -0
  69. package/dist/ai-service/app-interface/pnpm-lockfile.js.map +1 -0
  70. package/dist/ai-service/app-interface/shell.d.ts +43 -7
  71. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  72. package/dist/ai-service/app-interface/shell.js +252 -119
  73. package/dist/ai-service/app-interface/shell.js.map +1 -1
  74. package/dist/ai-service/app-interface/vulnerability-gate.d.ts +5 -0
  75. package/dist/ai-service/app-interface/vulnerability-gate.d.ts.map +1 -1
  76. package/dist/ai-service/app-interface/vulnerability-gate.js +3 -0
  77. package/dist/ai-service/app-interface/vulnerability-gate.js.map +1 -1
  78. package/dist/ai-service/app-skills/helpers.d.ts.map +1 -1
  79. package/dist/ai-service/app-skills/helpers.js +9 -9
  80. package/dist/ai-service/app-skills/helpers.js.map +1 -1
  81. package/dist/ai-service/chat/chat-session-store.d.ts +4 -1
  82. package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
  83. package/dist/ai-service/chat/chat-session-store.js +4 -0
  84. package/dist/ai-service/chat/chat-session-store.js.map +1 -1
  85. package/dist/ai-service/checklist/persisted-checklist-store.d.ts +66 -1
  86. package/dist/ai-service/checklist/persisted-checklist-store.d.ts.map +1 -1
  87. package/dist/ai-service/checklist/persisted-checklist-store.js +274 -4
  88. package/dist/ai-service/checklist/persisted-checklist-store.js.map +1 -1
  89. package/dist/ai-service/checklist/policy-remediation.d.ts +8 -0
  90. package/dist/ai-service/checklist/policy-remediation.d.ts.map +1 -0
  91. package/dist/ai-service/checklist/policy-remediation.js +46 -0
  92. package/dist/ai-service/checklist/policy-remediation.js.map +1 -0
  93. package/dist/ai-service/checklist/remediation-findings-client.d.ts +31 -0
  94. package/dist/ai-service/checklist/remediation-findings-client.d.ts.map +1 -0
  95. package/dist/ai-service/checklist/remediation-findings-client.js +117 -0
  96. package/dist/ai-service/checklist/remediation-findings-client.js.map +1 -0
  97. package/dist/ai-service/context-download.d.ts.map +1 -1
  98. package/dist/ai-service/context-download.js +76 -37
  99. package/dist/ai-service/context-download.js.map +1 -1
  100. package/dist/ai-service/dev-database-file-sync.d.ts +2 -1
  101. package/dist/ai-service/dev-database-file-sync.d.ts.map +1 -1
  102. package/dist/ai-service/dev-database-file-sync.js.map +1 -1
  103. package/dist/ai-service/index.d.ts +59 -9
  104. package/dist/ai-service/index.d.ts.map +1 -1
  105. package/dist/ai-service/index.js +1191 -479
  106. package/dist/ai-service/index.js.map +1 -1
  107. package/dist/ai-service/integrations/store.d.ts.map +1 -1
  108. package/dist/ai-service/integrations/store.js +11 -2
  109. package/dist/ai-service/integrations/store.js.map +1 -1
  110. package/dist/ai-service/llm/context-v2/adapter.d.ts +3 -0
  111. package/dist/ai-service/llm/context-v2/adapter.d.ts.map +1 -1
  112. package/dist/ai-service/llm/context-v2/adapter.js +6 -0
  113. package/dist/ai-service/llm/context-v2/adapter.js.map +1 -1
  114. package/dist/ai-service/llm/context-v2/context.d.ts +22 -1
  115. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  116. package/dist/ai-service/llm/context-v2/context.js +185 -30
  117. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  118. package/dist/ai-service/llm/context-v2/conversation-context.d.ts +7 -0
  119. package/dist/ai-service/llm/context-v2/conversation-context.d.ts.map +1 -1
  120. package/dist/ai-service/llm/context-v2/manager.d.ts +11 -10
  121. package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
  122. package/dist/ai-service/llm/context-v2/manager.js +128 -85
  123. package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
  124. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts +73 -1
  125. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts.map +1 -1
  126. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +152 -24
  127. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -1
  128. package/dist/ai-service/llm/context-v2/storage/event-types.js +141 -0
  129. package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -1
  130. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts +20 -2
  131. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts.map +1 -1
  132. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js +111 -49
  133. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js.map +1 -1
  134. package/dist/ai-service/llm/context-v2/storage/migration.d.ts.map +1 -1
  135. package/dist/ai-service/llm/context-v2/storage/migration.js +31 -4
  136. package/dist/ai-service/llm/context-v2/storage/migration.js.map +1 -1
  137. package/dist/ai-service/llm/context-v2/token-tracker.d.ts +34 -0
  138. package/dist/ai-service/llm/context-v2/token-tracker.d.ts.map +1 -1
  139. package/dist/ai-service/llm/context-v2/token-tracker.js +164 -0
  140. package/dist/ai-service/llm/context-v2/token-tracker.js.map +1 -1
  141. package/dist/ai-service/llm/context-v2/types.d.ts +5 -2
  142. package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
  143. package/dist/ai-service/llm/context-v2/types.js +1 -1
  144. package/dist/ai-service/llm/context-v2/types.js.map +1 -1
  145. package/dist/ai-service/llm/stream/config.d.ts +7 -0
  146. package/dist/ai-service/llm/stream/config.d.ts.map +1 -1
  147. package/dist/ai-service/llm/stream/config.js +20 -2
  148. package/dist/ai-service/llm/stream/config.js.map +1 -1
  149. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  150. package/dist/ai-service/llm/stream/orchestrator.js +30 -14
  151. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  152. package/dist/ai-service/llm/stream/session.d.ts +1 -1
  153. package/dist/ai-service/llm/stream/session.d.ts.map +1 -1
  154. package/dist/ai-service/llm/stream/session.js +1 -1
  155. package/dist/ai-service/llm/stream/session.js.map +1 -1
  156. package/dist/ai-service/security/secret-scanner-service.d.ts.map +1 -1
  157. package/dist/ai-service/security/secret-scanner-service.js +12 -2
  158. package/dist/ai-service/security/secret-scanner-service.js.map +1 -1
  159. package/dist/ai-service/security/secret-scanner.d.ts +1 -1
  160. package/dist/ai-service/security/secret-scanner.d.ts.map +1 -1
  161. package/dist/ai-service/security/secret-scanner.js +4 -1
  162. package/dist/ai-service/security/secret-scanner.js.map +1 -1
  163. package/dist/ai-service/security/vulnerability-triage.d.ts +2 -2
  164. package/dist/ai-service/state-machine/clark-fsm.d.ts +17 -1
  165. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  166. package/dist/ai-service/state-machine/clark-fsm.js +85 -8
  167. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  168. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  169. package/dist/ai-service/state-machine/handlers/agent-planning.js +97 -47
  170. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  171. package/dist/ai-service/state-machine/handlers/awaiting-user.d.ts.map +1 -1
  172. package/dist/ai-service/state-machine/handlers/awaiting-user.js +68 -50
  173. package/dist/ai-service/state-machine/handlers/awaiting-user.js.map +1 -1
  174. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  175. package/dist/ai-service/state-machine/handlers/llm-generating.js +91 -73
  176. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  177. package/dist/ai-service/state-machine/helpers/prompt-failure.d.ts +1 -1
  178. package/dist/ai-service/state-machine/helpers/prompt-failure.d.ts.map +1 -1
  179. package/dist/ai-service/state-machine/helpers/prompt-failure.js +1 -0
  180. package/dist/ai-service/state-machine/helpers/prompt-failure.js.map +1 -1
  181. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +2 -1
  182. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
  183. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +1 -0
  184. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
  185. package/dist/ai-service/state-machine/helpers/user-response-message.d.ts +4 -0
  186. package/dist/ai-service/state-machine/helpers/user-response-message.d.ts.map +1 -0
  187. package/dist/ai-service/state-machine/helpers/user-response-message.js +37 -0
  188. package/dist/ai-service/state-machine/helpers/user-response-message.js.map +1 -0
  189. package/dist/ai-service/template-renderer.d.ts.map +1 -1
  190. package/dist/ai-service/template-renderer.js +22 -9
  191. package/dist/ai-service/template-renderer.js.map +1 -1
  192. package/dist/ai-service/types.d.ts +2 -1
  193. package/dist/ai-service/types.d.ts.map +1 -1
  194. package/dist/ai-service/types.js.map +1 -1
  195. package/dist/components-manager.d.ts +9 -2
  196. package/dist/components-manager.d.ts.map +1 -1
  197. package/dist/components-manager.js +0 -4
  198. package/dist/components-manager.js.map +1 -1
  199. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  200. package/dist/file-sync-vite-plugin.js +2 -1
  201. package/dist/file-sync-vite-plugin.js.map +1 -1
  202. package/dist/file-system-helpers.d.ts +7 -0
  203. package/dist/file-system-helpers.d.ts.map +1 -1
  204. package/dist/file-system-helpers.js +9 -1
  205. package/dist/file-system-helpers.js.map +1 -1
  206. package/dist/file-system-manager.d.ts +12 -1
  207. package/dist/file-system-manager.d.ts.map +1 -1
  208. package/dist/file-system-manager.js +37 -22
  209. package/dist/file-system-manager.js.map +1 -1
  210. package/dist/index.d.ts +7 -0
  211. package/dist/index.d.ts.map +1 -1
  212. package/dist/index.js +7 -0
  213. package/dist/index.js.map +1 -1
  214. package/dist/lock-service/index.d.ts +20 -0
  215. package/dist/lock-service/index.d.ts.map +1 -1
  216. package/dist/lock-service/index.js +53 -5
  217. package/dist/lock-service/index.js.map +1 -1
  218. package/dist/migration/get-fullstack-template-dir.js +2 -2
  219. package/dist/migration/get-fullstack-template-dir.js.map +1 -1
  220. package/dist/migration/migration-routes.d.ts.map +1 -1
  221. package/dist/migration/migration-routes.js +8 -2
  222. package/dist/migration/migration-routes.js.map +1 -1
  223. package/dist/plugin-options.d.ts +1 -0
  224. package/dist/plugin-options.d.ts.map +1 -1
  225. package/dist/plugin-options.js.map +1 -1
  226. package/dist/router-parser.d.ts.map +1 -1
  227. package/dist/router-parser.js +336 -65
  228. package/dist/router-parser.js.map +1 -1
  229. package/dist/socket-manager.d.ts +25 -2
  230. package/dist/socket-manager.d.ts.map +1 -1
  231. package/dist/socket-manager.js +270 -55
  232. package/dist/socket-manager.js.map +1 -1
  233. package/dist/split-edit-after-clark.d.ts +14 -0
  234. package/dist/split-edit-after-clark.d.ts.map +1 -0
  235. package/dist/split-edit-after-clark.js +40 -0
  236. package/dist/split-edit-after-clark.js.map +1 -0
  237. package/dist/sync-service/index.d.ts +2 -1
  238. package/dist/sync-service/index.d.ts.map +1 -1
  239. package/dist/sync-service/index.js +5 -0
  240. package/dist/sync-service/index.js.map +1 -1
  241. package/dist/sync-service/list-dir.d.ts.map +1 -1
  242. package/dist/sync-service/list-dir.js +9 -4
  243. package/dist/sync-service/list-dir.js.map +1 -1
  244. package/dist/util/log-sanitizer.d.ts.map +1 -1
  245. package/dist/util/log-sanitizer.js +4 -0
  246. package/dist/util/log-sanitizer.js.map +1 -1
  247. package/dist/vite-module-reload.d.ts +34 -0
  248. package/dist/vite-module-reload.d.ts.map +1 -0
  249. package/dist/vite-module-reload.js +121 -0
  250. package/dist/vite-module-reload.js.map +1 -0
  251. package/package.json +9 -8
@@ -34,7 +34,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
34
34
  };
35
35
  import { isUtf8 } from "node:buffer";
36
36
  import { exec, execFile } from "node:child_process";
37
- import { readFile as fsReadFile, mkdir, writeFile, access, rm, stat, readdir, } from "node:fs/promises";
37
+ import { readFile as fsReadFile, mkdir, writeFile, access, rm, stat, readdir, mkdtemp, copyFile, } from "node:fs/promises";
38
+ import * as os from "node:os";
38
39
  import * as path from "node:path";
39
40
  import { promisify } from "node:util";
40
41
  import { metrics } from "@opentelemetry/api";
@@ -53,12 +54,14 @@ import { ToolExecutionError } from "../agent/tools2/types.js";
53
54
  import llmobs from "../llmobs/index.js";
54
55
  import { fixLucideDynamicTypeImports, isFixableLucidePath, } from "../transform/lucide-dynamic-type-imports/transformer.js";
55
56
  import { NpmInstallBlocked, NpmInstallError, scrubSecrets, } from "../types.js";
57
+ import { detectAppPackageManager, isPnpmEnabled, podPnpmEnv, } from "./app-package-manager.js";
56
58
  import { execCancellable } from "./cancellable-exec.js";
57
59
  import { CLARK_APP_BLACKLIST_PATTERNS, APPLICATION_FILE_PATTERNS, STANDARD_IGNORE_PATTERNS, } from "./constants.js";
58
60
  import { DraftManager, validateFileOperation, } from "./filesystem/index.js";
59
61
  import { Linter } from "./linter.js";
60
- import { parseNpmJsonDiagnostic, parseNpmJsonError, parseNpmJsonSuccess, } from "./npm-error-parser.js";
62
+ import { classifyNpmExecStderr, parseNpmJsonDiagnostic, parseNpmJsonError, parseNpmJsonSuccess, } from "./npm-error-parser.js";
61
63
  import { prepareForPrivateRegistry, shouldIgnoreInstallScripts, } from "./npm-registry.js";
64
+ import { readPnpmLockfile } from "./pnpm-lockfile.js";
62
65
  import { parseTypecheckOutputByPath } from "./typecheck-output-parser.js";
63
66
  import { applyPackageSubstitutions, runVulnerabilityGate, } from "./vulnerability-gate.js";
64
67
  const execPromise = promisify(exec);
@@ -71,6 +74,16 @@ const execFilePromise = promisify(execFile);
71
74
  // covers real-world npm summaries (per-package entries are tiny) while
72
75
  // keeping the worst-case buffer + parsed-object peak bounded.
73
76
  const NPM_JSON_MAX_BUFFER = 4 * 1024 * 1024;
77
+ /**
78
+ * Flags that keep an install from running the app's own code when the org
79
+ * disallows install scripts. pnpm also runs `.pnpmfile.cjs` / `.pnpmfile.mjs`
80
+ * hooks, which `--ignore-scripts` does not stop.
81
+ */
82
+ function ignoreScriptsFlags(packageManager) {
83
+ return packageManager === "pnpm" && isPnpmEnabled()
84
+ ? ["--ignore-scripts", "--ignore-pnpmfile"]
85
+ : ["--ignore-scripts"];
86
+ }
74
87
  /** Lowest version a declared range admits; skips `workspace:`/`file:`/`*`/git. */
75
88
  function declaredRangeFloor(range) {
76
89
  if (range == null || semver.validRange(range) == null)
@@ -720,13 +733,14 @@ let AppShell = (() => {
720
733
  * raw registry hostname or token cannot reach an exported attribute even
721
734
  * if an upstream caller forgot to scrub.
722
735
  */
723
- observeNpmInstall(runner, registryFetchResult, outcome, durationMs) {
736
+ observeNpmInstall(runner, registryFetchResult, outcome, durationMs, packageManager) {
724
737
  const tags = recordNpmInstall({
725
738
  runner,
726
739
  registryHost: registryUrlFromFetchResult(registryFetchResult),
727
740
  outcome,
728
741
  configured: registryFetchResult?.config.configured ?? false,
729
742
  durationMs,
743
+ packageManager,
730
744
  });
731
745
  // Record whether `--ignore-scripts` was added for this install on the span
732
746
  // only, not as a metric label: the question is "did this one install skip
@@ -872,6 +886,7 @@ let AppShell = (() => {
872
886
  await this.maybePreparePackageJsonForCloudNpm();
873
887
  await this.applyDeclaredDependencyUpgrades(await this.maybeRemediateVulnerabilities({
874
888
  abortSignal,
889
+ ignoreScripts: shouldIgnoreInstallScripts(registryFetchResult),
875
890
  mode: "all",
876
891
  }));
877
892
  });
@@ -884,18 +899,61 @@ let AppShell = (() => {
884
899
  * uses (`buildInstallEnv` in the SDK). These execs otherwise inherit
885
900
  * `process.env` implicitly, so we spread it before adding the override.
886
901
  * pnpm has no equivalent logs-dir config and ignores the var (npm-only).
902
+ * In the pod pnpm also gets the same settings as the startup install (see
903
+ * `podPnpmEnv`); npm ignores those vars. Local dev keeps the developer's own
904
+ * store and build approvals.
887
905
  */
888
906
  installLogEnv() {
889
907
  return {
890
908
  ...process.env,
891
909
  NPM_CONFIG_LOGS_DIR: path.join(this.appRootDirPath, ".superblocks", "logs"),
910
+ ...(process.env.SUPERBLOCKS_SERVER_ENV === "local" || !isPnpmEnabled()
911
+ ? {}
912
+ : podPnpmEnv(this.appRootDirPath)),
892
913
  };
893
914
  }
894
915
  /**
895
- * Cloud edit pods force npm for agent-driven installs. npm rejects pnpm's
896
- * `workspace:` protocol, so rewrite platform deps and strip any remaining
897
- * workspace entries before spawning npm (APPS-3752 bootstrap does this on
898
- * pod start; migration restructure and agent installs need the same guard).
916
+ * The package manager for agent-driven installs. Local dev (csb mock server)
917
+ * keeps pnpm, to stay out of the surrounding workspace's lockfile and store.
918
+ * Cloud edit asks `detectAppPackageManager`, the same decision the
919
+ * dev server's startup install makes, so the two never mix a pnpm
920
+ * node_modules with an npm install.
921
+ */
922
+ async appPackageManager() {
923
+ if (process.env.SUPERBLOCKS_SERVER_ENV === "local") {
924
+ return "pnpm";
925
+ }
926
+ return detectAppPackageManager(this.appRootDirPath);
927
+ }
928
+ /**
929
+ * The package manager for `runPackageManagerInstall` and
930
+ * `installSpecificPackages`. While pnpm is off, local dev detects the app's
931
+ * package manager, as these two did before pnpm support. Everything else is
932
+ * `appPackageManager`.
933
+ */
934
+ async installingPackageManager() {
935
+ if (isPnpmEnabled() || process.env.SUPERBLOCKS_SERVER_ENV !== "local") {
936
+ return this.appPackageManager();
937
+ }
938
+ const detected = await detect({
939
+ strategies: [
940
+ "packageManager-field",
941
+ "lockfile",
942
+ "install-metadata",
943
+ "devEngines-field",
944
+ ],
945
+ cwd: this.appRootDirPath,
946
+ });
947
+ if (!detected) {
948
+ throw new Error("Could not detect package manager");
949
+ }
950
+ return detected.agent;
951
+ }
952
+ /**
953
+ * npm rejects pnpm's `workspace:` protocol, so in the pod rewrite platform
954
+ * deps and strip any remaining workspace entries before an install (APPS-3752
955
+ * bootstrap does this on pod start; migration restructure and agent installs
956
+ * need the same guard).
899
957
  */
900
958
  async maybePreparePackageJsonForCloudNpm() {
901
959
  if (process.env.SUPERBLOCKS_SERVER_ENV === "local") {
@@ -910,6 +968,7 @@ let AppShell = (() => {
910
968
  const installStart = Date.now();
911
969
  let outcome = "success";
912
970
  let capturedFetchResult;
971
+ let capturedPackageManager;
913
972
  try {
914
973
  // `withAuthFailedRetry` owns the `.npmrc` materialization (via
915
974
  // `prepareForPrivateRegistry`) and the one-shot auth_failed retry —
@@ -925,22 +984,22 @@ let AppShell = (() => {
925
984
  await this.maybePreparePackageJsonForCloudNpm();
926
985
  const appliedSubstitutions = await this.applyDeclaredDependencyUpgrades(await this.maybeRemediateVulnerabilities({
927
986
  abortSignal,
987
+ ignoreScripts: shouldIgnoreInstallScripts(registryFetchResult),
928
988
  mode: "all",
929
989
  }));
930
990
  const ignoreScripts = shouldIgnoreInstallScripts(registryFetchResult);
931
- const isNpm = process.env.SUPERBLOCKS_SERVER_ENV !== "local";
991
+ capturedPackageManager = await this.appPackageManager();
992
+ const isNpm = capturedPackageManager === "npm";
932
993
  // prepare already ran above so Mode 2 cloud dry-run sees semver deps.
933
- // In csb mock server (local dev) we use pnpm to avoid lockfile / store
934
- // conflicts with the surrounding workspace; everywhere else we force npm
935
- // so the `--json` error contract — and therefore the registry-blocked
936
- // advice matrix — applies on this post-processing path too. When the
937
- // org has opted out via `allow_install_scripts=false`, append
938
- // `--ignore-scripts` (the flag is honored by both npm and pnpm).
994
+ // When the org has opted out via `allow_install_scripts=false`, append
995
+ // `--ignore-scripts` (and `--ignore-pnpmfile` for pnpm, see `ignoreScriptsFlags`).
939
996
  const baseCommand = isNpm
940
997
  ? "npm install --fund=false --audit=false --json"
941
- : "pnpm install --config.confirmModulesPurge=false";
998
+ : isPnpmEnabled()
999
+ ? "pnpm install --no-frozen-lockfile"
1000
+ : "pnpm install";
942
1001
  const command = ignoreScripts
943
- ? `${baseCommand} --ignore-scripts`
1002
+ ? `${baseCommand} ${ignoreScriptsFlags(capturedPackageManager).join(" ")}`
944
1003
  : baseCommand;
945
1004
  await new Promise((resolve, reject) => {
946
1005
  exec(command, {
@@ -956,14 +1015,12 @@ let AppShell = (() => {
956
1015
  // Route the npm path through the same classifier as the agent-tool
957
1016
  // install entry points so E404 / E401 / ENOTFOUND surface as a
958
1017
  // `NpmInstallBlocked` (with row-aware advice) instead of a raw
959
- // `NpmInstallError(stderr)`. The pnpm path doesn't emit the
960
- // `--json` envelope, so the classifier falls through to the
961
- // legacy error for it — registry-blocked scenarios only matter
962
- // in cloud-edit (which is the npm branch).
963
- if (isNpm) {
964
- return reject(this.classifyNpmExecError({ stdout, stderr, message: error.message }, undefined, registryFetchResult));
965
- }
966
- return reject(new NpmInstallError(stderr));
1018
+ // `NpmInstallError(stderr)`. pnpm has no `--json` envelope, so
1019
+ // its classifier reads pnpm's plain output instead.
1020
+ const failure = { stdout, stderr, message: error.message };
1021
+ return reject(isNpm
1022
+ ? this.classifyNpmExecError(failure, undefined, registryFetchResult)
1023
+ : this.classifyPnpmExecError(failure, undefined, registryFetchResult));
967
1024
  }
968
1025
  return resolve();
969
1026
  });
@@ -979,7 +1036,7 @@ let AppShell = (() => {
979
1036
  throw err;
980
1037
  }
981
1038
  finally {
982
- this.observeNpmInstall("install_all", capturedFetchResult, outcome, Date.now() - installStart);
1039
+ this.observeNpmInstall("install_all", capturedFetchResult, outcome, Date.now() - installStart, capturedPackageManager);
983
1040
  }
984
1041
  }
985
1042
  /**
@@ -1078,7 +1135,7 @@ let AppShell = (() => {
1078
1135
  * rewrite). Local stays floors-only — pnpm + lingering `workspace:*` mean
1079
1136
  * full-tree coverage is not available there.
1080
1137
  */
1081
- async resolveInstallTree(mode, packages, abortSignal) {
1138
+ async resolveInstallTree(mode, packages, abortSignal, ignoreScripts) {
1082
1139
  if (mode === "all") {
1083
1140
  const topLevel = [];
1084
1141
  for (const pkg of packages ?? []) {
@@ -1093,7 +1150,7 @@ let AppShell = (() => {
1093
1150
  const fromLock = await this.readPackagesFromPackageLock();
1094
1151
  let dryTree = [];
1095
1152
  try {
1096
- dryTree = (await this.resolveTreeViaNpmDryRun([], abortSignal)).tree;
1153
+ dryTree = (await this.resolveTreeViaNpmDryRun([], abortSignal, ignoreScripts)).tree;
1097
1154
  }
1098
1155
  catch (err) {
1099
1156
  // Do not continue remediation after cancel.
@@ -1113,7 +1170,7 @@ let AppShell = (() => {
1113
1170
  return { resolves: true, topLevel, tree: [...byKey.values()] };
1114
1171
  }
1115
1172
  const specs = (packages ?? []).map((pkg) => pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name);
1116
- const resolved = await this.resolveTreeViaNpmDryRun(specs, abortSignal);
1173
+ const resolved = await this.resolveTreeViaNpmDryRun(specs, abortSignal, ignoreScripts);
1117
1174
  // Prefer dry-run concrete pins for top-level. Never inject the raw
1118
1175
  // requested range/`latest` string into the lookup tree — the index
1119
1176
  // keys on concrete versions, and a range would fail coverage checks.
@@ -1149,9 +1206,13 @@ let AppShell = (() => {
1149
1206
  tree: [...byKey.values()],
1150
1207
  topLevel,
1151
1208
  resolves: resolved.resolves,
1209
+ unnamed: resolved.unnamed,
1152
1210
  };
1153
1211
  }
1154
- async resolveTreeViaNpmDryRun(specs, abortSignal) {
1212
+ async resolveTreeViaNpmDryRun(specs, abortSignal, ignoreScripts) {
1213
+ if (isPnpmEnabled() && (await this.appPackageManager()) === "pnpm") {
1214
+ return this.resolveTreeViaPnpmLockfile(specs, abortSignal, ignoreScripts);
1215
+ }
1155
1216
  const args = [
1156
1217
  "install",
1157
1218
  "--dry-run",
@@ -1202,6 +1263,72 @@ let AppShell = (() => {
1202
1263
  throw error;
1203
1264
  }
1204
1265
  }
1266
+ /**
1267
+ * pnpm's dry run for the install security check. npm's `--dry-run` crashes
1268
+ * on a node_modules that pnpm laid out, and pnpm has no dry run of its own,
1269
+ * so this resolves in a throwaway copy of the app's manifest and lockfile
1270
+ * with `--lockfile-only` (nothing downloaded or linked) and reads the tree
1271
+ * pnpm wrote. That is exactly what the real install will pick, overrides and
1272
+ * all. Any pnpm failure counts as "does not resolve", as for npm's error
1273
+ * envelope, so the check never vouches for a tree it couldn't see.
1274
+ */
1275
+ async resolveTreeViaPnpmLockfile(specs, abortSignal, ignoreScripts) {
1276
+ const dir = await mkdtemp(path.join(os.tmpdir(), "sb-pnpm-resolve-"));
1277
+ try {
1278
+ for (const file of [
1279
+ "package.json",
1280
+ "pnpm-lock.yaml",
1281
+ "pnpm-workspace.yaml",
1282
+ ".npmrc",
1283
+ // The real install runs the app's pnpmfile hooks, which can add
1284
+ // dependencies, so the check has to run them too. Unless the org
1285
+ // disallows install scripts: then no install runs them.
1286
+ ...(ignoreScripts ? [] : [".pnpmfile.cjs", ".pnpmfile.mjs"]),
1287
+ ]) {
1288
+ await copyFile(path.join(this.appRootDirPath, file), path.join(dir, file)).catch(() => undefined);
1289
+ }
1290
+ // `--` so a spec that looks like an option (`--registry=…`) is read as
1291
+ // a package name, as in the npm dry run. execFile, so no shell either.
1292
+ // `--ignore-scripts` always: resolving the tree needs no builds.
1293
+ const flags = [
1294
+ "--lockfile-only",
1295
+ ...(ignoreScripts ? ignoreScriptsFlags("pnpm") : ["--ignore-scripts"]),
1296
+ ];
1297
+ const args = specs.length > 0
1298
+ ? ["add", ...flags, "--", ...specs]
1299
+ : // The lockfile has to be allowed to change, as on the real
1300
+ // install. pnpm 12 already allows that with `--lockfile-only`
1301
+ // under `CI=true`; the flag says so outright. `pnpm add` rejects it.
1302
+ ["install", ...flags, "--no-frozen-lockfile"];
1303
+ await execFilePromise("pnpm", args, {
1304
+ cwd: dir,
1305
+ env: this.installLogEnv(),
1306
+ signal: abortSignal,
1307
+ maxBuffer: NPM_JSON_MAX_BUFFER,
1308
+ });
1309
+ const locked = readPnpmLockfile(await fsReadFile(path.join(dir, "pnpm-lock.yaml"), "utf8"));
1310
+ if (!locked)
1311
+ return { tree: [], topLevel: [], resolves: false };
1312
+ // A tarball or git package already in the app's lockfile is already
1313
+ // installed; only the ones this install adds are new.
1314
+ const installed = new Set((await this.readPackagesFromPackageLock())?.unnamed);
1315
+ return {
1316
+ ...locked,
1317
+ unnamed: locked.unnamed.filter((key) => !installed.has(key)),
1318
+ resolves: true,
1319
+ };
1320
+ }
1321
+ catch (error) {
1322
+ if (abortSignal?.aborted ||
1323
+ (error instanceof Error && error.name === "AbortError")) {
1324
+ throw error;
1325
+ }
1326
+ return { tree: [], topLevel: [], resolves: false };
1327
+ }
1328
+ finally {
1329
+ await rm(dir, { recursive: true, force: true });
1330
+ }
1331
+ }
1205
1332
  treeFromNpmDryRunJson(parsed, specs) {
1206
1333
  if (parsed == null || typeof parsed !== "object") {
1207
1334
  return { tree: [], topLevel: [], resolves: true };
@@ -1257,6 +1384,12 @@ let AppShell = (() => {
1257
1384
  return { tree, topLevel, resolves: true };
1258
1385
  }
1259
1386
  async readPackagesFromPackageLock() {
1387
+ // A pnpm app's installed tree is in `pnpm-lock.yaml`; a `package-lock.json`
1388
+ // left over from its npm days would describe packages pnpm never installs.
1389
+ if (isPnpmEnabled() && (await this.appPackageManager()) === "pnpm") {
1390
+ const locked = readPnpmLockfile(await fsReadFile(path.join(this.appRootDirPath, "pnpm-lock.yaml"), "utf8").catch(() => ""));
1391
+ return locked ? { ...locked, resolves: true } : null;
1392
+ }
1260
1393
  const lockPath = path.join(this.appRootDirPath, "package-lock.json");
1261
1394
  let raw;
1262
1395
  try {
@@ -1332,7 +1465,7 @@ let AppShell = (() => {
1332
1465
  * No-op when `packageLookup` is unwired. Index failures → malware fail-closed.
1333
1466
  */
1334
1467
  async maybeRemediateVulnerabilities(args) {
1335
- const { abortSignal, cveUserOverride, mode } = args;
1468
+ const { abortSignal, cveUserOverride, ignoreScripts, mode } = args;
1336
1469
  if (!this.packageLookup) {
1337
1470
  return { substitutions: [] };
1338
1471
  }
@@ -1348,8 +1481,8 @@ let AppShell = (() => {
1348
1481
  getLogger().warn(message, getErrorMeta(error));
1349
1482
  },
1350
1483
  pickQuarantineHoldback: this.packageLookup.pickQuarantineHoldback,
1351
- resolveInstallTree: async (resolveArgs) => await this.resolveInstallTree(resolveArgs.mode, resolveArgs.packages, resolveArgs.abortSignal),
1352
- resolvePackageSpecs: async (resolveArgs) => await this.resolveTreeViaNpmDryRun(resolveArgs.specs, resolveArgs.abortSignal),
1484
+ resolveInstallTree: async (resolveArgs) => await this.resolveInstallTree(resolveArgs.mode, resolveArgs.packages, resolveArgs.abortSignal, ignoreScripts),
1485
+ resolvePackageSpecs: async (resolveArgs) => await this.resolveTreeViaNpmDryRun(resolveArgs.specs, resolveArgs.abortSignal, ignoreScripts),
1353
1486
  },
1354
1487
  cveUserOverride,
1355
1488
  mode,
@@ -1398,9 +1531,8 @@ let AppShell = (() => {
1398
1531
  * pre-existing handler (e.g. agent-planning's POST_PROCESSING_ERRORED
1399
1532
  * branch) behaves exactly as it did before.
1400
1533
  *
1401
- * Only used on the npm code path; pnpm continues to throw `NpmInstallError`
1402
- * directly because pnpm's `--json` error contract differs and the
1403
- * registry-blocked scenarios only matter in cloud-edit (which forces npm).
1534
+ * Only used on the npm code path; pnpm failures go through
1535
+ * `classifyPnpmExecError`.
1404
1536
  *
1405
1537
  * `registryFetchResult` carries the most recent
1406
1538
  * `prepareForPrivateRegistry` result so the structured error can encode
@@ -1466,6 +1598,29 @@ let AppShell = (() => {
1466
1598
  }
1467
1599
  return new NpmInstallError(diagnostic || error.stderr || error.message || "");
1468
1600
  }
1601
+ /**
1602
+ * pnpm's counterpart to `classifyNpmExecError`. pnpm has no `--json` error
1603
+ * output, so this reads its plain-text error. pnpm 12 writes that to stderr,
1604
+ * pnpm 10 and older to stdout, so both are read. A registry failure
1605
+ * (`ERR_PNPM_FETCH_401` and friends) becomes an `NpmInstallBlocked`, which
1606
+ * gives the agent the same advice and auth retry as npm. Anything else keeps
1607
+ * pnpm's own message instead of an empty one.
1608
+ */
1609
+ classifyPnpmExecError(error, requestedPackages, registryFetchResult) {
1610
+ // While pnpm is off, a pnpm failure keeps its stderr as it did before.
1611
+ if (!isPnpmEnabled()) {
1612
+ return new NpmInstallError(error.stderr || error.message || "");
1613
+ }
1614
+ const output = `${error.stdout ?? ""}\n${error.stderr ?? ""}`.trim();
1615
+ const blocked = classifyNpmExecStderr(output, {
1616
+ requestedPackages,
1617
+ hasAnyRegistryConfigured: AppShell.deriveHasAnyRegistryConfigured(registryFetchResult),
1618
+ });
1619
+ // The message reaches the agent, and pnpm's output can echo a registry
1620
+ // URL with its token in it.
1621
+ return (blocked ??
1622
+ new NpmInstallError(scrubSecrets(output || error.message || "")));
1623
+ }
1469
1624
  /**
1470
1625
  * Wraps an install attempt so that a `registry_auth_failed` failure
1471
1626
  * triggers one cache-invalidation + re-fetch + retry cycle before
@@ -1502,80 +1657,69 @@ let AppShell = (() => {
1502
1657
  const installStart = Date.now();
1503
1658
  let outcome = "success";
1504
1659
  let capturedFetchResult;
1660
+ let capturedPackageManager;
1505
1661
  try {
1506
1662
  return await this.withAuthFailedRetry(async (registryFetchResult) => {
1507
1663
  capturedFetchResult = registryFetchResult;
1508
1664
  // APPS-4370: short-circuit before any exec when server is unreachable
1509
1665
  // AND this org's marker says it was previously configured.
1510
1666
  this.maybeShortCircuitForUnreachable(registryFetchResult, []);
1511
- // In cloud edit mode, always use npm because pnpm is not available in the container.
1512
- // The user's project may have pnpm signals (pnpm-lock.yaml, packageManager field)
1513
- // from local editing, but we must use npm in cloud mode.
1514
- const isCloudMode = process.env.SUPERBLOCKS_SERVER_ENV !== "local";
1515
- let packageManagerAgent;
1516
- if (isCloudMode) {
1517
- // Force npm in cloud mode; prepare before Mode 2 so dry-run malware
1518
- // scan does not choke on workspace: protocol entries.
1519
- packageManagerAgent = "npm";
1520
- await this.maybePreparePackageJsonForCloudNpm();
1521
- }
1522
- else {
1523
- // NOTE: this largely duplicates the logic in the CLI
1524
- const packageManager = await detect({
1525
- strategies: [
1526
- "packageManager-field",
1527
- "lockfile",
1528
- "install-metadata",
1529
- "devEngines-field",
1530
- ],
1531
- cwd: this.appRootDirPath,
1532
- });
1533
- if (!packageManager) {
1534
- throw new Error("Could not detect package manager");
1535
- }
1536
- packageManagerAgent = packageManager.agent;
1537
- }
1667
+ // Cloud edit uses npm unless the app is a pnpm app and pnpm is on for
1668
+ // the org (see `appPackageManager`).
1669
+ const packageManagerAgent = await this.installingPackageManager();
1670
+ // Prepare before Mode 2 so the dry-run malware scan does not choke on
1671
+ // workspace: protocol entries. pnpm in the pod needs it as much as
1672
+ // npm: the app is a single project there, so `workspace:*` has
1673
+ // nothing to resolve to. A no-op in local dev.
1674
+ await this.maybePreparePackageJsonForCloudNpm();
1675
+ capturedPackageManager = packageManagerAgent;
1538
1676
  const appliedSubstitutions = await this.applyDeclaredDependencyUpgrades(await this.maybeRemediateVulnerabilities({
1539
1677
  abortSignal,
1678
+ ignoreScripts: shouldIgnoreInstallScripts(registryFetchResult),
1540
1679
  mode: "all",
1541
1680
  }));
1542
1681
  const ignoreScripts = shouldIgnoreInstallScripts(registryFetchResult);
1543
1682
  const isNpm = packageManagerAgent === "npm";
1683
+ // pnpm: the lockfile has to be allowed to change, as for `npm
1684
+ // install`; pnpm would otherwise refuse under `CI=true`.
1544
1685
  const baseInstallArgs = isNpm
1545
1686
  ? ["--fund=false", "--audit=false", "--json"]
1546
- : [];
1687
+ : packageManagerAgent === "pnpm" && isPnpmEnabled()
1688
+ ? ["--no-frozen-lockfile"]
1689
+ : [];
1547
1690
  const installArgs = ignoreScripts
1548
- ? [...baseInstallArgs, "--ignore-scripts"]
1691
+ ? [...baseInstallArgs, ...ignoreScriptsFlags(packageManagerAgent)]
1549
1692
  : baseInstallArgs;
1550
1693
  const installCommand = resolveCommand(packageManagerAgent, "install", installArgs);
1551
1694
  if (!installCommand) {
1552
1695
  throw new Error(`Could not determine install command for ${packageManagerAgent}, skipping package installation`);
1553
1696
  }
1554
1697
  const { command, args } = installCommand;
1698
+ let stdout;
1555
1699
  try {
1556
- const { stdout } = await execPromise(`${command} ${args.join(" ")}`, {
1700
+ ({ stdout } = await execPromise(`${command} ${args.join(" ")}`, {
1557
1701
  cwd: this.appRootDirPath,
1558
1702
  env: this.installLogEnv(),
1559
1703
  signal: abortSignal,
1560
1704
  // See `NPM_JSON_MAX_BUFFER` — `--json` summary can outgrow the
1561
1705
  // default 1 MiB exec buffer on fat dep trees.
1562
1706
  maxBuffer: NPM_JSON_MAX_BUFFER,
1563
- });
1564
- // Audit only after the install succeeds (same as Mode 1).
1565
- this.maybeReportSubstitutions(appliedSubstitutions, "install_all");
1566
- // On the npm path stdout is a JSON blob; translate to a compact
1567
- // human-readable summary for the agent, falling back to raw stdout
1568
- // when the JSON shape drifts.
1569
- if (isNpm) {
1570
- return parseNpmJsonSuccess(stdout) ?? stdout;
1571
- }
1572
- return stdout;
1707
+ }));
1573
1708
  }
1574
1709
  catch (error) {
1575
1710
  if (isNpm)
1576
1711
  throw this.classifyNpmExecError(error, undefined, registryFetchResult);
1577
- throw new NpmInstallError(error.stderr || error.message);
1712
+ throw this.classifyPnpmExecError(error, undefined, registryFetchResult);
1713
+ }
1714
+ // Audit only after the install succeeds (same as Mode 1).
1715
+ this.maybeReportSubstitutions(appliedSubstitutions, "install_all");
1716
+ // On the npm path stdout is a JSON blob; translate to a compact
1717
+ // human-readable summary for the agent, falling back to raw stdout
1718
+ // when the JSON shape drifts.
1719
+ if (isNpm) {
1720
+ return parseNpmJsonSuccess(stdout) ?? stdout;
1578
1721
  }
1722
+ return stdout;
1579
1723
  });
1580
1724
  }
1581
1725
  catch (err) {
@@ -1584,7 +1728,7 @@ let AppShell = (() => {
1584
1728
  throw err;
1585
1729
  }
1586
1730
  finally {
1587
- this.observeNpmInstall("install_all", capturedFetchResult, outcome, Date.now() - installStart);
1731
+ this.observeNpmInstall("install_all", capturedFetchResult, outcome, Date.now() - installStart, capturedPackageManager);
1588
1732
  }
1589
1733
  }
1590
1734
  async installSpecificPackages(packages, abortSignal, options) {
@@ -1608,6 +1752,7 @@ let AppShell = (() => {
1608
1752
  const installStart = Date.now();
1609
1753
  let outcome = "success";
1610
1754
  let capturedFetchResult;
1755
+ let capturedPackageManager;
1611
1756
  try {
1612
1757
  return await this.withAuthFailedRetry(async (registryFetchResult) => {
1613
1758
  capturedFetchResult = registryFetchResult;
@@ -1616,37 +1761,19 @@ let AppShell = (() => {
1616
1761
  // surfaces them to the agent ("we tried to install lodash and the
1617
1762
  // private registry was unreachable").
1618
1763
  this.maybeShortCircuitForUnreachable(registryFetchResult, packages.map((pkg) => pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name));
1619
- // In cloud edit mode, always use npm because pnpm is not available in the container.
1620
- // The user's project may have pnpm signals (pnpm-lock.yaml, packageManager field)
1621
- // from local editing, but we must use npm in cloud mode. Prepare before the
1622
- // Mode 1 dry-run so leftover workspace: entries cannot break malware coverage.
1623
- const isCloudMode = process.env.SUPERBLOCKS_SERVER_ENV !== "local";
1624
- let packageManagerAgent;
1625
- if (isCloudMode) {
1626
- // Force npm in cloud mode
1627
- packageManagerAgent = "npm";
1628
- await this.maybePreparePackageJsonForCloudNpm();
1629
- }
1630
- else {
1631
- const packageManager = await detect({
1632
- strategies: [
1633
- "packageManager-field",
1634
- "lockfile",
1635
- "install-metadata",
1636
- "devEngines-field",
1637
- ],
1638
- cwd: this.appRootDirPath,
1639
- });
1640
- if (!packageManager) {
1641
- throw new Error("Could not detect package manager");
1642
- }
1643
- packageManagerAgent = packageManager.agent;
1644
- }
1764
+ // Cloud edit uses npm unless the app is a pnpm app and pnpm is on for
1765
+ // the org (see `appPackageManager`). Prepare before the Mode 1 dry-run
1766
+ // so leftover workspace: entries cannot break malware coverage, for
1767
+ // pnpm as well as npm (see `runPackageManagerInstall`).
1768
+ const packageManagerAgent = await this.installingPackageManager();
1769
+ await this.maybePreparePackageJsonForCloudNpm();
1770
+ capturedPackageManager = packageManagerAgent;
1645
1771
  // Malware hard-block on the dry-run resolved tree (top-level +
1646
1772
  // transitive) before the real install runs. Mode 1 also returns
1647
1773
  // silent CVE substitutions applied to the install argv below.
1648
1774
  const remediation = await this.maybeRemediateVulnerabilities({
1649
1775
  abortSignal,
1776
+ ignoreScripts: shouldIgnoreInstallScripts(registryFetchResult),
1650
1777
  cveUserOverride: options?.cveUserOverride,
1651
1778
  mode: "specific",
1652
1779
  packages,
@@ -1664,7 +1791,9 @@ let AppShell = (() => {
1664
1791
  const baseArgs = isNpm
1665
1792
  ? ["--fund=false", "--audit=false", "--json", ...packageSpecs]
1666
1793
  : packageSpecs;
1667
- const addCommand = resolveCommand(packageManagerAgent, "add", ignoreScripts ? ["--ignore-scripts", ...baseArgs] : baseArgs);
1794
+ const addCommand = resolveCommand(packageManagerAgent, "add", ignoreScripts
1795
+ ? [...ignoreScriptsFlags(packageManagerAgent), ...baseArgs]
1796
+ : baseArgs);
1668
1797
  if (!addCommand) {
1669
1798
  throw new Error(`Could not determine add command for ${packageManagerAgent}`);
1670
1799
  }
@@ -1683,7 +1812,7 @@ let AppShell = (() => {
1683
1812
  catch (error) {
1684
1813
  if (isNpm)
1685
1814
  throw this.classifyNpmExecError(error, regularPackages, registryFetchResult);
1686
- throw new NpmInstallError(error.stderr || error.message);
1815
+ throw this.classifyPnpmExecError(error, regularPackages, registryFetchResult);
1687
1816
  }
1688
1817
  }
1689
1818
  // Install dev dependencies
@@ -1698,7 +1827,9 @@ let AppShell = (() => {
1698
1827
  ...packageSpecs,
1699
1828
  ]
1700
1829
  : ["-D", ...packageSpecs];
1701
- const addCommand = resolveCommand(packageManagerAgent, "add", ignoreScripts ? ["--ignore-scripts", ...baseArgs] : baseArgs);
1830
+ const addCommand = resolveCommand(packageManagerAgent, "add", ignoreScripts
1831
+ ? [...ignoreScriptsFlags(packageManagerAgent), ...baseArgs]
1832
+ : baseArgs);
1702
1833
  if (!addCommand) {
1703
1834
  throw new Error(`Could not determine add command for ${packageManagerAgent}`);
1704
1835
  }
@@ -1717,7 +1848,7 @@ let AppShell = (() => {
1717
1848
  catch (error) {
1718
1849
  if (isNpm)
1719
1850
  throw this.classifyNpmExecError(error, devPackages, registryFetchResult);
1720
- throw new NpmInstallError(error.stderr || error.message);
1851
+ throw this.classifyPnpmExecError(error, devPackages, registryFetchResult);
1721
1852
  }
1722
1853
  }
1723
1854
  // Audit only after every install group succeeds: a selected
@@ -1744,7 +1875,7 @@ let AppShell = (() => {
1744
1875
  throw err;
1745
1876
  }
1746
1877
  finally {
1747
- this.observeNpmInstall("install_specific", capturedFetchResult, outcome, Date.now() - installStart);
1878
+ this.observeNpmInstall("install_specific", capturedFetchResult, outcome, Date.now() - installStart, capturedPackageManager);
1748
1879
  }
1749
1880
  }
1750
1881
  async uninstallPackages(packages, options) {
@@ -1765,6 +1896,7 @@ let AppShell = (() => {
1765
1896
  const installStart = Date.now();
1766
1897
  let outcome = "success";
1767
1898
  let capturedFetchResult;
1899
+ let capturedPackageManager;
1768
1900
  // Uninstalls hit the same registry resolution rules as installs (the
1769
1901
  // dependency-tree solver re-reifies the remaining tree and can re-fetch
1770
1902
  // metadata), so they run through the SAME safety wrapper as every
@@ -1799,13 +1931,14 @@ let AppShell = (() => {
1799
1931
  // surrounding workspace); npm everywhere else so the `--json` error
1800
1932
  // contract — and therefore the registry-blocked advice matrix —
1801
1933
  // applies. Mirrors `runNpmInstall` / `installSpecificPackages`.
1802
- const isNpm = process.env.SUPERBLOCKS_SERVER_ENV !== "local";
1934
+ capturedPackageManager = await this.appPackageManager();
1935
+ const isNpm = capturedPackageManager === "npm";
1803
1936
  const packageManager = isNpm ? "npm" : "pnpm";
1804
1937
  const uninstallCmd = isNpm ? "uninstall" : "remove";
1805
- const jsonFlag = isNpm ? " --json" : "";
1806
- const baseCommand = `${packageManager} ${uninstallCmd} ${packages.join(" ")}${jsonFlag}`;
1938
+ const flags = isNpm ? " --json" : "";
1939
+ const baseCommand = `${packageManager} ${uninstallCmd} ${packages.join(" ")}${flags}`;
1807
1940
  const command = ignoreScripts
1808
- ? `${baseCommand} --ignore-scripts`
1941
+ ? `${baseCommand} ${ignoreScriptsFlags(packageManager).join(" ")}`
1809
1942
  : baseCommand;
1810
1943
  return await new Promise((resolve, reject) => {
1811
1944
  exec(command, {
@@ -1822,12 +1955,12 @@ let AppShell = (() => {
1822
1955
  // `NpmInstallBlocked` (with row-aware advice + the
1823
1956
  // auth-failed retry signal consumed by `withAuthFailedRetry`);
1824
1957
  // anything else falls back to the legacy `NpmInstallError`.
1825
- // pnpm doesn't emit the `--json` envelope, so it keeps the
1826
- // raw-stderr error.
1827
- if (isNpm) {
1828
- return reject(this.classifyNpmExecError({ stdout, stderr, message: error.message }, undefined, registryFetchResult));
1829
- }
1830
- return reject(new NpmInstallError(stderr || error.message));
1958
+ // pnpm has no `--json` envelope, so its classifier reads
1959
+ // pnpm's plain output instead.
1960
+ const failure = { stdout, stderr, message: error.message };
1961
+ return reject(isNpm
1962
+ ? this.classifyNpmExecError(failure, undefined, registryFetchResult)
1963
+ : this.classifyPnpmExecError(failure, undefined, registryFetchResult));
1831
1964
  }
1832
1965
  // On the npm path stdout is a `--json` blob; collapse it to a
1833
1966
  // compact human-readable summary (same as
@@ -1849,7 +1982,7 @@ let AppShell = (() => {
1849
1982
  throw err;
1850
1983
  }
1851
1984
  finally {
1852
- this.observeNpmInstall("uninstall", capturedFetchResult, outcome, Date.now() - installStart);
1985
+ this.observeNpmInstall("uninstall", capturedFetchResult, outcome, Date.now() - installStart, capturedPackageManager);
1853
1986
  }
1854
1987
  }
1855
1988
  static MAX_GENERATION_RUNS = 10;