m365-cli 0.1.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.
- package/README.md +683 -0
- package/bin/m365.js +489 -0
- package/config/default.json +18 -0
- package/package.json +36 -0
- package/src/auth/device-flow.js +154 -0
- package/src/auth/token-manager.js +237 -0
- package/src/commands/calendar.js +279 -0
- package/src/commands/mail.js +353 -0
- package/src/commands/onedrive.js +423 -0
- package/src/commands/sharepoint.js +312 -0
- package/src/graph/client.js +875 -0
- package/src/utils/config.js +60 -0
- package/src/utils/error.js +114 -0
- package/src/utils/output.js +850 -0
- package/src/utils/trusted-senders.js +190 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Load default config
|
|
10
|
+
const configPath = join(__dirname, '../../config/default.json');
|
|
11
|
+
const defaultConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Expand ~ to home directory
|
|
15
|
+
*/
|
|
16
|
+
function expandHome(filepath) {
|
|
17
|
+
if (filepath.startsWith('~/')) {
|
|
18
|
+
return join(homedir(), filepath.slice(2));
|
|
19
|
+
}
|
|
20
|
+
return filepath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get configuration value
|
|
25
|
+
* Priority: ENV > default config
|
|
26
|
+
*/
|
|
27
|
+
export function getConfig(key) {
|
|
28
|
+
// Check environment variable
|
|
29
|
+
const envKey = `M365_${key.toUpperCase().replace(/\./g, '_')}`;
|
|
30
|
+
if (process.env[envKey]) {
|
|
31
|
+
return process.env[envKey];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return default config
|
|
35
|
+
return defaultConfig[key];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get credentials file path
|
|
40
|
+
*/
|
|
41
|
+
export function getCredsPath() {
|
|
42
|
+
const path = getConfig('credsPath');
|
|
43
|
+
return expandHome(path);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all config as object
|
|
48
|
+
*/
|
|
49
|
+
export function getAllConfig() {
|
|
50
|
+
return {
|
|
51
|
+
...defaultConfig,
|
|
52
|
+
credsPath: getCredsPath(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
get: getConfig,
|
|
58
|
+
getCredsPath,
|
|
59
|
+
getAll: getAllConfig,
|
|
60
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for M365 CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class M365Error extends Error {
|
|
6
|
+
constructor(message, code, details = null) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'M365Error';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.details = details;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class AuthError extends M365Error {
|
|
15
|
+
constructor(message, details = null) {
|
|
16
|
+
super(message, 'AUTH_ERROR', details);
|
|
17
|
+
this.name = 'AuthError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ApiError extends M365Error {
|
|
22
|
+
constructor(message, statusCode, details = null) {
|
|
23
|
+
super(message, 'API_ERROR', details);
|
|
24
|
+
this.name = 'ApiError';
|
|
25
|
+
this.statusCode = statusCode;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class TokenExpiredError extends AuthError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('Token expired and refresh failed. Please run: m365 login');
|
|
32
|
+
this.name = 'TokenExpiredError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle and format errors
|
|
38
|
+
*/
|
|
39
|
+
export function handleError(error, options = {}) {
|
|
40
|
+
const { json = false } = options;
|
|
41
|
+
|
|
42
|
+
if (json) {
|
|
43
|
+
const errorObj = {
|
|
44
|
+
error: true,
|
|
45
|
+
message: error.message,
|
|
46
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (error.details) {
|
|
50
|
+
errorObj.details = error.details;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (error.statusCode) {
|
|
54
|
+
errorObj.statusCode = error.statusCode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.error(JSON.stringify(errorObj, null, 2));
|
|
58
|
+
} else {
|
|
59
|
+
console.error(`❌ Error: ${error.message}`);
|
|
60
|
+
|
|
61
|
+
if (error.details) {
|
|
62
|
+
console.error(` Details: ${JSON.stringify(error.details, null, 2)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse Graph API error response
|
|
71
|
+
*/
|
|
72
|
+
export function parseGraphError(response, statusCode) {
|
|
73
|
+
let message = `API Error (${statusCode})`;
|
|
74
|
+
let details = null;
|
|
75
|
+
|
|
76
|
+
if (response.error) {
|
|
77
|
+
const errorCode = response.error.code;
|
|
78
|
+
|
|
79
|
+
// Map common error codes to user-friendly messages
|
|
80
|
+
const friendlyMessages = {
|
|
81
|
+
'ErrorInvalidIdMalformed': '无效的 ID 格式。请检查您提供的 ID 是否正确。',
|
|
82
|
+
'itemNotFound': '找不到指定的文件或文件夹。请检查路径是否存在。',
|
|
83
|
+
'InvalidRequest': '无效的请求。请检查您的参数格式。',
|
|
84
|
+
'invalidRequest': '无效的请求。请检查您的参数格式。',
|
|
85
|
+
'InvalidHostname': '无效的站点主机名。请检查 SharePoint 站点格式。',
|
|
86
|
+
'resourceNotFound': '找不到指定的资源。',
|
|
87
|
+
'ErrorItemNotFound': '找不到指定的项目。',
|
|
88
|
+
'mailboxNotFound': '找不到邮箱。',
|
|
89
|
+
'folderNotFound': '找不到指定的文件夹。',
|
|
90
|
+
'ErrorNonExistentMailbox': '邮箱不存在。',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Use friendly message if available, otherwise use original
|
|
94
|
+
message = friendlyMessages[errorCode] || response.error.message || message;
|
|
95
|
+
|
|
96
|
+
// Include error code in details for debugging
|
|
97
|
+
details = {
|
|
98
|
+
code: errorCode,
|
|
99
|
+
originalMessage: response.error.message,
|
|
100
|
+
innerError: response.error.innerError,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new ApiError(message, statusCode, details);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default {
|
|
108
|
+
M365Error,
|
|
109
|
+
AuthError,
|
|
110
|
+
ApiError,
|
|
111
|
+
TokenExpiredError,
|
|
112
|
+
handleError,
|
|
113
|
+
parseGraphError,
|
|
114
|
+
};
|