@runeya/runeya 2.0.146 → 2.0.148

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 (171) hide show
  1. package/agent/index.js +10 -4
  2. package/agent/index.js.map +1 -1
  3. package/{agent-manager-R7BPQO6D.js → agent-manager-46C4NIQ2.js} +4 -2
  4. package/{chat-LPML22J5.js → chat-CMNDMXIP.js} +9 -5
  5. package/{chunk-SEPAURTQ.js → chunk-2NE52CL5.js} +156 -923
  6. package/chunk-2NE52CL5.js.map +1 -0
  7. package/chunk-4D3GCBPO.js +462 -0
  8. package/chunk-4D3GCBPO.js.map +1 -0
  9. package/{chunk-KV4GJKXU.js → chunk-5XG3X2TM.js} +27 -1479
  10. package/chunk-5XG3X2TM.js.map +1 -0
  11. package/chunk-7QXP4FXD.js +969 -0
  12. package/chunk-7QXP4FXD.js.map +1 -0
  13. package/chunk-DA2DZBTL.js +43 -0
  14. package/chunk-DA2DZBTL.js.map +1 -0
  15. package/chunk-DFPZ4MTI.js +1041 -0
  16. package/chunk-DFPZ4MTI.js.map +1 -0
  17. package/{chunk-UD42LACH.js → chunk-JNK6H5KH.js} +164 -1007
  18. package/chunk-JNK6H5KH.js.map +1 -0
  19. package/chunk-WDHLEO5S.js +809 -0
  20. package/chunk-WDHLEO5S.js.map +1 -0
  21. package/{companion-W3UMZWGH.js → companion-XKMNRIJB.js} +8 -3
  22. package/environment-resolver-ZDTF3MHI.js +18 -0
  23. package/environment-resolver-ZDTF3MHI.js.map +1 -0
  24. package/index.js +7 -7
  25. package/{install-gate-PVMGJDWE.js → install-gate-PWPGC63Z.js} +2 -2
  26. package/mcp-ready-TAPBAOZY.js +9 -0
  27. package/mcp-ready-TAPBAOZY.js.map +1 -0
  28. package/mcp-server/index.js +24 -0
  29. package/mcp-server/index.js.map +1 -1
  30. package/mcp-server/launch.d.ts +6 -0
  31. package/mcp-server/launch.js +2 -1
  32. package/mcp-server/launch.js.map +1 -1
  33. package/package.json +1 -1
  34. package/project-store-FLXTAD3J.js +23 -0
  35. package/project-store-FLXTAD3J.js.map +1 -0
  36. package/{src-LEDGW5KP.js → src-I5TPC6GW.js} +155 -63
  37. package/src-I5TPC6GW.js.map +1 -0
  38. package/web/assets/{AgentScalarChatInterface.vue-BYszptaB.js → AgentScalarChatInterface.vue-DKBinB_l.js} +2 -2
  39. package/web/assets/AgentTerminalPage-CixU7x6O.js +1 -0
  40. package/web/assets/AiChatModals-BvGUd-QD.css +1 -0
  41. package/web/assets/AiChatModals-C-hXImkO.js +4 -0
  42. package/web/assets/AppLayout-At9wk0sx.js +1 -0
  43. package/web/assets/{AppLayout-tsw5Ow0w.css → AppLayout-BlhwflMq.css} +1 -1
  44. package/web/assets/AuditPage-BkqbHxjY.js +1 -0
  45. package/web/assets/CloudAccountPage-D5C2SCS9.js +1 -0
  46. package/web/assets/CloudMessagingPage-Bi57GGHO.js +2 -0
  47. package/web/assets/DashboardPage-dEQop9ql.js +1 -0
  48. package/web/assets/DirectoryPickerDialog-DIMSeDhw.js +1 -0
  49. package/web/assets/{EditorIcon-CypGLV6w.js → EditorIcon-W3Szca4J.js} +1 -1
  50. package/web/assets/EnvironmentForm-C5emavNh.js +1 -0
  51. package/web/assets/EnvironmentFormPage-BIoy8fsW.js +1 -0
  52. package/web/assets/EnvironmentListPage-BDYeh7XF.js +1 -0
  53. package/web/assets/{FlagChip-R_T5qYpL.js → FlagChip-DPtIBCjQ.js} +1 -1
  54. package/web/assets/KanbanBoardPage-BlCcjBwV.js +4 -0
  55. package/web/assets/KanbanListPage-DbXONxfC.js +1 -0
  56. package/web/assets/LaunchProjectPage-Cg92hHwQ.js +1 -0
  57. package/web/assets/LaunchServicesPage-CO8gOB9D.js +1 -0
  58. package/web/assets/LaunchServicesPage-h5o3zLcy.css +1 -0
  59. package/web/assets/{LaunchShell-DUrSunB4.js → LaunchShell-CDb21ihZ.js} +1 -1
  60. package/web/assets/LogViewer-DsusKk2b.js +3 -0
  61. package/web/assets/OrgKeyPrompt-CX9K7r9I.js +1 -0
  62. package/web/assets/ProcessControls-B7hWVqiR.css +1 -0
  63. package/web/assets/ProcessControls-TEZutuxq.js +1 -0
  64. package/web/assets/ProcessDetailPage-Cj23FECq.js +1 -0
  65. package/web/assets/ProcessMetrics-1P6iE2Il.js +1 -0
  66. package/web/assets/ProjectCreateDialog-D3aMWKfw.js +1 -0
  67. package/web/assets/{ProjectLocationGate-DhMVdIjN.js → ProjectLocationGate-DU0Jj8Nw.js} +1 -1
  68. package/web/assets/ProjectPublishDialog-D7sNKei3.js +1 -0
  69. package/web/assets/{RandomJobTestPage-BUS2Ty6-.js → RandomJobTestPage-DQUMRHyf.js} +1 -1
  70. package/web/assets/{ScenarioEditorPage-vMoRt0Qn.css → ScenarioEditorPage-BSjSX4bw.css} +1 -1
  71. package/web/assets/ScenarioEditorPage-DvCdcY9m.js +14 -0
  72. package/web/assets/SendToChatDialog-BdWKjYmv.js +1 -0
  73. package/web/assets/ServiceControlsBar-B_J5m-Kc.js +1 -0
  74. package/web/assets/ServiceDetailPage-DhJcjFfY.js +2 -0
  75. package/web/assets/{ServiceDetailPage-COdaCZ6r.css → ServiceDetailPage-DnSf5cm9.css} +1 -1
  76. package/web/assets/ServiceFormPage-fp8NCHSv.js +1 -0
  77. package/web/assets/{SettingsPage-4pKUgKPm.css → SettingsPage-NH2fQap4.css} +1 -1
  78. package/web/assets/SettingsPage-s28BodWe.js +2086 -0
  79. package/web/assets/UnlockPage-BXXIPHW_.js +1 -0
  80. package/web/assets/VaultMigrationPage-BfNuKtkJ.js +1 -0
  81. package/web/assets/{VersionBadge-BmArk24p.js → VersionBadge-CW7czacg.js} +1 -1
  82. package/web/assets/{WikiPage-DADu8WUA.js → WikiPage-BiwpCM3E.js} +1 -1
  83. package/web/assets/WikiView-CMch_06Z.js +1 -0
  84. package/web/assets/{add-scalar-classes-dWEWPlFD.js → add-scalar-classes-Ckanjq1h.js} +14 -14
  85. package/web/assets/{agent.store-CKLAkF9a.js → agent.store-piokQ9Dt.js} +1 -1
  86. package/web/assets/{auth-Cg9BWTfK.js → auth-BT_jf-5e.js} +2 -2
  87. package/web/assets/cloud-chat.store-CDKh4tIy.js +1 -0
  88. package/web/assets/{cloud-client-D_zCiW0s.js → cloud-client-CzPdZLDq.js} +2 -2
  89. package/web/assets/{cloud-session-DOaDpZTy.js → cloud-session-BYfn6YM4.js} +2 -2
  90. package/web/assets/floating-ui.dom-DF3oyfHd.js +1 -0
  91. package/web/assets/i18n-D0FT7rCj.js +1 -0
  92. package/web/assets/index-3LTzgeU-.css +1 -0
  93. package/web/assets/index-Ci2CNnit.js +309 -0
  94. package/web/assets/modal-iRaP1izE.js +5 -0
  95. package/web/assets/notification.store-CvvuTBVz.js +1 -0
  96. package/web/assets/{project-launch.store-y7JBMNiE.js → project-launch.store-CTmbl8UY.js} +1 -1
  97. package/web/assets/{project.store-DGJr0Qr9.js → project.store-CNC6_4r4.js} +1 -1
  98. package/web/assets/runtime-dom.esm-bundler-CyEVRK6B.js +1 -0
  99. package/web/assets/scenario.store-BIFQPXgl.js +7 -0
  100. package/web/assets/{service.store-5jWsJxPx.js → service.store-B40U4QzI.js} +1 -1
  101. package/web/assets/settings-scope-CyWMCjRY.js +1 -0
  102. package/web/assets/settings.store-Dw6bAoWE.js +1 -0
  103. package/web/assets/{src-Dnwk6NpV.js → src-CrNi4Pjg.js} +3 -3
  104. package/web/assets/src-DIBHat7I.js +3 -0
  105. package/web/assets/src-DtCW_XCh.js +1 -0
  106. package/web/assets/{trpc-Drabzwpd.js → trpc-BAZGRZBD.js} +3 -3
  107. package/web/assets/{useCloudOrg-BAQEtFPl.js → useCloudOrg-EnXFBwt1.js} +1 -1
  108. package/web/assets/useEnvVarAutocomplete-BxJh2Lw0.js +1 -0
  109. package/web/assets/useProjectCatalog-BrvUGv9A.js +1 -0
  110. package/web/assets/{useProjectCloudOrg-CEN9-_OA.js → useProjectCloudOrg-Czxlg00k.js} +1 -1
  111. package/web/assets/{useServiceUrlHost-BuoPEXZX.js → useServiceUrlHost-Szz9y3rh.js} +1 -1
  112. package/web/assets/vue-i18n-o7yraQpR.js +3 -0
  113. package/web/index.html +21 -21
  114. package/chunk-KV4GJKXU.js.map +0 -1
  115. package/chunk-SEPAURTQ.js.map +0 -1
  116. package/chunk-UD42LACH.js.map +0 -1
  117. package/src-LEDGW5KP.js.map +0 -1
  118. package/web/assets/AgentTerminalPage-C3TJ1NL9.js +0 -1
  119. package/web/assets/AiChatModals-D3scRE3S.js +0 -4
  120. package/web/assets/AiChatModals-EfRuvId7.css +0 -1
  121. package/web/assets/AppLayout-BMQUjPKe.js +0 -1
  122. package/web/assets/AuditPage-CKcExBws.js +0 -1
  123. package/web/assets/CloudAccountPage-B6oPg0BZ.js +0 -1
  124. package/web/assets/CloudMessagingPage-C5fwVe-F.js +0 -2
  125. package/web/assets/DashboardPage-9RV7sx3e.js +0 -1
  126. package/web/assets/DirectoryPickerDialog-C06BXyfJ.js +0 -1
  127. package/web/assets/EnvironmentForm-CAvDoira.js +0 -1
  128. package/web/assets/EnvironmentFormPage-BmAxhN-m.js +0 -1
  129. package/web/assets/EnvironmentListPage-B8uATPBI.js +0 -1
  130. package/web/assets/KanbanBoardPage-CQsOkRUf.js +0 -4
  131. package/web/assets/KanbanListPage-CWZqkjD6.js +0 -1
  132. package/web/assets/LaunchProjectPage-saSmC7Pr.js +0 -1
  133. package/web/assets/LaunchServicesPage-BkKyLMEz.js +0 -1
  134. package/web/assets/LaunchServicesPage-DoLDPqQq.css +0 -1
  135. package/web/assets/LogViewer-CZBYkH9J.js +0 -3
  136. package/web/assets/OrgKeyPrompt-DzQ7pwbs.js +0 -1
  137. package/web/assets/ProcessControls-BEeoNlNV.js +0 -1
  138. package/web/assets/ProcessControls-BlpnVylq.css +0 -1
  139. package/web/assets/ProcessDetailPage-CQgu5uKm.js +0 -1
  140. package/web/assets/ProcessMetrics-Crlfkelj.js +0 -1
  141. package/web/assets/ProjectCreateDialog-BolrhRD-.js +0 -1
  142. package/web/assets/ProjectPublishDialog-DsZNWrfz.js +0 -1
  143. package/web/assets/ScenarioEditorPage-MIAGh2us.js +0 -14
  144. package/web/assets/SendToChatDialog-BxWsyv29.js +0 -1
  145. package/web/assets/ServiceControlsBar-DqDPAZt7.js +0 -1
  146. package/web/assets/ServiceDetailPage-hkSAzZ-l.js +0 -2
  147. package/web/assets/ServiceFormPage-DXqhqXg8.js +0 -1
  148. package/web/assets/SettingsPage-DS2YhMIM.js +0 -2086
  149. package/web/assets/UnlockPage-s8SgVcKk.js +0 -1
  150. package/web/assets/VaultMigrationPage-s0b44MDM.js +0 -1
  151. package/web/assets/WikiView-DtGwIt_Q.js +0 -1
  152. package/web/assets/cloud-chat.store-b-BHGz7I.js +0 -1
  153. package/web/assets/editor.store-DCAZSeVG.js +0 -1
  154. package/web/assets/floating-ui.dom-DWhsZyqT.js +0 -1
  155. package/web/assets/i18n-D0afQEGs.js +0 -1
  156. package/web/assets/index-D8VRbyoP.js +0 -309
  157. package/web/assets/index-DsezNbSM.css +0 -1
  158. package/web/assets/modal-qYgTMkYb.js +0 -5
  159. package/web/assets/scenario.store-YWHL2_wu.js +0 -7
  160. package/web/assets/settings-scope-Bv7QGPrt.js +0 -1
  161. package/web/assets/settings.store-bEsB7oKb.js +0 -1
  162. package/web/assets/src--GO-VSJ0.js +0 -5
  163. package/web/assets/src-C-D_y7ou.js +0 -1
  164. package/web/assets/useEnvVarAutocomplete-BqGTODUv.js +0 -1
  165. package/web/assets/useProjectCatalog-yocjIKt6.js +0 -1
  166. package/web/assets/vue-draggable-plus-BHsveB8w.css +0 -1
  167. package/web/assets/vue-draggable-plus-DX4rTBiu.js +0 -1
  168. /package/{agent-manager-R7BPQO6D.js.map → agent-manager-46C4NIQ2.js.map} +0 -0
  169. /package/{chat-LPML22J5.js.map → chat-CMNDMXIP.js.map} +0 -0
  170. /package/{companion-W3UMZWGH.js.map → companion-XKMNRIJB.js.map} +0 -0
  171. /package/{install-gate-PVMGJDWE.js.map → install-gate-PWPGC63Z.js.map} +0 -0
@@ -0,0 +1,1041 @@
1
+ import {
2
+ decryptValueWith,
3
+ encryptValueWith
4
+ } from "./chunk-OZBSOSAS.js";
5
+ import {
6
+ atomicWriteSecure,
7
+ fingerprintFiles,
8
+ hasChangedSinceRead,
9
+ rememberFile,
10
+ wasRead
11
+ } from "./chunk-4LJQIQMN.js";
12
+ import {
13
+ allVaultRoots,
14
+ localVaultRoot,
15
+ vaultFor,
16
+ vaultIdOfRoot
17
+ } from "./chunk-VPSQJOSN.js";
18
+ import {
19
+ CreateEnvironmentSchema,
20
+ EnvironmentSchema
21
+ } from "./chunk-MZSDTA4A.js";
22
+
23
+ // ../server/src/services/store-disk-coordinator.ts
24
+ import { AsyncLocalStorage } from "async_hooks";
25
+ var insideReload = new AsyncLocalStorage();
26
+ var StoreDiskCoordinator = class {
27
+ reloading = null;
28
+ saves = 0;
29
+ queue = Promise.resolve();
30
+ /**
31
+ * `read` rend de quoi substituer le nouvel état, ou `null` s'il n'y a rien à
32
+ * relire. La substitution doit être synchrone : c'est ce qui la rend atomique.
33
+ */
34
+ load(read) {
35
+ const inside = insideReload.getStore();
36
+ if (inside && (inside.has(this) || this.reloading)) return Promise.resolve();
37
+ this.reloading ??= this.reload(read).finally(() => {
38
+ this.reloading = null;
39
+ });
40
+ return this.reloading;
41
+ }
42
+ async reload(read) {
43
+ for (; ; ) {
44
+ await this.queue;
45
+ const savesBefore = this.saves;
46
+ const outer = insideReload.getStore() ?? /* @__PURE__ */ new Set();
47
+ const commit = await insideReload.run(/* @__PURE__ */ new Set([...outer, this]), read);
48
+ if (!commit) return;
49
+ if (this.saves !== savesBefore) continue;
50
+ commit();
51
+ return;
52
+ }
53
+ }
54
+ save(write) {
55
+ this.saves += 1;
56
+ const run = this.queue.then(write);
57
+ this.queue = run.catch(() => {
58
+ });
59
+ return run;
60
+ }
61
+ };
62
+
63
+ // ../server/src/services/project-key.ts
64
+ function projectKey(ref) {
65
+ return `${ref.orgId ?? "local"}:${ref.id}`;
66
+ }
67
+ var AmbiguousProjectError = class extends Error {
68
+ constructor(projectId, orgIds) {
69
+ const where = orgIds.map((o) => o ?? "local").join(", ");
70
+ super(
71
+ `Project id ${projectId} exists in several organizations (${where}). Name the organization alongside the project id.`
72
+ );
73
+ this.projectId = projectId;
74
+ this.orgIds = orgIds;
75
+ this.name = "AmbiguousProjectError";
76
+ }
77
+ code = "AMBIGUOUS_PROJECT_ID";
78
+ };
79
+
80
+ // ../server/src/services/vault-registry.ts
81
+ var loadProjects = null;
82
+ var projectRoots = /* @__PURE__ */ new Map();
83
+ var projectRefs = /* @__PURE__ */ new Map();
84
+ var serviceOwners = /* @__PURE__ */ new Map();
85
+ function registerProjectLoader(load) {
86
+ loadProjects = load;
87
+ }
88
+ async function ensureProjectsLoaded() {
89
+ await loadProjects?.();
90
+ }
91
+ function setProjectVault(ref, root) {
92
+ const key = projectKey(ref);
93
+ projectRoots.set(key, root);
94
+ projectRefs.set(key, { id: ref.id, orgId: ref.orgId ?? null });
95
+ }
96
+ function forgetProject(ref) {
97
+ const key = projectKey(ref);
98
+ projectRoots.delete(key);
99
+ projectRefs.delete(key);
100
+ for (const [serviceId, owners] of serviceOwners) {
101
+ owners.delete(key);
102
+ if (owners.size === 0) serviceOwners.delete(serviceId);
103
+ }
104
+ }
105
+ function setProjectServices(ref, serviceIds) {
106
+ const key = projectKey(ref);
107
+ for (const [serviceId, owners] of serviceOwners) {
108
+ if (!serviceIds.includes(serviceId)) {
109
+ owners.delete(key);
110
+ if (owners.size === 0) serviceOwners.delete(serviceId);
111
+ }
112
+ }
113
+ for (const serviceId of serviceIds) {
114
+ const owners = serviceOwners.get(serviceId) ?? /* @__PURE__ */ new Set();
115
+ owners.add(key);
116
+ serviceOwners.set(serviceId, owners);
117
+ }
118
+ }
119
+ function rootOfProjectRef(ref) {
120
+ return projectRoots.get(projectKey(ref)) ?? localVaultRoot();
121
+ }
122
+ function refsOfProjectId(projectId) {
123
+ const matches = [];
124
+ for (const ref of projectRefs.values()) {
125
+ if (ref.id === projectId) matches.push(ref);
126
+ }
127
+ return matches;
128
+ }
129
+ function rootOfProjectAmong(projectId, hint) {
130
+ const matches = refsOfProjectId(projectId);
131
+ if (matches.length > 1) {
132
+ const known = matches.map((ref2) => rootOfProjectRef(ref2));
133
+ if (hint && known.includes(hint)) return hint;
134
+ throw new AmbiguousProjectError(projectId, matches.map((r) => r.orgId ?? null));
135
+ }
136
+ const [ref] = matches;
137
+ return ref ? rootOfProjectRef(ref) : hint ?? localVaultRoot();
138
+ }
139
+ function refOfProjectAmong(projectId, hint) {
140
+ const matches = refsOfProjectId(projectId);
141
+ if (matches.length > 1) {
142
+ const found = hint ? matches.find((ref) => rootOfProjectRef(ref) === hint) : void 0;
143
+ if (found) return found;
144
+ throw new AmbiguousProjectError(projectId, matches.map((r) => r.orgId ?? null));
145
+ }
146
+ return matches[0] ?? { id: projectId, orgId: null };
147
+ }
148
+ function rootOfService(serviceId, hint = null) {
149
+ const owners = serviceOwners.get(serviceId);
150
+ if (!owners || owners.size === 0) return hint ?? localVaultRoot();
151
+ const roots = Array.from(owners, (key) => projectRoots.get(key)).filter((r) => !!r);
152
+ if (hint && roots.includes(hint)) return hint;
153
+ return roots[0] ?? hint ?? localVaultRoot();
154
+ }
155
+
156
+ // ../server/src/services/local-removals.ts
157
+ var removed = /* @__PURE__ */ new Map();
158
+ var detached = /* @__PURE__ */ new Map();
159
+ var linkKey = (projectId, field) => `${projectId}:${field}`;
160
+ function recordLocalRemoval(kind, id) {
161
+ const ids = removed.get(kind) ?? /* @__PURE__ */ new Set();
162
+ ids.add(id);
163
+ removed.set(kind, ids);
164
+ }
165
+ function isLocallyRemoved(kind, id) {
166
+ return removed.get(kind)?.has(id) ?? false;
167
+ }
168
+ function clearLocalRemoval(kind, id) {
169
+ removed.get(kind)?.delete(id);
170
+ }
171
+ function recordDetached(projectId, field, before, after) {
172
+ const kept = new Set(after);
173
+ const gone = before.filter((id) => !kept.has(id));
174
+ if (gone.length === 0) return;
175
+ const key = linkKey(projectId, field);
176
+ const ids = detached.get(key) ?? /* @__PURE__ */ new Set();
177
+ for (const id of gone) ids.add(id);
178
+ detached.set(key, ids);
179
+ }
180
+ function isDetached(projectId, field, id) {
181
+ return detached.get(linkKey(projectId, field))?.has(id) ?? false;
182
+ }
183
+ function clearDetached(projectId, field) {
184
+ detached.delete(linkKey(projectId, field));
185
+ }
186
+
187
+ // ../server/src/services/environment-store.ts
188
+ import { randomUUID } from "crypto";
189
+ import { readFile, rm } from "fs/promises";
190
+ import { join } from "path";
191
+
192
+ // ../server/src/utils/encryption-key.ts
193
+ import { createHash } from "crypto";
194
+ var KEY_SIZE_BYTES = 32;
195
+ var DEFAULT_VAULT = "\0default";
196
+ var keyring = /* @__PURE__ */ new Map();
197
+ var lockedVaults = /* @__PURE__ */ new Set();
198
+ var _ciMode = false;
199
+ var _pendingLegacyKey = null;
200
+ function isCiMode() {
201
+ return _ciMode;
202
+ }
203
+ async function loadFromEnv() {
204
+ const keyHex = process.env["RUNEYA_ENCRYPTION_KEY"];
205
+ if (!keyHex) return false;
206
+ if (!/^[0-9a-f]{64}$/i.test(keyHex)) {
207
+ throw new Error("RUNEYA_ENCRYPTION_KEY invalide : attendu 64 caract\xE8res hex");
208
+ }
209
+ console.log("");
210
+ console.log("\u26A0 ATTENTION : RUNEYA_ENCRYPTION_KEY est d\xE9fini \u2014 l'authentification par master password est d\xE9sactiv\xE9e.");
211
+ console.log(" Cette variable d'environnement est r\xE9serv\xE9e aux environnements CI/CD.");
212
+ console.log(" Ne la d\xE9finissez jamais sur une machine de d\xE9veloppement.");
213
+ console.log("");
214
+ setEncryptionKeyInMemory(Buffer.from(keyHex, "hex"));
215
+ _ciMode = true;
216
+ return true;
217
+ }
218
+ function setPendingLegacyKey(key) {
219
+ if (key.length !== KEY_SIZE_BYTES) throw new Error("Taille de cl\xE9 invalide");
220
+ _pendingLegacyKey = key;
221
+ }
222
+ function getPendingLegacyKey() {
223
+ return _pendingLegacyKey;
224
+ }
225
+ function clearPendingLegacyKey() {
226
+ _pendingLegacyKey = null;
227
+ }
228
+ function hasPendingLegacyKey() {
229
+ return _pendingLegacyKey !== null;
230
+ }
231
+ function setEncryptionKeyInMemory(key, vault = DEFAULT_VAULT) {
232
+ if (key.length !== KEY_SIZE_BYTES) throw new Error("Taille de cl\xE9 invalide");
233
+ keyring.set(vault, key);
234
+ lockedVaults.delete(vault);
235
+ announceUnlock();
236
+ }
237
+ var unlockListeners = /* @__PURE__ */ new Set();
238
+ function onVaultUnlocked(listener) {
239
+ unlockListeners.add(listener);
240
+ return () => unlockListeners.delete(listener);
241
+ }
242
+ function announceUnlock() {
243
+ for (const listener of unlockListeners) {
244
+ try {
245
+ listener();
246
+ } catch {
247
+ }
248
+ }
249
+ }
250
+ function clearEncryptionKey(vault) {
251
+ if (vault === void 0) {
252
+ keyring.clear();
253
+ lockedVaults.clear();
254
+ } else {
255
+ keyring.delete(vault);
256
+ }
257
+ }
258
+ function hasEncryptionKey(vault = DEFAULT_VAULT) {
259
+ return keyring.has(vault);
260
+ }
261
+ function getEncryptionKey(vault = DEFAULT_VAULT) {
262
+ const own = keyring.get(vault);
263
+ if (own) return own;
264
+ if (vault === DEFAULT_VAULT || lockedVaults.has(vault)) return null;
265
+ return keyring.get(DEFAULT_VAULT) ?? null;
266
+ }
267
+ function markVaultLocked(vault) {
268
+ lockedVaults.add(vault);
269
+ keyring.delete(vault);
270
+ }
271
+ function isVaultLocked(vault) {
272
+ return lockedVaults.has(vault);
273
+ }
274
+ function clearVaultLock(vault) {
275
+ lockedVaults.delete(vault);
276
+ }
277
+ function deriveStableSecret(purpose, vault = DEFAULT_VAULT) {
278
+ const key = getEncryptionKey(vault);
279
+ if (!key) return null;
280
+ return createHash("sha256").update(key).update(purpose).digest("hex");
281
+ }
282
+ async function ensureEncryptionKey() {
283
+ return getEncryptionKey();
284
+ }
285
+ async function setEncryptionKey(keyHex) {
286
+ if (!/^[0-9a-f]{64}$/i.test(keyHex)) throw new Error("Invalid key format");
287
+ setEncryptionKeyInMemory(Buffer.from(keyHex, "hex"));
288
+ }
289
+
290
+ // ../server/src/services/vault-conflicts.ts
291
+ var conflicted = /* @__PURE__ */ new Map();
292
+ function isVaultClash(root, heldIn) {
293
+ return heldIn !== void 0 && heldIn !== root && heldIn !== localVaultRoot();
294
+ }
295
+ function markVaultConflict(root, detail) {
296
+ if (conflicted.has(root)) return;
297
+ conflicted.set(root, detail);
298
+ console.error(
299
+ "[vaults] %s partage des identifiants avec un autre coffre (%s) \u2014 son contenu est ignor\xE9 et laiss\xE9 intact. Cessez de suivre l'un des deux projets.",
300
+ root,
301
+ detail
302
+ );
303
+ }
304
+ function isVaultConflicted(root) {
305
+ return conflicted.has(root);
306
+ }
307
+
308
+ // ../server/src/services/crypto-helpers.ts
309
+ function encryptValue(plaintext, vault) {
310
+ return encryptValueWith(requireKey(vault), plaintext);
311
+ }
312
+ function decryptValue(encrypted, vault) {
313
+ return decryptValueWith(requireKey(vault), encrypted);
314
+ }
315
+ function requireKey(vault) {
316
+ const key = getEncryptionKey(vault);
317
+ if (!key) {
318
+ throw new Error("Encryption key not loaded \u2014 please provide the key via setup UI");
319
+ }
320
+ return key;
321
+ }
322
+ function stableEncryptValue(plaintext, existingEncrypted, vault) {
323
+ if (existingEncrypted) {
324
+ try {
325
+ if (decryptValue(existingEncrypted, vault) === plaintext) return existingEncrypted;
326
+ } catch {
327
+ }
328
+ }
329
+ return encryptValue(plaintext, vault);
330
+ }
331
+ function encryptVariableSlots(variables, existingEncrypted, vault) {
332
+ const result = {};
333
+ for (const [key, variable] of Object.entries(variables)) {
334
+ const slot = variable.value;
335
+ if (slot.isSecret && slot.value) {
336
+ result[key] = { ...variable, value: { ...slot, value: stableEncryptValue(slot.value, existingEncrypted?.[key], vault) } };
337
+ } else {
338
+ result[key] = { ...variable, value: { ...slot } };
339
+ }
340
+ }
341
+ return result;
342
+ }
343
+ function decryptVariableSlots(variables, contextLabel, onFailure, vault) {
344
+ const result = {};
345
+ for (const [key, variable] of Object.entries(variables)) {
346
+ const slot = variable.value;
347
+ if (slot.isSecret && slot.value) {
348
+ try {
349
+ result[key] = { ...variable, value: { ...slot, value: decryptValue(slot.value, vault) } };
350
+ } catch (err) {
351
+ console.error(`[crypto-helpers] Failed to decrypt variable "${key}" for ${contextLabel}: ${err.message} \u2014 la valeur chiffr\xE9e est conserv\xE9e telle quelle sur le disque`);
352
+ onFailure?.(key);
353
+ result[key] = { ...variable, value: { ...slot, value: "" } };
354
+ }
355
+ } else {
356
+ result[key] = { ...variable, value: { ...slot } };
357
+ }
358
+ }
359
+ return result;
360
+ }
361
+ function extractEncryptedVariableSlots(variables) {
362
+ const slots = {};
363
+ for (const [key, variable] of Object.entries(variables)) {
364
+ if (variable.value?.isSecret && variable.value?.value) {
365
+ slots[key] = variable.value.value;
366
+ }
367
+ }
368
+ return slots;
369
+ }
370
+ function isEncryptedValue(s) {
371
+ const parts = s.split(":");
372
+ return parts.length === 3 && /^[0-9a-f]{32}$/i.test(parts[0]) && /^[0-9a-f]{32}$/i.test(parts[1]);
373
+ }
374
+
375
+ // ../server/src/services/environment-store.ts
376
+ var FILENAME = "environments.json";
377
+ var EnvironmentStore = class {
378
+ environments = /* @__PURE__ */ new Map();
379
+ loaded = false;
380
+ /** Cache of encrypted secret values as last written to disk */
381
+ diskEncryptedSlots = /* @__PURE__ */ new Map();
382
+ /**
383
+ * Les secrets qu'aucune clé chargée n'a ouverts, par environnement.
384
+ *
385
+ * Vides en mémoire, ils doivent repartir sur le disque sous leur forme
386
+ * chiffrée : c'est ce que retient cette liste. Voir `keepUnreadableSecrets`.
387
+ */
388
+ unreadableSecrets = /* @__PURE__ */ new Map();
389
+ /**
390
+ * Coffres dont le fichier a bougé depuis la lecture.
391
+ *
392
+ * On ne les réécrit pas. Ni avec notre copie — elle décrirait l'état d'avant
393
+ * la réparation — ni sans elle, ce qui effacerait les secrets qu'elle
394
+ * protège. Entre les deux, la seule issue sûre est de ne pas toucher au
395
+ * fichier et de laisser le prochain chargement faire foi.
396
+ */
397
+ staleRoots = /* @__PURE__ */ new Set();
398
+ /**
399
+ * L'état du disque au dernier chargement — voir `ProjectStore.fingerprint`.
400
+ *
401
+ * `staleRoots` dit « ne réécris pas ce fichier, il a bougé » ; celui-ci dit
402
+ * « relis-le ». Les deux vont ensemble : refuser d'écrire protège le travail
403
+ * de l'autre instance, mais tant que la mémoire n'est pas reprise, l'écriture
404
+ * refusée l'est pour toujours et la modification de l'utilisateur se perd.
405
+ */
406
+ fingerprint = "";
407
+ disk = new StoreDiskCoordinator();
408
+ listeners = /* @__PURE__ */ new Set();
409
+ /** Notified after every persisted mutation, for the cloud sync bridge. */
410
+ onChange(fn) {
411
+ this.listeners.add(fn);
412
+ return () => this.listeners.delete(fn);
413
+ }
414
+ notify() {
415
+ for (const fn of this.listeners) fn();
416
+ }
417
+ /**
418
+ * Which vault each environment was read from, so a save puts it back where it
419
+ * belongs even before its project has been loaded.
420
+ */
421
+ roots = /* @__PURE__ */ new Map();
422
+ /** L'organisation d'où vient un environnement reçu du cloud — voir `vaultOf`. */
423
+ remoteOrgs = /* @__PURE__ */ new Map();
424
+ /**
425
+ * Oublier ce qui a été lu, pour tout relire sous la clé courante.
426
+ *
427
+ * Appelé après l'adoption d'une clé d'organisation : le disque vient d'être
428
+ * re-chiffré sous les pieds du store, si bien que sa mémoire décrit un état
429
+ * qui n'existe plus. La laisser en place n'affichait pas seulement des
430
+ * comptes périmés — la sauvegarde suivante réécrivait l'ancien chiffré
431
+ * par-dessus le nouveau, et défaisait l'adoption.
432
+ */
433
+ resetCache() {
434
+ this.environments.clear();
435
+ this.diskEncryptedSlots.clear();
436
+ this.unreadableSecrets.clear();
437
+ this.roots.clear();
438
+ this.remoteOrgs.clear();
439
+ this.loaded = false;
440
+ this.notify();
441
+ }
442
+ /**
443
+ * Combien de secrets ce coffre garde chiffrés sans savoir les ouvrir, par
444
+ * organisation (`null` pour la machine).
445
+ *
446
+ * Ils sont déjà préservés à l'écriture (`keepUnreadableSecrets`) ; ce qui
447
+ * manquait, c'est de le dire. Un secret qui s'affiche vide et se sauvegarde
448
+ * intact est rassurant sur le disque et déroutant à l'écran.
449
+ */
450
+ async unreadableByVault() {
451
+ await this.load();
452
+ const out = /* @__PURE__ */ new Map();
453
+ for (const [environmentId, failed] of this.unreadableSecrets) {
454
+ if (!failed.size) continue;
455
+ const environment = this.environments.get(environmentId);
456
+ const vault = (environment ? this.vaultOf(environment) : void 0) ?? null;
457
+ out.set(vault, (out.get(vault) ?? 0) + failed.size);
458
+ }
459
+ return out;
460
+ }
461
+ getFilePath(root) {
462
+ return join(root, FILENAME);
463
+ }
464
+ /**
465
+ * The vault an environment must be written to: its project's.
466
+ *
467
+ * An environment names its project by id alone, and that id can answer for a
468
+ * project in each organization. The vault it was read from names the couple —
469
+ * so it is handed over as the tie-breaker rather than left as a mere fallback.
470
+ *
471
+ * Falls back to where it was last read, then to the local vault — an
472
+ * environment whose project is unknown belongs to the launch directory, which
473
+ * is where it lived before vaults existed.
474
+ */
475
+ /**
476
+ * La clé sous laquelle chiffrer cet environnement.
477
+ *
478
+ * La provenance l'emporte sur le coffre tant que le projet n'est pas local :
479
+ * un environnement reçu du cloud arrive avant lui, et `rootFor` désignerait
480
+ * alors le répertoire de lancement — donc la clé de la machine plutôt que
481
+ * celle de l'organisation qui a chiffré ces secrets.
482
+ */
483
+ vaultOf(environment) {
484
+ return this.remoteOrgs.get(environment.id) ?? vaultIdOfRoot(this.rootFor(environment));
485
+ }
486
+ rootFor(environment) {
487
+ const known = this.roots.get(environment.id) ?? null;
488
+ const projectRoot = environment.projectId ? rootOfProjectAmong(environment.projectId, known) : null;
489
+ return projectRoot ?? known ?? localVaultRoot();
490
+ }
491
+ /**
492
+ * Le projet d'un environnement, organisation comprise.
493
+ *
494
+ * L'environnement ne nomme que l'id ; le coffre d'où il vient fournit
495
+ * l'organisation, et c'est ce couple qu'il faut pour agir sur son projet.
496
+ */
497
+ projectRefOf(environment) {
498
+ if (!environment.projectId) return null;
499
+ return refOfProjectAmong(environment.projectId, this.roots.get(environment.id) ?? null);
500
+ }
501
+ /**
502
+ * The vault holding the environment that defines a variable.
503
+ *
504
+ * Overrides answer a variable, not a project: this is what lets them find the
505
+ * same vault without re-resolving an id that may be ambiguous.
506
+ */
507
+ async rootOfVariable(variableId) {
508
+ await this.load();
509
+ for (const environment of this.environments.values()) {
510
+ if (environment.variables && variableId in environment.variables) {
511
+ return this.rootFor(environment);
512
+ }
513
+ }
514
+ return null;
515
+ }
516
+ extractEncryptedCache(environment) {
517
+ const envSlots = extractEncryptedVariableSlots(
518
+ environment.variables
519
+ );
520
+ return { envSlots };
521
+ }
522
+ /**
523
+ * `vault` est fourni quand l'appelant sait à qui appartient l'environnement
524
+ * mieux que le disque : à l'arrivée d'une entité distante, son projet n'est
525
+ * pas encore local, et le coffre déduit serait celui du répertoire de
526
+ * lancement — donc la mauvaise clé.
527
+ */
528
+ encryptEnvironment(environment, vault) {
529
+ const cache = this.diskEncryptedSlots.get(environment.id);
530
+ let encryptedVars = environment.variables;
531
+ if (environment.variables && Object.keys(environment.variables).length > 0) {
532
+ encryptedVars = encryptVariableSlots(
533
+ environment.variables,
534
+ cache?.envSlots,
535
+ // La clé est celle du coffre où l'environnement sera écrit : les
536
+ // secrets d'une organisation sont chiffrés par la sienne.
537
+ vault ?? this.vaultOf(environment)
538
+ );
539
+ }
540
+ return { ...environment, variables: this.keepUnreadableSecrets(environment.id, encryptedVars) };
541
+ }
542
+ /**
543
+ * Réécrire un secret illisible tel qu'il est sur le disque, jamais à blanc.
544
+ *
545
+ * Un secret qui ne s'ouvre pas est vide en mémoire (voir
546
+ * `decryptVariableSlots`) : le chiffrer donnerait un chiffré de la chaîne
547
+ * vide, et la sauvegarde effacerait pour de bon une valeur dont il ne
548
+ * manquait que la clé. Son chiffré d'origine est encore là — c'est celui que
549
+ * `diskEncryptedSlots` a mis de côté avant le déchiffrement — et c'est lui
550
+ * qui repart sur le disque.
551
+ *
552
+ * La condition tient à la valeur en mémoire : tant qu'elle est vide, la
553
+ * variable n'a pas été retouchée et son chiffré fait foi. Dès que
554
+ * l'utilisateur en saisit une nouvelle, elle l'emporte — et vider un secret
555
+ * volontairement reste possible, puisque cela ne concerne que les variables
556
+ * dont le déchiffrement a échoué.
557
+ */
558
+ keepUnreadableSecrets(environmentId, variables) {
559
+ const failed = this.unreadableSecrets.get(environmentId);
560
+ if (!failed?.size || !variables) return variables;
561
+ const onDisk = this.diskEncryptedSlots.get(environmentId)?.envSlots ?? {};
562
+ const result = { ...variables };
563
+ for (const variableId of failed) {
564
+ const ciphertext = onDisk[variableId];
565
+ const variable = result[variableId];
566
+ if (!ciphertext || !variable || variable.value.value) continue;
567
+ result[variableId] = { ...variable, value: { ...variable.value, value: ciphertext } };
568
+ }
569
+ return result;
570
+ }
571
+ decryptEnvironment(environment, vault, unreadable = this.unreadableSecrets) {
572
+ let decryptedVars = environment.variables;
573
+ if (environment.variables && Object.keys(environment.variables).length > 0) {
574
+ const failed = /* @__PURE__ */ new Set();
575
+ decryptedVars = decryptVariableSlots(
576
+ environment.variables,
577
+ `environment ${environment.id}`,
578
+ (variableId) => failed.add(variableId),
579
+ vault ?? this.vaultOf(environment)
580
+ );
581
+ if (failed.size > 0) unreadable.set(environment.id, failed);
582
+ else unreadable.delete(environment.id);
583
+ }
584
+ return { ...environment, variables: decryptedVars };
585
+ }
586
+ /** Ce que le disque porte : un fichier d'environnements par coffre. */
587
+ async diskFingerprint() {
588
+ return fingerprintFiles((await allVaultRoots()).map((root) => this.getFilePath(root)));
589
+ }
590
+ /**
591
+ * Read every vault — the launch directory and each materialized cloud project.
592
+ *
593
+ * The roots come from the disk rather than from the project store, so loading
594
+ * environments never has to wait on projects being loaded first (the project
595
+ * store reads environments while absorbing a project, which would deadlock).
596
+ */
597
+ async load() {
598
+ await this.disk.load(async () => {
599
+ const fingerprint = await this.diskFingerprint();
600
+ if (this.loaded && fingerprint === this.fingerprint) return null;
601
+ await ensureProjectsLoaded();
602
+ if (!getEncryptionKey()) return null;
603
+ const next = await this.readVaults();
604
+ return () => {
605
+ this.environments = next.environments;
606
+ this.diskEncryptedSlots = next.diskEncryptedSlots;
607
+ this.roots = next.roots;
608
+ this.unreadableSecrets = next.unreadableSecrets;
609
+ this.fingerprint = fingerprint;
610
+ this.loaded = true;
611
+ };
612
+ });
613
+ }
614
+ /** Lire tous les coffres dans des tables neuves, que `load` substitue d'un coup. */
615
+ async readVaults() {
616
+ const environments = /* @__PURE__ */ new Map();
617
+ const diskEncryptedSlots = /* @__PURE__ */ new Map();
618
+ const roots = /* @__PURE__ */ new Map();
619
+ const unreadableSecrets = /* @__PURE__ */ new Map();
620
+ for (const root of await allVaultRoots()) {
621
+ try {
622
+ const raw = await readFile(this.getFilePath(root), "utf-8");
623
+ await rememberFile(this.getFilePath(root));
624
+ const data = JSON.parse(raw);
625
+ const clash = data.find((e) => isVaultClash(root, roots.get(e.id)));
626
+ if (clash) {
627
+ markVaultConflict(root, `environnement ${clash.id}`);
628
+ continue;
629
+ }
630
+ for (const e of data) {
631
+ diskEncryptedSlots.set(e.id, this.extractEncryptedCache(e));
632
+ roots.set(e.id, root);
633
+ environments.set(e.id, this.decryptEnvironment(e, vaultIdOfRoot(root), unreadableSecrets));
634
+ }
635
+ } catch {
636
+ }
637
+ }
638
+ return { environments, diskEncryptedSlots, roots, unreadableSecrets };
639
+ }
640
+ /**
641
+ * Write each vault that holds — or held — an environment.
642
+ *
643
+ * A vault emptied by this save loses its file: an environment that moved to
644
+ * another vault must not survive in the old one, and a `[]` left behind would
645
+ * keep (or recreate) a `.runeya` in a directory that holds nothing.
646
+ */
647
+ /**
648
+ * Repérer les coffres réparés sous nos pieds, avant d'écrire.
649
+ *
650
+ * Le test est fait ici, une fois par sauvegarde, plutôt que par
651
+ * environnement : c'est le fichier du coffre qui change, pas la variable.
652
+ */
653
+ async markStaleRoots() {
654
+ this.staleRoots.clear();
655
+ for (const root of new Set(this.roots.values())) {
656
+ const path = this.getFilePath(root);
657
+ if (wasRead(path) && await hasChangedSinceRead(path)) this.staleRoots.add(root);
658
+ }
659
+ }
660
+ save() {
661
+ return this.disk.save(() => this.writeVaults());
662
+ }
663
+ async writeVaults() {
664
+ await this.markStaleRoots();
665
+ const byRoot = /* @__PURE__ */ new Map();
666
+ for (const root of this.roots.values()) {
667
+ if (!isVaultConflicted(root)) byRoot.set(root, []);
668
+ }
669
+ byRoot.set(localVaultRoot(), byRoot.get(localVaultRoot()) ?? []);
670
+ for (const environment of this.environments.values()) {
671
+ const root = this.rootFor(environment);
672
+ const prepared = this.encryptEnvironment(environment);
673
+ this.diskEncryptedSlots.set(prepared.id, this.extractEncryptedCache(prepared));
674
+ this.roots.set(environment.id, root);
675
+ if (isVaultConflicted(root)) continue;
676
+ const bucket = byRoot.get(root);
677
+ if (bucket) bucket.push(prepared);
678
+ else byRoot.set(root, [prepared]);
679
+ }
680
+ for (const [root, environments] of byRoot) {
681
+ if (this.staleRoots.has(root)) {
682
+ console.warn(
683
+ `[environment-store] ${this.getFilePath(root)} a chang\xE9 depuis sa lecture \u2014 \xE9criture ignor\xE9e pour ne pas \xE9craser une r\xE9paration faite en dehors du serveur.`
684
+ );
685
+ continue;
686
+ }
687
+ const filePath = this.getFilePath(root);
688
+ if (environments.length === 0) {
689
+ await rm(filePath, { force: true }).catch(() => {
690
+ });
691
+ continue;
692
+ }
693
+ try {
694
+ await atomicWriteSecure(filePath, JSON.stringify(environments, null, 2));
695
+ } catch (err) {
696
+ if (err.code !== "ENOENT") throw err;
697
+ console.warn(`[environment-store] ${filePath} a disparu pendant l'\xE9criture (projet supprim\xE9 ?) \u2014 coffre ignor\xE9.`);
698
+ continue;
699
+ }
700
+ await rememberFile(filePath);
701
+ }
702
+ this.fingerprint = await this.diskFingerprint();
703
+ this.notify();
704
+ }
705
+ /**
706
+ * Rewrite every vault, moving entities that now belong elsewhere.
707
+ *
708
+ * Called once the project store knows where projects live: entities read from
709
+ * the launch directory whose project turns out to be a cloud one are written
710
+ * into that project's vault, and disappear from the directory they came from.
711
+ */
712
+ async relocateAll() {
713
+ await this.load();
714
+ await this.save();
715
+ }
716
+ /**
717
+ * Which project a variable belongs to, by walking the environments that
718
+ * define it.
719
+ *
720
+ * Overrides are keyed by variable id alone — nothing in an override says
721
+ * which project it belongs to — so this is what lets them be stored with
722
+ * their project rather than in one machine-wide pile.
723
+ */
724
+ /**
725
+ * Drop a project's environments from memory, without touching the disk.
726
+ *
727
+ * Used when a project stops being followed: its vault is removed as a whole,
728
+ * and environments left in memory would be written back on the next save —
729
+ * into the local vault, since their project is no longer known.
730
+ */
731
+ async forgetByProject(projectId) {
732
+ await this.load();
733
+ for (const [id, environment] of this.environments) {
734
+ if (environment.projectId !== projectId) continue;
735
+ this.environments.delete(id);
736
+ this.diskEncryptedSlots.delete(id);
737
+ this.unreadableSecrets.delete(id);
738
+ this.roots.delete(id);
739
+ this.remoteOrgs.delete(id);
740
+ }
741
+ this.notify();
742
+ }
743
+ /**
744
+ * Les environnements d'un projet.
745
+ *
746
+ * `orgId` restreint au projet de cette organisation : deux projets homonymes
747
+ * ont chacun les siens, et filtrer sur le seul `projectId` mélangeait les deux
748
+ * listes à l'écran. Omis, le filtre reste sur l'id — ce que fait un appelant
749
+ * qui n'a pas d'organisation à donner.
750
+ */
751
+ async list(projectId, orgId) {
752
+ await this.load();
753
+ return this.ofProject(projectId, orgId);
754
+ }
755
+ /** Le filtre commun : l'id, puis le coffre quand l'organisation est connue. */
756
+ ofProject(projectId, orgId) {
757
+ const byId = Array.from(this.environments.values()).filter((e) => e.projectId === projectId);
758
+ if (orgId === void 0) return byId;
759
+ const root = vaultFor({ id: projectId, orgId }).root;
760
+ return byId.filter((e) => this.rootFor(e) === root);
761
+ }
762
+ async get(id) {
763
+ await this.load();
764
+ return this.environments.get(id) ?? null;
765
+ }
766
+ /**
767
+ * `orgId` ne fait pas partie de l'environnement : il dit dans quel coffre le
768
+ * poser. Un environnement neuf n'a pas encore de provenance, et son
769
+ * `projectId` seul peut répondre pour un projet de chaque organisation — sans
770
+ * cette précision, la création échouerait sur l'ambiguïté au lieu de viser le
771
+ * bon coffre.
772
+ */
773
+ async create(input, orgId) {
774
+ await this.load();
775
+ const parsed = CreateEnvironmentSchema.parse(input);
776
+ const environment = {
777
+ id: randomUUID(),
778
+ projectId: parsed.projectId,
779
+ name: parsed.name,
780
+ description: parsed.description,
781
+ parentIds: parsed.parentIds,
782
+ variables: parsed.variables,
783
+ scope: parsed.scope,
784
+ reference: parsed.reference,
785
+ activateOn: parsed.activateOn,
786
+ immutable: parsed.immutable,
787
+ default: parsed.default,
788
+ color: parsed.color,
789
+ bgColor: parsed.bgColor,
790
+ agentId: parsed.agentId
791
+ };
792
+ if (environment.default) {
793
+ for (const [id, e] of this.environments) {
794
+ if (e.projectId === environment.projectId && e.default) {
795
+ this.environments.set(id, { ...e, default: false });
796
+ }
797
+ }
798
+ }
799
+ this.environments.set(environment.id, environment);
800
+ if (environment.projectId && orgId !== void 0) {
801
+ this.roots.set(environment.id, vaultFor({ id: environment.projectId, orgId }).root);
802
+ }
803
+ await this.save();
804
+ return environment;
805
+ }
806
+ async update(input) {
807
+ await this.load();
808
+ const existing = this.environments.get(input.id);
809
+ if (!existing) return null;
810
+ const updated = {
811
+ ...existing,
812
+ // Immutable environments: allow changing variables, color, description — but not name
813
+ ...!existing.immutable && input.name !== void 0 && { name: input.name },
814
+ ...input.description !== void 0 && { description: input.description },
815
+ ...input.parentIds !== void 0 && { parentIds: input.parentIds },
816
+ ...input.variables !== void 0 && { variables: input.variables },
817
+ ...input.scope !== void 0 && { scope: input.scope },
818
+ ...input.reference !== void 0 && { reference: input.reference },
819
+ ...input.activateOn !== void 0 && { activateOn: input.activateOn },
820
+ ...input.color !== void 0 && { color: input.color },
821
+ ...input.bgColor !== void 0 && { bgColor: input.bgColor },
822
+ ...input.default !== void 0 && { default: input.default },
823
+ ...input.agentId !== void 0 && { agentId: input.agentId ?? void 0 }
824
+ };
825
+ if (updated.default) {
826
+ for (const [id, e] of this.environments) {
827
+ if (id !== input.id && e.projectId === updated.projectId && e.default) {
828
+ this.environments.set(id, { ...e, default: false });
829
+ }
830
+ }
831
+ }
832
+ this.environments.set(input.id, updated);
833
+ await this.save();
834
+ return updated;
835
+ }
836
+ async upsertVariable(environmentId, variableId, variable) {
837
+ const existing = this.environments.get(environmentId);
838
+ if (!existing) return null;
839
+ const variables = { ...existing.variables, [variableId]: variable };
840
+ return this.update({ id: environmentId, variables });
841
+ }
842
+ async removeVariable(environmentId, variableId) {
843
+ const existing = this.environments.get(environmentId);
844
+ if (!existing) return null;
845
+ const variables = { ...existing.variables };
846
+ delete variables[variableId];
847
+ return this.update({ id: environmentId, variables });
848
+ }
849
+ async delete(id) {
850
+ await this.load();
851
+ const env = this.environments.get(id);
852
+ if (!env) return false;
853
+ if (env.immutable) return false;
854
+ this.environments.delete(id);
855
+ recordLocalRemoval("environments", id);
856
+ await this.save();
857
+ return true;
858
+ }
859
+ async getByProjectId(projectId, orgId) {
860
+ await this.load();
861
+ return this.ofProject(projectId, orgId);
862
+ }
863
+ async listAllGlobal() {
864
+ await this.load();
865
+ return Array.from(this.environments.values()).filter((e) => e.scope === "global");
866
+ }
867
+ // --- Scope-based Query Methods ---
868
+ async listByScope(projectId, scope, orgId) {
869
+ await this.load();
870
+ return this.ofProject(projectId, orgId).filter((e) => e.scope === scope);
871
+ }
872
+ /** Les environnements de service qui s'activent avec un environnement global. */
873
+ async listByActivateOn(globalEnvId) {
874
+ await this.load();
875
+ return Array.from(this.environments.values()).filter((e) => e.scope === "service" && e.activateOn === globalEnvId);
876
+ }
877
+ async findServiceEnvironment(serviceId, globalEnvId) {
878
+ await this.load();
879
+ for (const e of this.environments.values()) {
880
+ if (e.scope === "service" && e.reference === serviceId && e.activateOn === globalEnvId) return e;
881
+ }
882
+ return null;
883
+ }
884
+ async findServiceEnvironments(serviceId) {
885
+ await this.load();
886
+ return Array.from(this.environments.values()).filter((e) => e.scope === "service" && e.reference === serviceId);
887
+ }
888
+ async deleteByReference(serviceId) {
889
+ await this.load();
890
+ let changed = false;
891
+ for (const [id, e] of this.environments) {
892
+ if (e.scope === "service" && e.reference === serviceId) {
893
+ this.environments.delete(id);
894
+ recordLocalRemoval("environments", id);
895
+ changed = true;
896
+ }
897
+ }
898
+ if (changed) await this.save();
899
+ }
900
+ async deleteByActivateOn(globalEnvId) {
901
+ await this.load();
902
+ let changed = false;
903
+ for (const [id, e] of this.environments) {
904
+ if (e.scope === "service" && e.activateOn === globalEnvId) {
905
+ this.environments.delete(id);
906
+ recordLocalRemoval("environments", id);
907
+ changed = true;
908
+ }
909
+ }
910
+ if (changed) await this.save();
911
+ }
912
+ /**
913
+ * When a service is renamed, update the name of all its service-scoped environments
914
+ * to keep the `${serviceName} (${globalEnvName})` pattern in sync.
915
+ */
916
+ async renameByReference(serviceId, newServiceName) {
917
+ await this.load();
918
+ let changed = false;
919
+ for (const [id, e] of this.environments) {
920
+ if (e.scope === "service" && e.reference === serviceId && e.activateOn) {
921
+ const globalEnv = this.environments.get(e.activateOn);
922
+ if (!globalEnv) continue;
923
+ this.environments.set(id, { ...e, name: `${newServiceName} (${globalEnv.name})` });
924
+ changed = true;
925
+ }
926
+ }
927
+ if (changed) await this.save();
928
+ }
929
+ /**
930
+ * When a global environment is renamed, update the name of all service-scoped
931
+ * environments that activateOn it to keep the naming pattern in sync.
932
+ * Caller provides a serviceId→name map since environmentStore can't access serviceStore.
933
+ */
934
+ async renameByActivateOn(globalEnvId, newGlobalName, serviceNames) {
935
+ await this.load();
936
+ let changed = false;
937
+ for (const [id, e] of this.environments) {
938
+ if (e.scope === "service" && e.activateOn === globalEnvId && e.reference) {
939
+ const serviceName = serviceNames.get(e.reference);
940
+ if (!serviceName) continue;
941
+ this.environments.set(id, { ...e, name: `${serviceName} (${newGlobalName})` });
942
+ changed = true;
943
+ }
944
+ }
945
+ if (changed) await this.save();
946
+ }
947
+ // ---- Cloud sync ------------------------------------------------------
948
+ /** Environments of the given projects, in their on-disk (encrypted) form. */
949
+ async listEncryptedByProjectIds(projectIds) {
950
+ await this.load();
951
+ const wanted = new Set(projectIds);
952
+ return Array.from(this.environments.values()).filter((e) => wanted.has(e.projectId)).map((e) => this.encryptEnvironment(e));
953
+ }
954
+ /**
955
+ * Apply an environment received from the cloud, in its on-disk form.
956
+ *
957
+ * Validated rather than trusted, and diff-guarded against the on-disk form so
958
+ * re-applying an identical environment neither saves nor notifies (loop-safe).
959
+ */
960
+ async applyRemoteUpsert(id, fields, vault) {
961
+ await this.load();
962
+ if (vault) this.remoteOrgs.set(id, vault);
963
+ const parsed = EnvironmentSchema.safeParse({ ...fields, id });
964
+ if (!parsed.success) {
965
+ console.error("[environment-store] Rejected remote environment %s:", id, parsed.error.message);
966
+ return { created: false };
967
+ }
968
+ const incoming = parsed.data;
969
+ const existing = this.environments.get(id);
970
+ if (existing && JSON.stringify(this.encryptEnvironment(existing, vault)) === JSON.stringify(incoming)) {
971
+ return { created: false };
972
+ }
973
+ this.diskEncryptedSlots.set(id, this.extractEncryptedCache(incoming));
974
+ this.environments.set(id, this.decryptEnvironment(incoming, vault));
975
+ await this.save();
976
+ return { created: !existing };
977
+ }
978
+ /** Delete an environment that was removed on another peer. */
979
+ async applyRemoteDelete(id) {
980
+ await this.load();
981
+ if (!this.environments.has(id)) return false;
982
+ this.environments.delete(id);
983
+ this.diskEncryptedSlots.delete(id);
984
+ this.unreadableSecrets.delete(id);
985
+ this.remoteOrgs.delete(id);
986
+ await this.save();
987
+ return true;
988
+ }
989
+ };
990
+ var environmentStore = new EnvironmentStore();
991
+
992
+ export {
993
+ isCiMode,
994
+ loadFromEnv,
995
+ setPendingLegacyKey,
996
+ getPendingLegacyKey,
997
+ clearPendingLegacyKey,
998
+ hasPendingLegacyKey,
999
+ setEncryptionKeyInMemory,
1000
+ onVaultUnlocked,
1001
+ clearEncryptionKey,
1002
+ hasEncryptionKey,
1003
+ getEncryptionKey,
1004
+ markVaultLocked,
1005
+ isVaultLocked,
1006
+ clearVaultLock,
1007
+ deriveStableSecret,
1008
+ ensureEncryptionKey,
1009
+ setEncryptionKey,
1010
+ encryptValue,
1011
+ decryptValue,
1012
+ stableEncryptValue,
1013
+ encryptVariableSlots,
1014
+ decryptVariableSlots,
1015
+ extractEncryptedVariableSlots,
1016
+ isEncryptedValue,
1017
+ StoreDiskCoordinator,
1018
+ projectKey,
1019
+ AmbiguousProjectError,
1020
+ registerProjectLoader,
1021
+ ensureProjectsLoaded,
1022
+ setProjectVault,
1023
+ forgetProject,
1024
+ setProjectServices,
1025
+ rootOfProjectRef,
1026
+ refsOfProjectId,
1027
+ rootOfProjectAmong,
1028
+ refOfProjectAmong,
1029
+ rootOfService,
1030
+ isVaultClash,
1031
+ markVaultConflict,
1032
+ isVaultConflicted,
1033
+ recordLocalRemoval,
1034
+ isLocallyRemoved,
1035
+ clearLocalRemoval,
1036
+ recordDetached,
1037
+ isDetached,
1038
+ clearDetached,
1039
+ environmentStore
1040
+ };
1041
+ //# sourceMappingURL=chunk-DFPZ4MTI.js.map