@symbo.ls/sdk 3.1.2 → 3.2.6
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.
- package/README.md +143 -2
- package/dist/cjs/config/environment.js +98 -30
- package/dist/cjs/index.js +144 -24
- package/dist/cjs/services/AdminService.js +351 -0
- package/dist/cjs/services/AuthService.js +738 -305
- package/dist/cjs/services/BaseService.js +158 -6
- package/dist/cjs/services/BranchService.js +484 -0
- package/dist/cjs/services/CollabService.js +743 -0
- package/dist/cjs/services/DnsService.js +340 -0
- package/dist/cjs/services/FeatureFlagService.js +175 -0
- package/dist/cjs/services/FileService.js +201 -0
- package/dist/cjs/services/IntegrationService.js +538 -0
- package/dist/cjs/services/MetricsService.js +62 -0
- package/dist/cjs/services/PaymentService.js +271 -0
- package/dist/cjs/services/PlanService.js +426 -0
- package/dist/cjs/services/ProjectService.js +1207 -0
- package/dist/cjs/services/PullRequestService.js +503 -0
- package/dist/cjs/services/ScreenshotService.js +304 -0
- package/dist/cjs/services/SubscriptionService.js +396 -0
- package/dist/cjs/services/TrackingService.js +661 -0
- package/dist/cjs/services/WaitlistService.js +148 -0
- package/dist/cjs/services/index.js +64 -16
- package/dist/cjs/state/RootStateManager.js +65 -0
- package/dist/cjs/state/rootEventBus.js +74 -0
- package/dist/cjs/utils/CollabClient.js +223 -0
- package/dist/cjs/utils/TokenManager.js +78 -30
- package/dist/cjs/utils/changePreprocessor.js +199 -0
- package/dist/cjs/utils/jsonDiff.js +145 -0
- package/dist/cjs/utils/ordering.js +309 -0
- package/dist/cjs/utils/services.js +301 -103
- package/dist/cjs/utils/validation.js +0 -3
- package/dist/esm/config/environment.js +98 -30
- package/dist/esm/index.js +49505 -8718
- package/dist/esm/services/AdminService.js +1132 -0
- package/dist/esm/services/AuthService.js +1493 -386
- package/dist/esm/services/BaseService.js +757 -6
- package/dist/esm/services/BranchService.js +1265 -0
- package/dist/esm/services/CollabService.js +26895 -0
- package/dist/esm/services/DnsService.js +1121 -0
- package/dist/esm/services/FeatureFlagService.js +956 -0
- package/dist/esm/services/FileService.js +982 -0
- package/dist/esm/services/IntegrationService.js +1319 -0
- package/dist/esm/services/MetricsService.js +843 -0
- package/dist/esm/services/PaymentService.js +1052 -0
- package/dist/esm/services/PlanService.js +1207 -0
- package/dist/esm/services/ProjectService.js +2526 -0
- package/dist/esm/services/PullRequestService.js +1284 -0
- package/dist/esm/services/ScreenshotService.js +1085 -0
- package/dist/esm/services/SubscriptionService.js +1177 -0
- package/dist/esm/services/TrackingService.js +18454 -0
- package/dist/esm/services/WaitlistService.js +929 -0
- package/dist/esm/services/index.js +49062 -8569
- package/dist/esm/state/RootStateManager.js +90 -0
- package/dist/esm/state/rootEventBus.js +56 -0
- package/dist/esm/utils/CollabClient.js +18889 -0
- package/dist/esm/utils/TokenManager.js +78 -30
- package/dist/esm/utils/changePreprocessor.js +542 -0
- package/dist/esm/utils/jsonDiff.js +7011 -0
- package/dist/esm/utils/ordering.js +291 -0
- package/dist/esm/utils/services.js +301 -103
- package/dist/esm/utils/validation.js +116 -50
- package/dist/node/config/environment.js +98 -30
- package/dist/node/index.js +175 -32
- package/dist/node/services/AdminService.js +332 -0
- package/dist/node/services/AuthService.js +742 -310
- package/dist/node/services/BaseService.js +148 -6
- package/dist/node/services/BranchService.js +465 -0
- package/dist/node/services/CollabService.js +724 -0
- package/dist/node/services/DnsService.js +321 -0
- package/dist/node/services/FeatureFlagService.js +156 -0
- package/dist/node/services/FileService.js +182 -0
- package/dist/node/services/IntegrationService.js +519 -0
- package/dist/node/services/MetricsService.js +43 -0
- package/dist/node/services/PaymentService.js +252 -0
- package/dist/node/services/PlanService.js +407 -0
- package/dist/node/services/ProjectService.js +1188 -0
- package/dist/node/services/PullRequestService.js +484 -0
- package/dist/node/services/ScreenshotService.js +285 -0
- package/dist/node/services/SubscriptionService.js +377 -0
- package/dist/node/services/TrackingService.js +632 -0
- package/dist/node/services/WaitlistService.js +129 -0
- package/dist/node/services/index.js +64 -16
- package/dist/node/state/RootStateManager.js +36 -0
- package/dist/node/state/rootEventBus.js +55 -0
- package/dist/node/utils/CollabClient.js +194 -0
- package/dist/node/utils/TokenManager.js +78 -30
- package/dist/node/utils/changePreprocessor.js +180 -0
- package/dist/node/utils/jsonDiff.js +116 -0
- package/dist/node/utils/ordering.js +290 -0
- package/dist/node/utils/services.js +301 -103
- package/dist/node/utils/validation.js +0 -3
- package/package.json +39 -21
- package/src/config/environment.js +99 -28
- package/src/index.js +181 -36
- package/src/services/AdminService.js +374 -0
- package/src/services/AuthService.js +874 -328
- package/src/services/BaseService.js +166 -6
- package/src/services/BranchService.js +536 -0
- package/src/services/CollabService.js +900 -0
- package/src/services/DnsService.js +366 -0
- package/src/services/FeatureFlagService.js +174 -0
- package/src/services/FileService.js +213 -0
- package/src/services/IntegrationService.js +548 -0
- package/src/services/MetricsService.js +40 -0
- package/src/services/PaymentService.js +287 -0
- package/src/services/PlanService.js +468 -0
- package/src/services/ProjectService.js +1366 -0
- package/src/services/PullRequestService.js +537 -0
- package/src/services/ScreenshotService.js +258 -0
- package/src/services/SubscriptionService.js +425 -0
- package/src/services/TrackingService.js +853 -0
- package/src/services/WaitlistService.js +130 -0
- package/src/services/index.js +80 -13
- package/src/services/tests/BranchService/createBranch.test.js +153 -0
- package/src/services/tests/BranchService/deleteBranch.test.js +173 -0
- package/src/services/tests/BranchService/getBranchChanges.test.js +146 -0
- package/src/services/tests/BranchService/listBranches.test.js +87 -0
- package/src/services/tests/BranchService/mergeBranch.test.js +210 -0
- package/src/services/tests/BranchService/publishVersion.test.js +183 -0
- package/src/services/tests/BranchService/renameBranch.test.js +240 -0
- package/src/services/tests/BranchService/resetBranch.test.js +152 -0
- package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +67 -0
- package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +75 -0
- package/src/services/tests/FileService/createFileFormData.test.js +74 -0
- package/src/services/tests/FileService/getFileUrl.test.js +69 -0
- package/src/services/tests/FileService/updateProjectIcon.test.js +109 -0
- package/src/services/tests/FileService/uploadDocument.test.js +36 -0
- package/src/services/tests/FileService/uploadFile.test.js +78 -0
- package/src/services/tests/FileService/uploadFileWithValidation.test.js +114 -0
- package/src/services/tests/FileService/uploadImage.test.js +36 -0
- package/src/services/tests/FileService/uploadMultipleFiles.test.js +111 -0
- package/src/services/tests/FileService/validateFile.test.js +63 -0
- package/src/services/tests/PlanService/createPlan.test.js +104 -0
- package/src/services/tests/PlanService/createPlanWithValidation.test.js +523 -0
- package/src/services/tests/PlanService/deletePlan.test.js +92 -0
- package/src/services/tests/PlanService/getActivePlans.test.js +123 -0
- package/src/services/tests/PlanService/getAdminPlans.test.js +84 -0
- package/src/services/tests/PlanService/getPlan.test.js +50 -0
- package/src/services/tests/PlanService/getPlanByKey.test.js +109 -0
- package/src/services/tests/PlanService/getPlanWithValidation.test.js +85 -0
- package/src/services/tests/PlanService/getPlans.test.js +53 -0
- package/src/services/tests/PlanService/getPlansByPriceRange.test.js +109 -0
- package/src/services/tests/PlanService/getPlansWithValidation.test.js +48 -0
- package/src/services/tests/PlanService/initializePlans.test.js +75 -0
- package/src/services/tests/PlanService/updatePlan.test.js +111 -0
- package/src/services/tests/PlanService/updatePlanWithValidation.test.js +556 -0
- package/src/state/RootStateManager.js +76 -0
- package/src/state/rootEventBus.js +67 -0
- package/src/utils/CollabClient.js +248 -0
- package/src/utils/TokenManager.js +88 -33
- package/src/utils/changePreprocessor.js +239 -0
- package/src/utils/jsonDiff.js +144 -0
- package/src/utils/ordering.js +271 -0
- package/src/utils/services.js +326 -107
- package/src/utils/validation.js +0 -3
- package/dist/cjs/services/AIService.js +0 -155
- package/dist/cjs/services/BasedService.js +0 -1185
- package/dist/cjs/services/CoreService.js +0 -1751
- package/dist/cjs/services/SocketIOService.js +0 -307
- package/dist/cjs/services/SocketService.js +0 -161
- package/dist/cjs/services/SymstoryService.js +0 -571
- package/dist/cjs/utils/basedQuerys.js +0 -181
- package/dist/cjs/utils/symstoryClient.js +0 -259
- package/dist/esm/services/AIService.js +0 -185
- package/dist/esm/services/BasedService.js +0 -5278
- package/dist/esm/services/CoreService.js +0 -2264
- package/dist/esm/services/SocketIOService.js +0 -470
- package/dist/esm/services/SocketService.js +0 -191
- package/dist/esm/services/SymstoryService.js +0 -7041
- package/dist/esm/utils/basedQuerys.js +0 -163
- package/dist/esm/utils/symstoryClient.js +0 -370
- package/dist/node/services/AIService.js +0 -136
- package/dist/node/services/BasedService.js +0 -1156
- package/dist/node/services/CoreService.js +0 -1722
- package/dist/node/services/SocketIOService.js +0 -278
- package/dist/node/services/SocketService.js +0 -142
- package/dist/node/services/SymstoryService.js +0 -542
- package/dist/node/utils/basedQuerys.js +0 -162
- package/dist/node/utils/symstoryClient.js +0 -230
- package/src/services/AIService.js +0 -150
- package/src/services/BasedService.js +0 -1301
- package/src/services/CoreService.js +0 -1943
- package/src/services/SocketIOService.js +0 -334
- package/src/services/SocketService.js +0 -168
- package/src/services/SymstoryService.js +0 -649
- package/src/utils/basedQuerys.js +0 -164
- package/src/utils/symstoryClient.js +0 -252
|
@@ -0,0 +1,1284 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/config/environment.js
|
|
6
|
+
import { isDevelopment } from "@domql/utils";
|
|
7
|
+
var CONFIG = {
|
|
8
|
+
// Common defaults for all environments
|
|
9
|
+
common: {
|
|
10
|
+
// NOTE: Google client id for google auth, need to configure URLs for each environment in Google console
|
|
11
|
+
googleClientId: "686286207466-bvd2fqs31rlm64fgich7rtpnc8ns2tqg.apps.googleusercontent.com",
|
|
12
|
+
// Feature toggles that apply across all environments by default
|
|
13
|
+
features: {
|
|
14
|
+
newUserOnboarding: true,
|
|
15
|
+
betaFeatures: false,
|
|
16
|
+
// Tracking is enabled by default unless overridden per environment
|
|
17
|
+
trackingEnabled: true
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
// Environment-specific configurations
|
|
21
|
+
local: {
|
|
22
|
+
// local
|
|
23
|
+
socketUrl: "http://localhost:8080",
|
|
24
|
+
// For socket api
|
|
25
|
+
apiUrl: "http://localhost:8080",
|
|
26
|
+
// For server api
|
|
27
|
+
basedEnv: "development",
|
|
28
|
+
// For based api
|
|
29
|
+
basedProject: "platform-v2-sm",
|
|
30
|
+
// For based api
|
|
31
|
+
basedOrg: "symbols",
|
|
32
|
+
// For based api
|
|
33
|
+
githubClientId: "Ov23liAFrsR0StbAO6PO",
|
|
34
|
+
// For github api
|
|
35
|
+
grafanaUrl: "",
|
|
36
|
+
// For grafana tracing
|
|
37
|
+
grafanaAppName: "Symbols Localhost",
|
|
38
|
+
// Environment-specific feature toggles (override common)
|
|
39
|
+
features: {
|
|
40
|
+
// Disable tracking by default on localhost/dev machines
|
|
41
|
+
trackingEnabled: false,
|
|
42
|
+
// Enable beta features in local dev
|
|
43
|
+
betaFeatures: true,
|
|
44
|
+
// Preserve common defaults explicitly for local
|
|
45
|
+
newUserOnboarding: true
|
|
46
|
+
},
|
|
47
|
+
typesenseCollectionName: "docs",
|
|
48
|
+
typesenseApiKey: "vZya3L2zpq8L6iI5WWMUZJZABvT63VDb",
|
|
49
|
+
typesenseHost: "localhost",
|
|
50
|
+
typesensePort: "8108",
|
|
51
|
+
typesenseProtocol: "http"
|
|
52
|
+
},
|
|
53
|
+
development: {
|
|
54
|
+
socketUrl: "https://dev.api.symbols.app",
|
|
55
|
+
apiUrl: "https://dev.api.symbols.app",
|
|
56
|
+
githubClientId: "Ov23liHxyWFBxS8f1gnF",
|
|
57
|
+
grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/7a3ba473cee2025c68513667024316b8",
|
|
58
|
+
// For grafana tracing
|
|
59
|
+
grafanaAppName: "Symbols Dev",
|
|
60
|
+
typesenseCollectionName: "docs",
|
|
61
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
62
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
63
|
+
typesensePort: "443",
|
|
64
|
+
typesenseProtocol: "https"
|
|
65
|
+
},
|
|
66
|
+
testing: {
|
|
67
|
+
socketUrl: "https://test.api.symbols.app",
|
|
68
|
+
apiUrl: "https://test.api.symbols.app",
|
|
69
|
+
basedEnv: "testing",
|
|
70
|
+
basedProject: "platform-v2-sm",
|
|
71
|
+
basedOrg: "symbols",
|
|
72
|
+
githubClientId: "Ov23liHxyWFBxS8f1gnF",
|
|
73
|
+
grafanaUrl: "",
|
|
74
|
+
// For grafana tracing
|
|
75
|
+
grafanaAppName: "Symbols Test",
|
|
76
|
+
typesenseCollectionName: "docs",
|
|
77
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
78
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
79
|
+
typesensePort: "443",
|
|
80
|
+
typesenseProtocol: "https"
|
|
81
|
+
},
|
|
82
|
+
upcoming: {
|
|
83
|
+
socketUrl: "https://upcoming.api.symbols.app",
|
|
84
|
+
apiUrl: "https://upcoming.api.symbols.app",
|
|
85
|
+
githubClientId: "Ov23liWF7NvdZ056RV5J",
|
|
86
|
+
grafanaUrl: "",
|
|
87
|
+
// For grafana tracing
|
|
88
|
+
grafanaAppName: "Symbols Upcoming",
|
|
89
|
+
typesenseCollectionName: "docs",
|
|
90
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
91
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
92
|
+
typesensePort: "443",
|
|
93
|
+
typesenseProtocol: "https"
|
|
94
|
+
},
|
|
95
|
+
staging: {
|
|
96
|
+
socketUrl: "https://staging.api.symbols.app",
|
|
97
|
+
apiUrl: "https://staging.api.symbols.app",
|
|
98
|
+
basedEnv: "staging",
|
|
99
|
+
basedProject: "platform-v2-sm",
|
|
100
|
+
basedOrg: "symbols",
|
|
101
|
+
githubClientId: "Ov23ligwZDQVD0VfuWNa",
|
|
102
|
+
grafanaUrl: "",
|
|
103
|
+
// For grafana tracing
|
|
104
|
+
grafanaAppName: "Symbols Staging",
|
|
105
|
+
typesenseCollectionName: "docs",
|
|
106
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
107
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
108
|
+
typesensePort: "443",
|
|
109
|
+
typesenseProtocol: "https"
|
|
110
|
+
},
|
|
111
|
+
preview: {
|
|
112
|
+
socketUrl: "https://api.symbols.app",
|
|
113
|
+
apiUrl: "https://api.symbols.app",
|
|
114
|
+
basedEnv: "production",
|
|
115
|
+
basedProject: "platform-v2-sm",
|
|
116
|
+
basedOrg: "symbols",
|
|
117
|
+
githubClientId: "Ov23liFAlOEIXtX3dBtR",
|
|
118
|
+
grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/5c1089f3c3eea4ec5658e05c3f53baae",
|
|
119
|
+
// For grafana tracing
|
|
120
|
+
grafanaAppName: "Symbols Preview",
|
|
121
|
+
typesenseCollectionName: "docs",
|
|
122
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
123
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
124
|
+
typesensePort: "443",
|
|
125
|
+
typesenseProtocol: "https"
|
|
126
|
+
},
|
|
127
|
+
production: {
|
|
128
|
+
socketUrl: "https://api.symbols.app",
|
|
129
|
+
apiUrl: "https://api.symbols.app",
|
|
130
|
+
basedEnv: "production",
|
|
131
|
+
basedProject: "platform-v2-sm",
|
|
132
|
+
basedOrg: "symbols",
|
|
133
|
+
githubClientId: "Ov23liFAlOEIXtX3dBtR",
|
|
134
|
+
grafanaUrl: "https://faro-collector-prod-us-east-0.grafana.net/collect/5c1089f3c3eea4ec5658e05c3f53baae",
|
|
135
|
+
// For grafana tracing
|
|
136
|
+
grafanaAppName: "Symbols",
|
|
137
|
+
typesenseCollectionName: "docs",
|
|
138
|
+
typesenseApiKey: "awmcVpbWqZi9IUgmvslp1C5LKDU8tMjA",
|
|
139
|
+
typesenseHost: "tl2qpnwxev4cjm36p-1.a1.typesense.net",
|
|
140
|
+
typesensePort: "443",
|
|
141
|
+
typesenseProtocol: "https"
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var getEnvironment = () => {
|
|
145
|
+
const env = process.env.SYMBOLS_APP_ENV || process.env.NODE_ENV;
|
|
146
|
+
if (!CONFIG[env]) {
|
|
147
|
+
throw new Error(`Unknown environment "${env}"`);
|
|
148
|
+
}
|
|
149
|
+
return env;
|
|
150
|
+
};
|
|
151
|
+
var getConfig = () => {
|
|
152
|
+
try {
|
|
153
|
+
const env = getEnvironment();
|
|
154
|
+
const envConfig = { ...CONFIG.common, ...CONFIG[env] };
|
|
155
|
+
const finalConfig = {
|
|
156
|
+
...envConfig,
|
|
157
|
+
// Deep-merge feature flags so env-specific overrides don't drop common defaults
|
|
158
|
+
features: {
|
|
159
|
+
...CONFIG.common.features || {},
|
|
160
|
+
...CONFIG[env] && CONFIG[env].features || {}
|
|
161
|
+
},
|
|
162
|
+
socketUrl: process.env.SYMBOLS_APP_SOCKET_URL || envConfig.socketUrl,
|
|
163
|
+
apiUrl: process.env.SYMBOLS_APP_API_URL || envConfig.apiUrl,
|
|
164
|
+
basedEnv: process.env.SYMBOLS_APP_BASED_ENV || envConfig.basedEnv,
|
|
165
|
+
basedProject: process.env.SYMBOLS_APP_BASED_PROJECT || envConfig.basedProject,
|
|
166
|
+
basedOrg: process.env.SYMBOLS_APP_BASED_ORG || envConfig.basedOrg,
|
|
167
|
+
githubClientId: process.env.SYMBOLS_APP_GITHUB_CLIENT_ID || envConfig.githubClientId,
|
|
168
|
+
grafanaUrl: process.env.SYMBOLS_APP_GRAFANA_URL || envConfig.grafanaUrl,
|
|
169
|
+
typesenseCollectionName: process.env.TYPESENSE_COLLECTION_NAME || envConfig.typesenseCollectionName,
|
|
170
|
+
typesenseApiKey: process.env.TYPESENSE_API_KEY || envConfig.typesenseApiKey,
|
|
171
|
+
typesenseHost: process.env.TYPESENSE_HOST || envConfig.typesenseHost,
|
|
172
|
+
typesensePort: process.env.TYPESENSE_PORT || envConfig.typesensePort,
|
|
173
|
+
typesenseProtocol: process.env.TYPESENSE_PROTOCOL || envConfig.typesenseProtocol,
|
|
174
|
+
isDevelopment: isDevelopment(env),
|
|
175
|
+
isTesting: env === "testing",
|
|
176
|
+
isStaging: env === "staging",
|
|
177
|
+
isPreview: env === "preview",
|
|
178
|
+
isProduction: env === "production"
|
|
179
|
+
// Store all environment variables for potential future use
|
|
180
|
+
};
|
|
181
|
+
const requiredFields = [
|
|
182
|
+
"socketUrl",
|
|
183
|
+
"apiUrl",
|
|
184
|
+
"githubClientId",
|
|
185
|
+
"googleClientId"
|
|
186
|
+
];
|
|
187
|
+
const missingFields = requiredFields.filter((field) => !finalConfig[field]);
|
|
188
|
+
if (missingFields.length > 0) {
|
|
189
|
+
console.error(
|
|
190
|
+
`Missing required configuration: ${missingFields.join(", ")}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (finalConfig.isDevelopment) {
|
|
194
|
+
console.warn(
|
|
195
|
+
"environment in SDK:",
|
|
196
|
+
env || process.env.NODE_ENV || process.env.NODE_ENV
|
|
197
|
+
);
|
|
198
|
+
console.log(finalConfig);
|
|
199
|
+
} else if (global.window) {
|
|
200
|
+
global.window.finalConfig = finalConfig;
|
|
201
|
+
}
|
|
202
|
+
return finalConfig;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error("Failed to load environment configuration:", error);
|
|
205
|
+
return {
|
|
206
|
+
...CONFIG.development
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
var environment_default = getConfig();
|
|
211
|
+
|
|
212
|
+
// src/utils/TokenManager.js
|
|
213
|
+
var TokenManager = class {
|
|
214
|
+
constructor(options = {}) {
|
|
215
|
+
/**
|
|
216
|
+
* Memory storage fallback for server-side rendering
|
|
217
|
+
*/
|
|
218
|
+
__publicField(this, "_memoryStorage", {
|
|
219
|
+
_data: {},
|
|
220
|
+
getItem: (key) => this._memoryStorage._data[key] || null,
|
|
221
|
+
setItem: (key, value) => {
|
|
222
|
+
this._memoryStorage._data[key] = value;
|
|
223
|
+
},
|
|
224
|
+
removeItem: (key) => {
|
|
225
|
+
delete this._memoryStorage._data[key];
|
|
226
|
+
},
|
|
227
|
+
clear: () => {
|
|
228
|
+
this._memoryStorage._data = {};
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
this.config = {
|
|
232
|
+
storagePrefix: "symbols_",
|
|
233
|
+
storageType: typeof window === "undefined" || process.env.NODE_ENV === "test" || process.env.NODE_ENV === "testing" ? "memory" : "localStorage",
|
|
234
|
+
// 'localStorage' | 'sessionStorage' | 'memory'
|
|
235
|
+
refreshBuffer: 60 * 1e3,
|
|
236
|
+
// Refresh 1 minute before expiry
|
|
237
|
+
maxRetries: 3,
|
|
238
|
+
apiUrl: options.apiUrl || "/api",
|
|
239
|
+
onTokenRefresh: options.onTokenRefresh || null,
|
|
240
|
+
onTokenExpired: options.onTokenExpired || null,
|
|
241
|
+
onTokenError: options.onTokenError || null,
|
|
242
|
+
...options
|
|
243
|
+
};
|
|
244
|
+
this.tokens = {
|
|
245
|
+
accessToken: null,
|
|
246
|
+
refreshToken: null,
|
|
247
|
+
expiresAt: null,
|
|
248
|
+
expiresIn: null
|
|
249
|
+
};
|
|
250
|
+
this.refreshPromise = null;
|
|
251
|
+
this.refreshTimeout = null;
|
|
252
|
+
this.retryCount = 0;
|
|
253
|
+
this.loadTokens();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Storage keys
|
|
257
|
+
*/
|
|
258
|
+
get storageKeys() {
|
|
259
|
+
return {
|
|
260
|
+
accessToken: `${this.config.storagePrefix}access_token`,
|
|
261
|
+
refreshToken: `${this.config.storagePrefix}refresh_token`,
|
|
262
|
+
expiresAt: `${this.config.storagePrefix}expires_at`,
|
|
263
|
+
expiresIn: `${this.config.storagePrefix}expires_in`
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get storage instance based on configuration
|
|
268
|
+
*/
|
|
269
|
+
get storage() {
|
|
270
|
+
if (typeof window === "undefined") {
|
|
271
|
+
return this._memoryStorage;
|
|
272
|
+
}
|
|
273
|
+
const safeGetStorage = (provider) => {
|
|
274
|
+
try {
|
|
275
|
+
const storage = provider();
|
|
276
|
+
const testKey = `${this.config.storagePrefix}__tm_test__`;
|
|
277
|
+
storage.setItem(testKey, "1");
|
|
278
|
+
storage.removeItem(testKey);
|
|
279
|
+
return storage;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const localStorageInstance = safeGetStorage(() => window.localStorage);
|
|
285
|
+
const sessionStorageInstance = safeGetStorage(() => window.sessionStorage);
|
|
286
|
+
switch (this.config.storageType) {
|
|
287
|
+
case "sessionStorage":
|
|
288
|
+
return sessionStorageInstance || this._memoryStorage;
|
|
289
|
+
case "memory":
|
|
290
|
+
return this._memoryStorage;
|
|
291
|
+
default:
|
|
292
|
+
return localStorageInstance || this._memoryStorage;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Set tokens and persist to storage
|
|
297
|
+
*/
|
|
298
|
+
setTokens(tokenData) {
|
|
299
|
+
const {
|
|
300
|
+
access_token: accessToken,
|
|
301
|
+
refresh_token: refreshToken,
|
|
302
|
+
expires_in: expiresIn,
|
|
303
|
+
token_type: tokenType = "Bearer"
|
|
304
|
+
} = tokenData;
|
|
305
|
+
if (!accessToken) {
|
|
306
|
+
throw new Error("Access token is required");
|
|
307
|
+
}
|
|
308
|
+
const now = Date.now();
|
|
309
|
+
const expiresAt = expiresIn ? now + expiresIn * 1e3 : null;
|
|
310
|
+
this.tokens = {
|
|
311
|
+
accessToken,
|
|
312
|
+
refreshToken: refreshToken || this.tokens.refreshToken,
|
|
313
|
+
expiresAt,
|
|
314
|
+
expiresIn,
|
|
315
|
+
tokenType
|
|
316
|
+
};
|
|
317
|
+
this.saveTokens();
|
|
318
|
+
this.scheduleRefresh();
|
|
319
|
+
if (this.config.onTokenRefresh) {
|
|
320
|
+
this.config.onTokenRefresh(this.tokens);
|
|
321
|
+
}
|
|
322
|
+
return this.tokens;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get current access token
|
|
326
|
+
*/
|
|
327
|
+
getAccessToken() {
|
|
328
|
+
return this.tokens.accessToken;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get current refresh token
|
|
332
|
+
*/
|
|
333
|
+
getRefreshToken() {
|
|
334
|
+
return this.tokens.refreshToken;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get authorization header value
|
|
338
|
+
*/
|
|
339
|
+
getAuthHeader() {
|
|
340
|
+
const token = this.getAccessToken();
|
|
341
|
+
if (!token) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
return `${this.tokens.tokenType || "Bearer"} ${token}`;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Check if access token is valid and not expired
|
|
348
|
+
*/
|
|
349
|
+
isAccessTokenValid() {
|
|
350
|
+
if (!this.tokens.accessToken) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
if (!this.tokens.expiresAt) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
const now = Date.now();
|
|
357
|
+
const isValid = now < this.tokens.expiresAt - this.config.refreshBuffer;
|
|
358
|
+
if (!isValid) {
|
|
359
|
+
console.log("[TokenManager] Access token is expired or near expiry:", {
|
|
360
|
+
now: new Date(now).toISOString(),
|
|
361
|
+
expiresAt: new Date(this.tokens.expiresAt).toISOString(),
|
|
362
|
+
refreshBuffer: this.config.refreshBuffer
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return isValid;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Check if access token exists and is not expired (without refresh buffer)
|
|
369
|
+
*/
|
|
370
|
+
isAccessTokenActuallyValid() {
|
|
371
|
+
if (!this.tokens.accessToken) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
if (!this.tokens.expiresAt) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
const now = Date.now();
|
|
378
|
+
return now < this.tokens.expiresAt;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Check if tokens exist (regardless of expiry)
|
|
382
|
+
*/
|
|
383
|
+
hasTokens() {
|
|
384
|
+
return Boolean(this.tokens.accessToken);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Check if refresh token exists
|
|
388
|
+
*/
|
|
389
|
+
hasRefreshToken() {
|
|
390
|
+
return Boolean(this.tokens.refreshToken);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Automatically refresh tokens if needed
|
|
394
|
+
*/
|
|
395
|
+
async ensureValidToken() {
|
|
396
|
+
if (!this.hasTokens()) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (this.isAccessTokenValid()) {
|
|
400
|
+
return this.getAccessToken();
|
|
401
|
+
}
|
|
402
|
+
if (!this.hasRefreshToken()) {
|
|
403
|
+
this.clearTokens();
|
|
404
|
+
if (this.config.onTokenExpired) {
|
|
405
|
+
this.config.onTokenExpired();
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
await this.refreshTokens();
|
|
411
|
+
return this.getAccessToken();
|
|
412
|
+
} catch (error) {
|
|
413
|
+
this.clearTokens();
|
|
414
|
+
if (this.config.onTokenError) {
|
|
415
|
+
this.config.onTokenError(error);
|
|
416
|
+
}
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Refresh access token using refresh token
|
|
422
|
+
*/
|
|
423
|
+
async refreshTokens() {
|
|
424
|
+
if (this.refreshPromise) {
|
|
425
|
+
return this.refreshPromise;
|
|
426
|
+
}
|
|
427
|
+
if (!this.hasRefreshToken()) {
|
|
428
|
+
throw new Error("No refresh token available");
|
|
429
|
+
}
|
|
430
|
+
if (this.retryCount >= this.config.maxRetries) {
|
|
431
|
+
throw new Error("Max refresh retries exceeded");
|
|
432
|
+
}
|
|
433
|
+
this.refreshPromise = this._performRefresh();
|
|
434
|
+
try {
|
|
435
|
+
const result = await this.refreshPromise;
|
|
436
|
+
this.retryCount = 0;
|
|
437
|
+
return result;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
this.retryCount++;
|
|
440
|
+
throw error;
|
|
441
|
+
} finally {
|
|
442
|
+
this.refreshPromise = null;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Perform the actual token refresh request
|
|
447
|
+
*/
|
|
448
|
+
async _performRefresh() {
|
|
449
|
+
var _a;
|
|
450
|
+
const refreshToken = this.getRefreshToken();
|
|
451
|
+
const response = await fetch(`${this.config.apiUrl}/core/auth/refresh`, {
|
|
452
|
+
method: "POST",
|
|
453
|
+
headers: {
|
|
454
|
+
"Content-Type": "application/json"
|
|
455
|
+
},
|
|
456
|
+
body: JSON.stringify({ refreshToken })
|
|
457
|
+
});
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
const errorData = await response.json().catch(() => ({}));
|
|
460
|
+
throw new Error(errorData.message || `Token refresh failed: ${response.status}`);
|
|
461
|
+
}
|
|
462
|
+
const responseData = await response.json();
|
|
463
|
+
if (responseData.success && responseData.data && responseData.data.tokens) {
|
|
464
|
+
const { tokens } = responseData.data;
|
|
465
|
+
const tokenData = {
|
|
466
|
+
access_token: tokens.accessToken,
|
|
467
|
+
refresh_token: tokens.refreshToken,
|
|
468
|
+
expires_in: (_a = tokens.accessTokenExp) == null ? void 0 : _a.expiresIn,
|
|
469
|
+
token_type: "Bearer"
|
|
470
|
+
};
|
|
471
|
+
return this.setTokens(tokenData);
|
|
472
|
+
}
|
|
473
|
+
return this.setTokens(responseData);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Schedule automatic token refresh
|
|
477
|
+
*/
|
|
478
|
+
scheduleRefresh() {
|
|
479
|
+
if (this.refreshTimeout) {
|
|
480
|
+
clearTimeout(this.refreshTimeout);
|
|
481
|
+
this.refreshTimeout = null;
|
|
482
|
+
}
|
|
483
|
+
if (!this.tokens.expiresAt || !this.hasRefreshToken()) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const now = Date.now();
|
|
487
|
+
const refreshTime = this.tokens.expiresAt - this.config.refreshBuffer;
|
|
488
|
+
const delay = Math.max(0, refreshTime - now);
|
|
489
|
+
this.refreshTimeout = setTimeout(async () => {
|
|
490
|
+
try {
|
|
491
|
+
await this.refreshTokens();
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error("Automatic token refresh failed:", error);
|
|
494
|
+
if (this.config.onTokenError) {
|
|
495
|
+
this.config.onTokenError(error);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}, delay);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Save tokens to storage
|
|
502
|
+
*/
|
|
503
|
+
saveTokens() {
|
|
504
|
+
try {
|
|
505
|
+
const { storage } = this;
|
|
506
|
+
const keys = this.storageKeys;
|
|
507
|
+
if (this.tokens.accessToken) {
|
|
508
|
+
storage.setItem(keys.accessToken, this.tokens.accessToken);
|
|
509
|
+
}
|
|
510
|
+
if (this.tokens.refreshToken) {
|
|
511
|
+
storage.setItem(keys.refreshToken, this.tokens.refreshToken);
|
|
512
|
+
}
|
|
513
|
+
if (this.tokens.expiresAt) {
|
|
514
|
+
storage.setItem(keys.expiresAt, this.tokens.expiresAt.toString());
|
|
515
|
+
}
|
|
516
|
+
if (this.tokens.expiresIn) {
|
|
517
|
+
storage.setItem(keys.expiresIn, this.tokens.expiresIn.toString());
|
|
518
|
+
}
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error("[TokenManager] Error saving tokens to storage:", error);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Load tokens from storage
|
|
525
|
+
*/
|
|
526
|
+
loadTokens() {
|
|
527
|
+
try {
|
|
528
|
+
const { storage } = this;
|
|
529
|
+
const keys = this.storageKeys;
|
|
530
|
+
const accessToken = storage.getItem(keys.accessToken);
|
|
531
|
+
const refreshToken = storage.getItem(keys.refreshToken);
|
|
532
|
+
const expiresAt = storage.getItem(keys.expiresAt);
|
|
533
|
+
const expiresIn = storage.getItem(keys.expiresIn);
|
|
534
|
+
if (accessToken) {
|
|
535
|
+
this.tokens = {
|
|
536
|
+
accessToken,
|
|
537
|
+
refreshToken,
|
|
538
|
+
expiresAt: expiresAt ? parseInt(expiresAt, 10) : null,
|
|
539
|
+
expiresIn: expiresIn ? parseInt(expiresIn, 10) : null,
|
|
540
|
+
tokenType: "Bearer"
|
|
541
|
+
};
|
|
542
|
+
this.scheduleRefresh();
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error("[TokenManager] Error loading tokens from storage:", error);
|
|
546
|
+
this.tokens = {
|
|
547
|
+
accessToken: null,
|
|
548
|
+
refreshToken: null,
|
|
549
|
+
expiresAt: null,
|
|
550
|
+
expiresIn: null
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Clear all tokens
|
|
556
|
+
*/
|
|
557
|
+
clearTokens() {
|
|
558
|
+
this.tokens = {
|
|
559
|
+
accessToken: null,
|
|
560
|
+
refreshToken: null,
|
|
561
|
+
expiresAt: null,
|
|
562
|
+
expiresIn: null
|
|
563
|
+
};
|
|
564
|
+
const { storage } = this;
|
|
565
|
+
const keys = this.storageKeys;
|
|
566
|
+
Object.values(keys).forEach((key) => {
|
|
567
|
+
storage.removeItem(key);
|
|
568
|
+
});
|
|
569
|
+
if (this.refreshTimeout) {
|
|
570
|
+
clearTimeout(this.refreshTimeout);
|
|
571
|
+
this.refreshTimeout = null;
|
|
572
|
+
}
|
|
573
|
+
this.retryCount = 0;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Get token status information
|
|
577
|
+
*/
|
|
578
|
+
getTokenStatus() {
|
|
579
|
+
const hasTokens = this.hasTokens();
|
|
580
|
+
const isValid = this.isAccessTokenValid();
|
|
581
|
+
const { expiresAt } = this.tokens;
|
|
582
|
+
const timeToExpiry = expiresAt ? expiresAt - Date.now() : null;
|
|
583
|
+
return {
|
|
584
|
+
hasTokens,
|
|
585
|
+
isValid,
|
|
586
|
+
hasRefreshToken: this.hasRefreshToken(),
|
|
587
|
+
expiresAt,
|
|
588
|
+
timeToExpiry,
|
|
589
|
+
willExpireSoon: timeToExpiry ? timeToExpiry < this.config.refreshBuffer : false
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Cleanup resources
|
|
594
|
+
*/
|
|
595
|
+
destroy() {
|
|
596
|
+
if (this.refreshTimeout) {
|
|
597
|
+
clearTimeout(this.refreshTimeout);
|
|
598
|
+
this.refreshTimeout = null;
|
|
599
|
+
}
|
|
600
|
+
this.refreshPromise = null;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var defaultTokenManager = null;
|
|
604
|
+
var getTokenManager = (options) => {
|
|
605
|
+
if (!defaultTokenManager) {
|
|
606
|
+
defaultTokenManager = new TokenManager(options);
|
|
607
|
+
}
|
|
608
|
+
return defaultTokenManager;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// src/services/BaseService.js
|
|
612
|
+
var BaseService = class {
|
|
613
|
+
constructor({ context, options } = {}) {
|
|
614
|
+
this._context = context || {};
|
|
615
|
+
this._options = options || {};
|
|
616
|
+
this._ready = false;
|
|
617
|
+
this._error = null;
|
|
618
|
+
this._apiUrl = null;
|
|
619
|
+
this._tokenManager = null;
|
|
620
|
+
}
|
|
621
|
+
// Initialize service
|
|
622
|
+
init({ context }) {
|
|
623
|
+
try {
|
|
624
|
+
const { apiUrl } = context || this._context;
|
|
625
|
+
this._apiUrl = apiUrl || environment_default.apiUrl;
|
|
626
|
+
if (!this._apiUrl) {
|
|
627
|
+
throw new Error("Service base URL not configured");
|
|
628
|
+
}
|
|
629
|
+
this._tokenManager = getTokenManager({
|
|
630
|
+
apiUrl: this._apiUrl,
|
|
631
|
+
onTokenError: (error) => {
|
|
632
|
+
console.error("Token management error:", error);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
this._setReady();
|
|
636
|
+
} catch (error) {
|
|
637
|
+
this._setError(error);
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// Update context
|
|
642
|
+
updateContext(context) {
|
|
643
|
+
if (context && typeof context === "object") {
|
|
644
|
+
Object.assign(this._context, context);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Get service status
|
|
648
|
+
getStatus() {
|
|
649
|
+
return {
|
|
650
|
+
ready: this._ready,
|
|
651
|
+
error: this._error,
|
|
652
|
+
context: { ...this._context }
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
// Check if service is ready
|
|
656
|
+
isReady() {
|
|
657
|
+
return this._ready;
|
|
658
|
+
}
|
|
659
|
+
// Protected helper methods
|
|
660
|
+
_setReady(ready = true) {
|
|
661
|
+
this._ready = ready;
|
|
662
|
+
this._error = null;
|
|
663
|
+
}
|
|
664
|
+
_setError(error) {
|
|
665
|
+
this._ready = false;
|
|
666
|
+
this._error = error;
|
|
667
|
+
}
|
|
668
|
+
_getTrackingService() {
|
|
669
|
+
var _a;
|
|
670
|
+
const services = (_a = this._context) == null ? void 0 : _a.services;
|
|
671
|
+
const tracking = services == null ? void 0 : services.tracking;
|
|
672
|
+
if (!tracking || typeof tracking.trackError !== "function") {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
return tracking;
|
|
676
|
+
}
|
|
677
|
+
_shouldTrackErrors() {
|
|
678
|
+
var _a;
|
|
679
|
+
const name = (_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name;
|
|
680
|
+
return name !== "TrackingService";
|
|
681
|
+
}
|
|
682
|
+
_trackServiceError(error, details = {}) {
|
|
683
|
+
var _a;
|
|
684
|
+
if (!this._shouldTrackErrors()) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const tracking = this._getTrackingService();
|
|
689
|
+
if (!tracking) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const context = {
|
|
693
|
+
service: ((_a = this == null ? void 0 : this.constructor) == null ? void 0 : _a.name) || "UnknownService",
|
|
694
|
+
apiUrl: this._apiUrl || null,
|
|
695
|
+
...details
|
|
696
|
+
};
|
|
697
|
+
tracking.trackError(error instanceof Error ? error : new Error(String(error)), context);
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
_requireAuth() {
|
|
702
|
+
if (!this.getAuthToken()) {
|
|
703
|
+
throw new Error("Authentication required");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
_requireReady(methodName = "unknown") {
|
|
707
|
+
if (!this.isReady()) {
|
|
708
|
+
throw new Error(`Service not initialized for method: ${methodName}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Shared HTTP request method
|
|
712
|
+
async _request(endpoint, options = {}) {
|
|
713
|
+
const url = `${this._apiUrl}/core${endpoint}`;
|
|
714
|
+
const defaultHeaders = {};
|
|
715
|
+
if (!(options.body instanceof FormData)) {
|
|
716
|
+
defaultHeaders["Content-Type"] = "application/json";
|
|
717
|
+
}
|
|
718
|
+
if (this._requiresInit(options.methodName) && this._tokenManager) {
|
|
719
|
+
try {
|
|
720
|
+
const validToken = await this._tokenManager.ensureValidToken();
|
|
721
|
+
if (validToken) {
|
|
722
|
+
const authHeader = this._tokenManager.getAuthHeader();
|
|
723
|
+
if (authHeader) {
|
|
724
|
+
defaultHeaders.Authorization = authHeader;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} catch (error) {
|
|
728
|
+
console.warn(
|
|
729
|
+
"Token management failed, proceeding without authentication:",
|
|
730
|
+
error
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
const response = await fetch(url, {
|
|
736
|
+
...options,
|
|
737
|
+
headers: {
|
|
738
|
+
...defaultHeaders,
|
|
739
|
+
...options.headers
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
if (!response.ok) {
|
|
743
|
+
let error = {
|
|
744
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
745
|
+
};
|
|
746
|
+
try {
|
|
747
|
+
error = await response.json();
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
this._trackServiceError(
|
|
751
|
+
new Error(error.message || error.error || `HTTP ${response.status}: ${response.statusText}`),
|
|
752
|
+
{
|
|
753
|
+
endpoint,
|
|
754
|
+
methodName: options.methodName,
|
|
755
|
+
status: response.status,
|
|
756
|
+
statusText: response.statusText
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
throw new Error(error.message || error.error || "Request failed", { cause: error });
|
|
760
|
+
}
|
|
761
|
+
return response.status === 204 ? null : response.json();
|
|
762
|
+
} catch (error) {
|
|
763
|
+
this._trackServiceError(error, {
|
|
764
|
+
endpoint,
|
|
765
|
+
methodName: options.methodName
|
|
766
|
+
});
|
|
767
|
+
throw new Error(`Request failed: ${error.message}`, { cause: error });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Helper method to determine if a method requires initialization
|
|
771
|
+
_requiresInit(methodName) {
|
|
772
|
+
const noInitMethods = /* @__PURE__ */ new Set([
|
|
773
|
+
"register",
|
|
774
|
+
"login",
|
|
775
|
+
"googleAuth",
|
|
776
|
+
"googleAuthCallback",
|
|
777
|
+
"githubAuth",
|
|
778
|
+
"requestPasswordReset",
|
|
779
|
+
"confirmPasswordReset",
|
|
780
|
+
"confirmRegistration",
|
|
781
|
+
"verifyEmail",
|
|
782
|
+
"getPlans",
|
|
783
|
+
"getPlan",
|
|
784
|
+
"listPublicProjects",
|
|
785
|
+
"getPublicProject"
|
|
786
|
+
]);
|
|
787
|
+
return !noInitMethods.has(methodName);
|
|
788
|
+
}
|
|
789
|
+
// Cleanup method
|
|
790
|
+
destroy() {
|
|
791
|
+
if (this._tokenManager) {
|
|
792
|
+
this._tokenManager.destroy();
|
|
793
|
+
this._tokenManager = null;
|
|
794
|
+
}
|
|
795
|
+
this._ready = false;
|
|
796
|
+
this._setReady(false);
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
// src/services/PullRequestService.js
|
|
801
|
+
var PullRequestService = class extends BaseService {
|
|
802
|
+
// ==================== PULL REQUEST METHODS ====================
|
|
803
|
+
/**
|
|
804
|
+
* Create a new pull request
|
|
805
|
+
*/
|
|
806
|
+
async createPullRequest(projectId, pullRequestData) {
|
|
807
|
+
this._requireReady("createPullRequest");
|
|
808
|
+
if (!projectId) {
|
|
809
|
+
throw new Error("Project ID is required");
|
|
810
|
+
}
|
|
811
|
+
if (!pullRequestData.source || !pullRequestData.target || !pullRequestData.title) {
|
|
812
|
+
throw new Error("Source branch, target branch, and title are required");
|
|
813
|
+
}
|
|
814
|
+
try {
|
|
815
|
+
const response = await this._request(
|
|
816
|
+
`/projects/${projectId}/pull-requests`,
|
|
817
|
+
{
|
|
818
|
+
method: "POST",
|
|
819
|
+
body: JSON.stringify(pullRequestData),
|
|
820
|
+
methodName: "createPullRequest"
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
if (response.success) {
|
|
824
|
+
return response.data;
|
|
825
|
+
}
|
|
826
|
+
throw new Error(response.message);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
throw new Error(`Failed to create pull request: ${error.message}`, { cause: error });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* List pull requests for a project with filtering options
|
|
833
|
+
*/
|
|
834
|
+
async listPullRequests(projectId, options = {}) {
|
|
835
|
+
this._requireReady("listPullRequests");
|
|
836
|
+
if (!projectId) {
|
|
837
|
+
throw new Error("Project ID is required");
|
|
838
|
+
}
|
|
839
|
+
const { status = "open", source, target, page = 1, limit = 20 } = options;
|
|
840
|
+
const queryParams = new URLSearchParams({
|
|
841
|
+
status,
|
|
842
|
+
page: page.toString(),
|
|
843
|
+
limit: limit.toString()
|
|
844
|
+
});
|
|
845
|
+
if (source) {
|
|
846
|
+
queryParams.append("source", source);
|
|
847
|
+
}
|
|
848
|
+
if (target) {
|
|
849
|
+
queryParams.append("target", target);
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
const response = await this._request(
|
|
853
|
+
`/projects/${projectId}/pull-requests?${queryParams.toString()}`,
|
|
854
|
+
{
|
|
855
|
+
method: "GET",
|
|
856
|
+
methodName: "listPullRequests"
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
if (response.success) {
|
|
860
|
+
return response.data;
|
|
861
|
+
}
|
|
862
|
+
throw new Error(response.message);
|
|
863
|
+
} catch (error) {
|
|
864
|
+
throw new Error(`Failed to list pull requests: ${error.message}`, { cause: error });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Get detailed information about a specific pull request
|
|
869
|
+
*/
|
|
870
|
+
async getPullRequest(projectId, prId) {
|
|
871
|
+
this._requireReady("getPullRequest");
|
|
872
|
+
if (!projectId) {
|
|
873
|
+
throw new Error("Project ID is required");
|
|
874
|
+
}
|
|
875
|
+
if (!prId) {
|
|
876
|
+
throw new Error("Pull request ID is required");
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
const response = await this._request(
|
|
880
|
+
`/projects/${projectId}/pull-requests/${prId}`,
|
|
881
|
+
{
|
|
882
|
+
method: "GET",
|
|
883
|
+
methodName: "getPullRequest"
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
if (response.success) {
|
|
887
|
+
return response.data;
|
|
888
|
+
}
|
|
889
|
+
throw new Error(response.message);
|
|
890
|
+
} catch (error) {
|
|
891
|
+
throw new Error(`Failed to get pull request: ${error.message}`, { cause: error });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Submit a review for a pull request
|
|
896
|
+
*/
|
|
897
|
+
async reviewPullRequest(projectId, prId, reviewData) {
|
|
898
|
+
this._requireReady("reviewPullRequest");
|
|
899
|
+
if (!projectId) {
|
|
900
|
+
throw new Error("Project ID is required");
|
|
901
|
+
}
|
|
902
|
+
if (!prId) {
|
|
903
|
+
throw new Error("Pull request ID is required");
|
|
904
|
+
}
|
|
905
|
+
const validStatuses = ["approved", "requested_changes", "feedback"];
|
|
906
|
+
if (reviewData.status && !validStatuses.includes(reviewData.status)) {
|
|
907
|
+
throw new Error(
|
|
908
|
+
`Invalid review status. Must be one of: ${validStatuses.join(", ")}`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const response = await this._request(
|
|
913
|
+
`/projects/${projectId}/pull-requests/${prId}/review`,
|
|
914
|
+
{
|
|
915
|
+
method: "POST",
|
|
916
|
+
body: JSON.stringify(reviewData),
|
|
917
|
+
methodName: "reviewPullRequest"
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
if (response.success) {
|
|
921
|
+
return response;
|
|
922
|
+
}
|
|
923
|
+
throw new Error(response.message);
|
|
924
|
+
} catch (error) {
|
|
925
|
+
throw new Error(`Failed to review pull request: ${error.message}`, { cause: error });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Add a comment to an existing review thread
|
|
930
|
+
*/
|
|
931
|
+
async addPullRequestComment(projectId, prId, commentData) {
|
|
932
|
+
this._requireReady("addPullRequestComment");
|
|
933
|
+
if (!projectId) {
|
|
934
|
+
throw new Error("Project ID is required");
|
|
935
|
+
}
|
|
936
|
+
if (!prId) {
|
|
937
|
+
throw new Error("Pull request ID is required");
|
|
938
|
+
}
|
|
939
|
+
if (!commentData.value) {
|
|
940
|
+
throw new Error("Comment value is required");
|
|
941
|
+
}
|
|
942
|
+
try {
|
|
943
|
+
const response = await this._request(
|
|
944
|
+
`/projects/${projectId}/pull-requests/${prId}/comment`,
|
|
945
|
+
{
|
|
946
|
+
method: "POST",
|
|
947
|
+
body: JSON.stringify(commentData),
|
|
948
|
+
methodName: "addPullRequestComment"
|
|
949
|
+
}
|
|
950
|
+
);
|
|
951
|
+
if (response.success) {
|
|
952
|
+
return response;
|
|
953
|
+
}
|
|
954
|
+
throw new Error(response.message);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
throw new Error(`Failed to add pull request comment: ${error.message}`, { cause: error });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Merge an approved pull request
|
|
961
|
+
*/
|
|
962
|
+
async mergePullRequest(projectId, prId) {
|
|
963
|
+
this._requireReady("mergePullRequest");
|
|
964
|
+
if (!projectId) {
|
|
965
|
+
throw new Error("Project ID is required");
|
|
966
|
+
}
|
|
967
|
+
if (!prId) {
|
|
968
|
+
throw new Error("Pull request ID is required");
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const response = await this._request(
|
|
972
|
+
`/projects/${projectId}/pull-requests/${prId}/merge`,
|
|
973
|
+
{
|
|
974
|
+
method: "POST",
|
|
975
|
+
methodName: "mergePullRequest"
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
if (response.success) {
|
|
979
|
+
return response;
|
|
980
|
+
}
|
|
981
|
+
throw new Error(response.message);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
if (error.message.includes("conflicts") || error.message.includes("409")) {
|
|
984
|
+
throw new Error(`Pull request has merge conflicts: ${error.message}`, { cause: error });
|
|
985
|
+
}
|
|
986
|
+
throw new Error(`Failed to merge pull request: ${error.message}`, { cause: error });
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Get the diff/changes for a pull request
|
|
991
|
+
*/
|
|
992
|
+
async getPullRequestDiff(projectId, prId) {
|
|
993
|
+
this._requireReady("getPullRequestDiff");
|
|
994
|
+
if (!projectId) {
|
|
995
|
+
throw new Error("Project ID is required");
|
|
996
|
+
}
|
|
997
|
+
if (!prId) {
|
|
998
|
+
throw new Error("Pull request ID is required");
|
|
999
|
+
}
|
|
1000
|
+
try {
|
|
1001
|
+
const response = await this._request(
|
|
1002
|
+
`/projects/${projectId}/pull-requests/${prId}/diff`,
|
|
1003
|
+
{
|
|
1004
|
+
method: "GET",
|
|
1005
|
+
methodName: "getPullRequestDiff"
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
if (response.success) {
|
|
1009
|
+
return response.data;
|
|
1010
|
+
}
|
|
1011
|
+
throw new Error(response.message);
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
throw new Error(`Failed to get pull request diff: ${error.message}`, { cause: error });
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// ==================== PULL REQUEST HELPER METHODS ====================
|
|
1017
|
+
/**
|
|
1018
|
+
* Helper method to create a pull request with validation
|
|
1019
|
+
*/
|
|
1020
|
+
async createPullRequestWithValidation(projectId, data) {
|
|
1021
|
+
const { source, target, title, description, changes } = data;
|
|
1022
|
+
if (source === target) {
|
|
1023
|
+
throw new Error("Source and target branches cannot be the same");
|
|
1024
|
+
}
|
|
1025
|
+
if (!title || title.trim().length === 0) {
|
|
1026
|
+
throw new Error("Pull request title cannot be empty");
|
|
1027
|
+
}
|
|
1028
|
+
if (title.length > 200) {
|
|
1029
|
+
throw new Error("Pull request title cannot exceed 200 characters");
|
|
1030
|
+
}
|
|
1031
|
+
const pullRequestData = {
|
|
1032
|
+
source: source.trim(),
|
|
1033
|
+
target: target.trim(),
|
|
1034
|
+
title: title.trim(),
|
|
1035
|
+
...description && { description: description.trim() },
|
|
1036
|
+
...changes && { changes }
|
|
1037
|
+
};
|
|
1038
|
+
return await this.createPullRequest(projectId, pullRequestData);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Helper method to approve a pull request
|
|
1042
|
+
*/
|
|
1043
|
+
async approvePullRequest(projectId, prId, comment = "") {
|
|
1044
|
+
const reviewData = {
|
|
1045
|
+
status: "approved",
|
|
1046
|
+
...comment && {
|
|
1047
|
+
threads: [
|
|
1048
|
+
{
|
|
1049
|
+
comment,
|
|
1050
|
+
type: "praise"
|
|
1051
|
+
}
|
|
1052
|
+
]
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
return await this.reviewPullRequest(projectId, prId, reviewData);
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Helper method to request changes on a pull request
|
|
1059
|
+
*/
|
|
1060
|
+
async requestPullRequestChanges(projectId, prId, threads = []) {
|
|
1061
|
+
if (!threads || threads.length === 0) {
|
|
1062
|
+
throw new Error("Must provide specific feedback when requesting changes");
|
|
1063
|
+
}
|
|
1064
|
+
const reviewData = {
|
|
1065
|
+
status: "requested_changes",
|
|
1066
|
+
threads
|
|
1067
|
+
};
|
|
1068
|
+
return await this.reviewPullRequest(projectId, prId, reviewData);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Helper method to get pull requests by status
|
|
1072
|
+
*/
|
|
1073
|
+
async getOpenPullRequests(projectId, options = {}) {
|
|
1074
|
+
return await this.listPullRequests(projectId, {
|
|
1075
|
+
...options,
|
|
1076
|
+
status: "open"
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
async getClosedPullRequests(projectId, options = {}) {
|
|
1080
|
+
return await this.listPullRequests(projectId, {
|
|
1081
|
+
...options,
|
|
1082
|
+
status: "closed"
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
async getMergedPullRequests(projectId, options = {}) {
|
|
1086
|
+
return await this.listPullRequests(projectId, {
|
|
1087
|
+
...options,
|
|
1088
|
+
status: "merged"
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Helper method to check if a pull request is canMerge
|
|
1093
|
+
*/
|
|
1094
|
+
async isPullRequestMergeable(projectId, prId) {
|
|
1095
|
+
var _a;
|
|
1096
|
+
try {
|
|
1097
|
+
const prData = await this.getPullRequest(projectId, prId);
|
|
1098
|
+
return ((_a = prData == null ? void 0 : prData.data) == null ? void 0 : _a.canMerge) || false;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`Failed to check pull request mergeability: ${error.message}`,
|
|
1102
|
+
{ cause: error }
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Helper method to get pull request status summary
|
|
1108
|
+
*/
|
|
1109
|
+
async getPullRequestStatusSummary(projectId, prId) {
|
|
1110
|
+
var _a, _b, _c;
|
|
1111
|
+
try {
|
|
1112
|
+
const prData = await this.getPullRequest(projectId, prId);
|
|
1113
|
+
const pr = prData == null ? void 0 : prData.data;
|
|
1114
|
+
if (!pr) {
|
|
1115
|
+
throw new Error("Pull request not found");
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
status: pr.status,
|
|
1119
|
+
reviewStatus: pr.reviewStatus,
|
|
1120
|
+
canMerge: pr.canMerge,
|
|
1121
|
+
hasConflicts: !pr.canMerge,
|
|
1122
|
+
reviewCount: ((_a = pr.reviews) == null ? void 0 : _a.length) || 0,
|
|
1123
|
+
approvedReviews: ((_b = pr.reviews) == null ? void 0 : _b.filter((r) => r.status === "approved").length) || 0,
|
|
1124
|
+
changesRequested: ((_c = pr.reviews) == null ? void 0 : _c.filter((r) => r.status === "requested_changes").length) || 0
|
|
1125
|
+
};
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
throw new Error(
|
|
1128
|
+
`Failed to get pull request status summary: ${error.message}`,
|
|
1129
|
+
{ cause: error }
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Helper method to validate pull request data
|
|
1135
|
+
*/
|
|
1136
|
+
validatePullRequestData(data) {
|
|
1137
|
+
const errors = [];
|
|
1138
|
+
if (!data.source || typeof data.source !== "string") {
|
|
1139
|
+
errors.push("Source branch is required and must be a string");
|
|
1140
|
+
}
|
|
1141
|
+
if (!data.target || typeof data.target !== "string") {
|
|
1142
|
+
errors.push("Target branch is required and must be a string");
|
|
1143
|
+
}
|
|
1144
|
+
if (data.source === data.target) {
|
|
1145
|
+
errors.push("Source and target branches cannot be the same");
|
|
1146
|
+
}
|
|
1147
|
+
if (!data.title || typeof data.title !== "string") {
|
|
1148
|
+
errors.push("Title is required and must be a string");
|
|
1149
|
+
} else if (data.title.trim().length === 0) {
|
|
1150
|
+
errors.push("Title cannot be empty");
|
|
1151
|
+
} else if (data.title.length > 200) {
|
|
1152
|
+
errors.push("Title cannot exceed 200 characters");
|
|
1153
|
+
}
|
|
1154
|
+
if (data.description && typeof data.description !== "string") {
|
|
1155
|
+
errors.push("Description must be a string");
|
|
1156
|
+
}
|
|
1157
|
+
return {
|
|
1158
|
+
isValid: errors.length === 0,
|
|
1159
|
+
errors
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Helper method to validate review data
|
|
1164
|
+
*/
|
|
1165
|
+
validateReviewData(reviewData) {
|
|
1166
|
+
const errors = [];
|
|
1167
|
+
const validStatuses = ["approved", "requested_changes", "feedback"];
|
|
1168
|
+
if (reviewData.status && !validStatuses.includes(reviewData.status)) {
|
|
1169
|
+
errors.push(`Invalid review status. Must be one of: ${validStatuses.join(", ")}`);
|
|
1170
|
+
}
|
|
1171
|
+
if (reviewData.threads && !Array.isArray(reviewData.threads)) {
|
|
1172
|
+
errors.push("Threads must be an array");
|
|
1173
|
+
}
|
|
1174
|
+
if (reviewData.threads) {
|
|
1175
|
+
reviewData.threads.forEach((thread, index) => {
|
|
1176
|
+
if (!thread.comment || typeof thread.comment !== "string") {
|
|
1177
|
+
errors.push(`Thread ${index + 1}: Comment is required and must be a string`);
|
|
1178
|
+
}
|
|
1179
|
+
if (thread.type && !["praise", "issue", "suggestion"].includes(thread.type)) {
|
|
1180
|
+
errors.push(`Thread ${index + 1}: Invalid thread type`);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
isValid: errors.length === 0,
|
|
1186
|
+
errors
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Helper method to get pull request statistics
|
|
1191
|
+
*/
|
|
1192
|
+
async getPullRequestStats(projectId, options = {}) {
|
|
1193
|
+
try {
|
|
1194
|
+
const { status = "open" } = options;
|
|
1195
|
+
const pullRequests = await this.listPullRequests(projectId, { status });
|
|
1196
|
+
const stats = {
|
|
1197
|
+
total: pullRequests.length,
|
|
1198
|
+
open: 0,
|
|
1199
|
+
closed: 0,
|
|
1200
|
+
merged: 0,
|
|
1201
|
+
withConflicts: 0,
|
|
1202
|
+
readyToMerge: 0
|
|
1203
|
+
};
|
|
1204
|
+
pullRequests.forEach((pr) => {
|
|
1205
|
+
if (pr.status === "open") {
|
|
1206
|
+
stats.open++;
|
|
1207
|
+
}
|
|
1208
|
+
if (pr.status === "closed") {
|
|
1209
|
+
stats.closed++;
|
|
1210
|
+
}
|
|
1211
|
+
if (pr.status === "merged") {
|
|
1212
|
+
stats.merged++;
|
|
1213
|
+
}
|
|
1214
|
+
if (pr.hasConflicts) {
|
|
1215
|
+
stats.withConflicts++;
|
|
1216
|
+
}
|
|
1217
|
+
if (pr.canMerge) {
|
|
1218
|
+
stats.readyToMerge++;
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
return stats;
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
throw new Error(`Failed to get pull request stats: ${error.message}`, { cause: error });
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Helper method to close a pull request
|
|
1228
|
+
*/
|
|
1229
|
+
async closePullRequest(projectId, prId) {
|
|
1230
|
+
this._requireReady("closePullRequest");
|
|
1231
|
+
if (!projectId) {
|
|
1232
|
+
throw new Error("Project ID is required");
|
|
1233
|
+
}
|
|
1234
|
+
if (!prId) {
|
|
1235
|
+
throw new Error("Pull request ID is required");
|
|
1236
|
+
}
|
|
1237
|
+
try {
|
|
1238
|
+
const response = await this._request(
|
|
1239
|
+
`/projects/${projectId}/pull-requests/${prId}/close`,
|
|
1240
|
+
{
|
|
1241
|
+
method: "POST",
|
|
1242
|
+
methodName: "closePullRequest"
|
|
1243
|
+
}
|
|
1244
|
+
);
|
|
1245
|
+
if (response.success) {
|
|
1246
|
+
return response.data;
|
|
1247
|
+
}
|
|
1248
|
+
throw new Error(response.message);
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
throw new Error(`Failed to close pull request: ${error.message}`, { cause: error });
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Helper method to reopen a closed pull request
|
|
1255
|
+
*/
|
|
1256
|
+
async reopenPullRequest(projectId, prId) {
|
|
1257
|
+
this._requireReady("reopenPullRequest");
|
|
1258
|
+
if (!projectId) {
|
|
1259
|
+
throw new Error("Project ID is required");
|
|
1260
|
+
}
|
|
1261
|
+
if (!prId) {
|
|
1262
|
+
throw new Error("Pull request ID is required");
|
|
1263
|
+
}
|
|
1264
|
+
try {
|
|
1265
|
+
const response = await this._request(
|
|
1266
|
+
`/projects/${projectId}/pull-requests/${prId}/reopen`,
|
|
1267
|
+
{
|
|
1268
|
+
method: "POST",
|
|
1269
|
+
methodName: "reopenPullRequest"
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
if (response.success) {
|
|
1273
|
+
return response.data;
|
|
1274
|
+
}
|
|
1275
|
+
throw new Error(response.message);
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
throw new Error(`Failed to reopen pull request: ${error.message}`, { cause: error });
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
export {
|
|
1282
|
+
PullRequestService
|
|
1283
|
+
};
|
|
1284
|
+
// @preserve-env
|