@optimizely/ocp-local-env 1.0.0-beta.4

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 (160) hide show
  1. package/README.md +165 -0
  2. package/dist/package.json +104 -0
  3. package/dist/public/bundle.da978bb5437cd82e6d37.js +3 -0
  4. package/dist/public/bundle.da978bb5437cd82e6d37.js.LICENSE.txt +49 -0
  5. package/dist/public/bundle.da978bb5437cd82e6d37.js.map +1 -0
  6. package/dist/public/index.html +1 -0
  7. package/dist/src/cli.d.ts +2 -0
  8. package/dist/src/cli.js +88 -0
  9. package/dist/src/cli.js.map +1 -0
  10. package/dist/src/executor/FunctionExecutor.d.ts +56 -0
  11. package/dist/src/executor/FunctionExecutor.js +175 -0
  12. package/dist/src/executor/FunctionExecutor.js.map +1 -0
  13. package/dist/src/executor/JobExecutor.d.ts +60 -0
  14. package/dist/src/executor/JobExecutor.js +203 -0
  15. package/dist/src/executor/JobExecutor.js.map +1 -0
  16. package/dist/src/executor/LifecycleExecutor.d.ts +45 -0
  17. package/dist/src/executor/LifecycleExecutor.js +153 -0
  18. package/dist/src/executor/LifecycleExecutor.js.map +1 -0
  19. package/dist/src/executor/watcher.d.ts +63 -0
  20. package/dist/src/executor/watcher.js +213 -0
  21. package/dist/src/executor/watcher.js.map +1 -0
  22. package/dist/src/functions/hello.d.ts +4 -0
  23. package/dist/src/functions/hello.js +8 -0
  24. package/dist/src/functions/hello.js.map +1 -0
  25. package/dist/src/index.d.ts +1 -0
  26. package/dist/src/index.js +9 -0
  27. package/dist/src/index.js.map +1 -0
  28. package/dist/src/jobs/dailyJob.d.ts +4 -0
  29. package/dist/src/jobs/dailyJob.js +8 -0
  30. package/dist/src/jobs/dailyJob.js.map +1 -0
  31. package/dist/src/local_engine/LocalNotifier.d.ts +10 -0
  32. package/dist/src/local_engine/LocalNotifier.js +26 -0
  33. package/dist/src/local_engine/LocalNotifier.js.map +1 -0
  34. package/dist/src/local_engine/local-engine-child-base.d.ts +79 -0
  35. package/dist/src/local_engine/local-engine-child-base.js +304 -0
  36. package/dist/src/local_engine/local-engine-child-base.js.map +1 -0
  37. package/dist/src/local_engine/local-engine-client.d.ts +80 -0
  38. package/dist/src/local_engine/local-engine-client.js +333 -0
  39. package/dist/src/local_engine/local-engine-client.js.map +1 -0
  40. package/dist/src/local_engine/local-engine-types.d.ts +132 -0
  41. package/dist/src/local_engine/local-engine-types.js +6 -0
  42. package/dist/src/local_engine/local-engine-types.js.map +1 -0
  43. package/dist/src/local_engine/local-engine-unified.d.ts +40 -0
  44. package/dist/src/local_engine/local-engine-unified.js +406 -0
  45. package/dist/src/local_engine/local-engine-unified.js.map +1 -0
  46. package/dist/src/local_engine/local-engine-utils.d.ts +70 -0
  47. package/dist/src/local_engine/local-engine-utils.js +192 -0
  48. package/dist/src/local_engine/local-engine-utils.js.map +1 -0
  49. package/dist/src/local_engine/localSDKConfig.d.ts +30 -0
  50. package/dist/src/local_engine/localSDKConfig.js +392 -0
  51. package/dist/src/local_engine/localSDKConfig.js.map +1 -0
  52. package/dist/src/local_engine/storage/LocalConfigStore.d.ts +56 -0
  53. package/dist/src/local_engine/storage/LocalConfigStore.js +129 -0
  54. package/dist/src/local_engine/storage/LocalConfigStore.js.map +1 -0
  55. package/dist/src/local_engine/storage/LocalJobStore.d.ts +110 -0
  56. package/dist/src/local_engine/storage/LocalJobStore.js +239 -0
  57. package/dist/src/local_engine/storage/LocalJobStore.js.map +1 -0
  58. package/dist/src/local_engine/storage/LocalKVStore.d.ts +105 -0
  59. package/dist/src/local_engine/storage/LocalKVStore.js +1002 -0
  60. package/dist/src/local_engine/storage/LocalKVStore.js.map +1 -0
  61. package/dist/src/local_engine/storage/LocalNotificationStore.d.ts +27 -0
  62. package/dist/src/local_engine/storage/LocalNotificationStore.js +125 -0
  63. package/dist/src/local_engine/storage/LocalNotificationStore.js.map +1 -0
  64. package/dist/src/local_engine/storage/LocalSecretsStore.d.ts +114 -0
  65. package/dist/src/local_engine/storage/LocalSecretsStore.js +319 -0
  66. package/dist/src/local_engine/storage/LocalSecretsStore.js.map +1 -0
  67. package/dist/src/local_engine/storage/LocalSettingsStore.d.ts +161 -0
  68. package/dist/src/local_engine/storage/LocalSettingsStore.js +417 -0
  69. package/dist/src/local_engine/storage/LocalSettingsStore.js.map +1 -0
  70. package/dist/src/local_engine/storage/NumberSet.d.ts +21 -0
  71. package/dist/src/local_engine/storage/NumberSet.js +32 -0
  72. package/dist/src/local_engine/storage/NumberSet.js.map +1 -0
  73. package/dist/src/local_engine/storage/StringSet.d.ts +21 -0
  74. package/dist/src/local_engine/storage/StringSet.js +32 -0
  75. package/dist/src/local_engine/storage/StringSet.js.map +1 -0
  76. package/dist/src/local_engine/types.d.ts +52 -0
  77. package/dist/src/local_engine/types.js +6 -0
  78. package/dist/src/local_engine/types.js.map +1 -0
  79. package/dist/src/local_engine/utils.d.ts +31 -0
  80. package/dist/src/local_engine/utils.js +126 -0
  81. package/dist/src/local_engine/utils.js.map +1 -0
  82. package/dist/src/logging/LogManager.d.ts +89 -0
  83. package/dist/src/logging/LogManager.js +237 -0
  84. package/dist/src/logging/LogManager.js.map +1 -0
  85. package/dist/src/server/api/functions.d.ts +7 -0
  86. package/dist/src/server/api/functions.js +80 -0
  87. package/dist/src/server/api/functions.js.map +1 -0
  88. package/dist/src/server/api/jobs.d.ts +8 -0
  89. package/dist/src/server/api/jobs.js +242 -0
  90. package/dist/src/server/api/jobs.js.map +1 -0
  91. package/dist/src/server/api/lifecycle.d.ts +6 -0
  92. package/dist/src/server/api/lifecycle.js +73 -0
  93. package/dist/src/server/api/lifecycle.js.map +1 -0
  94. package/dist/src/server/api/settings.d.ts +6 -0
  95. package/dist/src/server/api/settings.js +117 -0
  96. package/dist/src/server/api/settings.js.map +1 -0
  97. package/dist/src/server/api/stores.d.ts +2 -0
  98. package/dist/src/server/api/stores.js +341 -0
  99. package/dist/src/server/api/stores.js.map +1 -0
  100. package/dist/src/server/api/v1.d.ts +10 -0
  101. package/dist/src/server/api/v1.js +711 -0
  102. package/dist/src/server/api/v1.js.map +1 -0
  103. package/dist/src/server/api.d.ts +8 -0
  104. package/dist/src/server/api.js +154 -0
  105. package/dist/src/server/api.js.map +1 -0
  106. package/dist/src/server/app-discovery.d.ts +5 -0
  107. package/dist/src/server/app-discovery.js +81 -0
  108. package/dist/src/server/app-discovery.js.map +1 -0
  109. package/dist/src/server/config.d.ts +21 -0
  110. package/dist/src/server/config.js +100 -0
  111. package/dist/src/server/config.js.map +1 -0
  112. package/dist/src/server/websocket.d.ts +0 -0
  113. package/dist/src/server/websocket.js +2 -0
  114. package/dist/src/server/websocket.js.map +1 -0
  115. package/dist/src/server.d.ts +2 -0
  116. package/dist/src/server.js +546 -0
  117. package/dist/src/server.js.map +1 -0
  118. package/dist/src/types/app.d.ts +155 -0
  119. package/dist/src/types/app.js +24 -0
  120. package/dist/src/types/app.js.map +1 -0
  121. package/dist/src/types/kvstore.d.ts +320 -0
  122. package/dist/src/types/kvstore.js +5 -0
  123. package/dist/src/types/kvstore.js.map +1 -0
  124. package/dist/src/ui/components/App.d.ts +6 -0
  125. package/dist/src/ui/components/App.js +255 -0
  126. package/dist/src/ui/components/App.js.map +1 -0
  127. package/dist/src/ui/components/FunctionsView.d.ts +6 -0
  128. package/dist/src/ui/components/FunctionsView.js +217 -0
  129. package/dist/src/ui/components/FunctionsView.js.map +1 -0
  130. package/dist/src/ui/components/JobsView.d.ts +6 -0
  131. package/dist/src/ui/components/JobsView.js +257 -0
  132. package/dist/src/ui/components/JobsView.js.map +1 -0
  133. package/dist/src/ui/components/KVStoreViewer.d.ts +11 -0
  134. package/dist/src/ui/components/KVStoreViewer.js +168 -0
  135. package/dist/src/ui/components/KVStoreViewer.js.map +1 -0
  136. package/dist/src/ui/components/NotificationViewer.d.ts +16 -0
  137. package/dist/src/ui/components/NotificationViewer.js +69 -0
  138. package/dist/src/ui/components/NotificationViewer.js.map +1 -0
  139. package/dist/src/ui/components/SecretsStoreViewer.d.ts +11 -0
  140. package/dist/src/ui/components/SecretsStoreViewer.js +179 -0
  141. package/dist/src/ui/components/SecretsStoreViewer.js.map +1 -0
  142. package/dist/src/ui/components/SettingsStoreViewer.d.ts +24 -0
  143. package/dist/src/ui/components/SettingsStoreViewer.js +132 -0
  144. package/dist/src/ui/components/SettingsStoreViewer.js.map +1 -0
  145. package/dist/src/ui/components/StoreViewer.d.ts +16 -0
  146. package/dist/src/ui/components/StoreViewer.js +86 -0
  147. package/dist/src/ui/components/StoreViewer.js.map +1 -0
  148. package/dist/src/ui/components/TabbedConsole.d.ts +15 -0
  149. package/dist/src/ui/components/TabbedConsole.js +93 -0
  150. package/dist/src/ui/components/TabbedConsole.js.map +1 -0
  151. package/dist/src/ui/components/common/DataTree.d.ts +15 -0
  152. package/dist/src/ui/components/common/DataTree.js +95 -0
  153. package/dist/src/ui/components/common/DataTree.js.map +1 -0
  154. package/dist/src/ui/components/common/EyeIcon.d.ts +11 -0
  155. package/dist/src/ui/components/common/EyeIcon.js +11 -0
  156. package/dist/src/ui/components/common/EyeIcon.js.map +1 -0
  157. package/dist/src/ui/index.d.ts +1 -0
  158. package/dist/src/ui/index.js +20 -0
  159. package/dist/src/ui/index.js.map +1 -0
  160. package/package.json +104 -0
@@ -0,0 +1,711 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createV1Routes = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ /**
9
+ * Create v1 API routes matching the REST API documentation
10
+ */
11
+ function createV1Routes(app, watcher, configStore, lifecycleExecutor, settingsStore) {
12
+ const router = express_1.default.Router();
13
+ // Import individual v1 route modules
14
+ router.use('/apps', createAppsRoutes(app, watcher, configStore));
15
+ router.use("/accounts", createAccountsRoutes(app, watcher, configStore, lifecycleExecutor, settingsStore));
16
+ router.use('/installs', createInstallsRoutes(app, watcher, configStore, settingsStore, lifecycleExecutor));
17
+ // Data syncs endpoint
18
+ router.get('/data-syncs', (req, res) => {
19
+ // Accept query parameters but don't use them for now
20
+ const { tracker_id, sort_field, sort_direction, offset, limit } = req.query;
21
+ // Return empty array as requested
22
+ res.json({
23
+ dataSyncItems: []
24
+ });
25
+ });
26
+ return router;
27
+ }
28
+ exports.createV1Routes = createV1Routes;
29
+ /**
30
+ * Create apps-specific routes for /v1/apps
31
+ */
32
+ function createAppsRoutes(app, watcher, configStore) {
33
+ const router = express_1.default.Router();
34
+ /**
35
+ * GET /v1/apps
36
+ * Lists running app versions
37
+ */
38
+ router.get('/', (req, res) => {
39
+ const { tracker_id, include_manifest_json, categories, installed } = req.query;
40
+ // Get server URL from request
41
+ const protocol = req.secure ? 'https' : 'http';
42
+ const host = req.get('host') || 'localhost:3000';
43
+ const serverUrl = `${protocol}://${host}`;
44
+ // Build app metadata
45
+ const metadata = {
46
+ categories: app.manifest.meta.categories || [],
47
+ display_name: app.manifest.meta.display_name,
48
+ summary: app.manifest.meta.summary || '',
49
+ overview_url: `${serverUrl}/assets/overview.md`,
50
+ support_url: app.manifest.meta.support_url || '',
51
+ logo_url: `${serverUrl}/assets/logo.svg`,
52
+ icon_url: `${serverUrl}/assets/icon.svg`,
53
+ vendor: app.manifest.meta.vendor || '',
54
+ contact_email: app.manifest.meta.contact_email || '',
55
+ settings_url: `${serverUrl}/assets/settings.yml`,
56
+ content_settings_url: '',
57
+ content_template_url: '' // Not available in local app manifest
58
+ };
59
+ const now = new Date().toISOString();
60
+ // Check installation status from config store
61
+ const isInstalled = configStore.isInstalled();
62
+ // Build the app result
63
+ const appResult = {
64
+ id: {
65
+ app_id: app.manifest.meta.app_id,
66
+ version: app.manifest.meta.version
67
+ },
68
+ metadata: metadata,
69
+ installed: isInstalled,
70
+ installed_at: isInstalled ? now : undefined,
71
+ created_at: now,
72
+ updated_at: now,
73
+ installed_version: isInstalled ? app.manifest.meta.version : undefined,
74
+ manifest_json: include_manifest_json === 'true' ? JSON.stringify(app.manifest) : '',
75
+ install_id: isInstalled ? 1 : undefined // Static install ID for local development
76
+ };
77
+ // Apply filters if provided
78
+ let results = [appResult];
79
+ // Filter by categories if specified
80
+ if (categories) {
81
+ const categoryFilters = Array.isArray(categories) ? categories : [categories];
82
+ const appCategories = app.manifest.meta.categories || [];
83
+ const hasMatchingCategory = categoryFilters.some(cat => appCategories.includes(cat));
84
+ if (!hasMatchingCategory) {
85
+ results = [];
86
+ }
87
+ }
88
+ // Filter by installed status if specified
89
+ if (installed !== undefined) {
90
+ const isInstalledFilter = installed === 'true';
91
+ if (isInstalledFilter !== isInstalled) {
92
+ results = [];
93
+ }
94
+ }
95
+ // Return the results
96
+ res.json({
97
+ results: results
98
+ });
99
+ });
100
+ /**
101
+ * GET /v1/apps/{app_id}
102
+ * Fetches details of a specific app
103
+ */
104
+ router.get('/:app_id', (req, res) => {
105
+ const { app_id } = req.params;
106
+ const { tracker_id } = req.query;
107
+ // Get server URL from request
108
+ const protocol = req.secure ? 'https' : 'http';
109
+ const host = req.get('host') || 'localhost:3000';
110
+ const serverUrl = `${protocol}://${host}`;
111
+ // Check if the requested app_id matches our local app
112
+ if (app_id !== app.manifest.meta.app_id) {
113
+ return res.status(404).json({
114
+ error: {
115
+ message: `App with id '${app_id}' not found`,
116
+ code: 'APP_NOT_FOUND'
117
+ }
118
+ });
119
+ }
120
+ // Build app version metadata from manifest
121
+ const metadata = {
122
+ categories: app.manifest.meta.categories || [],
123
+ display_name: app.manifest.meta.display_name,
124
+ summary: app.manifest.meta.summary || '',
125
+ overview_url: `${serverUrl}/assets/overview.md`,
126
+ support_url: app.manifest.meta.support_url || '',
127
+ logo_url: `${serverUrl}/assets/logo.svg`,
128
+ icon_url: `${serverUrl}/assets/icon.svg`,
129
+ vendor: app.manifest.meta.vendor || '',
130
+ contact_email: app.manifest.meta.contact_email || '',
131
+ settings_url: `${serverUrl}/assets/settings.yml`,
132
+ content_settings_url: '',
133
+ content_template_url: '' // Not available in local app manifest
134
+ };
135
+ const now = new Date().toISOString();
136
+ // Check installation status from config store
137
+ const isInstalled = configStore.isInstalled();
138
+ // Return app details according to the API specification
139
+ const response = {
140
+ app: {
141
+ id: {
142
+ app_id: app.manifest.meta.app_id,
143
+ version: app.manifest.meta.version
144
+ },
145
+ metadata: metadata,
146
+ installed: isInstalled,
147
+ installed_at: isInstalled ? now : undefined,
148
+ installed_version: isInstalled ? app.manifest.meta.version : undefined,
149
+ created_at: now,
150
+ updated_at: now
151
+ }
152
+ };
153
+ res.json(response);
154
+ });
155
+ /**
156
+ * GET /v1/apps/{app_id}/versions/{version}
157
+ * Fetches details of a specific app version
158
+ */
159
+ router.get('/:app_id/versions/:version', (req, res) => {
160
+ const { app_id, version } = req.params;
161
+ // Get server URL from request
162
+ const protocol = req.secure ? 'https' : 'http';
163
+ const host = req.get('host') || 'localhost:3000';
164
+ const serverUrl = `${protocol}://${host}`;
165
+ // Check if the requested app_id and version match our local app
166
+ if (app_id !== app.manifest.meta.app_id || version !== app.manifest.meta.version) {
167
+ return res.status(404).json({
168
+ error: {
169
+ message: `App version '${app_id}@${version}' not found`,
170
+ code: 'APP_VERSION_NOT_FOUND'
171
+ }
172
+ });
173
+ }
174
+ // Build app version metadata from manifest
175
+ const metadata = {
176
+ categories: app.manifest.meta.categories || [],
177
+ display_name: app.manifest.meta.display_name,
178
+ summary: app.manifest.meta.summary || '',
179
+ overview_url: `${serverUrl}/assets/overview.md`,
180
+ support_url: app.manifest.meta.support_url || '',
181
+ logo_url: `${serverUrl}/assets/logo.svg`,
182
+ icon_url: `${serverUrl}/assets/icon.svg`,
183
+ vendor: app.manifest.meta.vendor || '',
184
+ contact_email: app.manifest.meta.contact_email || '',
185
+ settings_url: `${serverUrl}/assets/settings.yml`,
186
+ content_settings_url: '',
187
+ content_template_url: '' // Not available in local app manifest
188
+ };
189
+ const now = new Date().toISOString();
190
+ // Build channel information if available
191
+ let channelInfo = null;
192
+ if (app.manifest.channel) {
193
+ channelInfo = {
194
+ type: app.manifest.channel.type,
195
+ options: app.manifest.channel.options || {
196
+ prepare: false,
197
+ template_preview: false
198
+ },
199
+ targeting: app.manifest.channel.targeting === 'dynamic' ?
200
+ { dynamic: true, targets: [] } :
201
+ { dynamic: false, targets: [] },
202
+ delivery: {
203
+ batch_size: app.manifest.channel.delivery?.batch_size || 100,
204
+ concurrent_batches: app.manifest.channel.delivery?.concurrent_batches || 5,
205
+ rate_limits: app.manifest.channel.delivery?.rate_limits?.map(limit => ({
206
+ count: limit.count,
207
+ period: limit.period,
208
+ unit: limit.unit === 'minute' ? 2 : limit.unit === 'hour' ? 3 : limit.unit === 'day' ? 4 : 1,
209
+ grouping: limit.grouping === 'app' ? 1 : 2 // Convert to enum
210
+ })) || []
211
+ },
212
+ metrics: {
213
+ delivery: app.manifest.channel.metrics?.delivery || [],
214
+ engagement: app.manifest.channel.metrics?.engagement || [],
215
+ attributable: app.manifest.channel.metrics?.attributable || [],
216
+ disengagement: app.manifest.channel.metrics?.disengagement || [],
217
+ reachability: app.manifest.channel.metrics?.reachability || []
218
+ }
219
+ };
220
+ }
221
+ // Return app version details according to the API specification
222
+ const response = {
223
+ app_version: {
224
+ id: {
225
+ app_id: app.manifest.meta.app_id,
226
+ version: app.manifest.meta.version
227
+ },
228
+ metadata: metadata,
229
+ created_at: now,
230
+ updated_at: now,
231
+ ...(channelInfo && { channel: channelInfo })
232
+ }
233
+ };
234
+ res.json(response);
235
+ });
236
+ return router;
237
+ }
238
+ /**
239
+ * Create accounts-specific routes for /v1/accounts
240
+ */
241
+ function createAccountsRoutes(app, watcher, configStore, lifecycleExecutor, settingsStore) {
242
+ const router = express_1.default.Router();
243
+ /**
244
+ * GET /v1/accounts/{tracker_id}/installs
245
+ * Lists apps installed in an account
246
+ */
247
+ router.get("/:tracker_id/installs", (req, res) => {
248
+ const { tracker_id } = req.params;
249
+ const { categories, includes_features } = req.query;
250
+ const now = new Date().toISOString();
251
+ // Check installation status from config store
252
+ const isInstalled = configStore.isInstalled();
253
+ // If not installed, return empty array
254
+ if (!isInstalled) {
255
+ return res.json({
256
+ installations: [],
257
+ });
258
+ }
259
+ // Build the installation object for the local app
260
+ const installation = {
261
+ id: 1,
262
+ app_id: app.manifest.meta.app_id,
263
+ version: app.manifest.meta.version,
264
+ account_id: 123,
265
+ tracker_id: tracker_id,
266
+ app_instance_id: `${app.manifest.meta.app_id}-local-instance`,
267
+ created_at: now,
268
+ updated_at: now,
269
+ };
270
+ // Apply filters if provided
271
+ let installations = [installation];
272
+ // Filter by categories if specified
273
+ if (categories) {
274
+ const categoryFilters = Array.isArray(categories)
275
+ ? categories
276
+ : [categories];
277
+ const appCategories = app.manifest.meta.categories || [];
278
+ const hasMatchingCategory = categoryFilters.some((cat) => appCategories.includes(cat));
279
+ if (!hasMatchingCategory) {
280
+ installations = [];
281
+ }
282
+ }
283
+ // Filter by features if specified (1=DESTINATION_APP, 2=SOURCE_APP)
284
+ if (includes_features) {
285
+ const featureFilters = Array.isArray(includes_features)
286
+ ? includes_features
287
+ : [includes_features];
288
+ const hasDestinations = app.manifest.destinations &&
289
+ Object.keys(app.manifest.destinations).length > 0;
290
+ const hasSources = app.manifest.sources && Object.keys(app.manifest.sources).length > 0;
291
+ const hasMatchingFeature = featureFilters.some((feature) => {
292
+ return ((feature === "1" && hasDestinations) ||
293
+ (feature === "2" && hasSources));
294
+ });
295
+ if (!hasMatchingFeature) {
296
+ installations = [];
297
+ }
298
+ }
299
+ // Return the installations
300
+ res.json({
301
+ installations: installations,
302
+ });
303
+ });
304
+ /**
305
+ * POST /v1/accounts/{tracker_id}/installs
306
+ * Installs an AppVersion to an account
307
+ */
308
+ router.post("/:tracker_id/installs", async (req, res) => {
309
+ const { tracker_id } = req.params;
310
+ let { app_id, version } = req.body;
311
+ // Validate request body
312
+ if (!app_id) {
313
+ return res.status(400).json({
314
+ error: {
315
+ message: "app_id is required",
316
+ code: "INVALID_REQUEST",
317
+ },
318
+ });
319
+ }
320
+ if (!version) {
321
+ version = app.manifest.meta.version;
322
+ }
323
+ // Check if the requested app matches our local app
324
+ if (app_id !== app.manifest.meta.app_id ||
325
+ version !== app.manifest.meta.version) {
326
+ return res.status(404).json({
327
+ error: {
328
+ message: `App version '${app_id}@${version}' not found`,
329
+ code: "APP_VERSION_NOT_FOUND",
330
+ },
331
+ });
332
+ }
333
+ // Check if already installed
334
+ if (configStore.isInstalled()) {
335
+ return res.status(409).json({
336
+ error: {
337
+ message: "App is already installed",
338
+ code: "ALREADY_INSTALLED",
339
+ },
340
+ });
341
+ }
342
+ try {
343
+ // Execute the onInstall lifecycle method
344
+ const lifecycleResult = await lifecycleExecutor.executeInstall();
345
+ if (lifecycleResult.success) {
346
+ // Mark app as installed in config store
347
+ configStore.setInstalled(true);
348
+ const now = new Date().toISOString();
349
+ // Return installation record
350
+ const installation = {
351
+ id: 1,
352
+ app_id: app.manifest.meta.app_id,
353
+ version: app.manifest.meta.version,
354
+ account_id: 123,
355
+ tracker_id: tracker_id,
356
+ app_instance_id: `${app.manifest.meta.app_id}-local-instance`,
357
+ created_at: now,
358
+ updated_at: now,
359
+ };
360
+ res.json({
361
+ app_installation: installation,
362
+ });
363
+ }
364
+ else {
365
+ // Installation failed - return error with lifecycle logs
366
+ res.status(500).json({
367
+ error: {
368
+ message: "Installation failed during lifecycle execution",
369
+ code: "LIFECYCLE_EXECUTION_FAILED",
370
+ details: {
371
+ logs: lifecycleResult.logs,
372
+ error: lifecycleResult.error,
373
+ executionTime: lifecycleResult.executionTime,
374
+ },
375
+ },
376
+ });
377
+ }
378
+ }
379
+ catch (error) {
380
+ // Unexpected error during installation
381
+ const errorMessage = error instanceof Error ? error.message : String(error);
382
+ res.status(500).json({
383
+ error: {
384
+ message: `Installation failed: ${errorMessage}`,
385
+ code: "INSTALLATION_ERROR",
386
+ },
387
+ });
388
+ }
389
+ });
390
+ /**
391
+ * DELETE /v1/accounts/{tracker_id}/installs/{app_id}
392
+ * Uninstalls an AppVersion from an account
393
+ */
394
+ router.delete("/:tracker_id/installs/:app_id", async (req, res) => {
395
+ const { tracker_id, app_id } = req.params;
396
+ // Check if the requested app matches our local app
397
+ if (app_id !== app.manifest.meta.app_id) {
398
+ return res.status(404).json({
399
+ error: {
400
+ message: `App with id '${app_id}' not found`,
401
+ code: "APP_NOT_FOUND",
402
+ },
403
+ });
404
+ }
405
+ // Check if app is installed
406
+ if (!configStore.isInstalled()) {
407
+ return res.status(404).json({
408
+ error: {
409
+ message: "App is not installed",
410
+ code: "NOT_INSTALLED",
411
+ },
412
+ });
413
+ }
414
+ try {
415
+ // Execute the onUninstall lifecycle method
416
+ const lifecycleResult = await lifecycleExecutor.executeUninstall();
417
+ if (lifecycleResult.success) {
418
+ // Mark app as not installed in config store
419
+ configStore.setInstalled(false);
420
+ // Clear all settings data
421
+ settingsStore.clearSettings();
422
+ // Clear function GUIDs
423
+ configStore.clearFunctionGuids();
424
+ // Return 200 OK as specified in the API documentation
425
+ res.status(200).send({});
426
+ }
427
+ else {
428
+ // Uninstall failed - return error with lifecycle logs
429
+ res.status(500).json({
430
+ error: {
431
+ message: "Uninstall failed during lifecycle execution",
432
+ code: "LIFECYCLE_EXECUTION_FAILED",
433
+ details: {
434
+ logs: lifecycleResult.logs,
435
+ error: lifecycleResult.error,
436
+ executionTime: lifecycleResult.executionTime,
437
+ },
438
+ },
439
+ });
440
+ }
441
+ }
442
+ catch (error) {
443
+ // Unexpected error during uninstall
444
+ const errorMessage = error instanceof Error ? error.message : String(error);
445
+ res.status(500).json({
446
+ error: {
447
+ message: `Uninstall failed: ${errorMessage}`,
448
+ code: "UNINSTALL_ERROR",
449
+ },
450
+ });
451
+ }
452
+ });
453
+ return router;
454
+ }
455
+ /**
456
+ * Create installs-specific routes for /v1/installs
457
+ */
458
+ function createInstallsRoutes(app, watcher, configStore, settingsStore, lifecycleExecutor) {
459
+ const router = express_1.default.Router();
460
+ /**
461
+ * GET /v1/installs/{app_install_id}/settings
462
+ * Gets stored app secrets for a setup form
463
+ */
464
+ router.get("/:app_install_id/settings", (req, res) => {
465
+ const { app_install_id } = req.params;
466
+ // Validate app_install_id (should be numeric for our implementation)
467
+ const installId = parseInt(app_install_id, 10);
468
+ if (isNaN(installId)) {
469
+ return res.status(400).json({
470
+ error: {
471
+ message: "app_install_id must be a valid number",
472
+ code: "INVALID_INSTALL_ID",
473
+ },
474
+ });
475
+ }
476
+ // Check if app is installed (for local dev, we use install ID 1)
477
+ if (!configStore.isInstalled()) {
478
+ return res.status(404).json({
479
+ error: {
480
+ message: "App installation not found",
481
+ code: "INSTALLATION_NOT_FOUND",
482
+ },
483
+ });
484
+ }
485
+ // For local development, we only support install ID 1
486
+ if (installId !== 1) {
487
+ return res.status(404).json({
488
+ error: {
489
+ message: `Installation with ID ${installId} not found`,
490
+ code: "INSTALLATION_NOT_FOUND",
491
+ },
492
+ });
493
+ }
494
+ try {
495
+ // Get sections from settings.yml
496
+ const sections = settingsStore.getSections();
497
+ const formData = {};
498
+ // For each section, get the stored values from local settings store
499
+ sections.forEach((sectionName) => {
500
+ const sectionSettings = settingsStore.getSectionSettings(sectionName);
501
+ formData[sectionName] = sectionSettings;
502
+ });
503
+ // Convert to JSON string
504
+ const formDataJson = JSON.stringify(formData);
505
+ // Return settings according to API specification
506
+ res.json({
507
+ form_data_json: formDataJson,
508
+ error: null, // No error if successful
509
+ });
510
+ }
511
+ catch (error) {
512
+ // Return error response
513
+ const errorMessage = error instanceof Error ? error.message : String(error);
514
+ res.json({
515
+ form_data_json: "{}",
516
+ error: {
517
+ message: errorMessage,
518
+ },
519
+ });
520
+ }
521
+ });
522
+ /**
523
+ * POST /v1/installs/{app_install_id}/settings
524
+ * Submits app settings form and calls onSettingsForm lifecycle method
525
+ */
526
+ router.post("/:app_install_id/settings", async (req, res) => {
527
+ const { app_install_id } = req.params;
528
+ const { form_page, action, form_data_json } = req.body;
529
+ // Validate app_install_id (should be numeric for our implementation)
530
+ const installId = parseInt(app_install_id, 10);
531
+ if (isNaN(installId)) {
532
+ return res.status(400).json({
533
+ error: {
534
+ message: "app_install_id must be a valid number",
535
+ code: "INVALID_INSTALL_ID",
536
+ },
537
+ });
538
+ }
539
+ // Check if app is installed (for local dev, we use install ID 1)
540
+ if (!configStore.isInstalled()) {
541
+ return res.status(404).json({
542
+ error: {
543
+ message: "App installation not found",
544
+ code: "INSTALLATION_NOT_FOUND",
545
+ },
546
+ });
547
+ }
548
+ // For local development, we only support install ID 1
549
+ if (installId !== 1) {
550
+ return res.status(404).json({
551
+ error: {
552
+ message: `Installation with ID ${installId} not found`,
553
+ code: "INSTALLATION_NOT_FOUND",
554
+ },
555
+ });
556
+ }
557
+ // Validate required fields
558
+ if (!form_page) {
559
+ return res.status(400).json({
560
+ error: {
561
+ message: "section is required",
562
+ code: "INVALID_REQUEST",
563
+ },
564
+ });
565
+ }
566
+ if (!action) {
567
+ return res.status(400).json({
568
+ error: {
569
+ message: "action is required",
570
+ code: "INVALID_REQUEST",
571
+ },
572
+ });
573
+ }
574
+ try {
575
+ // Parse form_data_json if it's a string
576
+ let formData;
577
+ if (typeof form_data_json === "string") {
578
+ try {
579
+ formData = JSON.parse(form_data_json);
580
+ }
581
+ catch (e) {
582
+ formData = {};
583
+ }
584
+ }
585
+ else {
586
+ formData = form_data_json || {};
587
+ }
588
+ // Execute the onSettingsForm lifecycle method first
589
+ const lifecycleResult = await lifecycleExecutor.executeSettingsForm(form_page, action, formData);
590
+ if (lifecycleResult.success) {
591
+ // Get the lifecycle result which should be in the format: { result: LifecycleSettingsResponse, data: FormData }
592
+ const lifecycleData = lifecycleResult.result;
593
+ // Extract the response component from the lifecycle result
594
+ let settingsResponse = {};
595
+ if (lifecycleData && typeof lifecycleData === "object") {
596
+ settingsResponse = lifecycleData.result || {};
597
+ }
598
+ // Get the updated form data from settings store (always return fresh data)
599
+ const sections = settingsStore.getSections();
600
+ const updatedFormData = {};
601
+ sections.forEach((sectionName) => {
602
+ const sectionSettings = settingsStore.getSectionSettings(sectionName);
603
+ updatedFormData[sectionName] = sectionSettings;
604
+ });
605
+ // Return response in the format expected by the REST API (matching AnduinServer.submitForm)
606
+ res.json({
607
+ form_data_json: JSON.stringify(updatedFormData),
608
+ response_json: JSON.stringify(settingsResponse),
609
+ });
610
+ }
611
+ else {
612
+ // Lifecycle execution failed
613
+ res.status(500).json({
614
+ error: {
615
+ message: "Settings form processing failed during lifecycle execution",
616
+ code: "LIFECYCLE_EXECUTION_FAILED",
617
+ details: {
618
+ logs: lifecycleResult.logs,
619
+ error: lifecycleResult.error,
620
+ executionTime: lifecycleResult.executionTime,
621
+ },
622
+ },
623
+ });
624
+ }
625
+ }
626
+ catch (error) {
627
+ // Unexpected error during settings form processing
628
+ const errorMessage = error instanceof Error ? error.message : String(error);
629
+ res.status(500).json({
630
+ error: {
631
+ message: `Settings form processing failed: ${errorMessage}`,
632
+ code: "SETTINGS_FORM_ERROR",
633
+ },
634
+ });
635
+ }
636
+ });
637
+ /**
638
+ * GET /v1/installs/{app_install_id}/canUninstall
639
+ * Checks if an app installation can be uninstalled
640
+ */
641
+ router.get("/:app_install_id/canUninstall", async (req, res) => {
642
+ const { app_install_id } = req.params;
643
+ // Validate app_install_id (should be numeric for our implementation)
644
+ const installId = parseInt(app_install_id, 10);
645
+ if (isNaN(installId)) {
646
+ return res.status(400).json({
647
+ error: {
648
+ message: "app_install_id must be a valid number",
649
+ code: "INVALID_INSTALL_ID",
650
+ },
651
+ });
652
+ }
653
+ // Check if app is installed (for local dev, we use install ID 1)
654
+ if (!configStore.isInstalled()) {
655
+ return res.status(404).json({
656
+ error: {
657
+ message: "App installation not found",
658
+ code: "INSTALLATION_NOT_FOUND",
659
+ },
660
+ });
661
+ }
662
+ // For local development, we only support install ID 1
663
+ if (installId !== 1) {
664
+ return res.status(404).json({
665
+ error: {
666
+ message: `Installation with ID ${installId} not found`,
667
+ code: "INSTALLATION_NOT_FOUND",
668
+ },
669
+ });
670
+ }
671
+ try {
672
+ // Execute the canUninstall lifecycle method
673
+ const lifecycleResult = await lifecycleExecutor.executeCanUninstall();
674
+ if (lifecycleResult.success) {
675
+ // Parse the result from the lifecycle method
676
+ const canUninstallResult = lifecycleResult.result;
677
+ // Return the response in the expected format
678
+ res.json({
679
+ uninstallable: canUninstallResult?.uninstallable || false,
680
+ message: canUninstallResult?.message,
681
+ });
682
+ }
683
+ else {
684
+ // Lifecycle execution failed - return error response
685
+ res.status(500).json({
686
+ error: {
687
+ message: "Failed to check uninstall status during lifecycle execution",
688
+ code: "LIFECYCLE_EXECUTION_FAILED",
689
+ details: {
690
+ logs: lifecycleResult.logs,
691
+ error: lifecycleResult.error,
692
+ executionTime: lifecycleResult.executionTime,
693
+ },
694
+ },
695
+ });
696
+ }
697
+ }
698
+ catch (error) {
699
+ // Unexpected error during canUninstall check - return error response
700
+ const errorMessage = error instanceof Error ? error.message : String(error);
701
+ res.status(500).json({
702
+ error: {
703
+ message: `Error checking uninstall status: ${errorMessage}`,
704
+ code: "CAN_UNINSTALL_ERROR",
705
+ },
706
+ });
707
+ }
708
+ });
709
+ return router;
710
+ }
711
+ //# sourceMappingURL=v1.js.map