@superblocksteam/vite-plugin-file-sync 2.0.123 → 2.0.124-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 (271) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +16 -1
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  3. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +27 -1
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  5. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
  6. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +3 -1
  7. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
  8. package/dist/ai-service/agent/tools/apis/api-comparator.d.ts.map +1 -1
  9. package/dist/ai-service/agent/tools/apis/api-comparator.js +1 -4
  10. package/dist/ai-service/agent/tools/apis/api-comparator.js.map +1 -1
  11. package/dist/ai-service/agent/tools/apis/api-testing-state.js +7 -0
  12. package/dist/ai-service/agent/tools/apis/api-testing-state.js.map +1 -1
  13. package/dist/ai-service/agent/tools/apis/get-api-docs.d.ts +1 -1
  14. package/dist/ai-service/agent/tools/apis/write-api.d.ts +2 -2
  15. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  16. package/dist/ai-service/agent/tools/build-capture-screenshot.js +11 -33
  17. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  18. package/dist/ai-service/agent/tools/build-copy-directory.d.ts +1 -1
  19. package/dist/ai-service/agent/tools/build-finalize.d.ts.map +1 -1
  20. package/dist/ai-service/agent/tools/build-finalize.js +11 -2
  21. package/dist/ai-service/agent/tools/build-finalize.js.map +1 -1
  22. package/dist/ai-service/agent/tools/build-install-packages.d.ts +41 -1
  23. package/dist/ai-service/agent/tools/build-install-packages.d.ts.map +1 -1
  24. package/dist/ai-service/agent/tools/build-install-packages.js +208 -0
  25. package/dist/ai-service/agent/tools/build-install-packages.js.map +1 -1
  26. package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts +28 -0
  27. package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts.map +1 -0
  28. package/dist/ai-service/agent/tools/build-lookup-npm-package.js +78 -0
  29. package/dist/ai-service/agent/tools/build-lookup-npm-package.js.map +1 -0
  30. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +4 -4
  31. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts +16 -0
  32. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -0
  33. package/dist/ai-service/agent/tools/build-navigate-preview.js +68 -0
  34. package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -0
  35. package/dist/ai-service/agent/tools/build-write-file.d.ts +1 -1
  36. package/dist/ai-service/agent/tools/databases/dev-database.d.ts +9 -9
  37. package/dist/ai-service/agent/tools/index.d.ts +2 -0
  38. package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
  39. package/dist/ai-service/agent/tools/index.js +2 -0
  40. package/dist/ai-service/agent/tools/index.js.map +1 -1
  41. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +3 -3
  42. package/dist/ai-service/agent/tools/report-security-findings.d.ts +11 -5
  43. package/dist/ai-service/agent/tools/report-security-findings.d.ts.map +1 -1
  44. package/dist/ai-service/agent/tools/report-security-findings.js +1 -0
  45. package/dist/ai-service/agent/tools/report-security-findings.js.map +1 -1
  46. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  47. package/dist/ai-service/agent/tools.js +3 -1
  48. package/dist/ai-service/agent/tools.js.map +1 -1
  49. package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
  50. package/dist/ai-service/agent/tools2/tools/grep.d.ts +2 -2
  51. package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts +2 -2
  52. package/dist/ai-service/app-interface/npm-error-parser.d.ts +161 -0
  53. package/dist/ai-service/app-interface/npm-error-parser.d.ts.map +1 -0
  54. package/dist/ai-service/app-interface/npm-error-parser.js +510 -0
  55. package/dist/ai-service/app-interface/npm-error-parser.js.map +1 -0
  56. package/dist/ai-service/app-interface/npm-package-lookup.d.ts +286 -0
  57. package/dist/ai-service/app-interface/npm-package-lookup.d.ts.map +1 -0
  58. package/dist/ai-service/app-interface/npm-package-lookup.js +793 -0
  59. package/dist/ai-service/app-interface/npm-package-lookup.js.map +1 -0
  60. package/dist/ai-service/app-interface/npm-registry.d.ts +735 -47
  61. package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -1
  62. package/dist/ai-service/app-interface/npm-registry.js +1882 -153
  63. package/dist/ai-service/app-interface/npm-registry.js.map +1 -1
  64. package/dist/ai-service/app-interface/shell.d.ts +118 -1
  65. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  66. package/dist/ai-service/app-interface/shell.js +579 -157
  67. package/dist/ai-service/app-interface/shell.js.map +1 -1
  68. package/dist/ai-service/app-skills/manager.d.ts +1 -1
  69. package/dist/ai-service/app-skills/manager.d.ts.map +1 -1
  70. package/dist/ai-service/app-skills/manager.js +39 -7
  71. package/dist/ai-service/app-skills/manager.js.map +1 -1
  72. package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts +9 -0
  73. package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts.map +1 -0
  74. package/dist/ai-service/checklist/api-migration-checklist-gate.js +30 -0
  75. package/dist/ai-service/checklist/api-migration-checklist-gate.js.map +1 -0
  76. package/dist/ai-service/checklist/api-migration-origins.d.ts +4 -0
  77. package/dist/ai-service/checklist/api-migration-origins.d.ts.map +1 -0
  78. package/dist/ai-service/checklist/api-migration-origins.js +8 -0
  79. package/dist/ai-service/checklist/api-migration-origins.js.map +1 -0
  80. package/dist/ai-service/checklist/persisted-checklist-store.d.ts +2 -3
  81. package/dist/ai-service/checklist/persisted-checklist-store.d.ts.map +1 -1
  82. package/dist/ai-service/checklist/persisted-checklist-store.js +6 -7
  83. package/dist/ai-service/checklist/persisted-checklist-store.js.map +1 -1
  84. package/dist/ai-service/context-archive-paths.d.ts +4 -0
  85. package/dist/ai-service/context-archive-paths.d.ts.map +1 -0
  86. package/dist/ai-service/context-archive-paths.js +28 -0
  87. package/dist/ai-service/context-archive-paths.js.map +1 -0
  88. package/dist/ai-service/context-download.d.ts +3 -1
  89. package/dist/ai-service/context-download.d.ts.map +1 -1
  90. package/dist/ai-service/context-download.js +48 -9
  91. package/dist/ai-service/context-download.js.map +1 -1
  92. package/dist/ai-service/context-upload.d.ts +3 -2
  93. package/dist/ai-service/context-upload.d.ts.map +1 -1
  94. package/dist/ai-service/context-upload.js +44 -26
  95. package/dist/ai-service/context-upload.js.map +1 -1
  96. package/dist/ai-service/dev-database-client.d.ts +3 -11
  97. package/dist/ai-service/dev-database-client.d.ts.map +1 -1
  98. package/dist/ai-service/dev-database-client.js.map +1 -1
  99. package/dist/ai-service/features.d.ts +4 -0
  100. package/dist/ai-service/features.d.ts.map +1 -1
  101. package/dist/ai-service/features.js +8 -0
  102. package/dist/ai-service/features.js.map +1 -1
  103. package/dist/ai-service/filter-disabled-tools-for-migration.d.ts.map +1 -1
  104. package/dist/ai-service/filter-disabled-tools-for-migration.js +4 -0
  105. package/dist/ai-service/filter-disabled-tools-for-migration.js.map +1 -1
  106. package/dist/ai-service/index.d.ts +94 -0
  107. package/dist/ai-service/index.d.ts.map +1 -1
  108. package/dist/ai-service/index.js +564 -17
  109. package/dist/ai-service/index.js.map +1 -1
  110. package/dist/ai-service/llm/client.d.ts +38 -3
  111. package/dist/ai-service/llm/client.d.ts.map +1 -1
  112. package/dist/ai-service/llm/client.js +73 -22
  113. package/dist/ai-service/llm/client.js.map +1 -1
  114. package/dist/ai-service/llm/context-v2/adapter.d.ts +3 -1
  115. package/dist/ai-service/llm/context-v2/adapter.d.ts.map +1 -1
  116. package/dist/ai-service/llm/context-v2/adapter.js +11 -11
  117. package/dist/ai-service/llm/context-v2/adapter.js.map +1 -1
  118. package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts +44 -0
  119. package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -0
  120. package/dist/ai-service/llm/context-v2/compaction/client-side.js +170 -0
  121. package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -0
  122. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts +56 -0
  123. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts.map +1 -0
  124. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js +9 -0
  125. package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js.map +1 -0
  126. package/dist/ai-service/llm/context-v2/compaction/index.d.ts +5 -0
  127. package/dist/ai-service/llm/context-v2/compaction/index.d.ts.map +1 -0
  128. package/dist/ai-service/llm/context-v2/compaction/index.js +3 -0
  129. package/dist/ai-service/llm/context-v2/compaction/index.js.map +1 -0
  130. package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts +30 -0
  131. package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts.map +1 -0
  132. package/dist/ai-service/llm/context-v2/compaction/server-side.js +130 -0
  133. package/dist/ai-service/llm/context-v2/compaction/server-side.js.map +1 -0
  134. package/dist/ai-service/llm/context-v2/context-management.d.ts +203 -0
  135. package/dist/ai-service/llm/context-v2/context-management.d.ts.map +1 -0
  136. package/dist/ai-service/llm/context-v2/context-management.js +183 -0
  137. package/dist/ai-service/llm/context-v2/context-management.js.map +1 -0
  138. package/dist/ai-service/llm/context-v2/context.d.ts +64 -22
  139. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  140. package/dist/ai-service/llm/context-v2/context.js +233 -157
  141. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  142. package/dist/ai-service/llm/context-v2/conversation-context.d.ts +24 -7
  143. package/dist/ai-service/llm/context-v2/conversation-context.d.ts.map +1 -1
  144. package/dist/ai-service/llm/context-v2/conversation-context.js +1 -1
  145. package/dist/ai-service/llm/context-v2/index.d.ts +0 -4
  146. package/dist/ai-service/llm/context-v2/index.d.ts.map +1 -1
  147. package/dist/ai-service/llm/context-v2/index.js +0 -4
  148. package/dist/ai-service/llm/context-v2/index.js.map +1 -1
  149. package/dist/ai-service/llm/context-v2/manager.d.ts +24 -3
  150. package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
  151. package/dist/ai-service/llm/context-v2/manager.js +146 -4
  152. package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
  153. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts +2 -7
  154. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts.map +1 -1
  155. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js +5 -73
  156. package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js.map +1 -1
  157. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts +57 -0
  158. package/dist/ai-service/llm/context-v2/storage/event-store.d.ts.map +1 -0
  159. package/dist/ai-service/llm/context-v2/storage/event-store.js +10 -0
  160. package/dist/ai-service/llm/context-v2/storage/event-store.js.map +1 -0
  161. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +50 -0
  162. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -0
  163. package/dist/ai-service/llm/context-v2/storage/event-types.js +10 -0
  164. package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -0
  165. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts +87 -0
  166. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts.map +1 -0
  167. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js +184 -0
  168. package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js.map +1 -0
  169. package/dist/ai-service/llm/context-v2/storage/migration.d.ts +49 -0
  170. package/dist/ai-service/llm/context-v2/storage/migration.d.ts.map +1 -0
  171. package/dist/ai-service/llm/context-v2/storage/migration.js +126 -0
  172. package/dist/ai-service/llm/context-v2/storage/migration.js.map +1 -0
  173. package/dist/ai-service/llm/context-v2/types.d.ts +16 -11
  174. package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
  175. package/dist/ai-service/llm/context-v2/types.js +2 -2
  176. package/dist/ai-service/llm/context-v2/types.js.map +1 -1
  177. package/dist/ai-service/llm/impl/clark.d.ts +8 -0
  178. package/dist/ai-service/llm/impl/clark.d.ts.map +1 -1
  179. package/dist/ai-service/llm/impl/clark.js +8 -0
  180. package/dist/ai-service/llm/impl/clark.js.map +1 -1
  181. package/dist/ai-service/llm/interaction/adapters/vercel.d.ts.map +1 -1
  182. package/dist/ai-service/llm/interaction/adapters/vercel.js +17 -1
  183. package/dist/ai-service/llm/interaction/adapters/vercel.js.map +1 -1
  184. package/dist/ai-service/llm/provider.d.ts.map +1 -1
  185. package/dist/ai-service/llm/provider.js +1 -3
  186. package/dist/ai-service/llm/provider.js.map +1 -1
  187. package/dist/ai-service/llm/stream/observers/context.d.ts +23 -0
  188. package/dist/ai-service/llm/stream/observers/context.d.ts.map +1 -1
  189. package/dist/ai-service/llm/stream/observers/context.js +62 -0
  190. package/dist/ai-service/llm/stream/observers/context.js.map +1 -1
  191. package/dist/ai-service/llm/tool-context-integrity.d.ts +25 -0
  192. package/dist/ai-service/llm/tool-context-integrity.d.ts.map +1 -0
  193. package/dist/ai-service/llm/tool-context-integrity.js +141 -0
  194. package/dist/ai-service/llm/tool-context-integrity.js.map +1 -0
  195. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts +1 -1
  196. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts.map +1 -1
  197. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js +6 -1
  198. package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js.map +1 -1
  199. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts +1 -1
  200. package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts.map +1 -1
  201. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js +3 -2
  202. package/dist/ai-service/skills/system/third-party-migration/skill.generated.js.map +1 -1
  203. package/dist/ai-service/state-machine/clark-fsm.d.ts +23 -2
  204. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  205. package/dist/ai-service/state-machine/clark-fsm.js +37 -2
  206. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  207. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  208. package/dist/ai-service/state-machine/handlers/agent-planning.js +29 -2
  209. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  210. package/dist/ai-service/state-machine/handlers/awaiting-user.d.ts.map +1 -1
  211. package/dist/ai-service/state-machine/handlers/awaiting-user.js +9 -0
  212. package/dist/ai-service/state-machine/handlers/awaiting-user.js.map +1 -1
  213. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  214. package/dist/ai-service/state-machine/handlers/llm-generating.js +25 -2
  215. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  216. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts +6 -0
  217. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -0
  218. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +36 -0
  219. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -0
  220. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +30 -1
  221. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
  222. package/dist/ai-service/state-machine/helpers/stable-peer.js +85 -2
  223. package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
  224. package/dist/ai-service/state-machine/helpers/user-preferences.d.ts +8 -0
  225. package/dist/ai-service/state-machine/helpers/user-preferences.d.ts.map +1 -0
  226. package/dist/ai-service/state-machine/helpers/user-preferences.js +11 -0
  227. package/dist/ai-service/state-machine/helpers/user-preferences.js.map +1 -0
  228. package/dist/ai-service/template-renderer.d.ts +18 -1
  229. package/dist/ai-service/template-renderer.d.ts.map +1 -1
  230. package/dist/ai-service/template-renderer.js +73 -12
  231. package/dist/ai-service/template-renderer.js.map +1 -1
  232. package/dist/ai-service/types.d.ts +157 -0
  233. package/dist/ai-service/types.d.ts.map +1 -1
  234. package/dist/ai-service/types.js +137 -0
  235. package/dist/ai-service/types.js.map +1 -1
  236. package/dist/ai-service/util/llm-config-utils.d.ts +21 -22
  237. package/dist/ai-service/util/llm-config-utils.d.ts.map +1 -1
  238. package/dist/ai-service/util/llm-config-utils.js +40 -67
  239. package/dist/ai-service/util/llm-config-utils.js.map +1 -1
  240. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  241. package/dist/file-sync-vite-plugin.js +5 -0
  242. package/dist/file-sync-vite-plugin.js.map +1 -1
  243. package/dist/file-system-helpers.d.ts +17 -0
  244. package/dist/file-system-helpers.d.ts.map +1 -1
  245. package/dist/file-system-helpers.js +22 -0
  246. package/dist/file-system-helpers.js.map +1 -1
  247. package/dist/git-service/index.d.ts.map +1 -1
  248. package/dist/git-service/index.js +15 -1
  249. package/dist/git-service/index.js.map +1 -1
  250. package/dist/migration/migration-routes.d.ts.map +1 -1
  251. package/dist/migration/migration-routes.js +26 -3
  252. package/dist/migration/migration-routes.js.map +1 -1
  253. package/dist/migration/translation-prompt.d.ts.map +1 -1
  254. package/dist/migration/translation-prompt.js +5 -0
  255. package/dist/migration/translation-prompt.js.map +1 -1
  256. package/dist/migration-templates/app-fullstack/package.json +2 -2
  257. package/dist/policy-gate-callback-mapper.d.ts +24 -0
  258. package/dist/policy-gate-callback-mapper.d.ts.map +1 -0
  259. package/dist/policy-gate-callback-mapper.js +61 -0
  260. package/dist/policy-gate-callback-mapper.js.map +1 -0
  261. package/dist/policy-gate-runner.d.ts +32 -0
  262. package/dist/policy-gate-runner.d.ts.map +1 -0
  263. package/dist/policy-gate-runner.js +191 -0
  264. package/dist/policy-gate-runner.js.map +1 -0
  265. package/dist/router-parser.d.ts.map +1 -1
  266. package/dist/router-parser.js +107 -11
  267. package/dist/router-parser.js.map +1 -1
  268. package/dist/sync-service/list-dir.d.ts.map +1 -1
  269. package/dist/sync-service/list-dir.js +13 -3
  270. package/dist/sync-service/list-dir.js.map +1 -1
  271. package/package.json +28 -10
@@ -9,84 +9,737 @@
9
9
  */
10
10
  export declare const PRESERVE_NPMRC_SCOPES: string[];
11
11
  /**
12
- * Configuration for an npm registry sourced from environment variables.
12
+ * Wire shape for a single registry row returned by
13
+ * `GET /api/v1/organizations/:orgId/npm-registry`. Mirrors the server's
14
+ * `OrgNpmRegistryDto` (see
15
+ * `packages/server/src/controllers/v1/orgNpmRegistry/handlers.ts`).
13
16
  *
14
- * The "single-host MVP" surface is `url` + `token`; `scopes` is plumbed for
15
- * forward-compatibility (two-host setups like @superblocksteam on GitHub
16
- * Packages plus third-party deps on Artifactory) but admin UX is deferred.
17
+ * `scope === null` is the default (unscoped) registry; otherwise it is a
18
+ * single npm scope like `"@mycompany"`.
19
+ */
20
+ export interface NpmRegistryEntry {
21
+ id: string;
22
+ scope: string | null;
23
+ registryUrl: string;
24
+ token: string | null;
25
+ }
26
+ /**
27
+ * Renderable npm registry configuration. This is the shape `writeNpmrc`
28
+ * consumes — it is independent of where it came from (server fetch in
29
+ * production; constructed in tests). For a configured org, `default` is
30
+ * populated; for an org with no registries, both fields are empty and
31
+ * `configured` is false.
17
32
  */
18
33
  export interface NpmRegistryConfig {
19
- /** Default registry URL (e.g. https://artifactory.example.com/api/npm/npm-virtual/) */
20
- url: string;
21
- /** Optional auth token for the default registry host */
22
- token?: string;
34
+ /** True if at least one registry is configured for the org. */
35
+ configured: boolean;
36
+ /** Default (unscoped) registry URL + optional token. */
37
+ default?: {
38
+ url: string;
39
+ token?: string;
40
+ };
23
41
  /**
24
- * Optional per-scope registry mappings. e.g.
25
- * { "@superblocksteam": "https://npm.pkg.github.com/" }
42
+ * Scoped registries keyed by scope (e.g. `"@mycompany"`).
43
+ * Tokens are kept alongside so `writeNpmrc` can emit per-scope
44
+ * `_authToken=` lines.
26
45
  */
27
- scopes?: Record<string, string>;
46
+ scopes?: Record<string, {
47
+ url: string;
48
+ token?: string;
49
+ }>;
50
+ /**
51
+ * Org-level npm policy: `false` means AppShell appends `--ignore-scripts`
52
+ * to every npm/pnpm invocation; `true` or `undefined` keeps the default
53
+ * behaviour. Independent of `configured` — an org can set the policy
54
+ * without configuring any registry rows (in which case `configured` is
55
+ * false but the policy still flows through). Originates from the
56
+ * `superblocks.npm-registry.enabled` LD-flag-gated field on the server
57
+ * (APPS-4250); undefined when the flag is off or the server omits it.
58
+ */
59
+ allowInstallScripts?: boolean;
60
+ }
61
+ /**
62
+ * Logical outcome of `NpmRegistryClient.getConfig()`. The discriminator lets
63
+ * callers branch cleanly on the four cases without inspecting the cache
64
+ * itself.
65
+ *
66
+ * - `configured`: at least one registry row exists for the org; render
67
+ * `.npmrc` lines from `config`.
68
+ * - `not-configured`: a successful server observation said the org has
69
+ * no registry rows (flag off, 404, or empty `registries: []`). Caller
70
+ * can act on this as deliberate steady-state — e.g. cleanup of a
71
+ * previously-written `.npmrc`. Downstream `require` enforcement (when
72
+ * wired) decides whether to refuse npm.
73
+ * - `stale`: server unreachable, returning last-known-good non-empty
74
+ * entries. Same render path as `configured`, but the source field
75
+ * distinguishes it for observability and tests.
76
+ * - `unreachable`: server unreachable AND we have no usable cache (cold
77
+ * start or only an empty observation). `config.configured` is false so
78
+ * write-side consumers (`maybeWriteNpmrcForDir`, `prepareForPrivateRegistry`)
79
+ * no-op safely. Distinct from `not-configured` so consumers with
80
+ * destructive cleanup paths (e.g. `syncHomeNpmrc`) can refuse to act
81
+ * on what is really "we don't know" rather than "deliberately unset".
82
+ */
83
+ export type NpmRegistryFetchResult = {
84
+ source: "configured";
85
+ config: NpmRegistryConfig;
86
+ } | {
87
+ source: "stale";
88
+ config: NpmRegistryConfig;
89
+ } | {
90
+ source: "not-configured";
91
+ config: NpmRegistryConfig;
92
+ } | {
93
+ source: "unreachable";
94
+ config: NpmRegistryConfig;
28
95
  /**
29
- * When true, AppShell refuses to run npm if the registry env is unset.
30
- * Plumbed for the upcoming `SUPERBLOCKS_REQUIRE_PRIVATE_REGISTRY` enforcement
31
- * branch; not yet read by any consumer in this PR.
96
+ * APPS-4370. True if a `LastKnownRegistryPolicyStore` was wired into
97
+ * the `NpmRegistryClient` and reported that this org had at some prior
98
+ * point successfully resolved a configured registry (i.e. a marker file
99
+ * exists on disk). Used by AppShell install paths to fail closed with
100
+ * `NpmInstallBlocked(registry_unreachable)` rather than silently
101
+ * resolving through public npm during a server outage.
102
+ *
103
+ * - `true` — store reports a prior successful fetch; the install
104
+ * path MUST refuse to spawn npm.
105
+ * - `false` — store has no marker (first-install / greenfield);
106
+ * the install path proceeds as before so cold-boot
107
+ * first-time users are not blocked.
108
+ * - `undefined` — no store was wired in at all (test fixtures, older
109
+ * call sites). Install path falls back to current
110
+ * behaviour (proceed).
32
111
  */
33
- require?: boolean;
112
+ everConfigured?: boolean;
113
+ };
114
+ /**
115
+ * APPS-4370. Disk-backed per-org "this org has at some point been configured
116
+ * with a private npm registry" marker. The CLI's `NpmRegistryClient` writes
117
+ * the marker on every `configured` / `stale` resolution and reads it on
118
+ * `unreachable` so AppShell's install paths can fail closed with
119
+ * `NpmInstallBlocked(registry_unreachable)` on a server outage — rather than
120
+ * silently resolving through public npm with a possibly-stale managed
121
+ * `.npmrc`.
122
+ *
123
+ * Implementations MUST be best-effort: a write failure (disk full,
124
+ * permission denied) MUST NOT throw, and a read failure MUST resolve to
125
+ * `false` rather than rejecting. Either is a strict superset of today's
126
+ * behaviour (no marker → proceed), so a broken store keeps the install
127
+ * path healthy.
128
+ */
129
+ export interface LastKnownRegistryPolicyStore {
130
+ /** Persist the per-org marker. Best-effort; never throws. */
131
+ markConfigured(): Promise<void>;
132
+ /** True if the marker is present. Best-effort; resolves to false on error. */
133
+ hasEverBeenConfigured(): Promise<boolean>;
34
134
  }
35
135
  /**
36
- * Reads the npm registry env contract and returns a typed config, or undefined
37
- * when `SUPERBLOCKS_NPM_REGISTRY` is unset or invalid (preserves SaaS / local
38
- * behavior). Invalid values are logged as warnings and treated as unset rather
39
- * than half-broken (avoids landing a `.npmrc` with a malformed `registry=`
40
- * line and no `_authToken=`).
136
+ * Resolves the org `allow_install_scripts` policy from a `prepareForPrivateRegistry`
137
+ * (or `maybeWriteNpmrcForDir`) result. `false` means "the org has explicitly
138
+ * disabled npm install scripts" install entrypoints append `--ignore-scripts`.
139
+ * `true` or `undefined` (server omitted the field, LD flag off, no client wired
140
+ * up) keeps today's behaviour.
41
141
  *
42
- * Env contract:
43
- * - SUPERBLOCKS_NPM_REGISTRY: full registry URL
44
- * - SUPERBLOCKS_NPM_REGISTRY_TOKEN: optional auth token
45
- * - SUPERBLOCKS_NPM_REGISTRY_SCOPES_JSON: optional JSON map of scope -> URL
46
- * - SUPERBLOCKS_REQUIRE_PRIVATE_REGISTRY: opt-in strict mode (default off)
142
+ * Shared by AppShell install entrypoints (shell.ts) and the dev-server startup
143
+ * install (sdk/cli-replacement/dev.mts) so both paths interpret the policy
144
+ * identically.
47
145
  */
48
- export declare function getNpmRegistryConfig(env?: NodeJS.ProcessEnv): NpmRegistryConfig | undefined;
146
+ export declare function shouldIgnoreInstallScripts(result: NpmRegistryFetchResult | undefined): boolean;
147
+ /**
148
+ * Translates a list of server-shaped registry entries into the renderable
149
+ * `NpmRegistryConfig`. Centralised so `writeNpmrc`, tests, and any future
150
+ * consumer all interpret the wire shape the same way.
151
+ *
152
+ * Multiple `scope === null` entries should never happen — the server's
153
+ * UNIQUE (organization_id, scope) constraint forbids it — but we are
154
+ * defensive: the FIRST default wins, subsequent ones are logged and
155
+ * discarded so a server-side bug never silently rotates the default URL
156
+ * mid-fetch.
157
+ *
158
+ * Duplicate scoped entries (same `scope` string) follow the same rule:
159
+ * first wins.
160
+ */
161
+ export declare function registriesToConfig(entries: NpmRegistryEntry[], allowInstallScripts?: boolean): NpmRegistryConfig;
162
+ /**
163
+ * Dependencies the `NpmRegistryClient` needs to fetch from the server.
164
+ * Injected so the client itself stays free of `fs`, `~/.superblocks/auth.json`
165
+ * parsing, and LaunchDarkly client wiring — those live at the dev-server
166
+ * entrypoint / CLI bootstrap layer and are exercised by their own tests.
167
+ */
168
+ export interface NpmRegistryClientDeps {
169
+ /**
170
+ * Returns true if the `superblocks.npm-registry.enabled` LaunchDarkly
171
+ * flag is on for the active org. Called on every `getConfig()` because LD
172
+ * flag flips must propagate within the LD poll interval without a process
173
+ * restart (see APPS-4228 acceptance).
174
+ *
175
+ * Synchronous on purpose: the caller has already evaluated the flag
176
+ * against the org context; the client just reads the current value.
177
+ */
178
+ isFlagEnabled: () => boolean;
179
+ /**
180
+ * Returns the current user JWT for the active dev-server session.
181
+ * Called once per fetch attempt; on 401 the client first awaits
182
+ * `refreshJwt` (if provided) and then re-invokes this for the retry.
183
+ */
184
+ getJwt: () => Promise<string>;
185
+ /**
186
+ * Optional hook the client awaits between a 401 and its retry. The
187
+ * intended implementation subscribes once to a push-based token
188
+ * refresh signal (e.g. `tokenManager`'s `tokenUpdated` event) with a
189
+ * short timeout, so the retry has a fair chance of picking up a
190
+ * freshly-rotated JWT.
191
+ *
192
+ * If omitted, the client retries immediately with the next `getJwt()`
193
+ * read — useful for tests and for environments where no push-based
194
+ * refresh exists. The retry still benefits from any external refresh
195
+ * that happens to have landed by the time `getJwt()` is re-read; it
196
+ * just isn't given the chance to wait.
197
+ *
198
+ * Errors thrown by `refreshJwt` are treated as transient: the client
199
+ * routes through `serveStaleOrUnconfigured` rather than crashing the
200
+ * install path on a refresh-channel hiccup.
201
+ */
202
+ refreshJwt?: () => Promise<void>;
203
+ /** Active organization id. */
204
+ organizationId: string;
205
+ /**
206
+ * Base URL of the Superblocks server (e.g. `https://app.superblocks.com`,
207
+ * `http://localhost:3000`). The client appends
208
+ * `/api/v1/organizations/:orgId/npm-registry`.
209
+ */
210
+ baseUrl: string;
211
+ /**
212
+ * Optional `fetch` override for testing. Defaults to the global `fetch`
213
+ * available in Node 18+.
214
+ */
215
+ fetch?: typeof globalThis.fetch;
216
+ /**
217
+ * TTL for the in-memory cache, in milliseconds. Defaults to 5 minutes,
218
+ * matching the ticket's tunable.
219
+ */
220
+ ttlMs?: number;
221
+ /**
222
+ * Clock function returning the current time in ms. Defaults to
223
+ * `Date.now`. Overridable so tests can advance the cache deterministically
224
+ * without `vi.useFakeTimers()`.
225
+ */
226
+ now?: () => number;
227
+ /**
228
+ * APPS-4370. Optional disk-backed store the client uses to record that
229
+ * an org has at some point successfully resolved a configured registry,
230
+ * and to consult on `unreachable` resolutions so the result can carry
231
+ * `everConfigured` for the AppShell install fail-closed decision.
232
+ *
233
+ * When omitted, the client behaves exactly as before: marker writes are
234
+ * silently skipped and `unreachable` results carry no `everConfigured`
235
+ * field. Install paths that key off the field default to "proceed".
236
+ *
237
+ * Writes are fire-and-forget: a `markConfigured()` rejection is logged
238
+ * but never bubbles up to the install path.
239
+ */
240
+ lastKnownPolicyStore?: LastKnownRegistryPolicyStore;
241
+ }
242
+ /**
243
+ * Fetches per-org npm registry configuration from the server, caching the
244
+ * result in-memory with a short TTL. Designed so callers do not have to
245
+ * think about the LaunchDarkly flag, JWT lifecycle, or transient server
246
+ * outages — they just `await client.getConfig()` and get back a renderable
247
+ * shape (or "not configured").
248
+ *
249
+ * Lifecycle invariants (see APPS-4230 scope):
250
+ *
251
+ * 1. **Flag off → short-circuit**: returns `{ configured: false }` with
252
+ * no network I/O. Lets us dark-ship LinkedIn first without hammering
253
+ * the API for the rest of the fleet.
254
+ * 2. **Cache hit within TTL → no fetch**: avoids per-install round-trips
255
+ * against the server.
256
+ * 3. **401 → await `refreshJwt` then retry once with a fresh
257
+ * `getJwt()` read**: any push-based JWT refresh (auth-hot-reload)
258
+ * that landed between the original request and the retry is picked
259
+ * up. The cache is preserved across the retry so a failed retry
260
+ * can still fall back to last-known-good. Two disjoint failure
261
+ * modes:
262
+ * - The retry itself returns 401 → hard throw. We deliberately do
263
+ * not keep retrying because the only way a freshly-read JWT also
264
+ * 401s is a true re-auth failure.
265
+ * - `refreshJwt` itself throws or times out (e.g. the push channel
266
+ * hung) → route through `serveStaleOrUnconfigured` (same as a 5xx
267
+ * / network outage). A flaky refresh channel must not crash the
268
+ * install path, and we never reach the second `attempt()`.
269
+ * 4. **Other 4xx (403/400/…) → clear the cache and throw**: these are
270
+ * deliberate denials (RBAC revocation, malformed request), not
271
+ * transient outages. Serving stale would extend the revocation
272
+ * window beyond the cache TTL. The error propagates to the caller
273
+ * (typically `AppShell` / `TemplateRenderer`), which decides
274
+ * whether to fail the install or fall back to public npm.
275
+ * 5. **5xx or network unreachable + cached value → return cache +
276
+ * `logger.warn`**: install workflows continue against the
277
+ * last-known-good registry while we surface the outage; strictly
278
+ * better than falling back to `registry.npmjs.org` mid-deploy.
279
+ * 6. **5xx or network unreachable + no usable cache → return
280
+ * `source: "unreachable"` with `{ configured: false }`**: cold start
281
+ * or only an empty-list cache. `config.configured: false` so
282
+ * write-side consumers continue to no-op, but the distinct source
283
+ * keeps destructive cleanup paths (home-npmrc unlink) from acting on
284
+ * a transient outage.
285
+ *
286
+ * The class is intentionally a thin wrapper around a single Map cell — the
287
+ * cache key is `(organizationId)` since one client serves one org. Sharing
288
+ * one client across orgs would require per-org cache keys; we don't do
289
+ * that today because the dev-server pod is single-tenant.
290
+ */
291
+ export declare class NpmRegistryClient {
292
+ private readonly deps;
293
+ /**
294
+ * Last successful fetch + when it landed. Source of both the TTL check
295
+ * and the last-known-good fallback. We deliberately store the entries as
296
+ * received (not the rendered `NpmRegistryConfig`) so future readers can
297
+ * apply different render policies without a re-fetch.
298
+ *
299
+ * `npmAllowInstallScripts` rides alongside because the server returns it
300
+ * on the same envelope and AppShell wants the same TTL/fallback semantics
301
+ * for both. `undefined` here means the server omitted the field (LD flag
302
+ * off on the server side, or older server); we propagate that as
303
+ * `allowInstallScripts: undefined` rather than defaulting to `true` so
304
+ * the consumer can distinguish "policy known, scripts allowed" from
305
+ * "policy unknown".
306
+ */
307
+ private cache;
308
+ /**
309
+ * Inflight `fetchWithRefresh()` promise, if any. Used to deduplicate
310
+ * concurrent `getConfig()` callers during a cache miss (or stale-after-
311
+ * TTL re-fetch): rather than firing N network requests for N callers
312
+ * we share the single in-flight request. Cleared in a `.finally()` so a
313
+ * resolved or rejected fetch never sticks around.
314
+ *
315
+ * Without this, a burst of installs that all hit cold cache would race
316
+ * to the server, the first one's 401 would clear the cache for itself
317
+ * before the others observe their own 401s — and the others would
318
+ * then have no last-known-good to fall back to.
319
+ */
320
+ private inflight;
321
+ /**
322
+ * Last `npmAllowInstallScripts` value observed from a successful server
323
+ * response, retained across hard-fail cache invalidations (refreshJwt
324
+ * rejection, double-401, other 4xx). Registry credentials and the
325
+ * security policy live on the same envelope but have different
326
+ * security semantics: serving stale credentials to a revoked caller
327
+ * extends a deliberate denial, but dropping a known `false` policy
328
+ * silently weakens `--ignore-scripts` enforcement — strictly the
329
+ * wrong direction. So `this.cache` is cleared on hard-fail (revoke
330
+ * stale credentials), but this field survives so the next call's
331
+ * fall-through `serveStaleOrUnconfigured` still propagates the
332
+ * org's policy to AppShell.
333
+ */
334
+ private lastKnownPolicy;
335
+ constructor(deps: NpmRegistryClientDeps);
336
+ /**
337
+ * Clears the in-memory cache so the next `getConfig()` re-fetches from
338
+ * the server. Called by AppShell install retry when
339
+ * `registry_auth_failed` indicates a stale Artifactory token.
340
+ *
341
+ * `lastKnownPolicy` survives invalidation (same as the auth-failure
342
+ * branches in `fetchWithRefresh`) so `--ignore-scripts` enforcement
343
+ * is not weakened during the re-fetch.
344
+ */
345
+ invalidateCache(): void;
346
+ /**
347
+ * Persist the "ever configured" marker, AWAITED so the write is durable
348
+ * before the caller hands back a `configured` / `stale` resolution. Used
349
+ * on every such resolution so a subsequent server outage can fail closed
350
+ * instead of silently resolving through public npm.
351
+ *
352
+ * Awaited (not fire-and-forget) because the marker must be on disk before
353
+ * `getConfig()` returns: a crash in the gap between returning `configured`
354
+ * and an un-awaited write landing would lose the marker and silently break
355
+ * the "once configured, always fail-closed on unreachable" invariant. The
356
+ * write stays async (`fs/promises`, per the ai-service no-sync-fs rule);
357
+ * the disk store guards itself so the write happens at most once per
358
+ * process (see `createDiskRegistryPolicyStore`), keeping the awaited write
359
+ * off the hot path after the first configured resolution.
360
+ *
361
+ * Logs a warning on write failure but never propagates the error: a broken
362
+ * store is a strict superset of today's no-marker behaviour, and the
363
+ * install path must stay healthy when disk is full or the marker directory
364
+ * is unwritable.
365
+ */
366
+ private markPolicyConfigured;
367
+ /**
368
+ * Read the disk-backed marker that AppShell uses to decide whether to
369
+ * fail closed on `unreachable`. Treats a read failure as `false` so a
370
+ * transient store IO error preserves current behaviour (proceed) rather
371
+ * than degrading to a fail-closed install.
372
+ */
373
+ private readEverConfigured;
374
+ /**
375
+ * Resolve the current npm registry configuration for the active org. See
376
+ * the class docstring for the full state machine.
377
+ */
378
+ getConfig(): Promise<NpmRegistryFetchResult>;
379
+ private fetchWithRefresh;
380
+ /**
381
+ * One full fetch attempt with the current JWT. Throws on getJwt failure
382
+ * OR network failure so the caller's single try/catch handles both.
383
+ */
384
+ private attempt;
385
+ private serveStaleOrUnconfigured;
386
+ }
49
387
  export interface WriteNpmrcOptions {
50
388
  /**
51
389
  * Scope prefixes (e.g. `@superblocksteam`) whose existing
52
- * `<scope>:registry=` lines must survive. Used to preserve scope mappings
53
- * baked into a template `.npmrc` by setup-template.sh in EE builds.
390
+ * `<scope>:registry=` lines must survive together with every
391
+ * auth-family key (`_authToken`, `_password`, `username`, `email`,
392
+ * `_auth`) keyed on the same `//<origin>/` prefix as
393
+ * the preserved registry URL. Used to keep scope mappings baked into a
394
+ * template `.npmrc` by setup-template.sh in EE builds, including the
395
+ * GHPR token that authenticates `@superblocksteam/*` installs
396
+ * (APPS-4300). When the same scope is also overridden in
397
+ * `config.scopes`, both the registry line and its auth-family lines
398
+ * are dropped in favour of the override.
54
399
  */
55
400
  preserveScopeLines?: string[];
401
+ /**
402
+ * POSIX file mode for the written `.npmrc`. Defaults to `0o600` (owner
403
+ * read/write) because the per-install-site writers rewrite the file on
404
+ * every install; the immutability angle doesn't apply there. Callers that
405
+ * own a persistent file users should not clobber (e.g. the home-level
406
+ * writer at APPS-4231) pass `0o400` to drop the owner write bit. Atomic
407
+ * rename in this writer is governed by parent-dir perms, not the target
408
+ * file mode, so a stricter mode does not interfere with subsequent
409
+ * re-writes from the same caller.
410
+ */
411
+ mode?: number;
412
+ /**
413
+ * Optional plain-text header written as the first line of the file,
414
+ * prefixed with `; ` so npm and `ini.parse` treat it as a comment. Used as
415
+ * an in-band ownership marker so a delete-on-unset path can tell a file
416
+ * this writer created apart from one the operator hand-maintains. Must
417
+ * not contain `\r` or `\n`.
418
+ */
419
+ header?: string;
56
420
  }
57
421
  /**
58
422
  * Renders the npm registry config to an `.npmrc` file at `targetPath`.
59
423
  *
424
+ * Uses `ini.stringify` (the same library `@npmcli/config` uses to read and
425
+ * write `.npmrc`) so the file is guaranteed to round-trip through
426
+ * `ini.parse`. This handles value-quoting for `=`, `\r`, `\n`, leading `[`,
427
+ * leading/trailing whitespace, and escapes `;` / `#` so they're not treated
428
+ * as comments.
429
+ *
430
+ * Validation NOT covered by `ini`:
431
+ * - `noControlChars` Zod refinement: `ini.safe` JSON-quotes `\r`/`\n`
432
+ * rather than rejecting them, and accepts other C0 control bytes
433
+ * (`\x00..\x1f`) unchanged — so we reject the entire C0 range at the
434
+ * wire-schema layer instead.
435
+ * - Scope grammar (`@scope` must match npm's regex): `@npmcli/config`
436
+ * does not validate scope names.
437
+ * - https-only registry URLs: npm itself accepts http.
438
+ *
60
439
  * When `preserveScopeLines` is provided, any existing `<scope>:registry=`
61
- * lines for those scopes are preserved verbatim (so EE-baked
440
+ * keys for those scopes are preserved (so EE-baked
62
441
  * `@superblocksteam:registry=https://npm.pkg.github.com/` survives) UNLESS
63
- * the same scope is explicitly overridden in `config.scopes`.
442
+ * the same scope is explicitly overridden in `config.scopes`. The
443
+ * preserved value is read via `ini.parse` from the existing file, so
444
+ * `ini`'s own quoting rules apply (rather than our older line-grep that
445
+ * could mis-handle quoted values).
64
446
  *
65
447
  * Idempotent: rewrites the file in full each call. Writes are atomic (tmp +
66
448
  * rename) and the file mode is restricted to 0600 because the file may carry
67
449
  * an auth token.
450
+ *
451
+ * Requires `config.configured === true` OR `config.allowInstallScripts === false`
452
+ * (so the writer can emit a policy-only `.npmrc` with just `ignore-scripts=true`
453
+ * even when no registry rows are configured). A scoped-only config (no `default`
454
+ * entry) is valid — only scope `:registry=` lines are emitted, and unscoped
455
+ * packages fall through to public npm. This matches the server contract
456
+ * (the `(org, scope)` unique constraint allows scoped-only rows) and is
457
+ * the right shape for customers who only want to override one `@scope`
458
+ * without redirecting the rest of npm.
68
459
  */
69
460
  export declare function writeNpmrc(targetPath: string, config: NpmRegistryConfig, options?: WriteNpmrcOptions): Promise<void>;
70
461
  /**
71
- * Convenience: invoke `writeNpmrc` against `<dir>/.npmrc` if registry config is
72
- * present in the env. No-op when unset (preserves today's behavior for SaaS /
73
- * local dev).
462
+ * Filename of the on-disk backup of the baked-in `.npmrc` that ships with the
463
+ * dev-server image. Captured once per pod boot by `maybeWriteNpmrcForDir`
464
+ * before the first `writeNpmrc` rewrite, then used as the restore source when
465
+ * the org transitions back to `not-configured` (e.g. the last
466
+ * `org_npm_registry` row was deleted). Lives sibling to `.npmrc` in the same
467
+ * directory writes target.
468
+ */
469
+ export declare const NPMRC_DEFAULT_FILENAME = ".npmrc.default";
470
+ /**
471
+ * Outcome of a `snapshotInitialNpmrc` attempt. Lets a caller decide
472
+ * whether a usable backup now exists before it acts on the symmetric
473
+ * restore path — in particular, whether it is safe to ask
474
+ * `restoreInitialNpmrc` to UNLINK a Superblocks-owned target when the
475
+ * backup turns up missing.
74
476
  *
75
- * Returns the loaded config when an `.npmrc` was written, otherwise `undefined`.
477
+ * - `created` — this call wrote the hardlink; the backup is now present
478
+ * and holds the baked-in baseline.
479
+ * - `exists` — the backup was already present (prior boot, or a
480
+ * concurrent caller won the race). A usable backup exists.
481
+ * - `no-source` — `targetPath` had no baked-in file to snapshot (ENOENT).
482
+ * The natural restore baseline is "no file"; there is
483
+ * nothing to back up and nothing to restore to.
484
+ * - `failed` — the snapshot was attempted against a real target but
485
+ * `link(2)` failed for a reason other than EEXIST/ENOENT
486
+ * (e.g. EXDEV when target and backup straddle filesystems).
487
+ * A baked-in file existed but NO backup was produced — the
488
+ * restore path must NOT treat a missing backup as "nothing
489
+ * to preserve" and unlink the target.
76
490
  */
77
- export declare function maybeWriteNpmrcForDir(dir: string, options?: WriteNpmrcOptions, env?: NodeJS.ProcessEnv): Promise<NpmRegistryConfig | undefined>;
491
+ export type SnapshotInitialNpmrcOutcome = "created" | "exists" | "no-source" | "failed";
78
492
  /**
79
- * Materializes the private-registry `.npmrc` (when the env contract is set)
80
- * AND unconditionally strips `resolved` URLs from any lockfile present in
81
- * `dir`, so a subsequent `npm install` is guaranteed to re-resolve through
82
- * the active registry.
493
+ * Snapshot `targetPath` to `backupPath` exactly once if the backup does
494
+ * not already exist. Lets a subsequent `writeNpmrc`-style rewrite of
495
+ * `targetPath` happen non-destructively: a restore path has a baked-in
496
+ * source to copy back when the org transitions to `not-configured`.
497
+ *
498
+ * Uses `link(2)` rather than read + write so the snapshot is concurrency-
499
+ * safe. Two properties matter:
500
+ *
501
+ * - The kernel serializes hardlink creation: only one of N concurrent
502
+ * callers wins; the rest see EEXIST. No read-then-write window where
503
+ * a slow caller could observe a `writeNpmrc`-rewritten target and
504
+ * poison the backup with the private registry content.
505
+ * - `writeNpmrc` rewrites the target via tmp + rename, which gives it
506
+ * a fresh inode. The hardlink at `backupPath` keeps the original
507
+ * inode (with the baked-in content) alive — that's the whole point.
508
+ *
509
+ * Silently no-ops in two cases:
510
+ * - `backupPath` is already present (subsequent boots, or a second
511
+ * call in the same boot after the first capture landed). Important so
512
+ * a later `writeNpmrc`-rewritten target is never mistaken for the
513
+ * baked-in baseline.
514
+ * - `targetPath` does not exist (image without a baked-in `.npmrc`,
515
+ * or a brownfield app dir that never had one). The natural "restore"
516
+ * state is "no file", which the restore path handles by treating an
517
+ * absent backup as "no file" too — for that case `restoreInitialNpmrc`
518
+ * unlinks the target if asked, otherwise leaves it alone.
519
+ *
520
+ * Other errors degrade to a warning + no-op rather than throwing — the
521
+ * capture is a best-effort safety net for the restore path, not a
522
+ * precondition for the install path itself.
523
+ *
524
+ * Returns a `SnapshotInitialNpmrcOutcome` so a caller can tell whether a
525
+ * usable backup now exists. This matters for the destructive restore path:
526
+ * a silent `failed` (e.g. EXDEV) leaves no backup behind, so a later
527
+ * `not-configured` transition must NOT mistake the absent backup for
528
+ * "nothing to preserve" and unlink a real baked-in userconfig (APPS-4428).
529
+ * Still never throws — the outcome is advisory, not an error channel.
530
+ *
531
+ * Shared with the home-npmrc writer (CLI startup, target =
532
+ * `~/.superblocks/npmrc`) so both project-dir and home flows agree on the
533
+ * snapshot/restore semantics.
534
+ */
535
+ export declare function snapshotInitialNpmrc(targetPath: string, backupPath: string): Promise<SnapshotInitialNpmrcOutcome>;
536
+ export interface RestoreInitialNpmrcOptions {
537
+ /**
538
+ * When `true`, missing backup AND a present target file means "remove
539
+ * the target": the caller owns the target file and has no image-baked
540
+ * baseline to fall back to, so leaving a stale rewrite in place would
541
+ * be wrong. The home-npmrc writer (`~/.superblocks/npmrc`) passes this
542
+ * because the file is Superblocks-owned — nothing else should be
543
+ * reading from it, and leaving a stale configured-org registry/auth
544
+ * after a transition to `not-configured` is the exact case
545
+ * `gpoulios-sb` flagged on APPS-4328.
546
+ *
547
+ * When `false` (the default), missing backup means "leave the target
548
+ * alone": the caller (project `.npmrc`) shares the file with template
549
+ * tooling and lockfile-aware installs; an empty or absent file would
550
+ * break unrelated callers. This is the original APPS-4320 semantics.
551
+ */
552
+ unlinkTargetWhenBackupMissing?: boolean;
553
+ }
554
+ /**
555
+ * Restore `targetPath` from `backupPath` when the org has transitioned
556
+ * back to `not-configured` (e.g. last `org_npm_registry` row deleted).
557
+ * Without this, a file previously rewritten by `writeNpmrc` stays in
558
+ * place pointing at a registry the customer just removed — the exact
559
+ * case APPS-4320 closes for project `.npmrc`, and APPS-4328 closes for
560
+ * the home-level userconfig.
561
+ *
562
+ * Best-effort, never throws. The project-dir caller
563
+ * (`maybeWriteNpmrcForDir` → `prepareForPrivateRegistry`) needs to fall
564
+ * through to `stripResolvedFromLockfile` even when the restore can't
565
+ * run; the home-level caller (`syncHomeNpmrc`) needs to finish dev-server
566
+ * startup. Any error here degrades to a warn + no-op so the surrounding
567
+ * install path stays alive.
568
+ *
569
+ * Skips the rewrite (without a warning) in three harmless cases:
570
+ * - `targetPath` does not exist. The baked-in baseline for this dir is
571
+ * "no file"; there is nothing to restore to.
572
+ * - `backupPath` is missing AND `options.unlinkTargetWhenBackupMissing`
573
+ * is not set. Expected on pods that never had `org_npm_registry`
574
+ * configured (snapshot is gated on the configured branch), AND on
575
+ * older pods whose target was rewritten before this commit landed.
576
+ * In both cases the running file is what the pod has lived with —
577
+ * leave it alone.
578
+ * - `targetPath` already byte-matches `backupPath`. Avoids a no-op
579
+ * rewrite that would bump mtime for downstream watchers (e.g. Tilt /
580
+ * Vite file-sync).
581
+ *
582
+ * When `options.unlinkTargetWhenBackupMissing` is true and the backup is
583
+ * absent but the target exists, the target is unlinked — see the option
584
+ * comment.
585
+ */
586
+ export declare function restoreInitialNpmrc(targetPath: string, backupPath: string, options?: RestoreInitialNpmrcOptions): Promise<void>;
587
+ /**
588
+ * Outcome of `ensureNpmrcGitignored`. The caller's fail-closed decision is
589
+ * `!ignored`: any state in which we cannot positively guarantee that
590
+ * `.npmrc` is ignored from `<dir>` — whether `.git` exists today or not —
591
+ * blocks the credential write. `hasGit` is retained for logging /
592
+ * diagnostics so warnings can distinguish the "native git can stage on the
593
+ * next commit" case from the "no native git today, but a future `git init`
594
+ * would stage" case.
595
+ */
596
+ export interface EnsureNpmrcGitignoredResult {
597
+ /**
598
+ * True iff `.npmrc` is now positively known to be ignored from `<dir>`.
599
+ * The bar depends on whether a `.git` is present:
600
+ *
601
+ * - Native git (`<dir>/.git` exists): an ignore entry was written
602
+ * (`<dir>/.gitignore` or the per-clone `<dir>/.git/info/exclude`) AND
603
+ * `git check-ignore .npmrc` confirms the file is effectively ignored
604
+ * (catches later `!.npmrc` negations) AND `git ls-files .npmrc` shows
605
+ * the file is not already tracked (an ignore line does not protect a
606
+ * file already in the index).
607
+ *
608
+ * - Non-native (no `<dir>/.git`): `<dir>/.gitignore` was appended to
609
+ * OR created with the `.npmrc` line. We create `.gitignore` even
610
+ * when none exists so a subsequent `git init` in the same dir is
611
+ * future-safe: a customer who later turns on native git for an app
612
+ * where a credential-bearing `.npmrc` already sits on disk would
613
+ * otherwise stage the token on their first `git add .` before any
614
+ * install path re-runs this helper. Returns `false` only when the
615
+ * `.gitignore` write itself fails (rare — disk full, permission
616
+ * denied, `.gitignore` is a directory); in that case the caller MUST
617
+ * refuse to write the credential-bearing `.npmrc`, because the
618
+ * future-git leak path the reviewer flagged is still open (a later
619
+ * `git init` finds the token on disk with no ignore rule).
620
+ */
621
+ ignored: boolean;
622
+ /** True iff `<dir>` is inside any git working tree — either because
623
+ * `<dir>/.git` exists OR because a parent directory has `.git` (nested-repo
624
+ * layout). Diagnostic only — the fail-closed decision in
625
+ * `maybeWriteNpmrcForDir` is `!ignored`, regardless of `hasGit`. The
626
+ * nested-repo case matters because a parent's `git add .` will stage
627
+ * `<dir>/.npmrc` unless an ignore rule excludes it; see PR #19642 review
628
+ * from cursor[bot]. */
629
+ hasGit: boolean;
630
+ }
631
+ /**
632
+ * Ensure `.npmrc` is git-ignored from `<dir>` before any credential-bearing
633
+ * `.npmrc` is written.
634
+ *
635
+ * Why this matters: `maybeWriteNpmrcForDir` writes a `.npmrc` containing
636
+ * `//host/:_authToken=<customer token>` at the app root. The DBFS sync
637
+ * already excludes `.npmrc` from upload (`codeModeExcludedFiles` in
638
+ * `file-system-helpers.ts`), but the native-git path calls `git.add(".")`
639
+ * (socket-manager `gitCommitLocal`/`gitPull`, agent `git`/`git_raw` tools)
640
+ * which stages everything not listed in the app's on-disk ignore. Without
641
+ * this enforcement, a customer with a configured private registry pushes
642
+ * their `_authToken` to their external git remote on the next commit. The
643
+ * template `.gitignore` covers new apps; this helper covers the existing
644
+ * fleet whose `.gitignore` predates the registry feature.
645
+ *
646
+ * Behaviour, in three layers:
647
+ *
648
+ * 1. Non-native-git (`<dir>/.git` does not exist):
649
+ * - Append `.npmrc` to `<dir>/.gitignore`, or create the file if
650
+ * it doesn't exist. We create `.gitignore` here (not just when
651
+ * `.git` already exists) so a subsequent `git init` in the same
652
+ * directory is future-safe: a customer who later turns on native
653
+ * git for an app where a credential-bearing `.npmrc` already
654
+ * sits on disk would otherwise stage the token on their first
655
+ * `git add .` before any install path re-runs this helper.
656
+ * - On success return `{ ignored: true, hasGit: false }`. On a
657
+ * read or write failure (`.gitignore` is a directory, disk full,
658
+ * permission denied) return `{ ignored: false, hasGit: false }`.
659
+ * The caller MUST refuse to write the credential-bearing
660
+ * `.npmrc` in that case: even though no native git checkout
661
+ * exists today, a later `git init` (or wiring this dir into an
662
+ * existing repo) would find the on-disk token and stage it on
663
+ * the first `git add .` — exactly the future-git leak path
664
+ * gpoulios-sb flagged in code review.
665
+ *
666
+ * 2. Native-git (`<dir>/.git` exists), `.gitignore` path:
667
+ * - Read or create `.gitignore`, append `.npmrc` if missing. After
668
+ * the append, verify with git itself (`verifyNpmrcIgnoredByGit`)
669
+ * that the file is effectively ignored AND not already tracked.
670
+ * Only then return `ignored: true`.
671
+ *
672
+ * 3. Native-git, `.git/info/exclude` fallback:
673
+ * - Only attempted when the `.gitignore` write failed AND `.git` is a
674
+ * directory (regular repo). `.git/info/exclude` is the per-clone
675
+ * ignore file: not synced to remotes, but exactly as effective as
676
+ * `.gitignore` at preventing `git add` from staging the file. The
677
+ * fallback lets us still fail-closed when the working tree is
678
+ * read-only but the local clone metadata is writable. After the
679
+ * exclude append, the same `verifyNpmrcIgnoredByGit` check runs.
680
+ *
681
+ * Why git verification, not just text: a `.gitignore` containing `.npmrc`
682
+ * is necessary but not sufficient. Two real leak paths bypass a naive
683
+ * "any line matches" textual check:
684
+ * - A later `!.npmrc` re-includes the file (gitignore is order-sensitive).
685
+ * `textIgnoresNpmrc` is already negation-aware (returns true only when
686
+ * the LAST matching rule is positive), so for this case the textual
687
+ * check correctly reports "not ignored" and `appendNpmrcIgnore` writes
688
+ * a fresh positive `.npmrc` line that becomes the new last-match.
689
+ * This is the gpoulios-sb PR #19642 re-review "always append" remedy,
690
+ * applied uniformly to both branches (non-native-git relies on it as
691
+ * the authoritative check; native-git double-checks with git below).
692
+ * - An already-tracked `.npmrc` stages on every commit regardless of
693
+ * ignore rules. No textual check sees the index, so for native-git
694
+ * `git ls-files --error-unmatch` is the only catch.
695
+ *
696
+ * Returns `{ ignored, hasGit }`. The caller's fail-closed signal is
697
+ * `ignored === false` regardless of `hasGit`: any state in which we cannot
698
+ * guarantee `.npmrc` is ignored from `<dir>` blocks the credential write,
699
+ * because either the current native git checkout would stage the token on
700
+ * the next `git add .`, or a future `git init` in the same dir would find
701
+ * the token on disk and stage it on the first `git add .`. `hasGit` is
702
+ * retained for logging only — it lets the warning distinguish the
703
+ * "tomorrow's risk" case from the "today's risk" case.
704
+ */
705
+ export declare function ensureNpmrcGitignored(dir: string): Promise<EnsureNpmrcGitignoredResult>;
706
+ /**
707
+ * Convenience: invoke `writeNpmrc` against `<dir>/.npmrc` when the client
708
+ * resolves to a configured (or stale) registry config. No-op when:
709
+ *
710
+ * - `client` is undefined (test-only opt-out — production code paths
711
+ * always pass a client constructed by AiService); or
712
+ * - the resolved config is `source: "unreachable"` (transient cold-cache
713
+ * outage — we leave `.npmrc` whatever it currently is and reconcile on
714
+ * the next successful fetch, rather than clobbering a last-known-good
715
+ * private-registry `.npmrc` back to baseline during the outage window);
716
+ * or
717
+ * - the resolved config is `source: "not-configured"` (deliberate unset
718
+ * observation). If a previously-written `.npmrc` exists that no longer
719
+ * matches the baked-in `.npmrc.default`, restore it — see APPS-4320.
720
+ *
721
+ * On the first call per pod boot for `<dir>`, captures `<dir>/.npmrc` to
722
+ * `<dir>/.npmrc.default` before any rewrite so the restore path above has
723
+ * a source to copy back from.
724
+ *
725
+ * Returns the resolved fetch result so callers can branch on `source` for
726
+ * observability, or `undefined` when no client was provided.
727
+ */
728
+ export declare function maybeWriteNpmrcForDir(dir: string, options?: WriteNpmrcOptions, client?: NpmRegistryClient): Promise<NpmRegistryFetchResult | undefined>;
729
+ /**
730
+ * Materializes the private-registry `.npmrc` AND unconditionally strips
731
+ * `resolved` URLs from any lockfile present in `dir`, so a subsequent
732
+ * `npm install` is guaranteed to re-resolve through the active registry.
733
+ * The `.npmrc` write is gated on whether the client resolves to a configured
734
+ * registry; the lockfile strip runs regardless.
83
735
  *
84
736
  * Why the strip is unconditional (decoupled from the `.npmrc` write):
85
- * - SaaS dev-server pods (staging/prod) run without `SUPERBLOCKS_NPM_REGISTRY`
86
- * set, but their lockfiles can still be poisoned with cross-registry
87
- * `resolved` URLs via DBFS state (APPS-4300). Gating the strip on the
88
- * env meant the only safety net never fired in the exact environment
89
- * where it mattered. Always stripping closes that read path.
737
+ * - SaaS dev-server pods (staging/prod) often run without a configured
738
+ * org npm registry, but their lockfiles can still be poisoned with
739
+ * cross-registry `resolved` URLs via DBFS state (APPS-4300). Gating
740
+ * the strip on the client's configured-ness meant the only safety net
741
+ * never fired in the exact environment where it mattered. Always
742
+ * stripping closes that read path.
90
743
  * - The mutation is transient: `npm install` rewrites `resolved` with
91
744
  * URLs from the active `.npmrc` registry on the very next line, so a
92
745
  * local dev whose lockfile pointed at npmjs.org sees the same URLs
@@ -102,10 +755,11 @@ export declare function maybeWriteNpmrcForDir(dir: string, options?: WriteNpmrcO
102
755
  * (only configure GHPR scope for ephemeral builds) is the actual durable
103
756
  * fix; this strip is the runtime safety net for already-poisoned DBFS state.
104
757
  *
105
- * Returns the loaded config when an `.npmrc` was written, otherwise
106
- * `undefined`. The lockfile strip happens in both cases.
758
+ * Returns the resolved fetch result when a client is provided (so callers
759
+ * can branch on `source` for observability), or `undefined` when no client
760
+ * was provided. The lockfile strip happens in both cases.
107
761
  */
108
- export declare function prepareForPrivateRegistry(dir: string, options?: WriteNpmrcOptions, env?: NodeJS.ProcessEnv): Promise<NpmRegistryConfig | undefined>;
762
+ export declare function prepareForPrivateRegistry(dir: string, options?: WriteNpmrcOptions, client?: NpmRegistryClient): Promise<NpmRegistryFetchResult | undefined>;
109
763
  /**
110
764
  * Removes the `resolved` field from every entry in `.packages` of an npm v2+
111
765
  * lockfile. TS-side counterpart to `scripts/strip-lockfile-resolved.sh`.
@@ -134,4 +788,38 @@ export declare function prepareForPrivateRegistry(dir: string, options?: WriteNp
134
788
  * crash mid-write.
135
789
  */
136
790
  export declare function stripResolvedFromLockfile(dir: string): Promise<void>;
791
+ export declare function stripResolvedFromLockfilePath(lockfilePath: string): Promise<void>;
792
+ /**
793
+ * APPS-4370. Build a disk-backed `LastKnownRegistryPolicyStore` rooted at
794
+ * `<rootDir>/.superblocks/orgs/<orgId>/.registry-was-configured`.
795
+ *
796
+ * The marker's PRESENCE is the semantic signal; its content is informal
797
+ * (an ISO timestamp for forensics — "when was this org first seen with a
798
+ * configured registry"). Writes are best-effort: a write failure (EACCES
799
+ * on a 0o500 parent, ENOSPC) is logged and swallowed. Reads return false
800
+ * on any error so a transient IO problem doesn't degrade today's
801
+ * "proceed" behaviour into a fail-closed install.
802
+ *
803
+ * APPS-4427: `markConfigured` is AWAITED by its caller
804
+ * (`NpmRegistryClient.markPolicyConfigured`) before a `configured` / `stale`
805
+ * resolution is handed back, so the marker is durably on disk before that
806
+ * result is used — closing the crash window an un-awaited write would leave
807
+ * open. The write stays async (`fs/promises`, per the ai-service no-sync-fs
808
+ * rule) and is guarded to run at most once per process, so awaiting it stays
809
+ * off the install hot path after the first configured resolution.
810
+ *
811
+ * The `rootDir` is injected (rather than hard-coded to `os.homedir()`)
812
+ * so tests can isolate to a tmp directory and so the dev-server can
813
+ * point the store at the same disk it already manages
814
+ * (`~/.superblocks/...`). The per-org path keeps multiple orgs running
815
+ * against the same machine from contaminating each other's marker state.
816
+ */
817
+ export declare function createDiskRegistryPolicyStore(opts: {
818
+ organizationId: string;
819
+ rootDir: string;
820
+ }): LastKnownRegistryPolicyStore;
821
+ export { parseNpmJsonError, parseNpmJsonDiagnostic, classifyNpmExecStderr, } from "./npm-error-parser.js";
822
+ export type { ParseContext } from "./npm-error-parser.js";
823
+ export { NpmInstallBlocked, NpmInstallError, scrubSecrets } from "../types.js";
824
+ export type { NpmInstallBlockedReason, NpmInstallBlockedSubreason, NpmInstallBlockedDetails, } from "../types.js";
137
825
  //# sourceMappingURL=npm-registry.d.ts.map