sphere-cli 0.1.41 → 0.2.1

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/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "sphere-cli",
3
- "version": "0.1.41",
4
- "description": "SPHERE CLI — synthetic data generation, evaluation, and certification",
5
- "keywords": ["synthetic-data", "privacy", "cli", "data-science"],
3
+ "version": "0.2.1",
4
+ "description": "SPHERE CLI — synthetic data generation, evaluation, and certification (sealed native binary)",
5
+ "keywords": [
6
+ "synthetic-data",
7
+ "privacy",
8
+ "cli",
9
+ "data-science"
10
+ ],
6
11
  "homepage": "https://github.com/statzihuai/sphere-cli",
7
12
  "bugs": "https://github.com/statzihuai/sphere-cli/issues",
8
13
  "repository": {
@@ -14,23 +19,22 @@
14
19
  "sphere": "bin/sphere.js"
15
20
  },
16
21
  "scripts": {
17
- "postinstall": "node scripts/postinstall.js"
18
- },
19
- "dependencies": {
20
- "bytenode": "^1.5.7"
22
+ "postinstall": "node scripts/postinstall.js",
23
+ "release": "bash scripts/release.sh"
21
24
  },
22
25
  "files": [
23
- "bin/",
24
- "examples/",
25
- "scripts/postinstall.js",
26
- "scripts/evaluate.py",
27
- "scripts/certify.py",
28
- "sphere_cli/",
29
- "sphere-node.js"
26
+ "bin/sphere.js",
27
+ "scripts/postinstall.js"
30
28
  ],
31
29
  "engines": {
32
30
  "node": ">=18.0.0"
33
31
  },
34
- "os": ["darwin", "linux"],
35
- "cpu": ["arm64", "x64"]
32
+ "os": [
33
+ "darwin",
34
+ "linux"
35
+ ],
36
+ "cpu": [
37
+ "arm64",
38
+ "x64"
39
+ ]
36
40
  }
@@ -1,93 +1,133 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * SPHERE CLI postinstall — compiles sphere-node.js to V8 bytecode.
4
+ * SPHERE CLI postinstall — download the platform-specific sealed binary.
5
5
  *
6
- * sphere-node.js in this package is an obfuscated JS bundle of the SPHERE
7
- * algorithm. Compiling it to V8 bytecode (.jsc) at install time ensures the
8
- * bytecode matches the Node.js version on this machine, preventing
9
- * decompilation. After compilation the source file is replaced by a tiny
10
- * loader stub so only bytecode remains on disk.
6
+ * The SPHERE engine ships as a signed + notarized native binary (one per
7
+ * platform) hosted on GitHub Releases. This script detects the platform,
8
+ * downloads the matching tarball, verifies its SHA-256 against the published
9
+ * SHA256SUMS.txt, and extracts it into ./vendor/. No algorithm source ships in
10
+ * the npm package only this downloader + a thin launcher.
11
11
  *
12
- * This mirrors exactly how the SPHERE desktop app protects its code.
12
+ * Env:
13
+ * SPHERE_SKIP_POSTINSTALL=1 skip download (CI / offline)
14
+ * SPHERE_BINARY_BASEURL=... override the release base URL (testing)
13
15
  */
14
16
 
17
+ const fs = require('fs');
15
18
  const path = require('path');
16
- const fs = require('fs');
19
+ const crypto = require('crypto');
20
+ const { execFileSync } = require('child_process');
17
21
 
18
- const PKG_DIR = path.join(__dirname, '..');
19
- const SRC = path.join(PKG_DIR, 'sphere-node.js');
20
- const OUT = path.join(PKG_DIR, 'sphere-node.jsc');
21
- const STUB = "'use strict';\nrequire('bytenode');\nrequire('./sphere-node.jsc');\n";
22
- const VERSION = require('../package.json').version;
23
- const MARKER = path.join(PKG_DIR, '.sphere-node-version');
22
+ const PKG = require('../package.json');
23
+ const VERSION = PKG.version;
24
+ const REPO = 'statzihuai/sphere-cli';
25
+ // Binary release tag — decoupled from the npm package version so JS-only patch
26
+ // releases reuse the same prebuilt/notarized binaries without re-uploading them.
27
+ const BINARY_RELEASE = 'v0.2.0';
28
+
29
+ const PLATFORM = process.platform; // 'darwin' | 'linux'
30
+ const ARCH = process.arch; // 'arm64' | 'x64'
31
+ const KEY = `${PLATFORM}-${ARCH}`;
32
+ const SUPPORTED = new Set(['darwin-arm64', 'darwin-x64', 'linux-x64']);
33
+
34
+ const PKG_ROOT = path.join(__dirname, '..');
35
+ const VENDOR = path.join(PKG_ROOT, 'vendor');
36
+ const ASSET = `sphere-${KEY}.tar.gz`;
37
+ const BASE = process.env.SPHERE_BINARY_BASEURL
38
+ || `https://github.com/${REPO}/releases/download/${BINARY_RELEASE}`;
39
+
40
+ function log(m) { process.stdout.write(`sphere-cli: ${m}\n`); }
41
+ function fail(m) { process.stderr.write(`sphere-cli: ${m}\n`); process.exit(1); }
24
42
 
25
- // ── Skip flag ─────────────────────────────────────────────────────────────────
26
43
  if (process.env.SPHERE_SKIP_POSTINSTALL === '1') {
27
- process.stdout.write('SPHERE_SKIP_POSTINSTALL=1 — skipping bytecode compilation.\n');
44
+ log('SPHERE_SKIP_POSTINSTALL=1 — skipping binary download.');
28
45
  process.exit(0);
29
46
  }
30
47
 
31
- // ── Already compiled for this version? ───────────────────────────────────────
32
- const installedVer = fs.existsSync(MARKER)
33
- ? fs.readFileSync(MARKER, 'utf8').trim()
34
- : null;
35
-
36
- if (fs.existsSync(OUT) && installedVer === VERSION) {
37
- process.stdout.write(`✓ SPHERE algorithm already compiled for v${VERSION}.\n`);
48
+ if (!SUPPORTED.has(KEY)) {
49
+ // Don't hard-fail the install; the launcher prints guidance if run.
50
+ process.stderr.write(
51
+ `sphere-cli: no prebuilt binary for ${KEY} yet ` +
52
+ `(supported: ${[...SUPPORTED].join(', ')}).\n`,
53
+ );
38
54
  process.exit(0);
39
55
  }
40
56
 
41
- // ── Source check ──────────────────────────────────────────────────────────────
42
- if (!fs.existsSync(SRC)) {
43
- process.stderr.write('sphere-node.js not found in package — reinstall sphere-cli.\n');
44
- process.exit(1);
57
+ // Already installed for this version?
58
+ const STAMP = path.join(VENDOR, '.version');
59
+ const BIN = path.join(VENDOR, 'sphere-cli', 'sphere');
60
+ if (fs.existsSync(BIN) && fs.existsSync(STAMP) &&
61
+ fs.readFileSync(STAMP, 'utf8').trim() === BINARY_RELEASE) {
62
+ log(`binary already present (${BINARY_RELEASE}).`);
63
+ process.exit(0);
45
64
  }
46
65
 
47
- const srcText = fs.readFileSync(SRC, 'utf8');
48
- if (srcText.includes("require('bytenode')") && srcText.length < 200) {
49
- // Already stubbed from a previous install .jsc must exist
50
- if (fs.existsSync(OUT)) {
51
- process.stdout.write('✓ SPHERE algorithm already compiled.\n');
52
- process.exit(0);
53
- }
54
- process.stderr.write('sphere-node.js is a stub but sphere-node.jsc is missing. Reinstall sphere-cli.\n');
55
- process.exit(1);
66
+ function curl(url, dest) {
67
+ execFileSync('curl', ['-fL', '--retry', '3', '--retry-delay', '2',
68
+ '--connect-timeout', '30', '-o', dest, url], { stdio: ['ignore', 'ignore', 'inherit'] });
56
69
  }
57
70
 
58
- // ── Compile ───────────────────────────────────────────────────────────────────
59
- process.stdout.write('Compiling SPHERE algorithm for your Node.js version …\n');
71
+ function sha256(file) {
72
+ const h = crypto.createHash('sha256');
73
+ h.update(fs.readFileSync(file));
74
+ return h.digest('hex');
75
+ }
60
76
 
61
- let bytenode;
62
77
  try {
63
- bytenode = require('bytenode');
64
- } catch {
65
- process.stderr.write(
66
- 'bytenode is not installed — this is unexpected.\n' +
67
- 'Try: cd "$(npm root -g)/sphere-cli" && npm install bytenode\n',
68
- );
69
- process.exit(1);
70
- }
78
+ fs.mkdirSync(VENDOR, { recursive: true });
79
+ const tarball = path.join(VENDOR, ASSET);
80
+
81
+ log(`downloading ${ASSET} (v${VERSION}) …`);
82
+ curl(`${BASE}/${ASSET}`, tarball);
83
+
84
+ // Verify checksum against the published SHA256SUMS.txt
85
+ const sumsFile = path.join(VENDOR, 'SHA256SUMS.txt');
86
+ curl(`${BASE}/SHA256SUMS.txt`, sumsFile);
87
+ const sums = fs.readFileSync(sumsFile, 'utf8');
88
+ const expected = sums.split('\n')
89
+ .map((l) => l.trim().split(/\s+/))
90
+ .find((p) => p[1] && p[1].endsWith(ASSET));
91
+ if (!expected) fail(`no checksum for ${ASSET} in SHA256SUMS.txt`);
92
+ const got = sha256(tarball);
93
+ if (got !== expected[0]) {
94
+ fail(`checksum mismatch for ${ASSET}\n expected ${expected[0]}\n got ${got}`);
95
+ }
96
+ log('checksum verified ✓');
71
97
 
72
- Promise.resolve(bytenode.compileFile({ filename: SRC, output: OUT }))
73
- .then(() => {
74
- // Replace source with loader stub so no readable code remains on disk
75
- fs.writeFileSync(SRC, STUB, 'utf8');
76
- // Write version marker
77
- fs.writeFileSync(MARKER, VERSION, 'utf8');
78
-
79
- const sizeKB = (fs.statSync(OUT).size / 1024).toFixed(1);
80
- process.stdout.write(`✓ SPHERE algorithm compiled (${sizeKB} KB).\n\n`);
81
- process.stdout.write(' Quick start:\n');
82
- process.stdout.write(' sphere generate data.csv -o synthetic.csv\n');
83
- process.stdout.write(" Run 'sphere --help' for all options.\n\n");
84
- })
85
- .catch((err) => {
86
- process.stderr.write(`\n✗ SPHERE compilation failed: ${err.message}\n`);
87
- process.stderr.write(
88
- 'The package was installed but the algorithm could not be compiled.\n' +
89
- 'Try reinstalling: npm install -g sphere-cli\n\n',
90
- );
91
- // Exit 0 so npm install doesn't fail — will error on first use
92
- process.exit(0);
93
- });
98
+ // Extract
99
+ fs.rmSync(path.join(VENDOR, 'sphere-cli'), { recursive: true, force: true });
100
+ execFileSync('tar', ['-xzf', tarball, '-C', VENDOR], { stdio: 'inherit' });
101
+ fs.chmodSync(BIN, 0o755);
102
+ fs.unlinkSync(tarball);
103
+ fs.rmSync(sumsFile, { force: true });
104
+ fs.writeFileSync(STAMP, BINARY_RELEASE);
105
+
106
+ // ── macOS: avoid a painfully slow FIRST run ────────────────────────────────
107
+ // A downloaded, unstapled notarized onedir pays a one-time Gatekeeper check of
108
+ // all ~330 bundled libraries on first launch (looks frozen on "loading pandas").
109
+ // (1) strip quarantine/provenance xattrs skips the slow *online* assessment;
110
+ // (2) warm the binary now (during install, when waiting is expected) so the
111
+ // user's first real command is instant. Both are best-effort.
112
+ if (PLATFORM === 'darwin') {
113
+ try { execFileSync('xattr', ['-cr', path.join(VENDOR, 'sphere-cli')], { stdio: 'ignore' }); } catch (_) {}
114
+ try {
115
+ log('warming binary (one-time macOS security scan, ~30–60s) …');
116
+ const warmIn = path.join(VENDOR, '.warm.csv');
117
+ const warmOut = path.join(VENDOR, '.warm_out.csv');
118
+ fs.writeFileSync(warmIn, 'a,b\n1.0,2.0\n3.0,4.0\n5.0,6.0\n');
119
+ execFileSync(BIN, ['generate', warmIn, '-o', warmOut], {
120
+ stdio: 'ignore',
121
+ timeout: 180000,
122
+ env: { ...process.env, SPHERE_LICENSE_REQUIRED: 'false' },
123
+ });
124
+ for (const f of [warmIn, warmOut, warmOut + '.sphere.json']) fs.rmSync(f, { force: true });
125
+ log('binary warmed ✓ — first run will be fast.');
126
+ } catch (_) { /* best-effort; user just pays the scan on first run */ }
127
+ }
128
+
129
+ log(`installed sealed binary for ${KEY} ✓`);
130
+ } catch (e) {
131
+ fail(`failed to install binary: ${e.message}\n` +
132
+ `You can retry with: npm rebuild sphere-cli`);
133
+ }