@backtomyfuture/exchange-cli 0.2.6 → 0.2.7
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/bin/exchange-cli.js +40 -1
- package/bin/runtime-layout.js +86 -32
- package/package.json +15 -8
package/bin/exchange-cli.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { spawn } = require('child_process');
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
|
|
8
|
+
const startTime = process.hrtime.bigint ? process.hrtime.bigint() : Date.now();
|
|
9
|
+
|
|
10
|
+
function getRequestId() {
|
|
11
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
12
|
+
const arg = process.argv[i];
|
|
13
|
+
if (arg.startsWith('--request-id=')) {
|
|
14
|
+
const val = arg.slice('--request-id='.length).trim();
|
|
15
|
+
if (val) return val;
|
|
16
|
+
}
|
|
17
|
+
if (arg === '--request-id' && i + 1 < process.argv.length) {
|
|
18
|
+
const val = process.argv[i + 1].trim();
|
|
19
|
+
if (val) return val;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (typeof crypto.randomUUID === 'function') {
|
|
23
|
+
return crypto.randomUUID();
|
|
24
|
+
}
|
|
25
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
const PLATFORM_PACKAGES = {
|
|
8
29
|
'darwin-arm64': '@backtomyfuture/exchange-cli-darwin-arm64',
|
|
9
30
|
'darwin-x64': '@backtomyfuture/exchange-cli-darwin-x64',
|
|
@@ -24,12 +45,21 @@ function renderError(message, code = 'BINARY_NOT_FOUND', exitCode = 1) {
|
|
|
24
45
|
if (isText) {
|
|
25
46
|
console.error(`Error [${code}]: ${message}`);
|
|
26
47
|
} else {
|
|
48
|
+
const requestId = getRequestId();
|
|
49
|
+
const elapsedMs = process.hrtime.bigint
|
|
50
|
+
? Number(process.hrtime.bigint() - startTime) / 1e6
|
|
51
|
+
: Date.now() - startTime;
|
|
27
52
|
console.log(
|
|
28
53
|
JSON.stringify({
|
|
29
54
|
ok: false,
|
|
30
55
|
error: message,
|
|
31
56
|
code: code,
|
|
32
57
|
retryable: false,
|
|
58
|
+
request_id: requestId,
|
|
59
|
+
meta: {
|
|
60
|
+
request_id: requestId,
|
|
61
|
+
elapsed_ms: Math.round(elapsedMs * 100) / 100,
|
|
62
|
+
},
|
|
33
63
|
})
|
|
34
64
|
);
|
|
35
65
|
}
|
|
@@ -46,6 +76,15 @@ function getBinaryPath() {
|
|
|
46
76
|
1
|
|
47
77
|
);
|
|
48
78
|
}
|
|
79
|
+
try {
|
|
80
|
+
fs.accessSync(customBin, fs.constants.X_OK);
|
|
81
|
+
} catch {
|
|
82
|
+
renderError(
|
|
83
|
+
`exchange-cli: binary at EXCHANGE_CLI_BINARY is not executable: ${customBin}`,
|
|
84
|
+
'BINARY_SPAWN_FAILED',
|
|
85
|
+
1
|
|
86
|
+
);
|
|
87
|
+
}
|
|
49
88
|
return customBin;
|
|
50
89
|
}
|
|
51
90
|
|
|
@@ -92,7 +131,7 @@ try {
|
|
|
92
131
|
child.on('error', (err) => {
|
|
93
132
|
renderError(
|
|
94
133
|
`exchange-cli: failed to spawn binary: ${err.message}`,
|
|
95
|
-
'
|
|
134
|
+
'BINARY_SPAWN_FAILED',
|
|
96
135
|
1
|
|
97
136
|
);
|
|
98
137
|
});
|
package/bin/runtime-layout.js
CHANGED
|
@@ -2,9 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const crypto = require('crypto');
|
|
5
6
|
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
6
8
|
const path = require('path');
|
|
7
9
|
|
|
10
|
+
function getUserCacheMarkerPath(internalDir) {
|
|
11
|
+
const homeDir = os.homedir();
|
|
12
|
+
let cacheBase;
|
|
13
|
+
if (process.platform === 'darwin') {
|
|
14
|
+
cacheBase = path.join(homeDir, 'Library', 'Caches', 'exchange-cli');
|
|
15
|
+
} else if (process.platform === 'win32') {
|
|
16
|
+
cacheBase = path.join(
|
|
17
|
+
process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'),
|
|
18
|
+
'exchange-cli'
|
|
19
|
+
);
|
|
20
|
+
} else {
|
|
21
|
+
cacheBase = path.join(
|
|
22
|
+
process.env.XDG_CACHE_HOME || path.join(homeDir, '.cache'),
|
|
23
|
+
'exchange-cli'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const hash = crypto
|
|
27
|
+
.createHash('sha256')
|
|
28
|
+
.update(path.resolve(internalDir))
|
|
29
|
+
.digest('hex')
|
|
30
|
+
.slice(0, 16);
|
|
31
|
+
return path.join(cacheBase, `runtime_layout_${hash}.ok`);
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
function copyFileWithMode(src, dst) {
|
|
9
35
|
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
10
36
|
fs.copyFileSync(src, dst);
|
|
@@ -33,39 +59,47 @@ function copyDirRecursive(srcDir, dstDir) {
|
|
|
33
59
|
let inMemoryLayoutChecked = false;
|
|
34
60
|
|
|
35
61
|
function linkOrCopyFile(src, dst) {
|
|
36
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
37
|
-
// Try hardlink first (fast, zero extra disk space, works on same filesystem)
|
|
38
62
|
try {
|
|
39
|
-
fs.
|
|
40
|
-
|
|
41
|
-
} catch {
|
|
42
|
-
// Fall back to relative symlink
|
|
63
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
64
|
+
// Try hardlink first (fast, zero extra disk space, works on same filesystem)
|
|
43
65
|
try {
|
|
44
|
-
|
|
45
|
-
fs.symlinkSync(rel, dst);
|
|
66
|
+
fs.linkSync(src, dst);
|
|
46
67
|
return;
|
|
47
68
|
} catch {
|
|
48
|
-
// Fall back to
|
|
49
|
-
|
|
69
|
+
// Fall back to relative symlink
|
|
70
|
+
try {
|
|
71
|
+
const rel = path.relative(path.dirname(dst), src);
|
|
72
|
+
fs.symlinkSync(rel, dst);
|
|
73
|
+
return;
|
|
74
|
+
} catch {
|
|
75
|
+
// Fall back to file copy
|
|
76
|
+
fs.copyFileSync(src, dst);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const stat = fs.statSync(src);
|
|
81
|
+
fs.chmodSync(dst, stat.mode & 0o777);
|
|
82
|
+
} catch {
|
|
83
|
+
// Best effort only.
|
|
50
84
|
}
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const stat = fs.statSync(src);
|
|
54
|
-
fs.chmodSync(dst, stat.mode & 0o777);
|
|
55
85
|
} catch {
|
|
56
|
-
// Best effort only.
|
|
86
|
+
// Best effort only: dst dir may be read-only.
|
|
57
87
|
}
|
|
58
88
|
}
|
|
59
89
|
|
|
60
90
|
function linkOrCopyDir(srcDir, dstDir) {
|
|
61
|
-
fs.mkdirSync(path.dirname(dstDir), { recursive: true });
|
|
62
|
-
// Try symlink first for directories (like Versions/Current -> 3.12)
|
|
63
91
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
fs.mkdirSync(path.dirname(dstDir), { recursive: true });
|
|
93
|
+
// Try symlink first for directories (like Versions/Current -> 3.12)
|
|
94
|
+
try {
|
|
95
|
+
const rel = path.relative(path.dirname(dstDir), srcDir);
|
|
96
|
+
fs.symlinkSync(rel, dstDir, 'junction');
|
|
97
|
+
return;
|
|
98
|
+
} catch {
|
|
99
|
+
copyDirRecursive(srcDir, dstDir);
|
|
100
|
+
}
|
|
67
101
|
} catch {
|
|
68
|
-
|
|
102
|
+
// Best effort only: dst dir may be read-only.
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
105
|
|
|
@@ -73,16 +107,23 @@ function linkOrCopyIfMissing(src, dst, logger) {
|
|
|
73
107
|
if (fs.existsSync(dst) || !fs.existsSync(src)) {
|
|
74
108
|
return false;
|
|
75
109
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
try {
|
|
111
|
+
const stat = fs.statSync(src);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
linkOrCopyDir(src, dst);
|
|
114
|
+
} else {
|
|
115
|
+
linkOrCopyFile(src, dst);
|
|
116
|
+
}
|
|
117
|
+
if (fs.existsSync(dst)) {
|
|
118
|
+
if (logger) {
|
|
119
|
+
logger(`exchange-cli: repaired missing runtime path ${path.basename(dst)}`);
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
84
126
|
}
|
|
85
|
-
return true;
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
function resolveFrameworkVersionDir(internalDir) {
|
|
@@ -124,7 +165,8 @@ function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
|
124
165
|
}
|
|
125
166
|
|
|
126
167
|
const markerPath = path.join(internalDir, '.runtime_layout_ok');
|
|
127
|
-
|
|
168
|
+
const userCacheMarkerPath = getUserCacheMarkerPath(internalDir);
|
|
169
|
+
if (fs.existsSync(markerPath) || fs.existsSync(userCacheMarkerPath)) {
|
|
128
170
|
inMemoryLayoutChecked = true;
|
|
129
171
|
return { changed: false };
|
|
130
172
|
}
|
|
@@ -158,10 +200,21 @@ function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
|
158
200
|
// Best effort only.
|
|
159
201
|
}
|
|
160
202
|
|
|
203
|
+
let markerWritten = false;
|
|
161
204
|
try {
|
|
162
205
|
fs.writeFileSync(markerPath, '');
|
|
206
|
+
markerWritten = true;
|
|
163
207
|
} catch {
|
|
164
|
-
// Best effort only.
|
|
208
|
+
// Best effort only: internalDir may be read-only in system installations.
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!markerWritten) {
|
|
212
|
+
try {
|
|
213
|
+
fs.mkdirSync(path.dirname(userCacheMarkerPath), { recursive: true });
|
|
214
|
+
fs.writeFileSync(userCacheMarkerPath, '');
|
|
215
|
+
} catch {
|
|
216
|
+
// Best effort only.
|
|
217
|
+
}
|
|
165
218
|
}
|
|
166
219
|
|
|
167
220
|
inMemoryLayoutChecked = true;
|
|
@@ -170,4 +223,5 @@ function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
|
170
223
|
|
|
171
224
|
module.exports = {
|
|
172
225
|
ensureDarwinArm64RuntimeLayout,
|
|
226
|
+
getUserCacheMarkerPath,
|
|
173
227
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtomyfuture/exchange-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Cross-platform CLI for on-premises Microsoft Exchange Server",
|
|
5
5
|
"bin": {
|
|
6
6
|
"exchange-cli": "./bin/exchange-cli.js"
|
|
@@ -13,17 +13,24 @@
|
|
|
13
13
|
"install.js"
|
|
14
14
|
],
|
|
15
15
|
"optionalDependencies": {
|
|
16
|
-
"@backtomyfuture/exchange-cli-darwin-arm64": "0.2.
|
|
17
|
-
"@backtomyfuture/exchange-cli-darwin-x64": "0.2.
|
|
18
|
-
"@backtomyfuture/exchange-cli-linux-x64": "0.2.
|
|
19
|
-
"@backtomyfuture/exchange-cli-linux-arm64": "0.2.
|
|
20
|
-
"@backtomyfuture/exchange-cli-win32-x64": "0.2.
|
|
21
|
-
"@backtomyfuture/exchange-cli-win32-ia32": "0.2.
|
|
16
|
+
"@backtomyfuture/exchange-cli-darwin-arm64": "0.2.7",
|
|
17
|
+
"@backtomyfuture/exchange-cli-darwin-x64": "0.2.7",
|
|
18
|
+
"@backtomyfuture/exchange-cli-linux-x64": "0.2.7",
|
|
19
|
+
"@backtomyfuture/exchange-cli-linux-arm64": "0.2.7",
|
|
20
|
+
"@backtomyfuture/exchange-cli-win32-x64": "0.2.7",
|
|
21
|
+
"@backtomyfuture/exchange-cli-win32-ia32": "0.2.7"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=14"
|
|
25
25
|
},
|
|
26
|
-
"keywords": [
|
|
26
|
+
"keywords": [
|
|
27
|
+
"exchange",
|
|
28
|
+
"exchange-server",
|
|
29
|
+
"ews",
|
|
30
|
+
"cli",
|
|
31
|
+
"email",
|
|
32
|
+
"ai-agent"
|
|
33
|
+
],
|
|
27
34
|
"license": "Apache-2.0",
|
|
28
35
|
"homepage": "https://github.com/backtomyfuture/exchange-cli#readme",
|
|
29
36
|
"repository": {
|