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.
@@ -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
+ };