@officecli/officecli 1.0.102 → 1.0.122
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 +34 -2
- package/install.js +21 -0
- package/lib/install-binary.js +247 -0
- package/package.json +46 -3
package/README.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# officecli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for reading and writing Office documents (`.docx`, `.xlsx`, `.pptx`) via a
|
|
4
|
+
document DOM API.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g @officecli/officecli
|
|
8
|
+
# or run without installing:
|
|
9
|
+
npx @officecli/officecli --help
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
On install, the native binary for your platform (macOS / Linux / Windows,
|
|
13
|
+
x64 / arm64) is downloaded from the official release mirror
|
|
14
|
+
(`d.officecli.ai`, with GitHub Releases as a fallback) and verified against
|
|
15
|
+
its published `SHA256SUMS`. macOS builds are Developer ID signed and notarized.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
officecli create report.docx
|
|
21
|
+
officecli add report.docx /body --type paragraph --prop text="Hello"
|
|
22
|
+
officecli get report.docx '/body/p[1]'
|
|
23
|
+
officecli --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
- Supported platforms: macOS (arm64/x64), Linux glibc & musl/Alpine
|
|
29
|
+
(arm64/x64), Windows (arm64/x64).
|
|
30
|
+
- Set `OFFICECLI_SKIP_BINARY_DOWNLOAD=1` to skip the download during
|
|
31
|
+
`npm install` (the binary is then fetched on first run).
|
|
32
|
+
- Source, issues and full docs:
|
|
33
|
+
<https://github.com/iOfficeAI/OfficeCLI>
|
|
34
|
+
|
|
35
|
+
Licensed under Apache-2.0.
|
package/install.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// postinstall entry point. Downloads the platform binary up-front so the first
|
|
4
|
+
// `officecli` call is instant. A failure here is non-fatal: the bin shim
|
|
5
|
+
// (bin/officecli.js) lazily downloads on first run, so an offline/proxied
|
|
6
|
+
// install still leaves a working command once connectivity returns. Set
|
|
7
|
+
// OFFICECLI_SKIP_BINARY_DOWNLOAD=1 to skip the download entirely.
|
|
8
|
+
|
|
9
|
+
if (process.env.OFFICECLI_SKIP_BINARY_DOWNLOAD) {
|
|
10
|
+
process.stderr.write('[officecli] OFFICECLI_SKIP_BINARY_DOWNLOAD set, skipping binary download.\n');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
require('./lib/install-binary')
|
|
15
|
+
.ensureBinary()
|
|
16
|
+
.catch(function (err) {
|
|
17
|
+
process.stderr.write('[officecli] postinstall could not fetch the binary: ' + err.message + '\n');
|
|
18
|
+
process.stderr.write('[officecli] it will be downloaded on first run instead.\n');
|
|
19
|
+
// Exit 0 so `npm install` succeeds; the shim retries lazily.
|
|
20
|
+
process.exit(0);
|
|
21
|
+
});
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared installer logic for the @officecli/officecli (and @aionui/officecli)
|
|
4
|
+
// npm packages. The package itself ships no native code: on install it fetches
|
|
5
|
+
// the platform binary from the SAME release mirror install.sh uses
|
|
6
|
+
// (d.officecli.ai primary, GitHub releases fallback), pinned to the IMMUTABLE
|
|
7
|
+
// versioned path so a freshly-published release never collides with a CDN-cached
|
|
8
|
+
// `latest`. Asset names and the mirror/fallback order mirror install.sh exactly.
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const https = require('https');
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const REPO = 'iOfficeAI/OfficeCLI';
|
|
18
|
+
const MIRROR_BASE = 'https://d.officecli.ai';
|
|
19
|
+
const GITHUB_BASE = 'https://github.com/' + REPO;
|
|
20
|
+
|
|
21
|
+
// The package version is set to the release version at publish time, so the
|
|
22
|
+
// tag we download from is derived directly from it (immutable, never stale).
|
|
23
|
+
const VERSION = require('../package.json').version;
|
|
24
|
+
const TAG = 'v' + VERSION;
|
|
25
|
+
|
|
26
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
27
|
+
const BIN_DIR = path.join(PKG_ROOT, 'bin');
|
|
28
|
+
|
|
29
|
+
function log(msg) {
|
|
30
|
+
// postinstall output goes to stderr so it never pollutes a command's stdout.
|
|
31
|
+
process.stderr.write('[officecli] ' + msg + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// musl detection, mirroring install.sh's gnu-vs-musl branch. process.report
|
|
35
|
+
// exposes glibcVersionRuntime on a glibc system; its absence (plus the Alpine
|
|
36
|
+
// marker / `ldd` text) means musl.
|
|
37
|
+
function isMusl() {
|
|
38
|
+
if (process.platform !== 'linux') return false;
|
|
39
|
+
try {
|
|
40
|
+
const report = process.report && process.report.getReport();
|
|
41
|
+
const header = report && report.header;
|
|
42
|
+
if (header && header.glibcVersionRuntime) return false;
|
|
43
|
+
if (header && header.glibcVersionRuntime === undefined) {
|
|
44
|
+
// No glibc runtime reported — treat as musl, but confirm below.
|
|
45
|
+
}
|
|
46
|
+
} catch (_) { /* fall through to filesystem/ldd probes */ }
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync('/etc/alpine-release')) return true;
|
|
49
|
+
} catch (_) { /* ignore */ }
|
|
50
|
+
try {
|
|
51
|
+
const out = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
|
|
52
|
+
if (/musl/i.test(out)) return true;
|
|
53
|
+
} catch (_) { /* ignore */ }
|
|
54
|
+
// Default to glibc when nothing positively indicates musl.
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function detectAsset() {
|
|
59
|
+
const platform = process.platform;
|
|
60
|
+
const arch = process.arch;
|
|
61
|
+
if (platform === 'darwin') {
|
|
62
|
+
if (arch === 'arm64') return 'officecli-mac-arm64';
|
|
63
|
+
if (arch === 'x64') return 'officecli-mac-x64';
|
|
64
|
+
} else if (platform === 'linux') {
|
|
65
|
+
const musl = isMusl();
|
|
66
|
+
if (arch === 'x64') return musl ? 'officecli-linux-alpine-x64' : 'officecli-linux-x64';
|
|
67
|
+
if (arch === 'arm64') return musl ? 'officecli-linux-alpine-arm64' : 'officecli-linux-arm64';
|
|
68
|
+
} else if (platform === 'win32') {
|
|
69
|
+
if (arch === 'x64') return 'officecli-win-x64.exe';
|
|
70
|
+
if (arch === 'arm64') return 'officecli-win-arm64.exe';
|
|
71
|
+
}
|
|
72
|
+
throw new Error(
|
|
73
|
+
'Unsupported platform: ' + platform + ' ' + arch +
|
|
74
|
+
'. Download manually from ' + GITHUB_BASE + '/releases'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function binaryName() {
|
|
79
|
+
return process.platform === 'win32' ? 'officecli.exe' : 'officecli';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function binaryPath() {
|
|
83
|
+
return path.join(BIN_DIR, binaryName());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function assetUrls(asset) {
|
|
87
|
+
// Mirror first (issues surface fast), GitHub fallback — same order as
|
|
88
|
+
// install.sh. Both use the immutable /releases/download/<tag>/ path.
|
|
89
|
+
return [
|
|
90
|
+
MIRROR_BASE + '/releases/download/' + TAG + '/' + asset,
|
|
91
|
+
GITHUB_BASE + '/releases/download/' + TAG + '/' + asset
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sumsUrls() {
|
|
96
|
+
return [
|
|
97
|
+
MIRROR_BASE + '/releases/download/' + TAG + '/SHA256SUMS',
|
|
98
|
+
GITHUB_BASE + '/releases/download/' + TAG + '/SHA256SUMS'
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function httpGet(url, onResponse, onError, redirects) {
|
|
103
|
+
redirects = redirects || 0;
|
|
104
|
+
if (redirects > 10) {
|
|
105
|
+
onError(new Error('Too many redirects for ' + url));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const req = https.get(
|
|
109
|
+
url,
|
|
110
|
+
{ headers: { 'User-Agent': 'officecli-npm-installer' } },
|
|
111
|
+
function (res) {
|
|
112
|
+
const code = res.statusCode;
|
|
113
|
+
if (code >= 300 && code < 400 && res.headers.location) {
|
|
114
|
+
res.resume();
|
|
115
|
+
const next = new URL(res.headers.location, url).toString();
|
|
116
|
+
httpGet(next, onResponse, onError, redirects + 1);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (code !== 200) {
|
|
120
|
+
res.resume();
|
|
121
|
+
onError(new Error('HTTP ' + code + ' for ' + url));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
onResponse(res);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
req.on('error', onError);
|
|
128
|
+
req.setTimeout(300000, function () {
|
|
129
|
+
req.destroy(new Error('Timeout downloading ' + url));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function fetchToFile(url, dest) {
|
|
134
|
+
return new Promise(function (resolve, reject) {
|
|
135
|
+
httpGet(
|
|
136
|
+
url,
|
|
137
|
+
function (res) {
|
|
138
|
+
const tmp = dest + '.download';
|
|
139
|
+
const out = fs.createWriteStream(tmp);
|
|
140
|
+
res.pipe(out);
|
|
141
|
+
out.on('error', reject);
|
|
142
|
+
out.on('finish', function () {
|
|
143
|
+
out.close(function () {
|
|
144
|
+
try {
|
|
145
|
+
fs.renameSync(tmp, dest);
|
|
146
|
+
resolve();
|
|
147
|
+
} catch (e) {
|
|
148
|
+
reject(e);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
reject
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function fetchBuffer(url) {
|
|
159
|
+
return new Promise(function (resolve, reject) {
|
|
160
|
+
httpGet(
|
|
161
|
+
url,
|
|
162
|
+
function (res) {
|
|
163
|
+
const chunks = [];
|
|
164
|
+
res.on('data', function (c) { chunks.push(c); });
|
|
165
|
+
res.on('end', function () { resolve(Buffer.concat(chunks)); });
|
|
166
|
+
res.on('error', reject);
|
|
167
|
+
},
|
|
168
|
+
reject
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function verifyChecksum(asset, file) {
|
|
174
|
+
let sums = null;
|
|
175
|
+
for (const url of sumsUrls()) {
|
|
176
|
+
try {
|
|
177
|
+
sums = (await fetchBuffer(url)).toString('utf8');
|
|
178
|
+
break;
|
|
179
|
+
} catch (_) { /* try next source */ }
|
|
180
|
+
}
|
|
181
|
+
if (!sums) {
|
|
182
|
+
log(' SHA256SUMS not available, skipping checksum verification.');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// SHA256SUMS rows are "<hex> <name>" (sha256sum text mode). Match the
|
|
186
|
+
// filename column EXACTLY (a leading '*' marks binary mode), never a
|
|
187
|
+
// substring — same rule as install.sh / the C# self-updater.
|
|
188
|
+
let expected = null;
|
|
189
|
+
for (const line of sums.split('\n')) {
|
|
190
|
+
const parts = line.trim().split(/\s+/);
|
|
191
|
+
if (parts.length >= 2) {
|
|
192
|
+
const name = parts[1].replace(/^\*/, '');
|
|
193
|
+
if (name === asset) { expected = parts[0]; break; }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!expected) {
|
|
197
|
+
log(' ' + asset + ' not listed in SHA256SUMS, skipping verification.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
201
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
202
|
+
throw new Error('Checksum mismatch for ' + asset + ' (expected ' + expected + ', got ' + actual + ')');
|
|
203
|
+
}
|
|
204
|
+
log(' checksum verified.');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Download the platform binary into bin/ if it is not already present.
|
|
208
|
+
// Idempotent: a non-empty binary is treated as already installed (the package
|
|
209
|
+
// version pins the release, so existence is sufficient).
|
|
210
|
+
async function ensureBinary() {
|
|
211
|
+
const dest = binaryPath();
|
|
212
|
+
if (fs.existsSync(dest) && fs.statSync(dest).size > 0) {
|
|
213
|
+
return dest;
|
|
214
|
+
}
|
|
215
|
+
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
216
|
+
const asset = detectAsset();
|
|
217
|
+
let lastErr = null;
|
|
218
|
+
for (const url of assetUrls(asset)) {
|
|
219
|
+
try {
|
|
220
|
+
log('Downloading ' + asset + ' (' + TAG + ') from ' + url + ' ...');
|
|
221
|
+
await fetchToFile(url, dest);
|
|
222
|
+
await verifyChecksum(asset, dest);
|
|
223
|
+
if (process.platform !== 'win32') {
|
|
224
|
+
fs.chmodSync(dest, 0o755);
|
|
225
|
+
}
|
|
226
|
+
log('OfficeCLI ' + VERSION + ' installed.');
|
|
227
|
+
return dest;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
lastErr = e;
|
|
230
|
+
try { fs.rmSync(dest, { force: true }); } catch (_) { /* ignore */ }
|
|
231
|
+
log(' failed: ' + e.message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
throw new Error(
|
|
235
|
+
'Could not download OfficeCLI binary (' + asset + ' @ ' + TAG + '). ' +
|
|
236
|
+
'Last error: ' + (lastErr && lastErr.message) +
|
|
237
|
+
'. Install manually from ' + GITHUB_BASE + '/releases'
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
ensureBinary: ensureBinary,
|
|
243
|
+
binaryPath: binaryPath,
|
|
244
|
+
detectAsset: detectAsset,
|
|
245
|
+
VERSION: VERSION,
|
|
246
|
+
TAG: TAG
|
|
247
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@officecli/officecli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "OfficeCli —
|
|
3
|
+
"version": "1.0.122",
|
|
4
|
+
"description": "OfficeCli — CLI for reading and writing Office documents (.docx, .xlsx, .pptx) via a document DOM API. The native binary is fetched on install for your platform.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"author": "goworm"
|
|
6
|
+
"author": "goworm",
|
|
7
|
+
"homepage": "https://github.com/iOfficeAI/OfficeCLI",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/iOfficeAI/OfficeCLI.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/iOfficeAI/OfficeCLI/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"office",
|
|
17
|
+
"docx",
|
|
18
|
+
"xlsx",
|
|
19
|
+
"pptx",
|
|
20
|
+
"word",
|
|
21
|
+
"excel",
|
|
22
|
+
"powerpoint",
|
|
23
|
+
"ooxml",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"officecli": "bin/officecli.js"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"postinstall": "node install.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin/officecli.js",
|
|
34
|
+
"lib/install-binary.js",
|
|
35
|
+
"install.js",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"os": [
|
|
39
|
+
"darwin",
|
|
40
|
+
"linux",
|
|
41
|
+
"win32"
|
|
42
|
+
],
|
|
43
|
+
"cpu": [
|
|
44
|
+
"x64",
|
|
45
|
+
"arm64"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=14"
|
|
49
|
+
}
|
|
7
50
|
}
|