@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 CHANGED
@@ -1,3 +1,35 @@
1
- # @officecli/officecli
1
+ # officecli
2
2
 
3
- Placeholder package. Work in progress.
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.102",
4
- "description": "OfficeCli — placeholder, work in progress.",
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
  }