@qwickapps/server 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +4 -4
  3. package/dist/src/core/control-panel.d.ts.map +1 -1
  4. package/dist/src/core/control-panel.js +10 -7
  5. package/dist/src/core/control-panel.js.map +1 -1
  6. package/dist/src/core/gateway.d.ts.map +1 -1
  7. package/dist/src/core/gateway.js +46 -47
  8. package/dist/src/core/gateway.js.map +1 -1
  9. package/dist/src/plugins/api-keys/ApiKeysManagementPage.d.ts.map +1 -1
  10. package/dist/src/plugins/api-keys/ApiKeysManagementPage.js +1 -1
  11. package/dist/src/plugins/api-keys/ApiKeysManagementPage.js.map +1 -1
  12. package/dist/src/plugins/api-keys/ApiKeysStatusWidget.d.ts.map +1 -1
  13. package/dist/src/plugins/api-keys/ApiKeysStatusWidget.js +1 -1
  14. package/dist/src/plugins/api-keys/ApiKeysStatusWidget.js.map +1 -1
  15. package/dist/src/plugins/auth/AuthManagementPage.d.ts.map +1 -1
  16. package/dist/src/plugins/auth/AuthManagementPage.js +1 -1
  17. package/dist/src/plugins/auth/AuthManagementPage.js.map +1 -1
  18. package/dist/src/plugins/auth/AuthStatusWidget.d.ts.map +1 -1
  19. package/dist/src/plugins/auth/AuthStatusWidget.js +1 -1
  20. package/dist/src/plugins/auth/AuthStatusWidget.js.map +1 -1
  21. package/dist/src/plugins/auth/auth-plugin.d.ts.map +1 -1
  22. package/dist/src/plugins/auth/auth-plugin.js +9 -0
  23. package/dist/src/plugins/auth/auth-plugin.js.map +1 -1
  24. package/dist/src/plugins/bans/BansManagementPage.d.ts.map +1 -1
  25. package/dist/src/plugins/bans/BansManagementPage.js +1 -1
  26. package/dist/src/plugins/bans/BansManagementPage.js.map +1 -1
  27. package/dist/src/plugins/bans/BansStatusWidget.d.ts.map +1 -1
  28. package/dist/src/plugins/bans/BansStatusWidget.js +1 -1
  29. package/dist/src/plugins/bans/BansStatusWidget.js.map +1 -1
  30. package/dist/src/plugins/cache/CacheManagementPage.js +1 -1
  31. package/dist/src/plugins/cache/CacheManagementPage.js.map +1 -1
  32. package/dist/src/plugins/cache/CacheStatusWidget.js +1 -1
  33. package/dist/src/plugins/cache/CacheStatusWidget.js.map +1 -1
  34. package/dist/src/plugins/devices/DevicesManagementPage.d.ts.map +1 -1
  35. package/dist/src/plugins/devices/DevicesManagementPage.js +1 -1
  36. package/dist/src/plugins/devices/DevicesManagementPage.js.map +1 -1
  37. package/dist/src/plugins/devices/DevicesStatusWidget.d.ts.map +1 -1
  38. package/dist/src/plugins/devices/DevicesStatusWidget.js +1 -1
  39. package/dist/src/plugins/devices/DevicesStatusWidget.js.map +1 -1
  40. package/dist/src/plugins/diagnostics/DiagnosticsManagementPage.js +1 -1
  41. package/dist/src/plugins/diagnostics/DiagnosticsManagementPage.js.map +1 -1
  42. package/dist/src/plugins/diagnostics/DiagnosticsStatusWidget.js +1 -1
  43. package/dist/src/plugins/diagnostics/DiagnosticsStatusWidget.js.map +1 -1
  44. package/dist/src/plugins/entitlements/EntitlementsManagementPage.d.ts.map +1 -1
  45. package/dist/src/plugins/entitlements/EntitlementsManagementPage.js +1 -1
  46. package/dist/src/plugins/entitlements/EntitlementsManagementPage.js.map +1 -1
  47. package/dist/src/plugins/entitlements/EntitlementsStatusWidget.d.ts.map +1 -1
  48. package/dist/src/plugins/entitlements/EntitlementsStatusWidget.js +1 -1
  49. package/dist/src/plugins/entitlements/EntitlementsStatusWidget.js.map +1 -1
  50. package/dist/src/plugins/health/HealthManagementPage.js +1 -1
  51. package/dist/src/plugins/health/HealthManagementPage.js.map +1 -1
  52. package/dist/src/plugins/health/HealthStatusWidget.js +1 -1
  53. package/dist/src/plugins/health/HealthStatusWidget.js.map +1 -1
  54. package/dist/src/plugins/logs/LogsManagementPage.js +1 -1
  55. package/dist/src/plugins/logs/LogsManagementPage.js.map +1 -1
  56. package/dist/src/plugins/logs/LogsStatusWidget.js +1 -1
  57. package/dist/src/plugins/logs/LogsStatusWidget.js.map +1 -1
  58. package/dist/src/plugins/maintenance/MaintenanceManagementPage.js +1 -1
  59. package/dist/src/plugins/maintenance/MaintenanceManagementPage.js.map +1 -1
  60. package/dist/src/plugins/maintenance/MaintenanceStatusWidget.js +1 -1
  61. package/dist/src/plugins/maintenance/MaintenanceStatusWidget.js.map +1 -1
  62. package/dist/src/plugins/maintenance/SeedManagementPage.js +1 -1
  63. package/dist/src/plugins/maintenance/SeedManagementPage.js.map +1 -1
  64. package/dist/src/plugins/maintenance/seed-executor.js +2 -2
  65. package/dist/src/plugins/maintenance/seed-executor.js.map +1 -1
  66. package/dist/src/plugins/maintenance-plugin.d.ts +2 -0
  67. package/dist/src/plugins/maintenance-plugin.d.ts.map +1 -1
  68. package/dist/src/plugins/maintenance-plugin.js +402 -2
  69. package/dist/src/plugins/maintenance-plugin.js.map +1 -1
  70. package/dist/src/plugins/notifications/NotificationsManagementPage.js +1 -1
  71. package/dist/src/plugins/notifications/NotificationsManagementPage.js.map +1 -1
  72. package/dist/src/plugins/notifications/NotificationsStatusWidget.d.ts.map +1 -1
  73. package/dist/src/plugins/notifications/NotificationsStatusWidget.js +1 -1
  74. package/dist/src/plugins/notifications/NotificationsStatusWidget.js.map +1 -1
  75. package/dist/src/plugins/parental/ParentalManagementPage.d.ts.map +1 -1
  76. package/dist/src/plugins/parental/ParentalManagementPage.js +1 -1
  77. package/dist/src/plugins/parental/ParentalManagementPage.js.map +1 -1
  78. package/dist/src/plugins/parental/ParentalStatusWidget.d.ts.map +1 -1
  79. package/dist/src/plugins/parental/ParentalStatusWidget.js +1 -1
  80. package/dist/src/plugins/parental/ParentalStatusWidget.js.map +1 -1
  81. package/dist/src/plugins/postgres/PostgresManagementPage.js +1 -1
  82. package/dist/src/plugins/postgres/PostgresManagementPage.js.map +1 -1
  83. package/dist/src/plugins/postgres/PostgresStatusWidget.js +1 -1
  84. package/dist/src/plugins/postgres/PostgresStatusWidget.js.map +1 -1
  85. package/dist/src/plugins/preferences/PreferencesManagementPage.d.ts.map +1 -1
  86. package/dist/src/plugins/preferences/PreferencesManagementPage.js +1 -1
  87. package/dist/src/plugins/preferences/PreferencesManagementPage.js.map +1 -1
  88. package/dist/src/plugins/preferences/PreferencesStatusWidget.d.ts.map +1 -1
  89. package/dist/src/plugins/preferences/PreferencesStatusWidget.js +1 -1
  90. package/dist/src/plugins/preferences/PreferencesStatusWidget.js.map +1 -1
  91. package/dist/src/plugins/profiles/ProfilesManagementPage.d.ts.map +1 -1
  92. package/dist/src/plugins/profiles/ProfilesManagementPage.js +1 -1
  93. package/dist/src/plugins/profiles/ProfilesManagementPage.js.map +1 -1
  94. package/dist/src/plugins/profiles/ProfilesStatusWidget.d.ts.map +1 -1
  95. package/dist/src/plugins/profiles/ProfilesStatusWidget.js +1 -1
  96. package/dist/src/plugins/profiles/ProfilesStatusWidget.js.map +1 -1
  97. package/dist/src/plugins/qwickbrain/QwickbrainManagementPage.js +1 -1
  98. package/dist/src/plugins/qwickbrain/QwickbrainManagementPage.js.map +1 -1
  99. package/dist/src/plugins/qwickbrain/QwickbrainStatusWidget.d.ts.map +1 -1
  100. package/dist/src/plugins/qwickbrain/QwickbrainStatusWidget.js +1 -1
  101. package/dist/src/plugins/qwickbrain/QwickbrainStatusWidget.js.map +1 -1
  102. package/dist/src/plugins/rate-limit/RateLimitManagementPage.js +1 -1
  103. package/dist/src/plugins/rate-limit/RateLimitManagementPage.js.map +1 -1
  104. package/dist/src/plugins/rate-limit/RateLimitStatusWidget.d.ts.map +1 -1
  105. package/dist/src/plugins/rate-limit/RateLimitStatusWidget.js +1 -1
  106. package/dist/src/plugins/rate-limit/RateLimitStatusWidget.js.map +1 -1
  107. package/dist/src/plugins/subscriptions/SubscriptionsManagementPage.d.ts.map +1 -1
  108. package/dist/src/plugins/subscriptions/SubscriptionsManagementPage.js +1 -1
  109. package/dist/src/plugins/subscriptions/SubscriptionsManagementPage.js.map +1 -1
  110. package/dist/src/plugins/subscriptions/SubscriptionsStatusWidget.d.ts.map +1 -1
  111. package/dist/src/plugins/subscriptions/SubscriptionsStatusWidget.js +1 -1
  112. package/dist/src/plugins/subscriptions/SubscriptionsStatusWidget.js.map +1 -1
  113. package/dist/src/plugins/usage/UsageManagementPage.d.ts.map +1 -1
  114. package/dist/src/plugins/usage/UsageManagementPage.js +1 -1
  115. package/dist/src/plugins/usage/UsageManagementPage.js.map +1 -1
  116. package/dist/src/plugins/usage/UsageStatusWidget.d.ts.map +1 -1
  117. package/dist/src/plugins/usage/UsageStatusWidget.js +1 -1
  118. package/dist/src/plugins/usage/UsageStatusWidget.js.map +1 -1
  119. package/dist/src/plugins/users/UsersManagementPage.js +1 -1
  120. package/dist/src/plugins/users/UsersManagementPage.js.map +1 -1
  121. package/dist/src/plugins/users/UsersStatusWidget.js +1 -1
  122. package/dist/src/plugins/users/UsersStatusWidget.js.map +1 -1
  123. package/dist/ui/src/api/clientBuilder.d.ts +3 -3
  124. package/dist/ui/src/api/clientBuilder.js +5 -5
  125. package/dist/ui/src/api/clientBuilder.js.map +1 -1
  126. package/dist/ui/src/api/controlPanelApi.js +19 -19
  127. package/dist/ui/src/api/controlPanelApi.js.map +1 -1
  128. package/dist/ui/src/components/ControlPanelApp.d.ts.map +1 -1
  129. package/dist/ui/src/components/ControlPanelApp.js +5 -4
  130. package/dist/ui/src/components/ControlPanelApp.js.map +1 -1
  131. package/dist/ui/src/dashboard/builtInWidgets.d.ts.map +1 -1
  132. package/dist/ui/src/dashboard/builtInWidgets.js +3 -1
  133. package/dist/ui/src/dashboard/builtInWidgets.js.map +1 -1
  134. package/dist/ui/src/dashboard/widgets/CMSMaintenanceWidget.js +8 -8
  135. package/dist/ui/src/dashboard/widgets/CMSMaintenanceWidget.js.map +1 -1
  136. package/dist/ui/src/dashboard/widgets/CMSStatusWidget.js +2 -2
  137. package/dist/ui/src/dashboard/widgets/CMSStatusWidget.js.map +1 -1
  138. package/dist/ui/src/dashboard/widgets/CacheMaintenanceWidget.js +4 -4
  139. package/dist/ui/src/dashboard/widgets/CacheMaintenanceWidget.js.map +1 -1
  140. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.d.ts.map +1 -1
  141. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.js +2 -1
  142. package/dist/ui/src/dashboard/widgets/DatabaseOperationsWidget.js.map +1 -1
  143. package/dist/ui/src/dashboard/widgets/LogsMaintenanceWidget.js +6 -6
  144. package/dist/ui/src/dashboard/widgets/LogsMaintenanceWidget.js.map +1 -1
  145. package/dist/ui/src/dashboard/widgets/MigrationManagementWidget.d.ts +8 -0
  146. package/dist/ui/src/dashboard/widgets/MigrationManagementWidget.d.ts.map +1 -0
  147. package/dist/ui/src/dashboard/widgets/MigrationManagementWidget.js +132 -0
  148. package/dist/ui/src/dashboard/widgets/MigrationManagementWidget.js.map +1 -0
  149. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.js +6 -6
  150. package/dist/ui/src/dashboard/widgets/SeedManagementWidget.js.map +1 -1
  151. package/dist/ui/src/dashboard/widgets/index.d.ts +1 -0
  152. package/dist/ui/src/dashboard/widgets/index.d.ts.map +1 -1
  153. package/dist/ui/src/dashboard/widgets/index.js +1 -0
  154. package/dist/ui/src/dashboard/widgets/index.js.map +1 -1
  155. package/dist-ui/assets/{index-DRG9n0cx.css → index-De-dCl_t.css} +1 -1
  156. package/dist-ui/assets/{index-D-4HKPkw.js → index-DnEQCOGR.js} +112 -109
  157. package/dist-ui/assets/{index-D-4HKPkw.js.map → index-DnEQCOGR.js.map} +1 -1
  158. package/dist-ui/index.html +2 -2
  159. package/dist-ui-lib/index.js +2623 -2441
  160. package/dist-ui-lib/index.js.map +1 -1
  161. package/dist-ui-lib/src/api/clientBuilder.d.ts +3 -3
  162. package/dist-ui-lib/src/dashboard/widgets/MigrationManagementWidget.d.ts +7 -0
  163. package/dist-ui-lib/src/dashboard/widgets/index.d.ts +1 -0
  164. package/package.json +5 -2
  165. package/src/core/control-panel.ts +10 -7
  166. package/src/core/gateway.ts +48 -51
  167. package/src/plugins/api-keys/ApiKeysManagementPage.tsx +1 -1
  168. package/src/plugins/api-keys/ApiKeysStatusWidget.tsx +1 -1
  169. package/src/plugins/auth/AuthManagementPage.tsx +1 -1
  170. package/src/plugins/auth/AuthStatusWidget.tsx +1 -1
  171. package/src/plugins/auth/adapters/supertokens-adapter.ts +9 -13
  172. package/src/plugins/auth/auth-plugin.ts +10 -0
  173. package/src/plugins/auth/env-config.ts +1 -0
  174. package/src/plugins/auth/types.ts +2 -0
  175. package/src/plugins/bans/BansManagementPage.tsx +1 -1
  176. package/src/plugins/bans/BansStatusWidget.tsx +1 -1
  177. package/src/plugins/cache/CacheManagementPage.tsx +1 -1
  178. package/src/plugins/cache/CacheStatusWidget.tsx +1 -1
  179. package/src/plugins/devices/DevicesManagementPage.tsx +1 -1
  180. package/src/plugins/devices/DevicesStatusWidget.tsx +1 -1
  181. package/src/plugins/diagnostics/DiagnosticsManagementPage.tsx +1 -1
  182. package/src/plugins/diagnostics/DiagnosticsStatusWidget.tsx +1 -1
  183. package/src/plugins/entitlements/EntitlementsManagementPage.tsx +1 -1
  184. package/src/plugins/entitlements/EntitlementsStatusWidget.tsx +1 -1
  185. package/src/plugins/health/HealthManagementPage.tsx +1 -1
  186. package/src/plugins/health/HealthStatusWidget.tsx +1 -1
  187. package/src/plugins/logs/LogsManagementPage.tsx +1 -1
  188. package/src/plugins/logs/LogsStatusWidget.tsx +1 -1
  189. package/src/plugins/maintenance/MaintenanceManagementPage.tsx +1 -1
  190. package/src/plugins/maintenance/MaintenanceStatusWidget.tsx +1 -1
  191. package/src/plugins/maintenance/SeedManagementPage.tsx +1 -1
  192. package/src/plugins/maintenance/seed-executor.ts +7 -6
  193. package/src/plugins/maintenance-plugin.ts +501 -5
  194. package/src/plugins/notifications/NotificationsManagementPage.tsx +1 -1
  195. package/src/plugins/notifications/NotificationsStatusWidget.tsx +1 -1
  196. package/src/plugins/parental/ParentalManagementPage.tsx +1 -1
  197. package/src/plugins/parental/ParentalStatusWidget.tsx +1 -1
  198. package/src/plugins/postgres/PostgresManagementPage.tsx +1 -1
  199. package/src/plugins/postgres/PostgresStatusWidget.tsx +1 -1
  200. package/src/plugins/preferences/PreferencesManagementPage.tsx +1 -1
  201. package/src/plugins/preferences/PreferencesStatusWidget.tsx +1 -1
  202. package/src/plugins/profiles/ProfilesManagementPage.tsx +1 -1
  203. package/src/plugins/profiles/ProfilesStatusWidget.tsx +1 -1
  204. package/src/plugins/qwickbrain/QwickbrainManagementPage.tsx +1 -1
  205. package/src/plugins/qwickbrain/QwickbrainStatusWidget.tsx +1 -1
  206. package/src/plugins/rate-limit/RateLimitManagementPage.tsx +1 -1
  207. package/src/plugins/rate-limit/RateLimitStatusWidget.tsx +1 -1
  208. package/src/plugins/subscriptions/SubscriptionsManagementPage.tsx +1 -1
  209. package/src/plugins/subscriptions/SubscriptionsStatusWidget.tsx +1 -1
  210. package/src/plugins/usage/UsageManagementPage.tsx +1 -1
  211. package/src/plugins/usage/UsageStatusWidget.tsx +1 -1
  212. package/src/plugins/users/UsersManagementPage.tsx +1 -1
  213. package/src/plugins/users/UsersStatusWidget.tsx +1 -1
  214. package/ui/src/App.tsx +4 -3
  215. package/ui/src/api/clientBuilder.ts +5 -5
  216. package/ui/src/api/controlPanelApi.ts +19 -19
  217. package/ui/src/components/ControlPanelApp.tsx +5 -4
  218. package/ui/src/dashboard/builtInWidgets.tsx +3 -0
  219. package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +3 -8
  220. package/ui/src/dashboard/widgets/CMSMaintenanceWidget.tsx +8 -8
  221. package/ui/src/dashboard/widgets/CMSStatusWidget.tsx +2 -2
  222. package/ui/src/dashboard/widgets/CacheMaintenanceWidget.tsx +4 -4
  223. package/ui/src/dashboard/widgets/DatabaseOperationsWidget.tsx +2 -1
  224. package/ui/src/dashboard/widgets/LogsMaintenanceWidget.tsx +6 -6
  225. package/ui/src/dashboard/widgets/MigrationManagementWidget.tsx +319 -0
  226. package/ui/src/dashboard/widgets/SeedManagementWidget.tsx +6 -6
  227. package/ui/src/dashboard/widgets/index.ts +1 -0
  228. package/ui/src/hooks/useJobStream.ts +2 -2
  229. package/ui/src/pages/ContentOpsJobsPage.tsx +3 -3
  230. package/ui/src/pages/PluginPage.tsx +1 -1
  231. package/ui/src/pages/TenantsManagementPage.tsx +1 -1
@@ -18,15 +18,15 @@ export interface RouteManifestEntry {
18
18
  /**
19
19
  * Build typed API client from server manifest
20
20
  *
21
- * Fetches the manifest from /api/client-manifest and generates a nested
21
+ * Fetches the manifest from /client-manifest (baseUrl already includes /qapi) and generates a nested
22
22
  * client object with methods for each route.
23
23
  *
24
- * @param baseUrl - Base URL of the server (e.g., 'http://localhost:3000')
24
+ * @param baseUrl - Base URL for QwickApps Server APIs (e.g., '/qapi' or 'http://localhost:3000/qapi')
25
25
  * @returns Promise resolving to the generated client
26
26
  *
27
27
  * @example
28
28
  * ```typescript
29
- * const client = await buildClientFromManifest<APIClient>('http://localhost:3000');
29
+ * const client = await buildClientFromManifest<APIClient>('/qapi');
30
30
  * const logs = await client.logs.query({ limit: 10 });
31
31
  * ```
32
32
  */
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Migration Management Widget
3
+ *
4
+ * Allows executing Payload CMS database migrations from the control panel.
5
+ * Part of the maintenance plugin.
6
+ */
7
+ export declare function MigrationManagementWidget(): import("react/jsx-runtime").JSX.Element;
@@ -10,6 +10,7 @@ export { NotificationsStatsWidget } from './NotificationsStatsWidget';
10
10
  export { CMSStatusWidget } from './CMSStatusWidget';
11
11
  export { CMSMaintenanceWidget } from './CMSMaintenanceWidget';
12
12
  export { SeedManagementWidget } from './SeedManagementWidget';
13
+ export { MigrationManagementWidget } from './MigrationManagementWidget';
13
14
  export { ServiceControlWidget } from './ServiceControlWidget';
14
15
  export { EnvironmentConfigWidget } from './EnvironmentConfigWidget';
15
16
  export { DatabaseOpsWidget } from './DatabaseOpsWidget';
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@qwickapps/server",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
5
+ "type": "module",
5
6
  "main": "dist/src/index.js",
6
7
  "types": "dist/src/index.d.ts",
7
8
  "exports": {
@@ -59,6 +60,8 @@
59
60
  "express": "^4.18.2",
60
61
  "helmet": "^7.1.0",
61
62
  "http-proxy-middleware": "^3.0.3",
63
+ "prop-types": "^15.8.1",
64
+ "react-is": "^18.2.0",
62
65
  "zod": "^3.22.4"
63
66
  },
64
67
  "devDependencies": {
@@ -90,7 +93,7 @@
90
93
  "react": "^18.2.0",
91
94
  "react-dom": "^18.2.0",
92
95
  "react-router-dom": "^6.30.1",
93
- "supertokens-node": "^20.1.7",
96
+ "supertokens-node": "^18.0.0",
94
97
  "tsx": "^4.20.6",
95
98
  "typescript": "^5.3.3",
96
99
  "vite": "^6.0.0",
@@ -200,10 +200,11 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
200
200
  next();
201
201
  });
202
202
 
203
- // CRITICAL: System APIs always mount at /api regardless of mountPath
204
- // This ensures consistent API paths across all products
205
- // See: ADR-012-QwickApps-Server-Routing-Architecture
206
- app.use('/api', router);
203
+ // CRITICAL: QwickApps Server APIs always mount at /qapi regardless of mountPath
204
+ // This avoids conflicts with application-level APIs (e.g., Payload CMS uses /api)
205
+ // /qapi = QwickApps framework APIs (SuperTokens, health, postgres, cache, etc.)
206
+ // /api = Application/CMS APIs (Payload, custom app routes)
207
+ app.use('/qapi', router);
207
208
 
208
209
  // Built-in routes
209
210
 
@@ -238,7 +239,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
238
239
 
239
240
  manifest[key] = {
240
241
  method: route.method.toUpperCase(),
241
- path: `/api${route.path}`,
242
+ path: route.path,
242
243
  auth: route.auth?.required || false,
243
244
  };
244
245
  }
@@ -284,8 +285,10 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
284
285
  const effectivePath = forwardedPrefix || mountPath;
285
286
  const normalizedPath = effectivePath === '/' ? '' : effectivePath;
286
287
 
287
- // Inject base path as global variable before other scripts
288
- const basePathScript = `<script>window.__APP_BASE_PATH__="${normalizedPath}";</script>`;
288
+ // Inject base paths as global variables before other scripts
289
+ // __APP_BASE_PATH__: UI mount path for routing (e.g., '/cpanel')
290
+ // __API_BASE_PATH__: Base URL for QwickApps Server APIs (always '/qapi')
291
+ const basePathScript = `<script>window.__APP_BASE_PATH__="${normalizedPath}";window.__API_BASE_PATH__="/qapi";</script>`;
289
292
  let html = indexHtmlTemplate.replace('<head>', `<head>\n ${basePathScript}`);
290
293
 
291
294
  // Rewrite asset paths if mounted at a subpath
@@ -1311,54 +1311,37 @@ export function createGateway(config: GatewayConfig): GatewayInstance {
1311
1311
  );
1312
1312
  }
1313
1313
 
1314
- // 2. Setup control panel proxies (BEFORE server.listen)
1315
- // Control panel runs at / internally with APIs at /api and UI at /
1316
- // We proxy both /api and /cpanel to the control panel
1317
-
1318
- // Proxy control panel APIs at {cpPath}/api
1319
- // IMPORTANT: Express strips the API path prefix before passing to middleware,
1320
- // so we need pathRewrite to reconstruct the full path for the control panel
1321
- const cpApiPath = `${cpPath}/api`;
1322
- const apiProxy = createProxyMiddleware({
1314
+ // 2. Create HTTP server (but don't listen yet - need server object for WS)
1315
+ server = createServer(app);
1316
+
1317
+ // 3. Setup mounted apps WITH QwickApps Server API proxy injected at the right position
1318
+ // QwickApps Server APIs (/qapi/*) need to be registered BEFORE root path proxy
1319
+ // to prevent being caught by frontend catch-all routing
1320
+ const qwickAppsApiProxy = createProxyMiddleware({
1323
1321
  target: `http://localhost:${cpPort}`,
1324
1322
  changeOrigin: true,
1325
- pathRewrite: (path) => `/api${path}`, // Express removed {cpPath}/api, add back just /api (control panel APIs are at /api/*)
1326
1323
  on: {
1327
1324
  proxyReq: (proxyReq, req) => {
1328
- // Set X-Forwarded-Prefix so control panel knows its public mount path
1329
- proxyReq.setHeader('X-Forwarded-Prefix', cpPath);
1330
- logger.debug(`[API Proxy] Forwarding ${req.method} ${req.url} to control panel`);
1325
+ logger.debug(`[QwickApps API] Forwarding ${req.method} ${req.url} to QwickApps Server`);
1331
1326
  },
1332
1327
  proxyRes: (proxyRes, req) => {
1333
- logger.debug(`[API Proxy] Response for ${req.url}: ${proxyRes.statusCode} ${proxyRes.headers['content-type']}`);
1328
+ logger.debug(`[QwickApps API] Response for ${req.url}: ${proxyRes.statusCode}`);
1334
1329
  },
1335
1330
  error: (err: Error, req: IncomingMessage, res: ServerResponse | Socket) => {
1336
- logger.error(`[API Proxy] Error for ${req.url}`, { error: err.message });
1331
+ logger.error(`[QwickApps API] Error for ${req.url}`, { error: err.message });
1337
1332
  if (res && 'writeHead' in res && !res.headersSent) {
1338
1333
  res.writeHead(503, { 'Content-Type': 'application/json' });
1339
1334
  res.end(JSON.stringify({
1340
1335
  error: 'Service Unavailable',
1341
- message: 'The control panel API is currently unavailable.',
1336
+ message: 'QwickApps Server APIs are currently unavailable.',
1342
1337
  details: nodeEnv === 'development' ? err.message : undefined,
1343
1338
  }));
1344
1339
  }
1345
1340
  },
1346
1341
  },
1347
1342
  });
1348
- app.use(cpApiPath, apiProxy);
1349
- logger.debug(`Setting up proxy: ${cpApiPath} -> http://localhost:${cpPort}/api`);
1350
- mountedApps.push({ path: cpApiPath, type: 'proxy', target: `http://localhost:${cpPort}` });
1351
- }
1352
-
1353
- // 3. Create HTTP server (but don't listen yet - need server object for WS)
1354
- server = createServer(app);
1355
1343
 
1356
- // 4. Setup control panel UI proxy (needs server for WebSocket handling)
1357
- if (cpEnabled) {
1358
- const cpPort = parseInt(process.env.CPANEL_PORT || String(gatewayPort + 1), 10);
1359
-
1360
- // Proxy /cpanel to control panel UI
1361
- // Express strips cpPath prefix, so we need custom proxy to add it back
1344
+ // Create control panel UI proxy (will be registered before root proxy)
1362
1345
  const cpUiProxy = createProxyMiddleware({
1363
1346
  target: `http://localhost:${cpPort}`,
1364
1347
  changeOrigin: true,
@@ -1378,23 +1361,46 @@ export function createGateway(config: GatewayConfig): GatewayInstance {
1378
1361
  },
1379
1362
  },
1380
1363
  });
1381
- app.use(cpPath, cpUiProxy);
1382
1364
 
1383
- // WebSocket upgrade handling for control panel
1384
- server!.on('upgrade', (req: IncomingMessage, socket: Duplex, head: Buffer) => {
1385
- if (req.url?.startsWith(cpPath)) {
1386
- cpUiProxy.upgrade?.(req, socket as Socket, head);
1365
+ const apps = config.apps || [];
1366
+ for (const appConfig of apps) {
1367
+ // IMPORTANT: Insert QwickApps Server and Control Panel proxies BEFORE root path proxy
1368
+ // This ensures /qapi/* and /cpanel requests don't get caught by frontend catch-all routing
1369
+ if (appConfig.path === '/') {
1370
+ // 1. Register QwickApps Server API proxy at /qapi/* BEFORE the root proxy
1371
+ // These are framework APIs: SuperTokens auth, health checks, postgres, cache, etc.
1372
+ // Payload CMS uses natural Next.js /api/* path
1373
+ app.use((req, res, next) => {
1374
+ if (req.path.startsWith('/qapi/')) {
1375
+ return qwickAppsApiProxy(req, res, next);
1376
+ }
1377
+ next();
1378
+ });
1379
+ logger.debug(`Setting up proxy: /qapi/* -> http://localhost:${cpPort} (QwickApps Server APIs)`);
1380
+ mountedApps.push({ path: '/qapi', type: 'proxy', target: `http://localhost:${cpPort}` });
1381
+
1382
+ // 2. Register Control Panel UI proxy BEFORE the root proxy
1383
+ app.use(cpPath, cpUiProxy);
1384
+ mountedApps.push({ path: cpPath, type: 'proxy', target: `http://localhost:${cpPort}` });
1385
+
1386
+ // 3. Setup WebSocket upgrade handling for control panel UI
1387
+ server!.on('upgrade', (req: IncomingMessage, socket: Duplex, head: Buffer) => {
1388
+ if (req.url?.startsWith(cpPath)) {
1389
+ cpUiProxy.upgrade?.(req, socket as Socket, head);
1390
+ }
1391
+ });
1387
1392
  }
1388
- });
1389
1393
 
1390
- mountedApps.push({ path: cpPath, type: 'proxy', target: `http://localhost:${cpPort}` });
1394
+ // Now register the app itself
1395
+ if (appConfig.source.type === 'proxy') {
1396
+ setupProxyApp(appConfig, server);
1397
+ } else {
1398
+ setupStaticApp(appConfig);
1399
+ }
1400
+ }
1391
1401
  }
1392
1402
 
1393
- // 5. Setup mounted apps (proxy and static)
1394
- const apps = config.apps || [];
1395
-
1396
- // If plugins need maintenance, add root-level maintenance page
1397
- // but keep /cpanel and /diagnostics accessible
1403
+ // 6. Add maintenance page if needed (after all proxies are set up)
1398
1404
  if (cpEnabled) {
1399
1405
  const pluginsNeedingMaintenance = controlPanelInstance!.getPluginRegistry().getPluginsNeedingMaintenance();
1400
1406
  if (pluginsNeedingMaintenance.length > 0) {
@@ -1429,16 +1435,7 @@ export function createGateway(config: GatewayConfig): GatewayInstance {
1429
1435
  }
1430
1436
  }
1431
1437
 
1432
- // Setup additional apps
1433
- for (const appConfig of apps) {
1434
- if (appConfig.source.type === 'proxy') {
1435
- setupProxyApp(appConfig, server);
1436
- } else {
1437
- setupStaticApp(appConfig);
1438
- }
1439
- }
1440
-
1441
- // 6. Setup frontend app at root
1438
+ // 7. Setup frontend app at root
1442
1439
  setupFrontendApp();
1443
1440
 
1444
1441
  // 7. Start listening (LAST STEP - after all middleware is registered)
@@ -24,7 +24,7 @@ interface ApiKey {
24
24
  usageCount: number;
25
25
  }
26
26
 
27
- export function ApiKeysManagementPage({ apiPrefix = '/api/api-keys' }: ApiKeysManagementPageProps) {
27
+ export function ApiKeysManagementPage({ apiPrefix = '/qapi/api-keys' }: ApiKeysManagementPageProps) {
28
28
  const [activeTab, setActiveTab] = useState<'all' | 'active' | 'expired' | 'config'>('all');
29
29
  const [keys, setKeys] = useState<ApiKey[]>([]);
30
30
  const [loading, setLoading] = useState(true);
@@ -10,7 +10,7 @@ export interface ApiKeysStatusWidgetProps {
10
10
  apiPrefix?: string;
11
11
  }
12
12
 
13
- export function ApiKeysStatusWidget({ apiPrefix = '/api/api-keys' }: ApiKeysStatusWidgetProps) {
13
+ export function ApiKeysStatusWidget({ apiPrefix = '/qapi/api-keys' }: ApiKeysStatusWidgetProps) {
14
14
  const [stats, setStats] = useState<{
15
15
  totalKeys: number;
16
16
  activeKeys: number;
@@ -30,7 +30,7 @@ interface AuthSession {
30
30
  ipAddress?: string;
31
31
  }
32
32
 
33
- export function AuthManagementPage({ apiPrefix = '/api/auth' }: AuthManagementPageProps) {
33
+ export function AuthManagementPage({ apiPrefix = '/qapi/auth' }: AuthManagementPageProps) {
34
34
  const [activeTab, setActiveTab] = useState<'overview' | 'providers' | 'sessions' | 'config'>('overview');
35
35
  const [providers, setProviders] = useState<AuthProvider[]>([]);
36
36
  const [sessions, setSessions] = useState<AuthSession[]>([]);
@@ -10,7 +10,7 @@ export interface AuthStatusWidgetProps {
10
10
  apiPrefix?: string;
11
11
  }
12
12
 
13
- export function AuthStatusWidget({ apiPrefix = '/api/auth' }: AuthStatusWidgetProps) {
13
+ export function AuthStatusWidget({ apiPrefix = '/qapi/auth' }: AuthStatusWidgetProps) {
14
14
  const [stats, setStats] = useState<{
15
15
  totalProviders: number;
16
16
  activeProviders: number;
@@ -47,14 +47,12 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
47
47
  // Store response on request for later use in getUser()
48
48
  (req as SupertokensExtendedRequest)[REQUEST_RES_KEY] = res;
49
49
 
50
- // Skip if already initialized with error
50
+ // Skip if already initialized with error — let auth-checking middleware
51
+ // decide whether to block the request based on authRequired config.
52
+ // Returning 500 here blocks ALL routes (including /auth/config/status)
53
+ // even when authRequired is false.
51
54
  if (initializationError) {
52
- return res.status(500).json({
53
- error: 'Auth Configuration Error',
54
- message:
55
- 'Supertokens is not properly configured. Install supertokens-node package: npm install supertokens-node',
56
- details: initializationError.message,
57
- });
55
+ return next();
58
56
  }
59
57
 
60
58
  // Lazy initialize Supertokens
@@ -163,12 +161,10 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
163
161
  initializationError =
164
162
  error instanceof Error ? error : new Error('Failed to initialize Supertokens');
165
163
  console.error('[SupertokensAdapter] Initialization error:', error);
166
- return res.status(500).json({
167
- error: 'Auth Configuration Error',
168
- message:
169
- 'Supertokens is not properly configured. Install supertokens-node package: npm install supertokens-node',
170
- details: initializationError.message,
171
- });
164
+ // Let the auth-checking middleware decide whether to block this request.
165
+ // Non-auth routes (e.g. /auth/config/status) must remain accessible
166
+ // so the UI can display the configuration error state.
167
+ return next();
172
168
  }
173
169
  }
174
170
 
@@ -73,6 +73,16 @@ export function createAuthPlugin(config: AuthPluginConfig): Plugin {
73
73
  }
74
74
  }
75
75
 
76
+ // Register SuperTokens middleware on router for auth paths
77
+ // Uses config.apiBasePath so Gateway forwards requests to the correct path
78
+ // e.g. SUPERTOKENS_API_BASE_PATH=/qapi/auth → router.use('/qapi/auth', ...)
79
+ const authBasePath = config.apiBasePath ?? '/auth';
80
+ if (Array.isArray(primaryMiddleware)) {
81
+ router.use(authBasePath, ...primaryMiddleware);
82
+ } else {
83
+ router.use(authBasePath, primaryMiddleware);
84
+ }
85
+
76
86
  // Add the auth checking middleware to router (not app)
77
87
  // This ensures it processes requests to /api/* routes
78
88
  router.use(createAuthMiddleware());
@@ -402,6 +402,7 @@ export function createAuthPluginFromEnv(options?: AuthEnvPluginOptions): Plugin
402
402
  excludePaths,
403
403
  authRequired,
404
404
  debug,
405
+ apiBasePath: getEnv('SUPERTOKENS_API_BASE_PATH') ?? getEnv('AUTH_API_BASE_PATH'),
405
406
  onUnauthorized: options?.onUnauthorized,
406
407
  onAuthenticated: options?.onAuthenticated,
407
408
  };
@@ -180,6 +180,8 @@ export interface AuthPluginConfig {
180
180
  excludePaths?: string[];
181
181
  /** Whether auth is required for all routes (default: true) */
182
182
  authRequired?: boolean;
183
+ /** API base path for auth routes registered on the Express router (default: '/auth') */
184
+ apiBasePath?: string;
183
185
  /** Custom unauthorized handler */
184
186
  onUnauthorized?: (req: Request, res: Response) => void;
185
187
  /**
@@ -23,7 +23,7 @@ interface Ban {
23
23
  createdBy?: string;
24
24
  }
25
25
 
26
- export function BansManagementPage({ apiPrefix = '/api/bans' }: BansManagementPageProps) {
26
+ export function BansManagementPage({ apiPrefix = '/qapi/bans' }: BansManagementPageProps) {
27
27
  const [activeTab, setActiveTab] = useState<'all' | 'active' | 'expired' | 'config'>('all');
28
28
  const [bans, setBans] = useState<Ban[]>([]);
29
29
  const [loading, setLoading] = useState(true);
@@ -10,7 +10,7 @@ export interface BansStatusWidgetProps {
10
10
  apiPrefix?: string;
11
11
  }
12
12
 
13
- export function BansStatusWidget({ apiPrefix = '/api/bans' }: BansStatusWidgetProps) {
13
+ export function BansStatusWidget({ apiPrefix = '/qapi/bans' }: BansStatusWidgetProps) {
14
14
  const [stats, setStats] = useState<{
15
15
  totalBans: number;
16
16
  activeBans: number;
@@ -32,7 +32,7 @@ interface CacheInfo {
32
32
  }
33
33
 
34
34
  export const CacheManagementPage: React.FC<CacheManagementPageProps> = ({
35
- apiPrefix = '/api/plugins/cache',
35
+ apiPrefix = '/qapi/plugins/cache',
36
36
  }) => {
37
37
  const [keys, setKeys] = useState<CacheKey[]>([]);
38
38
  const [info, setInfo] = useState<CacheInfo | null>(null);
@@ -26,7 +26,7 @@ interface CacheStats {
26
26
  }
27
27
 
28
28
  export const CacheStatusWidget: React.FC<CacheStatusWidgetProps> = ({
29
- apiPrefix = '/api/plugins/cache',
29
+ apiPrefix = '/qapi/plugins/cache',
30
30
  }) => {
31
31
  const [stats, setStats] = useState<CacheStats | null>(null);
32
32
  const [loading, setLoading] = useState(true);
@@ -23,7 +23,7 @@ interface Device {
23
23
  lastActiveAt?: string;
24
24
  }
25
25
 
26
- export function DevicesManagementPage({ apiPrefix = '/api/devices' }: DevicesManagementPageProps) {
26
+ export function DevicesManagementPage({ apiPrefix = '/qapi/devices' }: DevicesManagementPageProps) {
27
27
  const [activeTab, setActiveTab] = useState<'all' | 'active' | 'pending' | 'config'>('all');
28
28
  const [devices, setDevices] = useState<Device[]>([]);
29
29
  const [loading, setLoading] = useState(true);
@@ -10,7 +10,7 @@ export interface DevicesStatusWidgetProps {
10
10
  apiPrefix?: string;
11
11
  }
12
12
 
13
- export function DevicesStatusWidget({ apiPrefix = '/api/devices' }: DevicesStatusWidgetProps) {
13
+ export function DevicesStatusWidget({ apiPrefix = '/qapi/devices' }: DevicesStatusWidgetProps) {
14
14
  const [stats, setStats] = useState<{
15
15
  totalDevices: number;
16
16
  activeDevices: number;
@@ -38,7 +38,7 @@ interface DiagnosticsReport {
38
38
  }
39
39
 
40
40
  export const DiagnosticsManagementPage: React.FC<DiagnosticsManagementPageProps> = ({
41
- apiPrefix = '/api/plugins/diagnostics',
41
+ apiPrefix = '/qapi/plugins/diagnostics',
42
42
  }) => {
43
43
  const [report, setReport] = useState<DiagnosticsReport | null>(null);
44
44
  const [loading, setLoading] = useState(true);
@@ -26,7 +26,7 @@ interface DiagnosticsStats {
26
26
  }
27
27
 
28
28
  export const DiagnosticsStatusWidget: React.FC<DiagnosticsStatusWidgetProps> = ({
29
- apiPrefix = '/api/plugins/diagnostics',
29
+ apiPrefix = '/qapi/plugins/diagnostics',
30
30
  }) => {
31
31
  const [stats, setStats] = useState<DiagnosticsStats | null>(null);
32
32
  const [loading, setLoading] = useState(true);
@@ -22,7 +22,7 @@ interface Entitlement {
22
22
  grantedBy?: string;
23
23
  }
24
24
 
25
- export function EntitlementsManagementPage({ apiPrefix = '/api/entitlements' }: EntitlementsManagementPageProps) {
25
+ export function EntitlementsManagementPage({ apiPrefix = '/qapi/entitlements' }: EntitlementsManagementPageProps) {
26
26
  const [activeTab, setActiveTab] = useState<'all' | 'active' | 'expired' | 'config'>('all');
27
27
  const [entitlements, setEntitlements] = useState<Entitlement[]>([]);
28
28
  const [loading, setLoading] = useState(true);
@@ -10,7 +10,7 @@ export interface EntitlementsStatusWidgetProps {
10
10
  apiPrefix?: string;
11
11
  }
12
12
 
13
- export function EntitlementsStatusWidget({ apiPrefix = '/api/entitlements' }: EntitlementsStatusWidgetProps) {
13
+ export function EntitlementsStatusWidget({ apiPrefix = '/qapi/entitlements' }: EntitlementsStatusWidgetProps) {
14
14
  const [stats, setStats] = useState<{
15
15
  totalEntitlements: number;
16
16
  activeEntitlements: number;
@@ -33,7 +33,7 @@ interface HealthSummary {
33
33
  }
34
34
 
35
35
  export const HealthManagementPage: React.FC<HealthManagementPageProps> = ({
36
- apiPrefix = '/api/plugins/health',
36
+ apiPrefix = '/qapi/plugins/health',
37
37
  }) => {
38
38
  const [summary, setSummary] = useState<HealthSummary | null>(null);
39
39
  const [selectedCheck, setSelectedCheck] = useState<HealthCheckResult | null>(null);
@@ -33,7 +33,7 @@ interface HealthSummary {
33
33
  }
34
34
 
35
35
  export const HealthStatusWidget: React.FC<HealthStatusWidgetProps> = ({
36
- apiPrefix = '/api/plugins/health',
36
+ apiPrefix = '/qapi/plugins/health',
37
37
  }) => {
38
38
  const [summary, setSummary] = useState<HealthSummary | null>(null);
39
39
  const [loading, setLoading] = useState(true);
@@ -42,7 +42,7 @@ interface LogStats {
42
42
  }
43
43
 
44
44
  export const LogsManagementPage: React.FC<LogsManagementPageProps> = ({
45
- apiPrefix = '/api/plugins/logs',
45
+ apiPrefix = '/qapi/plugins/logs',
46
46
  }) => {
47
47
  const [logs, setLogs] = useState<LogEntry[]>([]);
48
48
  const [sources, setSources] = useState<LogSource[]>([]);
@@ -28,7 +28,7 @@ interface LogStats {
28
28
  }
29
29
 
30
30
  export const LogsStatusWidget: React.FC<LogsStatusWidgetProps> = ({
31
- apiPrefix = '/api/plugins/logs',
31
+ apiPrefix = '/qapi/plugins/logs',
32
32
  }) => {
33
33
  const [stats, setStats] = useState<LogStats | null>(null);
34
34
  const [loading, setLoading] = useState(true);
@@ -30,7 +30,7 @@ interface MaintenanceStatus {
30
30
  }
31
31
 
32
32
  export const MaintenanceManagementPage: React.FC<MaintenanceManagementPageProps> = ({
33
- apiPrefix = '/api/plugins/maintenance',
33
+ apiPrefix = '/qapi/plugins/maintenance',
34
34
  }) => {
35
35
  const [status, setStatus] = useState<MaintenanceStatus | null>(null);
36
36
  const [loading, setLoading] = useState(true);
@@ -26,7 +26,7 @@ interface MaintenanceStatus {
26
26
  }
27
27
 
28
28
  export const MaintenanceStatusWidget: React.FC<MaintenanceStatusWidgetProps> = ({
29
- apiPrefix = '/api/plugins/maintenance',
29
+ apiPrefix = '/qapi/plugins/maintenance',
30
30
  }) => {
31
31
  const [status, setStatus] = useState<MaintenanceStatus | null>(null);
32
32
  const [loading, setLoading] = useState(true);
@@ -19,7 +19,7 @@ export interface SeedManagementPageProps {
19
19
  type Tab = 'list' | 'execute' | 'history';
20
20
 
21
21
  export const SeedManagementPage: React.FC<SeedManagementPageProps> = ({
22
- apiPrefix = '/api/plugins/maintenance',
22
+ apiPrefix = '/qapi/plugins/maintenance',
23
23
  }) => {
24
24
  const [activeTab, setActiveTab] = useState<Tab>('list');
25
25
  const [selectedSeed, setSelectedSeed] = useState<string | null>(null);
@@ -45,8 +45,8 @@ const MAX_OUTPUT_SIZE = 100 * 1024;
45
45
  * @returns Resolved absolute path if valid, null if invalid
46
46
  */
47
47
  export function validateScriptPath(scriptPath: string, scriptsPath: string): string | null {
48
- // Only allow .mjs files
49
- if (!scriptPath.endsWith('.mjs')) {
48
+ // Only allow .mjs and .ts files
49
+ if (!scriptPath.endsWith('.mjs') && !scriptPath.endsWith('.ts')) {
50
50
  return null;
51
51
  }
52
52
 
@@ -106,11 +106,12 @@ export class SeedExecutor {
106
106
  this.outputSize = 0;
107
107
 
108
108
  return new Promise((resolvePromise, rejectPromise) => {
109
- // Determine if we need tsx (for .ts/.mts files that import TS modules)
110
- // Use tsx for .mjs files too since they might import from TS source
111
- const needsTsx = scriptPath.match(/\.(mjs|ts|mts)$/);
109
+ // .ts/.mts files require tsx for TypeScript compilation.
110
+ // .mjs files are native ESM and run directly with node — tsx is not
111
+ // available in the system PATH in Docker production builds.
112
+ const needsTsx = scriptPath.match(/\.(ts|mts)$/);
112
113
  const execCommand = needsTsx ? 'tsx' : process.execPath;
113
- const execArgs = needsTsx ? [scriptPath] : [scriptPath];
114
+ const execArgs = [scriptPath];
114
115
 
115
116
  // Spawn process with TypeScript support if needed
116
117
  this.child = spawn(execCommand, execArgs, {