@backtomyfuture/exchange-cli 0.2.5 → 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 +114 -13
- 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);
|
|
@@ -30,20 +56,74 @@ function copyDirRecursive(srcDir, dstDir) {
|
|
|
30
56
|
}
|
|
31
57
|
}
|
|
32
58
|
|
|
33
|
-
|
|
59
|
+
let inMemoryLayoutChecked = false;
|
|
60
|
+
|
|
61
|
+
function linkOrCopyFile(src, dst) {
|
|
62
|
+
try {
|
|
63
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
64
|
+
// Try hardlink first (fast, zero extra disk space, works on same filesystem)
|
|
65
|
+
try {
|
|
66
|
+
fs.linkSync(src, dst);
|
|
67
|
+
return;
|
|
68
|
+
} catch {
|
|
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.
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Best effort only: dst dir may be read-only.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function linkOrCopyDir(srcDir, dstDir) {
|
|
91
|
+
try {
|
|
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
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Best effort only: dst dir may be read-only.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function linkOrCopyIfMissing(src, dst, logger) {
|
|
34
107
|
if (fs.existsSync(dst) || !fs.existsSync(src)) {
|
|
35
108
|
return false;
|
|
36
109
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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;
|
|
45
126
|
}
|
|
46
|
-
return true;
|
|
47
127
|
}
|
|
48
128
|
|
|
49
129
|
function resolveFrameworkVersionDir(internalDir) {
|
|
@@ -70,22 +150,30 @@ function resolveFrameworkVersionDir(internalDir) {
|
|
|
70
150
|
}
|
|
71
151
|
|
|
72
152
|
function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
153
|
+
if (inMemoryLayoutChecked) {
|
|
154
|
+
return { changed: false };
|
|
155
|
+
}
|
|
73
156
|
if (!(process.platform === 'darwin' && process.arch === 'arm64')) {
|
|
157
|
+
inMemoryLayoutChecked = true;
|
|
74
158
|
return { changed: false };
|
|
75
159
|
}
|
|
76
160
|
const binDir = path.dirname(binaryPath);
|
|
77
161
|
const internalDir = path.join(binDir, '_internal');
|
|
78
162
|
if (!fs.existsSync(internalDir)) {
|
|
163
|
+
inMemoryLayoutChecked = true;
|
|
79
164
|
return { changed: false };
|
|
80
165
|
}
|
|
81
166
|
|
|
82
167
|
const markerPath = path.join(internalDir, '.runtime_layout_ok');
|
|
83
|
-
|
|
168
|
+
const userCacheMarkerPath = getUserCacheMarkerPath(internalDir);
|
|
169
|
+
if (fs.existsSync(markerPath) || fs.existsSync(userCacheMarkerPath)) {
|
|
170
|
+
inMemoryLayoutChecked = true;
|
|
84
171
|
return { changed: false };
|
|
85
172
|
}
|
|
86
173
|
|
|
87
174
|
const frameworkVersionDir = resolveFrameworkVersionDir(internalDir);
|
|
88
175
|
if (!frameworkVersionDir) {
|
|
176
|
+
inMemoryLayoutChecked = true;
|
|
89
177
|
return { changed: false };
|
|
90
178
|
}
|
|
91
179
|
const sourcePython = path.join(frameworkVersionDir, 'Python');
|
|
@@ -103,7 +191,7 @@ function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
|
103
191
|
|
|
104
192
|
let changed = false;
|
|
105
193
|
for (const target of targets) {
|
|
106
|
-
changed =
|
|
194
|
+
changed = linkOrCopyIfMissing(target.src, target.dst, logger) || changed;
|
|
107
195
|
}
|
|
108
196
|
|
|
109
197
|
try {
|
|
@@ -112,15 +200,28 @@ function ensureDarwinArm64RuntimeLayout(binaryPath, logger = null) {
|
|
|
112
200
|
// Best effort only.
|
|
113
201
|
}
|
|
114
202
|
|
|
203
|
+
let markerWritten = false;
|
|
115
204
|
try {
|
|
116
205
|
fs.writeFileSync(markerPath, '');
|
|
206
|
+
markerWritten = true;
|
|
117
207
|
} catch {
|
|
118
|
-
// 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
|
+
}
|
|
119
218
|
}
|
|
120
219
|
|
|
220
|
+
inMemoryLayoutChecked = true;
|
|
121
221
|
return { changed };
|
|
122
222
|
}
|
|
123
223
|
|
|
124
224
|
module.exports = {
|
|
125
225
|
ensureDarwinArm64RuntimeLayout,
|
|
226
|
+
getUserCacheMarkerPath,
|
|
126
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": {
|