@softeria/ms-365-mcp-server 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -10
- package/dist/auth.js +2 -1
- package/dist/graph-client.js +20 -2
- package/dist/index.js +11 -1
- package/package.json +1 -1
- package/remove-recursive-refs.js +294 -0
package/README.md
CHANGED
|
@@ -60,6 +60,29 @@ get-sharepoint-site-drive-by-id, list-sharepoint-site-items, get-sharepoint-site
|
|
|
60
60
|
get-sharepoint-site-list, list-sharepoint-site-list-items, get-sharepoint-site-list-item,
|
|
61
61
|
get-sharepoint-sites-delta</sub>
|
|
62
62
|
|
|
63
|
+
### Work Scopes Issues
|
|
64
|
+
|
|
65
|
+
If you're having issues accessing work/school features (Teams, SharePoint, etc.), you should pass the
|
|
66
|
+
`--force-work-scopes` flag!
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"ms365": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": [
|
|
74
|
+
"-y",
|
|
75
|
+
"@softeria/ms-365-mcp-server",
|
|
76
|
+
"--force-work-scopes"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
While the server should attempt to force a re-login when work scopes are needed, passing the flag explicitly is safer
|
|
84
|
+
and ensures proper scope permissions from the start.
|
|
85
|
+
|
|
63
86
|
**User Profile**
|
|
64
87
|
<sub>get-current-user</sub>
|
|
65
88
|
|
|
@@ -146,22 +169,23 @@ MCP clients will automatically handle the OAuth flow when they see the advertise
|
|
|
146
169
|
|
|
147
170
|
##### Setting up Azure AD for OAuth Testing
|
|
148
171
|
|
|
149
|
-
To use OAuth mode with custom Azure credentials (recommended for production), you'll need to set up an Azure AD app
|
|
172
|
+
To use OAuth mode with custom Azure credentials (recommended for production), you'll need to set up an Azure AD app
|
|
173
|
+
registration:
|
|
150
174
|
|
|
151
175
|
1. **Create Azure AD App Registration**:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
176
|
+
- Go to [Azure Portal](https://portal.azure.com)
|
|
177
|
+
- Navigate to Azure Active Directory → App registrations → New registration
|
|
178
|
+
- Set name: "MS365 MCP Server"
|
|
155
179
|
|
|
156
180
|
2. **Configure Redirect URIs**:
|
|
157
181
|
Add these redirect URIs for testing with MCP Inspector:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
- `http://localhost:6274/oauth/callback`
|
|
183
|
+
- `http://localhost:6274/oauth/callback/debug`
|
|
184
|
+
- `http://localhost:3000/callback` (optional, for server callback)
|
|
161
185
|
|
|
162
186
|
3. **Get Credentials**:
|
|
163
|
-
|
|
164
|
-
|
|
187
|
+
- Copy the **Application (client) ID** from Overview page
|
|
188
|
+
- Go to Certificates & secrets → New client secret → Copy the secret value
|
|
165
189
|
|
|
166
190
|
4. **Configure Environment Variables**:
|
|
167
191
|
Create a `.env` file in your project root:
|
|
@@ -175,13 +199,15 @@ With these configured, the server will use your custom Azure app instead of the
|
|
|
175
199
|
|
|
176
200
|
#### 3. Bring Your Own Token (BYOT)
|
|
177
201
|
|
|
178
|
-
If you are running ms-365-mcp-server as part of a larger system that manages Microsoft OAuth tokens externally, you can
|
|
202
|
+
If you are running ms-365-mcp-server as part of a larger system that manages Microsoft OAuth tokens externally, you can
|
|
203
|
+
provide an access token directly to this MCP server:
|
|
179
204
|
|
|
180
205
|
```bash
|
|
181
206
|
MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @softeria/ms-365-mcp-server
|
|
182
207
|
```
|
|
183
208
|
|
|
184
209
|
This method:
|
|
210
|
+
|
|
185
211
|
- Bypasses the interactive authentication flows
|
|
186
212
|
- Uses your pre-existing OAuth token for Microsoft Graph API requests
|
|
187
213
|
- Does not handle token refresh (token lifecycle management is your responsibility)
|
package/dist/auth.js
CHANGED
|
@@ -141,8 +141,9 @@ class AuthManager {
|
|
|
141
141
|
};
|
|
142
142
|
try {
|
|
143
143
|
logger.info('Requesting device code...');
|
|
144
|
-
logger.info(`
|
|
144
|
+
logger.info(`Requesting scopes: ${this.scopes.join(', ')}`);
|
|
145
145
|
const response = await this.msalApp.acquireTokenByDeviceCode(deviceCodeRequest);
|
|
146
|
+
logger.info(`Granted scopes: ${response?.scopes?.join(', ') || 'none'}`);
|
|
146
147
|
logger.info('Device code login successful');
|
|
147
148
|
this.accessToken = response?.accessToken || null;
|
|
148
149
|
this.tokenExpiry = response?.expiresOn ? new Date(response.expiresOn).getTime() : null;
|
package/dist/graph-client.js
CHANGED
|
@@ -47,7 +47,7 @@ class GraphClient {
|
|
|
47
47
|
}
|
|
48
48
|
async makeRequest(endpoint, options = {}) {
|
|
49
49
|
// Use OAuth tokens if available, otherwise fall back to authManager
|
|
50
|
-
let accessToken = options.accessToken || this.accessToken || await this.authManager.getToken();
|
|
50
|
+
let accessToken = options.accessToken || this.accessToken || (await this.authManager.getToken());
|
|
51
51
|
let refreshToken = options.refreshToken || this.refreshToken;
|
|
52
52
|
if (!accessToken) {
|
|
53
53
|
throw new Error('No access token available');
|
|
@@ -65,6 +65,24 @@ class GraphClient {
|
|
|
65
65
|
// Retry the request with new token
|
|
66
66
|
return this.performRequest(endpoint, accessToken, options);
|
|
67
67
|
}
|
|
68
|
+
if (response.status === 403) {
|
|
69
|
+
const errorText = await response.text();
|
|
70
|
+
if (errorText.includes('scope') || errorText.includes('permission')) {
|
|
71
|
+
const hasWorkPermissions = await this.authManager.hasWorkAccountPermissions();
|
|
72
|
+
if (!hasWorkPermissions) {
|
|
73
|
+
logger.info('403 scope error detected, attempting to expand to work account scopes...');
|
|
74
|
+
const expanded = await this.authManager.expandToWorkAccountScopes();
|
|
75
|
+
if (expanded) {
|
|
76
|
+
const newToken = await this.authManager.getToken();
|
|
77
|
+
if (newToken) {
|
|
78
|
+
logger.info('Retrying request with expanded scopes...');
|
|
79
|
+
return this.performRequest(endpoint, newToken, options);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
85
|
+
}
|
|
68
86
|
if (!response.ok) {
|
|
69
87
|
throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText}`);
|
|
70
88
|
}
|
|
@@ -116,7 +134,7 @@ class GraphClient {
|
|
|
116
134
|
throw new Error('Excel operation requested without specifying a file');
|
|
117
135
|
}
|
|
118
136
|
const headers = {
|
|
119
|
-
|
|
137
|
+
Authorization: `Bearer ${accessToken}`,
|
|
120
138
|
'Content-Type': 'application/json',
|
|
121
139
|
...(sessionId && { 'workbook-session-id': sessionId }),
|
|
122
140
|
...options.headers,
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,17 @@ import { buildScopesFromEndpoints } from './auth.js';
|
|
|
9
9
|
async function main() {
|
|
10
10
|
try {
|
|
11
11
|
const args = parseArgs();
|
|
12
|
-
|
|
12
|
+
let includeWorkScopes = args.forceWorkScopes;
|
|
13
|
+
if (!includeWorkScopes) {
|
|
14
|
+
const tempAuthManager = new AuthManager(undefined, buildScopesFromEndpoints(false));
|
|
15
|
+
await tempAuthManager.loadTokenCache();
|
|
16
|
+
const hasWorkPermissions = await tempAuthManager.hasWorkAccountPermissions();
|
|
17
|
+
if (hasWorkPermissions) {
|
|
18
|
+
includeWorkScopes = true;
|
|
19
|
+
logger.info('Detected existing work account permissions, including work scopes');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const scopes = buildScopesFromEndpoints(includeWorkScopes);
|
|
13
23
|
const authManager = new AuthManager(undefined, scopes);
|
|
14
24
|
await authManager.loadTokenCache();
|
|
15
25
|
if (args.login) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect if a schema definition creates recursive references
|
|
12
|
+
* Handles complex recursive paths like #/definitions/X/properties/body/anyOf/1
|
|
13
|
+
*
|
|
14
|
+
* I really really hope this solves
|
|
15
|
+
* https://github.com/Softeria/ms-365-mcp-server/issues/36 and perhaps even
|
|
16
|
+
* https://github.com/Softeria/ms-365-mcp-server/issues/62
|
|
17
|
+
*
|
|
18
|
+
* Or any other silly tool that doesn't support recursive $refs
|
|
19
|
+
*
|
|
20
|
+
* Note - if the tool still struggles with $ref in general, this fix won't help!
|
|
21
|
+
*/
|
|
22
|
+
function detectRecursiveRefs(schema, definitionName) {
|
|
23
|
+
if (!schema || typeof schema !== 'object') return [];
|
|
24
|
+
|
|
25
|
+
const recursions = [];
|
|
26
|
+
const currentDefPath = `#/definitions/${definitionName}`;
|
|
27
|
+
|
|
28
|
+
function findAllRefs(obj, path = []) {
|
|
29
|
+
const refs = [];
|
|
30
|
+
|
|
31
|
+
function traverse(current, currentPath) {
|
|
32
|
+
if (!current || typeof current !== 'object') return;
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(current)) {
|
|
35
|
+
current.forEach((item, index) => traverse(item, [...currentPath, index]));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (current.$ref) {
|
|
40
|
+
refs.push({
|
|
41
|
+
ref: current.$ref,
|
|
42
|
+
path: currentPath.join('.'),
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Object.entries(current).forEach(([key, value]) => {
|
|
48
|
+
traverse(value, [...currentPath, key]);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
traverse(obj, path);
|
|
53
|
+
return refs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const allRefs = findAllRefs(schema);
|
|
57
|
+
|
|
58
|
+
for (const refInfo of allRefs) {
|
|
59
|
+
const ref = refInfo.ref;
|
|
60
|
+
|
|
61
|
+
if (ref.startsWith(currentDefPath)) {
|
|
62
|
+
recursions.push({
|
|
63
|
+
path: refInfo.path,
|
|
64
|
+
ref: ref,
|
|
65
|
+
type: 'recursive_reference',
|
|
66
|
+
});
|
|
67
|
+
} else if (ref === currentDefPath) {
|
|
68
|
+
recursions.push({
|
|
69
|
+
path: refInfo.path,
|
|
70
|
+
ref: ref,
|
|
71
|
+
type: 'direct_self_reference',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return recursions;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeRecursiveProperties(schema, recursions) {
|
|
80
|
+
if (!schema || typeof schema !== 'object' || recursions.length === 0) {
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cleaned = JSON.parse(JSON.stringify(schema));
|
|
85
|
+
|
|
86
|
+
const propertiesToRemove = new Set();
|
|
87
|
+
|
|
88
|
+
for (const recursion of recursions) {
|
|
89
|
+
const pathParts = recursion.path.split('.').filter((p) => p !== '');
|
|
90
|
+
|
|
91
|
+
if (pathParts[pathParts.length - 1] === 'items' && pathParts.length > 1) {
|
|
92
|
+
const propertyPath = pathParts.slice(0, -1).join('.');
|
|
93
|
+
propertiesToRemove.add(propertyPath);
|
|
94
|
+
} else {
|
|
95
|
+
propertiesToRemove.add(recursion.path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sortedPaths = Array.from(propertiesToRemove).sort(
|
|
100
|
+
(a, b) => b.split('.').length - a.split('.').length
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
for (const propertyPath of sortedPaths) {
|
|
104
|
+
const pathParts = propertyPath.split('.');
|
|
105
|
+
|
|
106
|
+
let current = cleaned;
|
|
107
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
108
|
+
const part = pathParts[i];
|
|
109
|
+
if (current && typeof current === 'object' && part in current) {
|
|
110
|
+
current = current[part];
|
|
111
|
+
} else {
|
|
112
|
+
current = null;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (current && typeof current === 'object') {
|
|
118
|
+
const propertyName = pathParts[pathParts.length - 1];
|
|
119
|
+
if (propertyName in current) {
|
|
120
|
+
console.log(` Removing recursive property: ${propertyPath}`);
|
|
121
|
+
delete current[propertyName];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return cleaned;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Process a tool to remove recursive references while keeping other $refs
|
|
131
|
+
*/
|
|
132
|
+
function processToolSchema(tool) {
|
|
133
|
+
if (!tool.inputSchema || !tool.inputSchema.definitions) {
|
|
134
|
+
return tool;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const definitions = tool.inputSchema.definitions;
|
|
138
|
+
const processedDefinitions = {};
|
|
139
|
+
let totalRecursionsRemoved = 0;
|
|
140
|
+
|
|
141
|
+
console.log(`\n🔧 Processing ${tool.name}:`);
|
|
142
|
+
|
|
143
|
+
for (const [defName, defSchema] of Object.entries(definitions)) {
|
|
144
|
+
const recursions = detectRecursiveRefs(defSchema, defName);
|
|
145
|
+
|
|
146
|
+
if (recursions.length > 0) {
|
|
147
|
+
console.log(` Found ${recursions.length} recursive references in ${defName}`);
|
|
148
|
+
processedDefinitions[defName] = removeRecursiveProperties(defSchema, recursions);
|
|
149
|
+
totalRecursionsRemoved += recursions.length;
|
|
150
|
+
} else {
|
|
151
|
+
processedDefinitions[defName] = defSchema;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const cleanedTool = {
|
|
156
|
+
...tool,
|
|
157
|
+
inputSchema: {
|
|
158
|
+
...tool.inputSchema,
|
|
159
|
+
definitions: processedDefinitions,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
console.log(` ✂️ Removed ${totalRecursionsRemoved} recursive references`);
|
|
164
|
+
return cleanedTool;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function removeRecursiveRefs() {
|
|
168
|
+
try {
|
|
169
|
+
console.log('✂️ Removing Recursive References (Keeping Other $refs)\n');
|
|
170
|
+
console.log('='.repeat(60));
|
|
171
|
+
|
|
172
|
+
const inputPath = join(__dirname, 'schemas-with-refs-direct.json');
|
|
173
|
+
if (!fs.existsSync(inputPath)) {
|
|
174
|
+
throw new Error('Schema file not found. Run extract-schemas-direct.js first.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const originalData = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
|
178
|
+
const tools = originalData.result?.tools || [];
|
|
179
|
+
|
|
180
|
+
console.log(`Processing ${tools.length} tools...`);
|
|
181
|
+
|
|
182
|
+
const processedTools = tools.map(processToolSchema);
|
|
183
|
+
|
|
184
|
+
const cleanedData = {
|
|
185
|
+
...originalData,
|
|
186
|
+
result: {
|
|
187
|
+
...originalData.result,
|
|
188
|
+
tools: processedTools,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const outputPath = join(__dirname, 'schemas-properties-removed.json');
|
|
193
|
+
const cleanedString = JSON.stringify(cleanedData, null, 2);
|
|
194
|
+
fs.writeFileSync(outputPath, cleanedString);
|
|
195
|
+
|
|
196
|
+
console.log(`\n💾 Cleaned schemas saved to: ${outputPath}`);
|
|
197
|
+
|
|
198
|
+
const originalString = JSON.stringify(originalData);
|
|
199
|
+
const originalRefs = (originalString.match(/\$ref/g) || []).length;
|
|
200
|
+
const cleanedRefs = (cleanedString.match(/\$ref/g) || []).length;
|
|
201
|
+
const removedRefs = originalRefs - cleanedRefs;
|
|
202
|
+
|
|
203
|
+
console.log('\n📊 CLEANING ANALYSIS');
|
|
204
|
+
console.log('-'.repeat(40));
|
|
205
|
+
console.log(
|
|
206
|
+
`Original size: ${originalString.length.toLocaleString()} chars (${(originalString.length / 1024).toFixed(2)} KB)`
|
|
207
|
+
);
|
|
208
|
+
console.log(
|
|
209
|
+
`Cleaned size: ${cleanedString.length.toLocaleString()} chars (${(cleanedString.length / 1024).toFixed(2)} KB)`
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const sizeDiff = cleanedString.length - originalString.length;
|
|
213
|
+
const sizeChange = ((sizeDiff / originalString.length) * 100).toFixed(1);
|
|
214
|
+
console.log(`Size change: ${sizeDiff.toLocaleString()} chars (${sizeChange}%)`);
|
|
215
|
+
|
|
216
|
+
console.log(`\nOriginal $refs: ${originalRefs}`);
|
|
217
|
+
console.log(`Cleaned $refs: ${cleanedRefs}`);
|
|
218
|
+
console.log(`Removed $refs: ${removedRefs}`);
|
|
219
|
+
console.log(`Refs remaining: ${((cleanedRefs / originalRefs) * 100).toFixed(1)}%`);
|
|
220
|
+
|
|
221
|
+
console.log('\n🧪 QUICK RECURSION CHECK');
|
|
222
|
+
console.log('-'.repeat(40));
|
|
223
|
+
|
|
224
|
+
const sampleRecursiveTools = [
|
|
225
|
+
'create-calendar-event',
|
|
226
|
+
'update-calendar-event',
|
|
227
|
+
'create-onenote-page',
|
|
228
|
+
];
|
|
229
|
+
let foundRecursions = 0;
|
|
230
|
+
|
|
231
|
+
for (const toolName of sampleRecursiveTools) {
|
|
232
|
+
const tool = processedTools.find((t) => t.name === toolName);
|
|
233
|
+
if (tool) {
|
|
234
|
+
const toolString = JSON.stringify(tool);
|
|
235
|
+
const recursivePattern = `#/definitions/${toolName}Parameters/properties/body/anyOf/1`;
|
|
236
|
+
if (toolString.includes(recursivePattern)) {
|
|
237
|
+
foundRecursions++;
|
|
238
|
+
console.log(` ❌ ${toolName}: Still contains recursive pattern`);
|
|
239
|
+
} else {
|
|
240
|
+
console.log(` ✅ ${toolName}: Recursive pattern removed`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (foundRecursions === 0) {
|
|
246
|
+
console.log('\n✅ No recursive patterns found in sample tools!');
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`\n⚠️ ${foundRecursions} tools still contain recursive patterns`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('\n💡 SUMMARY');
|
|
252
|
+
console.log('='.repeat(40));
|
|
253
|
+
|
|
254
|
+
if (removedRefs > 0) {
|
|
255
|
+
console.log(`✅ Successfully removed ${removedRefs} recursive references`);
|
|
256
|
+
console.log(`✅ Kept ${cleanedRefs} non-recursive $ref references`);
|
|
257
|
+
|
|
258
|
+
if (sizeDiff < 0) {
|
|
259
|
+
console.log(`✅ Reduced schema size by ${Math.abs(sizeDiff).toLocaleString()} characters`);
|
|
260
|
+
} else if (sizeDiff < originalString.length * 0.1) {
|
|
261
|
+
console.log(`✅ Minimal size increase (${sizeChange}%)`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log('\n📋 BENEFITS:');
|
|
265
|
+
console.log('• Eliminates infinite recursion during flattening');
|
|
266
|
+
console.log('• Preserves beneficial $ref references for shared types');
|
|
267
|
+
console.log('• May allow partial flattening for LangChain compatibility');
|
|
268
|
+
console.log('• Reduces schema complexity while maintaining functionality');
|
|
269
|
+
} else {
|
|
270
|
+
console.log('ℹ️ No recursive references found to remove');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const remainingRefTypes = new Set();
|
|
274
|
+
cleanedString.match(/"#\/definitions\/[^"]+"/g)?.forEach((ref) => {
|
|
275
|
+
const defName = ref.split('/').pop()?.replace('"', '');
|
|
276
|
+
if (defName) remainingRefTypes.add(defName);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
console.log(`\n🔗 Remaining reference types: ${remainingRefTypes.size}`);
|
|
280
|
+
if (remainingRefTypes.size <= 10) {
|
|
281
|
+
console.log('Sample remaining refs:', Array.from(remainingRefTypes).slice(0, 5).join(', '));
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('Error removing recursive refs:', error.message);
|
|
285
|
+
console.error(error.stack);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export { removeRecursiveRefs };
|
|
291
|
+
|
|
292
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
293
|
+
removeRecursiveRefs();
|
|
294
|
+
}
|