@serve.zone/gitops 2.13.1

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 (258) hide show
  1. package/.smartconfig.json +114 -0
  2. package/binary/gitops.ts +4 -0
  3. package/changelog.md +185 -0
  4. package/cli.child.js +4 -0
  5. package/cli.js +4 -0
  6. package/cli.ts.js +5 -0
  7. package/deno.json +10 -0
  8. package/dist_serve/bundle.js +36362 -0
  9. package/dist_serve/index.html +33 -0
  10. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  11. package/dist_ts/00_commitinfo_data.js +9 -0
  12. package/dist_ts/cache/classes.cache.cleaner.d.ts +23 -0
  13. package/dist_ts/cache/classes.cache.cleaner.js +61 -0
  14. package/dist_ts/cache/classes.cached.document.d.ts +30 -0
  15. package/dist_ts/cache/classes.cached.document.js +101 -0
  16. package/dist_ts/cache/classes.cachedb.d.ts +22 -0
  17. package/dist_ts/cache/classes.cachedb.js +58 -0
  18. package/dist_ts/cache/classes.secrets.scan.service.d.ts +51 -0
  19. package/dist_ts/cache/classes.secrets.scan.service.js +237 -0
  20. package/dist_ts/cache/documents/classes.cached.project.d.ts +13 -0
  21. package/dist_ts/cache/documents/classes.cached.project.js +101 -0
  22. package/dist_ts/cache/documents/classes.cached.secret.d.ts +24 -0
  23. package/dist_ts/cache/documents/classes.cached.secret.js +158 -0
  24. package/dist_ts/cache/documents/index.d.ts +2 -0
  25. package/dist_ts/cache/documents/index.js +3 -0
  26. package/dist_ts/cache/index.d.ts +7 -0
  27. package/dist_ts/cache/index.js +6 -0
  28. package/dist_ts/classes/actionlog.d.ts +19 -0
  29. package/dist_ts/classes/actionlog.js +44 -0
  30. package/dist_ts/classes/connectionmanager.d.ts +57 -0
  31. package/dist_ts/classes/connectionmanager.js +247 -0
  32. package/dist_ts/classes/gitopsapp.d.ts +30 -0
  33. package/dist_ts/classes/gitopsapp.js +101 -0
  34. package/dist_ts/classes/jobmanager.d.ts +47 -0
  35. package/dist_ts/classes/jobmanager.js +301 -0
  36. package/dist_ts/classes/jobrunners/autobookstackdocs.runner.d.ts +29 -0
  37. package/dist_ts/classes/jobrunners/autobookstackdocs.runner.js +361 -0
  38. package/dist_ts/classes/jobrunners/base.jobrunner.d.ts +14 -0
  39. package/dist_ts/classes/jobrunners/base.jobrunner.js +3 -0
  40. package/dist_ts/classes/jobrunners/index.d.ts +5 -0
  41. package/dist_ts/classes/jobrunners/index.js +14 -0
  42. package/dist_ts/classes/managedsecrets.manager.d.ts +47 -0
  43. package/dist_ts/classes/managedsecrets.manager.js +247 -0
  44. package/dist_ts/classes/syncmanager.d.ts +189 -0
  45. package/dist_ts/classes/syncmanager.js +1787 -0
  46. package/dist_ts/index.d.ts +6 -0
  47. package/dist_ts/index.js +32 -0
  48. package/dist_ts/logging.d.ts +49 -0
  49. package/dist_ts/logging.js +134 -0
  50. package/dist_ts/opsserver/classes.opsserver.d.ts +25 -0
  51. package/dist_ts/opsserver/classes.opsserver.js +70 -0
  52. package/dist_ts/opsserver/handlers/actionlog.handler.d.ts +9 -0
  53. package/dist_ts/opsserver/handlers/actionlog.handler.js +24 -0
  54. package/dist_ts/opsserver/handlers/actions.handler.d.ts +9 -0
  55. package/dist_ts/opsserver/handlers/actions.handler.js +38 -0
  56. package/dist_ts/opsserver/handlers/admin.handler.d.ts +19 -0
  57. package/dist_ts/opsserver/handlers/admin.handler.js +96 -0
  58. package/dist_ts/opsserver/handlers/connections.handler.d.ts +10 -0
  59. package/dist_ts/opsserver/handlers/connections.handler.js +109 -0
  60. package/dist_ts/opsserver/handlers/groups.handler.d.ts +9 -0
  61. package/dist_ts/opsserver/handlers/groups.handler.js +24 -0
  62. package/dist_ts/opsserver/handlers/index.d.ts +13 -0
  63. package/dist_ts/opsserver/handlers/index.js +14 -0
  64. package/dist_ts/opsserver/handlers/jobs.handler.d.ts +16 -0
  65. package/dist_ts/opsserver/handlers/jobs.handler.js +146 -0
  66. package/dist_ts/opsserver/handlers/logs.handler.d.ts +9 -0
  67. package/dist_ts/opsserver/handlers/logs.handler.js +21 -0
  68. package/dist_ts/opsserver/handlers/managedsecrets.handler.d.ts +11 -0
  69. package/dist_ts/opsserver/handlers/managedsecrets.handler.js +110 -0
  70. package/dist_ts/opsserver/handlers/pipelines.handler.d.ts +31 -0
  71. package/dist_ts/opsserver/handlers/pipelines.handler.js +204 -0
  72. package/dist_ts/opsserver/handlers/projects.handler.d.ts +9 -0
  73. package/dist_ts/opsserver/handlers/projects.handler.js +24 -0
  74. package/dist_ts/opsserver/handlers/secrets.handler.d.ts +10 -0
  75. package/dist_ts/opsserver/handlers/secrets.handler.js +171 -0
  76. package/dist_ts/opsserver/handlers/sync.handler.d.ts +16 -0
  77. package/dist_ts/opsserver/handlers/sync.handler.js +166 -0
  78. package/dist_ts/opsserver/handlers/webhook.handler.d.ts +7 -0
  79. package/dist_ts/opsserver/handlers/webhook.handler.js +55 -0
  80. package/dist_ts/opsserver/helpers/guards.d.ts +5 -0
  81. package/dist_ts/opsserver/helpers/guards.js +12 -0
  82. package/dist_ts/opsserver/index.d.ts +1 -0
  83. package/dist_ts/opsserver/index.js +2 -0
  84. package/dist_ts/paths.d.ts +9 -0
  85. package/dist_ts/paths.js +13 -0
  86. package/dist_ts/plugins.d.ts +25 -0
  87. package/dist_ts/plugins.js +32 -0
  88. package/dist_ts/providers/classes.baseprovider.d.ts +51 -0
  89. package/dist_ts/providers/classes.baseprovider.js +17 -0
  90. package/dist_ts/providers/classes.giteaprovider.d.ts +40 -0
  91. package/dist_ts/providers/classes.giteaprovider.js +224 -0
  92. package/dist_ts/providers/classes.gitlabprovider.d.ts +39 -0
  93. package/dist_ts/providers/classes.gitlabprovider.js +207 -0
  94. package/dist_ts/providers/index.d.ts +3 -0
  95. package/dist_ts/providers/index.js +4 -0
  96. package/dist_ts/storage/classes.storagemanager.d.ts +33 -0
  97. package/dist_ts/storage/classes.storagemanager.js +135 -0
  98. package/dist_ts/storage/index.d.ts +2 -0
  99. package/dist_ts/storage/index.js +2 -0
  100. package/dist_ts/timers.d.ts +4 -0
  101. package/dist_ts/timers.js +24 -0
  102. package/dist_ts_bundled/bundle.d.ts +4 -0
  103. package/dist_ts_bundled/bundle.js +12 -0
  104. package/dist_ts_interfaces/data/actionlog.d.ts +12 -0
  105. package/dist_ts_interfaces/data/actionlog.js +2 -0
  106. package/dist_ts_interfaces/data/branch.d.ts +8 -0
  107. package/dist_ts_interfaces/data/branch.js +2 -0
  108. package/dist_ts_interfaces/data/connection.d.ts +12 -0
  109. package/dist_ts_interfaces/data/connection.js +2 -0
  110. package/dist_ts_interfaces/data/group.d.ts +10 -0
  111. package/dist_ts_interfaces/data/group.js +2 -0
  112. package/dist_ts_interfaces/data/identity.d.ts +7 -0
  113. package/dist_ts_interfaces/data/identity.js +2 -0
  114. package/dist_ts_interfaces/data/index.d.ts +11 -0
  115. package/dist_ts_interfaces/data/index.js +12 -0
  116. package/dist_ts_interfaces/data/job.d.ts +37 -0
  117. package/dist_ts_interfaces/data/job.js +2 -0
  118. package/dist_ts_interfaces/data/managedsecret.d.ts +37 -0
  119. package/dist_ts_interfaces/data/managedsecret.js +2 -0
  120. package/dist_ts_interfaces/data/pipeline.d.ts +22 -0
  121. package/dist_ts_interfaces/data/pipeline.js +2 -0
  122. package/dist_ts_interfaces/data/project.d.ts +12 -0
  123. package/dist_ts_interfaces/data/project.js +2 -0
  124. package/dist_ts_interfaces/data/secret.d.ts +11 -0
  125. package/dist_ts_interfaces/data/secret.js +2 -0
  126. package/dist_ts_interfaces/data/sync.d.ts +34 -0
  127. package/dist_ts_interfaces/data/sync.js +2 -0
  128. package/dist_ts_interfaces/index.d.ts +5 -0
  129. package/dist_ts_interfaces/index.js +8 -0
  130. package/dist_ts_interfaces/plugins.d.ts +2 -0
  131. package/dist_ts_interfaces/plugins.js +4 -0
  132. package/dist_ts_interfaces/requests/actionlog.d.ts +15 -0
  133. package/dist_ts_interfaces/requests/actionlog.js +3 -0
  134. package/dist_ts_interfaces/requests/actions.d.ts +31 -0
  135. package/dist_ts_interfaces/requests/actions.js +3 -0
  136. package/dist_ts_interfaces/requests/admin.d.ts +31 -0
  137. package/dist_ts_interfaces/requests/admin.js +3 -0
  138. package/dist_ts_interfaces/requests/connections.d.ts +71 -0
  139. package/dist_ts_interfaces/requests/connections.js +3 -0
  140. package/dist_ts_interfaces/requests/groups.d.ts +14 -0
  141. package/dist_ts_interfaces/requests/groups.js +3 -0
  142. package/dist_ts_interfaces/requests/index.d.ts +13 -0
  143. package/dist_ts_interfaces/requests/index.js +14 -0
  144. package/dist_ts_interfaces/requests/jobs.d.ts +86 -0
  145. package/dist_ts_interfaces/requests/jobs.js +3 -0
  146. package/dist_ts_interfaces/requests/logs.d.ts +14 -0
  147. package/dist_ts_interfaces/requests/logs.js +3 -0
  148. package/dist_ts_interfaces/requests/managedsecrets.d.ts +84 -0
  149. package/dist_ts_interfaces/requests/managedsecrets.js +3 -0
  150. package/dist_ts_interfaces/requests/pipelines.d.ts +55 -0
  151. package/dist_ts_interfaces/requests/pipelines.js +3 -0
  152. package/dist_ts_interfaces/requests/projects.d.ts +14 -0
  153. package/dist_ts_interfaces/requests/projects.js +3 -0
  154. package/dist_ts_interfaces/requests/secrets.d.ts +72 -0
  155. package/dist_ts_interfaces/requests/secrets.js +3 -0
  156. package/dist_ts_interfaces/requests/sync.d.ts +120 -0
  157. package/dist_ts_interfaces/requests/sync.js +3 -0
  158. package/dist_ts_interfaces/requests/webhook.d.ts +13 -0
  159. package/dist_ts_interfaces/requests/webhook.js +3 -0
  160. package/license +21 -0
  161. package/package.json +81 -0
  162. package/readme.md +177 -0
  163. package/readme.todo.md +3 -0
  164. package/ts/00_commitinfo_data.ts +8 -0
  165. package/ts/cache/classes.cache.cleaner.ts +69 -0
  166. package/ts/cache/classes.cached.document.ts +57 -0
  167. package/ts/cache/classes.cachedb.ts +72 -0
  168. package/ts/cache/classes.secrets.scan.service.ts +267 -0
  169. package/ts/cache/documents/classes.cached.project.ts +32 -0
  170. package/ts/cache/documents/classes.cached.secret.ts +81 -0
  171. package/ts/cache/documents/index.ts +2 -0
  172. package/ts/cache/index.ts +7 -0
  173. package/ts/classes/actionlog.ts +57 -0
  174. package/ts/classes/connectionmanager.ts +263 -0
  175. package/ts/classes/gitopsapp.ts +128 -0
  176. package/ts/classes/jobmanager.ts +337 -0
  177. package/ts/classes/jobrunners/autobookstackdocs.runner.ts +435 -0
  178. package/ts/classes/jobrunners/base.jobrunner.ts +16 -0
  179. package/ts/classes/jobrunners/index.ts +17 -0
  180. package/ts/classes/managedsecrets.manager.ts +322 -0
  181. package/ts/classes/syncmanager.ts +2117 -0
  182. package/ts/index.ts +37 -0
  183. package/ts/logging.ts +162 -0
  184. package/ts/opsserver/classes.opsserver.ts +86 -0
  185. package/ts/opsserver/handlers/actionlog.handler.ts +30 -0
  186. package/ts/opsserver/handlers/actions.handler.ts +50 -0
  187. package/ts/opsserver/handlers/admin.handler.ts +122 -0
  188. package/ts/opsserver/handlers/connections.handler.ts +162 -0
  189. package/ts/opsserver/handlers/groups.handler.ts +32 -0
  190. package/ts/opsserver/handlers/index.ts +13 -0
  191. package/ts/opsserver/handlers/jobs.handler.ts +189 -0
  192. package/ts/opsserver/handlers/logs.handler.ts +29 -0
  193. package/ts/opsserver/handlers/managedsecrets.handler.ts +158 -0
  194. package/ts/opsserver/handlers/pipelines.handler.ts +281 -0
  195. package/ts/opsserver/handlers/projects.handler.ts +32 -0
  196. package/ts/opsserver/handlers/secrets.handler.ts +224 -0
  197. package/ts/opsserver/handlers/sync.handler.ts +224 -0
  198. package/ts/opsserver/handlers/webhook.handler.ts +62 -0
  199. package/ts/opsserver/helpers/guards.ts +16 -0
  200. package/ts/opsserver/index.ts +1 -0
  201. package/ts/paths.ts +19 -0
  202. package/ts/plugins.ts +38 -0
  203. package/ts/providers/classes.baseprovider.ts +99 -0
  204. package/ts/providers/classes.giteaprovider.ts +279 -0
  205. package/ts/providers/classes.gitlabprovider.ts +265 -0
  206. package/ts/providers/index.ts +3 -0
  207. package/ts/storage/classes.storagemanager.ts +144 -0
  208. package/ts/storage/index.ts +2 -0
  209. package/ts/timers.ts +34 -0
  210. package/ts_interfaces/data/actionlog.ts +13 -0
  211. package/ts_interfaces/data/branch.ts +9 -0
  212. package/ts_interfaces/data/connection.ts +13 -0
  213. package/ts_interfaces/data/group.ts +10 -0
  214. package/ts_interfaces/data/identity.ts +7 -0
  215. package/ts_interfaces/data/index.ts +11 -0
  216. package/ts_interfaces/data/job.ts +42 -0
  217. package/ts_interfaces/data/managedsecret.ts +41 -0
  218. package/ts_interfaces/data/pipeline.ts +32 -0
  219. package/ts_interfaces/data/project.ts +12 -0
  220. package/ts_interfaces/data/secret.ts +11 -0
  221. package/ts_interfaces/data/sync.ts +37 -0
  222. package/ts_interfaces/index.ts +9 -0
  223. package/ts_interfaces/plugins.ts +6 -0
  224. package/ts_interfaces/requests/actionlog.ts +19 -0
  225. package/ts_interfaces/requests/actions.ts +39 -0
  226. package/ts_interfaces/requests/admin.ts +43 -0
  227. package/ts_interfaces/requests/connections.ts +95 -0
  228. package/ts_interfaces/requests/groups.ts +18 -0
  229. package/ts_interfaces/requests/index.ts +13 -0
  230. package/ts_interfaces/requests/jobs.ts +118 -0
  231. package/ts_interfaces/requests/logs.ts +18 -0
  232. package/ts_interfaces/requests/managedsecrets.ts +112 -0
  233. package/ts_interfaces/requests/pipelines.ts +71 -0
  234. package/ts_interfaces/requests/projects.ts +18 -0
  235. package/ts_interfaces/requests/secrets.ts +92 -0
  236. package/ts_interfaces/requests/sync.ts +157 -0
  237. package/ts_interfaces/requests/webhook.ts +18 -0
  238. package/ts_web/00_commitinfo_data.ts +8 -0
  239. package/ts_web/appstate.ts +1251 -0
  240. package/ts_web/elements/gitops-dashboard.ts +350 -0
  241. package/ts_web/elements/index.ts +10 -0
  242. package/ts_web/elements/shared/css.ts +29 -0
  243. package/ts_web/elements/shared/index.ts +1 -0
  244. package/ts_web/elements/views/actionlog/index.ts +101 -0
  245. package/ts_web/elements/views/actions/index.ts +209 -0
  246. package/ts_web/elements/views/buildlog/index.ts +196 -0
  247. package/ts_web/elements/views/connections/index.ts +260 -0
  248. package/ts_web/elements/views/groups/index.ts +134 -0
  249. package/ts_web/elements/views/jobs/index.ts +424 -0
  250. package/ts_web/elements/views/managedsecrets/index.ts +502 -0
  251. package/ts_web/elements/views/overview/index.ts +86 -0
  252. package/ts_web/elements/views/pipelines/index.ts +561 -0
  253. package/ts_web/elements/views/projects/index.ts +149 -0
  254. package/ts_web/elements/views/secrets/index.ts +310 -0
  255. package/ts_web/elements/views/sync/index.ts +512 -0
  256. package/ts_web/index.ts +7 -0
  257. package/ts_web/plugins.ts +15 -0
  258. package/tsconfig.json +15 -0
@@ -0,0 +1,322 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logging.js';
3
+ import type * as interfaces from '../../ts_interfaces/index.js';
4
+ import type { StorageManager } from '../storage/index.js';
5
+ import type { ConnectionManager } from './connectionmanager.js';
6
+
7
+ const MANAGED_SECRETS_PREFIX = '/managed-secrets/';
8
+ const KEYCHAIN_PREFIX = 'keychain:';
9
+ const KEYCHAIN_ID_PREFIX = 'gitops-msecret-';
10
+ const SECRET_KEY_PREFIX = 'GITOPS_';
11
+
12
+ export class ManagedSecretsManager {
13
+ private secrets: interfaces.data.IManagedSecretStored[] = [];
14
+
15
+ constructor(
16
+ private storageManager: StorageManager,
17
+ private smartSecret: plugins.smartsecret.SmartSecret,
18
+ private connectionManager: ConnectionManager,
19
+ ) {}
20
+
21
+ async init(): Promise<void> {
22
+ await this.loadSecrets();
23
+ }
24
+
25
+ // ---- Storage helpers ----
26
+
27
+ private keychainId(secretId: string): string {
28
+ return `${KEYCHAIN_ID_PREFIX}${secretId}`;
29
+ }
30
+
31
+ private prefixedKey(key: string): string {
32
+ return `${SECRET_KEY_PREFIX}${key}`;
33
+ }
34
+
35
+ private async loadSecrets(): Promise<void> {
36
+ const keys = await this.storageManager.list(MANAGED_SECRETS_PREFIX);
37
+ this.secrets = [];
38
+ for (const key of keys) {
39
+ const stored = await this.storageManager.getJSON<interfaces.data.IManagedSecretStored>(key);
40
+ if (stored) {
41
+ this.secrets.push(stored);
42
+ }
43
+ }
44
+ if (this.secrets.length > 0) {
45
+ logger.info(`Loaded ${this.secrets.length} managed secret(s)`);
46
+ }
47
+ }
48
+
49
+ private async persistSecret(stored: interfaces.data.IManagedSecretStored, realValue: string): Promise<void> {
50
+ // Store real value in keychain
51
+ await this.smartSecret.setSecret(this.keychainId(stored.id), realValue);
52
+ // Save JSON with sentinel
53
+ const jsonStored = { ...stored, value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}` };
54
+ await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, jsonStored);
55
+ // Update in-memory sentinel too
56
+ stored.value = jsonStored.value;
57
+ }
58
+
59
+ private async removeFromStorage(id: string): Promise<void> {
60
+ await this.smartSecret.deleteSecret(this.keychainId(id));
61
+ await this.storageManager.delete(`${MANAGED_SECRETS_PREFIX}${id}.json`);
62
+ }
63
+
64
+ private async getSecretValue(id: string): Promise<string | null> {
65
+ return await this.smartSecret.getSecret(this.keychainId(id));
66
+ }
67
+
68
+ private toApiModel(stored: interfaces.data.IManagedSecretStored): interfaces.data.IManagedSecret {
69
+ return {
70
+ id: stored.id,
71
+ key: stored.key,
72
+ description: stored.description,
73
+ targets: stored.targets,
74
+ targetStatuses: stored.targetStatuses,
75
+ createdAt: stored.createdAt,
76
+ updatedAt: stored.updatedAt,
77
+ lastPushedAt: stored.lastPushedAt,
78
+ };
79
+ }
80
+
81
+ // ---- Push logic ----
82
+
83
+ private async pushToTargets(
84
+ stored: interfaces.data.IManagedSecretStored,
85
+ mode: 'upsert' | 'delete',
86
+ targetsOverride?: interfaces.data.IManagedSecretTarget[],
87
+ ): Promise<interfaces.data.IManagedSecretTargetStatus[]> {
88
+ const targets = targetsOverride || stored.targets;
89
+ const value = mode === 'upsert' ? await this.getSecretValue(stored.id) : null;
90
+ const prefixedKey = this.prefixedKey(stored.key);
91
+ const results: interfaces.data.IManagedSecretTargetStatus[] = [];
92
+
93
+ for (const target of targets) {
94
+ const status: interfaces.data.IManagedSecretTargetStatus = {
95
+ connectionId: target.connectionId,
96
+ scope: target.scope,
97
+ scopeId: target.scopeId,
98
+ scopeName: target.scopeName,
99
+ status: 'pending',
100
+ };
101
+ try {
102
+ const provider = this.connectionManager.getProvider(target.connectionId);
103
+ if (mode === 'upsert') {
104
+ // Try update first; if it fails, create
105
+ try {
106
+ if (target.scope === 'project') {
107
+ await provider.updateProjectSecret(target.scopeId, prefixedKey, value!);
108
+ } else {
109
+ await provider.updateGroupSecret(target.scopeId, prefixedKey, value!);
110
+ }
111
+ } catch {
112
+ // Secret doesn't exist yet — create it
113
+ if (target.scope === 'project') {
114
+ await provider.createProjectSecret(target.scopeId, prefixedKey, value!);
115
+ } else {
116
+ await provider.createGroupSecret(target.scopeId, prefixedKey, value!);
117
+ }
118
+ }
119
+ } else {
120
+ // mode === 'delete'
121
+ try {
122
+ if (target.scope === 'project') {
123
+ await provider.deleteProjectSecret(target.scopeId, prefixedKey);
124
+ } else {
125
+ await provider.deleteGroupSecret(target.scopeId, prefixedKey);
126
+ }
127
+ } catch {
128
+ // Secret may not exist on target — that's fine
129
+ }
130
+ }
131
+ status.status = 'success';
132
+ status.lastPushedAt = Date.now();
133
+ } catch (err) {
134
+ status.status = 'error';
135
+ status.error = err instanceof Error ? err.message : String(err);
136
+ }
137
+ results.push(status);
138
+ }
139
+ return results;
140
+ }
141
+
142
+ // ---- Public API ----
143
+
144
+ async getAll(): Promise<interfaces.data.IManagedSecret[]> {
145
+ return this.secrets.map((s) => this.toApiModel(s));
146
+ }
147
+
148
+ async getById(id: string): Promise<interfaces.data.IManagedSecret | null> {
149
+ const stored = this.secrets.find((s) => s.id === id);
150
+ return stored ? this.toApiModel(stored) : null;
151
+ }
152
+
153
+ async create(
154
+ key: string,
155
+ value: string,
156
+ description: string | undefined,
157
+ targets: interfaces.data.IManagedSecretTarget[],
158
+ ): Promise<{
159
+ managedSecret: interfaces.data.IManagedSecret;
160
+ pushResults: interfaces.data.IManagedSecretTargetStatus[];
161
+ }> {
162
+ // Validate key
163
+ if (key.toUpperCase().startsWith(SECRET_KEY_PREFIX)) {
164
+ throw new Error(`Key must not start with ${SECRET_KEY_PREFIX} — the prefix is added automatically`);
165
+ }
166
+ if (this.secrets.some((s) => s.key === key)) {
167
+ throw new Error(`A managed secret with key "${key}" already exists`);
168
+ }
169
+
170
+ const now = Date.now();
171
+ const stored: interfaces.data.IManagedSecretStored = {
172
+ id: crypto.randomUUID(),
173
+ key,
174
+ description,
175
+ value: '', // will be set by persistSecret
176
+ targets,
177
+ targetStatuses: [],
178
+ createdAt: now,
179
+ updatedAt: now,
180
+ };
181
+
182
+ this.secrets.push(stored);
183
+ await this.persistSecret(stored, value);
184
+
185
+ // Push to all targets
186
+ const pushResults = await this.pushToTargets(stored, 'upsert');
187
+ stored.targetStatuses = pushResults;
188
+ stored.lastPushedAt = now;
189
+ stored.updatedAt = now;
190
+ await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
191
+ ...stored,
192
+ value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
193
+ });
194
+
195
+ logger.info(`Created managed secret "${key}" with ${targets.length} target(s)`);
196
+ return { managedSecret: this.toApiModel(stored), pushResults };
197
+ }
198
+
199
+ async update(
200
+ id: string,
201
+ updates: {
202
+ value?: string;
203
+ description?: string;
204
+ targets?: interfaces.data.IManagedSecretTarget[];
205
+ },
206
+ ): Promise<{
207
+ managedSecret: interfaces.data.IManagedSecret;
208
+ pushResults: interfaces.data.IManagedSecretTargetStatus[];
209
+ }> {
210
+ const stored = this.secrets.find((s) => s.id === id);
211
+ if (!stored) throw new Error(`Managed secret not found: ${id}`);
212
+
213
+ const now = Date.now();
214
+
215
+ // Update value in keychain if provided
216
+ if (updates.value !== undefined) {
217
+ await this.smartSecret.setSecret(this.keychainId(id), updates.value);
218
+ }
219
+
220
+ if (updates.description !== undefined) {
221
+ stored.description = updates.description;
222
+ }
223
+
224
+ // Handle target changes — delete from removed targets
225
+ let removedTargets: interfaces.data.IManagedSecretTarget[] = [];
226
+ if (updates.targets !== undefined) {
227
+ const oldTargets = stored.targets;
228
+ const newTargetKeys = new Set(
229
+ updates.targets.map((t) => `${t.connectionId}:${t.scope}:${t.scopeId}`),
230
+ );
231
+ removedTargets = oldTargets.filter(
232
+ (t) => !newTargetKeys.has(`${t.connectionId}:${t.scope}:${t.scopeId}`),
233
+ );
234
+ stored.targets = updates.targets;
235
+ }
236
+
237
+ stored.updatedAt = now;
238
+
239
+ // Delete from removed targets
240
+ if (removedTargets.length > 0) {
241
+ await this.pushToTargets(stored, 'delete', removedTargets);
242
+ }
243
+
244
+ // Push to current targets
245
+ const pushResults = await this.pushToTargets(stored, 'upsert');
246
+ stored.targetStatuses = pushResults;
247
+ stored.lastPushedAt = now;
248
+
249
+ await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
250
+ ...stored,
251
+ value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
252
+ });
253
+
254
+ logger.info(`Updated managed secret "${stored.key}"`);
255
+ return { managedSecret: this.toApiModel(stored), pushResults };
256
+ }
257
+
258
+ async delete(id: string): Promise<{
259
+ ok: boolean;
260
+ deleteResults: interfaces.data.IManagedSecretTargetStatus[];
261
+ }> {
262
+ const stored = this.secrets.find((s) => s.id === id);
263
+ if (!stored) throw new Error(`Managed secret not found: ${id}`);
264
+
265
+ // Best-effort: remove from all targets
266
+ const deleteResults = await this.pushToTargets(stored, 'delete');
267
+
268
+ // Remove from local storage regardless
269
+ const idx = this.secrets.indexOf(stored);
270
+ this.secrets.splice(idx, 1);
271
+ await this.removeFromStorage(id);
272
+
273
+ logger.info(`Deleted managed secret "${stored.key}"`);
274
+ return { ok: true, deleteResults };
275
+ }
276
+
277
+ async pushOne(id: string): Promise<{
278
+ managedSecret: interfaces.data.IManagedSecret;
279
+ pushResults: interfaces.data.IManagedSecretTargetStatus[];
280
+ }> {
281
+ const stored = this.secrets.find((s) => s.id === id);
282
+ if (!stored) throw new Error(`Managed secret not found: ${id}`);
283
+
284
+ const now = Date.now();
285
+ const pushResults = await this.pushToTargets(stored, 'upsert');
286
+ stored.targetStatuses = pushResults;
287
+ stored.lastPushedAt = now;
288
+ stored.updatedAt = now;
289
+
290
+ await this.storageManager.setJSON(`${MANAGED_SECRETS_PREFIX}${stored.id}.json`, {
291
+ ...stored,
292
+ value: `${KEYCHAIN_PREFIX}${this.keychainId(stored.id)}`,
293
+ });
294
+
295
+ return { managedSecret: this.toApiModel(stored), pushResults };
296
+ }
297
+
298
+ async pushAll(): Promise<
299
+ Array<{
300
+ managedSecretId: string;
301
+ key: string;
302
+ pushResults: interfaces.data.IManagedSecretTargetStatus[];
303
+ }>
304
+ > {
305
+ const results: Array<{
306
+ managedSecretId: string;
307
+ key: string;
308
+ pushResults: interfaces.data.IManagedSecretTargetStatus[];
309
+ }> = [];
310
+
311
+ for (const stored of this.secrets) {
312
+ const { pushResults } = await this.pushOne(stored.id);
313
+ results.push({
314
+ managedSecretId: stored.id,
315
+ key: stored.key,
316
+ pushResults,
317
+ });
318
+ }
319
+
320
+ return results;
321
+ }
322
+ }