graphonomous 0.1.0

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 ADDED
@@ -0,0 +1,210 @@
1
+ # graphonomous (npm wrapper)
2
+
3
+ This package provides an npm-friendly launcher for the Graphonomous MCP server CLI.
4
+
5
+ It installs (or reuses) a platform-specific `graphonomous` binary and exposes:
6
+
7
+ - `graphonomous ...`
8
+ - `npx graphonomous ...`
9
+
10
+ The underlying server communicates over **STDIO**, so it works well with MCP-capable editors/clients (for example Zed custom context servers).
11
+
12
+ ---
13
+
14
+ ## What this package does
15
+
16
+ - Detects your OS/arch (`darwin|linux` + `x64|arm64`)
17
+ - Downloads a matching release asset at install time
18
+ - Installs the OTP release command path under `vendor/<platform>-<arch>/graphonomous/bin/graphonomous` when available
19
+ - Creates/uses `vendor/<platform>-<arch>/graphonomous` as the launcher target for consistent execution
20
+ - Runs the resolved Graphonomous command with all arguments passed through
21
+
22
+ ---
23
+
24
+ ## Requirements
25
+
26
+ - Node.js `>= 18`
27
+ - Supported platforms:
28
+ - macOS: `x64`, `arm64`
29
+ - Linux: `x64`, `arm64`
30
+
31
+ ---
32
+
33
+ ## Install
34
+
35
+ ### Global install
36
+
37
+ ```sh
38
+ npm i -g graphonomous
39
+ ```
40
+
41
+ Then run:
42
+
43
+ ```sh
44
+ graphonomous --help
45
+ ```
46
+
47
+ ### One-off execution
48
+
49
+ ```sh
50
+ npx -y graphonomous --help
51
+ ```
52
+
53
+ ### Local project install
54
+
55
+ ```sh
56
+ npm i graphonomous
57
+ npx graphonomous --help
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Run examples
63
+
64
+ Start Graphonomous MCP server with a local DB path:
65
+
66
+ ```sh
67
+ graphonomous --db ~/.graphonomous/knowledge.db --embedder-backend fallback
68
+ ```
69
+
70
+ Safe laptop-oriented defaults:
71
+
72
+ ```sh
73
+ graphonomous \
74
+ --db ~/.graphonomous/knowledge.db \
75
+ --embedder-backend fallback \
76
+ --log-level info
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Zed configuration example
82
+
83
+ In Zed settings JSON:
84
+
85
+ ```json
86
+ {
87
+ "context_servers": {
88
+ "graphonomous": {
89
+ "command": "graphonomous",
90
+ "args": ["--db", "~/.graphonomous/knowledge.db", "--embedder-backend", "fallback"],
91
+ "env": {
92
+ "GRAPHONOMOUS_EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
93
+ }
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ If you prefer not to install globally:
100
+
101
+ ```json
102
+ {
103
+ "context_servers": {
104
+ "graphonomous": {
105
+ "command": "npx",
106
+ "args": ["-y", "graphonomous", "--db", "~/.graphonomous/knowledge.db", "--embedder-backend", "fallback"],
107
+ "env": {}
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Release asset override instructions
116
+
117
+ The installer supports override environment variables for custom repos/tags/asset hosting.
118
+
119
+ ### Override GitHub owner/repo/tag
120
+
121
+ ```sh
122
+ GRAPHONOMOUS_GITHUB_OWNER=my-org \
123
+ GRAPHONOMOUS_GITHUB_REPO=graphonomous \
124
+ GRAPHONOMOUS_RELEASE_TAG=v0.1.0 \
125
+ npm i graphonomous
126
+ ```
127
+
128
+ ### Override version used for asset naming
129
+
130
+ ```sh
131
+ GRAPHONOMOUS_VERSION=0.1.0 npm i graphonomous
132
+ ```
133
+
134
+ ### Use custom release base URL (bypass GitHub release URL construction)
135
+
136
+ `GRAPHONOMOUS_RELEASE_BASE_URL` should point to a directory containing assets named like:
137
+
138
+ `graphonomous-v<version>-<platform>-<arch>.tar.gz`
139
+
140
+ Example:
141
+
142
+ ```sh
143
+ GRAPHONOMOUS_RELEASE_BASE_URL=https://downloads.example.com/graphonomous \
144
+ GRAPHONOMOUS_VERSION=0.1.0 \
145
+ npm i graphonomous
146
+ ```
147
+
148
+ ### Private release download token
149
+
150
+ ```sh
151
+ GRAPHONOMOUS_GITHUB_TOKEN=ghp_xxx npm i graphonomous
152
+ ```
153
+
154
+ (You can also use `GITHUB_TOKEN`.)
155
+
156
+ ### Skip, force, and tune download behavior
157
+
158
+ ```sh
159
+ # Skip download entirely
160
+ GRAPHONOMOUS_SKIP_DOWNLOAD=1 npm i graphonomous
161
+
162
+ # Force re-download even if binary exists
163
+ GRAPHONOMOUS_FORCE_DOWNLOAD=1 npm i graphonomous
164
+
165
+ # Timeout and redirect controls
166
+ GRAPHONOMOUS_DOWNLOAD_TIMEOUT_MS=120000 \
167
+ GRAPHONOMOUS_DOWNLOAD_MAX_REDIRECTS=10 \
168
+ npm i graphonomous
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Runtime command override
174
+
175
+ You can bypass installed vendor binaries/release layout and point directly to a custom executable:
176
+
177
+ ```sh
178
+ GRAPHONOMOUS_BINARY_PATH=/absolute/path/to/graphonomous graphonomous --help
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Troubleshooting
184
+
185
+ ### Binary not found after install
186
+ Try reinstalling or rebuilding:
187
+
188
+ ```sh
189
+ npm rebuild graphonomous
190
+ # or
191
+ npm i graphonomous@latest
192
+ ```
193
+
194
+ ### Unsupported platform message
195
+ Current prebuilt targets are Linux/macOS + x64/arm64.
196
+
197
+ ### Permission issue on command path
198
+ Reinstall the package, or manually set executable bit on unix-like systems:
199
+
200
+ ```sh
201
+ chmod +x node_modules/graphonomous/vendor/<target>/graphonomous
202
+ chmod +x node_modules/graphonomous/vendor/<target>/graphonomous/bin/graphonomous
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Source of truth
208
+
209
+ The npm package is a distribution wrapper around the Graphonomous Elixir CLI.
210
+ Core implementation and release process live in the Graphonomous repository.
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * npm bin launcher for Graphonomous.
6
+ *
7
+ * This script resolves the installed platform-specific Graphonomous command in:
8
+ * npm/vendor/<platform>-<arch>/graphonomous/bin/graphonomous
9
+ *
10
+ * For OTP release assets, it executes the release command through:
11
+ * eval "Graphonomous.CLI.main(System.argv())"
12
+ * and passes through all CLI arguments.
13
+ */
14
+
15
+ const fs = require("fs");
16
+ const path = require("path");
17
+ const { spawn } = require("child_process");
18
+ const {
19
+ resolvePlatform,
20
+ isSupportedPlatform,
21
+ } = require("../scripts/resolve-platform");
22
+
23
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
24
+ const VENDOR_ROOT = path.join(PACKAGE_ROOT, "vendor");
25
+
26
+ function fail(message, code = 1) {
27
+ console.error(`[graphonomous] ${message}`);
28
+ process.exit(code);
29
+ }
30
+
31
+ function fileExists(p) {
32
+ try {
33
+ fs.accessSync(p, fs.constants.F_OK);
34
+ return true;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ function isExecutable(p) {
41
+ try {
42
+ fs.accessSync(p, fs.constants.X_OK);
43
+ return true;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ function resolveBinaryPath() {
50
+ const override = process.env.GRAPHONOMOUS_BINARY_PATH;
51
+ if (override && override.trim()) {
52
+ const candidate = path.resolve(override.trim());
53
+ if (!fileExists(candidate)) {
54
+ fail(
55
+ `GRAPHONOMOUS_BINARY_PATH is set but file does not exist: ${candidate}`,
56
+ );
57
+ }
58
+ return candidate;
59
+ }
60
+
61
+ if (!isSupportedPlatform(process.platform, process.arch)) {
62
+ fail(
63
+ `Unsupported platform "${process.platform}-${process.arch}". ` +
64
+ `Supported targets: darwin/linux + x64/arm64.`,
65
+ );
66
+ }
67
+
68
+ const info = resolvePlatform({
69
+ platform: process.platform,
70
+ arch: process.arch,
71
+ });
72
+
73
+ // Preferred layout for OTP release assets.
74
+ const releaseCommandPath = path.join(
75
+ VENDOR_ROOT,
76
+ info.target,
77
+ "graphonomous",
78
+ "bin",
79
+ info.exeName,
80
+ );
81
+
82
+ if (fileExists(releaseCommandPath)) {
83
+ return releaseCommandPath;
84
+ }
85
+
86
+ // Backward-compatible fallback for single-file binary assets.
87
+ const binaryPath = path.join(VENDOR_ROOT, info.target, info.exeName);
88
+ if (fileExists(binaryPath)) {
89
+ return binaryPath;
90
+ }
91
+
92
+ fail(
93
+ `Installed Graphonomous command not found.\n` +
94
+ `Checked:\n` +
95
+ ` - ${releaseCommandPath}\n` +
96
+ ` - ${binaryPath}\n` +
97
+ `Try reinstalling package or rerunning install scripts:\n` +
98
+ ` npm rebuild graphonomous\n` +
99
+ `or\n` +
100
+ ` npm i graphonomous@latest`,
101
+ );
102
+ }
103
+
104
+ function run() {
105
+ const binaryPath = resolveBinaryPath();
106
+
107
+ // Best-effort chmod for unix-like systems if executable bit is missing.
108
+ if (process.platform !== "win32" && !isExecutable(binaryPath)) {
109
+ try {
110
+ fs.chmodSync(binaryPath, 0o755);
111
+ } catch {
112
+ // ignore; spawn will report if it still cannot execute
113
+ }
114
+ }
115
+
116
+ const args = process.argv.slice(2);
117
+ const otpReleasePathPattern = new RegExp(
118
+ `[\\\\/]graphonomous[\\\\/]bin[\\\\/]graphonomous$`,
119
+ );
120
+ const spawnArgs = otpReleasePathPattern.test(binaryPath)
121
+ ? ["eval", "Graphonomous.CLI.main(System.argv())", ...args]
122
+ : args;
123
+
124
+ const child = spawn(binaryPath, spawnArgs, {
125
+ stdio: "inherit",
126
+ env: process.env,
127
+ });
128
+
129
+ child.on("error", (err) => {
130
+ fail(`Failed to start binary at ${binaryPath}: ${err.message}`);
131
+ });
132
+
133
+ child.on("exit", (code, signal) => {
134
+ if (signal) {
135
+ // Mirror termination signal behavior.
136
+ process.kill(process.pid, signal);
137
+ return;
138
+ }
139
+
140
+ process.exit(typeof code === "number" ? code : 1);
141
+ });
142
+
143
+ // Forward common termination signals to child.
144
+ const forward = (sig) => {
145
+ if (!child.killed) {
146
+ try {
147
+ child.kill(sig);
148
+ } catch {
149
+ // no-op
150
+ }
151
+ }
152
+ };
153
+
154
+ process.on("SIGINT", () => forward("SIGINT"));
155
+ process.on("SIGTERM", () => forward("SIGTERM"));
156
+ }
157
+
158
+ run();
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "graphonomous",
3
+ "version": "0.1.0",
4
+ "description": "npm wrapper for the Graphonomous MCP server CLI",
5
+ "private": false,
6
+ "license": "UNLICENSED",
7
+ "author": "Ampersandbox",
8
+ "homepage": "https://github.com/ampersandbox/graphonomous#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ampersandbox/graphonomous.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/ampersandbox/graphonomous/issues"
15
+ },
16
+ "type": "commonjs",
17
+ "bin": {
18
+ "graphonomous": "bin/graphonomous.js"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "scripts",
23
+ "vendor",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "postinstall": "node ./scripts/postinstall.js",
28
+ "start": "node ./bin/graphonomous.js --help"
29
+ },
30
+ "keywords": [
31
+ "mcp",
32
+ "model-context-protocol",
33
+ "graphonomous",
34
+ "ai",
35
+ "agent",
36
+ "elixir",
37
+ "cli"
38
+ ],
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "os": [
43
+ "darwin",
44
+ "linux"
45
+ ],
46
+ "cpu": [
47
+ "x64",
48
+ "arm64"
49
+ ]
50
+ }
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const fsp = require("fs/promises");
5
+ const path = require("path");
6
+ const os = require("os");
7
+ const http = require("http");
8
+ const https = require("https");
9
+ const { pipeline } = require("stream/promises");
10
+ const { spawn } = require("child_process");
11
+
12
+ const DEFAULT_TIMEOUT_MS = 60_000;
13
+ const DEFAULT_MAX_REDIRECTS = 5;
14
+
15
+ function normalizeTag(tag) {
16
+ if (!tag || typeof tag !== "string") {
17
+ throw new Error("A release tag is required (e.g. v0.1.0).");
18
+ }
19
+ return tag.startsWith("v") ? tag : `v${tag}`;
20
+ }
21
+
22
+ function buildGitHubReleaseAssetUrl({ owner, repo, tag, assetName }) {
23
+ if (!owner) throw new Error("Missing GitHub owner.");
24
+ if (!repo) throw new Error("Missing GitHub repo.");
25
+ if (!assetName) throw new Error("Missing release asset name.");
26
+ const normalizedTag = normalizeTag(tag);
27
+ return `https://github.com/${owner}/${repo}/releases/download/${normalizedTag}/${assetName}`;
28
+ }
29
+
30
+ function ensureDir(dirPath) {
31
+ return fsp.mkdir(dirPath, { recursive: true });
32
+ }
33
+
34
+ function streamRequestToFile(url, destinationPath, opts = {}) {
35
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
36
+ const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
37
+ const headers = { ...(opts.headers || {}) };
38
+
39
+ return new Promise((resolve, reject) => {
40
+ const visited = [];
41
+ let settled = false;
42
+
43
+ const onError = (err) => {
44
+ if (!settled) {
45
+ settled = true;
46
+ reject(err);
47
+ }
48
+ };
49
+
50
+ const requestUrl = (targetUrl, redirectsLeft) => {
51
+ visited.push(targetUrl);
52
+
53
+ if (redirectsLeft < 0) {
54
+ onError(
55
+ new Error(
56
+ `Too many redirects while downloading release asset. Visited: ${visited.join(" -> ")}`
57
+ )
58
+ );
59
+ return;
60
+ }
61
+
62
+ const parsed = new URL(targetUrl);
63
+ const client = parsed.protocol === "http:" ? http : https;
64
+
65
+ const req = client.get(
66
+ targetUrl,
67
+ {
68
+ headers: {
69
+ "user-agent": "graphonomous-npm-installer",
70
+ ...headers
71
+ },
72
+ timeout: timeoutMs
73
+ },
74
+ async (res) => {
75
+ const status = res.statusCode || 0;
76
+
77
+ // Follow redirects
78
+ if (status >= 300 && status < 400 && res.headers.location) {
79
+ res.resume();
80
+ const nextUrl = new URL(res.headers.location, targetUrl).toString();
81
+ requestUrl(nextUrl, redirectsLeft - 1);
82
+ return;
83
+ }
84
+
85
+ if (status < 200 || status >= 300) {
86
+ const chunks = [];
87
+ for await (const chunk of res) chunks.push(chunk);
88
+ const body = Buffer.concat(chunks).toString("utf8");
89
+ onError(
90
+ new Error(
91
+ `Failed downloading asset. HTTP ${status}. URL=${targetUrl}${
92
+ body ? `\nResponse: ${body.slice(0, 2000)}` : ""
93
+ }`
94
+ )
95
+ );
96
+ return;
97
+ }
98
+
99
+ try {
100
+ await ensureDir(path.dirname(destinationPath));
101
+ await pipeline(res, fs.createWriteStream(destinationPath));
102
+ if (!settled) {
103
+ settled = true;
104
+ resolve({ finalUrl: targetUrl, destinationPath });
105
+ }
106
+ } catch (err) {
107
+ onError(err);
108
+ }
109
+ }
110
+ );
111
+
112
+ req.on("timeout", () => {
113
+ req.destroy(new Error(`Download timed out after ${timeoutMs}ms`));
114
+ });
115
+
116
+ req.on("error", onError);
117
+ };
118
+
119
+ requestUrl(url, maxRedirects);
120
+ });
121
+ }
122
+
123
+ function runTarExtract({ archivePath, destinationDir, stripComponents = 0 }) {
124
+ return new Promise((resolve, reject) => {
125
+ const args = ["-xzf", archivePath, "-C", destinationDir];
126
+ if (stripComponents > 0) {
127
+ args.push(`--strip-components=${stripComponents}`);
128
+ }
129
+
130
+ const child = spawn("tar", args, {
131
+ stdio: ["ignore", "pipe", "pipe"]
132
+ });
133
+
134
+ let stderr = "";
135
+ child.stderr.on("data", (chunk) => {
136
+ stderr += chunk.toString("utf8");
137
+ });
138
+
139
+ child.on("error", (err) => {
140
+ reject(
141
+ new Error(
142
+ `Failed to start tar process. Ensure 'tar' is available on this system.\n${err.message}`
143
+ )
144
+ );
145
+ });
146
+
147
+ child.on("close", (code) => {
148
+ if (code === 0) {
149
+ resolve();
150
+ return;
151
+ }
152
+
153
+ reject(
154
+ new Error(
155
+ `Failed to extract archive with tar (exit code ${code}).` +
156
+ (stderr ? `\n${stderr.trim()}` : "")
157
+ )
158
+ );
159
+ });
160
+ });
161
+ }
162
+
163
+ async function fileExists(filePath) {
164
+ try {
165
+ await fsp.access(filePath, fs.constants.F_OK);
166
+ return true;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Downloads and extracts a release asset archive (tar.gz), then optionally marks
174
+ * the expected executable as executable.
175
+ *
176
+ * Options:
177
+ * - url: direct asset URL (optional if owner/repo/tag/assetName are provided)
178
+ * - owner, repo, tag, assetName: used to build GitHub release URL
179
+ * - destinationDir: where the archive should be extracted (required)
180
+ * - archivePath: optional explicit temp archive path
181
+ * - stripComponents: optional tar --strip-components value (default 0)
182
+ * - executableName: optional executable to chmod +x after extraction
183
+ * - timeoutMs: download timeout (default 60s)
184
+ * - maxRedirects: max redirects (default 5)
185
+ * - token: optional GitHub token for private assets
186
+ * - keepArchive: keep downloaded archive on disk (default false)
187
+ * - logger: optional logger object ({ info, warn, error })
188
+ */
189
+ async function downloadAndExtractReleaseAsset(options = {}) {
190
+ const logger = options.logger || console;
191
+ const destinationDir = options.destinationDir;
192
+
193
+ if (!destinationDir) {
194
+ throw new Error("downloadAndExtractReleaseAsset: destinationDir is required.");
195
+ }
196
+
197
+ const stripComponents = Number.isInteger(options.stripComponents)
198
+ ? options.stripComponents
199
+ : 0;
200
+
201
+ const assetUrl =
202
+ options.url ||
203
+ buildGitHubReleaseAssetUrl({
204
+ owner: options.owner,
205
+ repo: options.repo,
206
+ tag: options.tag,
207
+ assetName: options.assetName
208
+ });
209
+
210
+ const archivePath =
211
+ options.archivePath ||
212
+ path.join(
213
+ os.tmpdir(),
214
+ `graphonomous-${Date.now()}-${Math.random().toString(16).slice(2)}.tar.gz`
215
+ );
216
+
217
+ const headers = {};
218
+ if (options.token && typeof options.token === "string") {
219
+ headers.authorization = `Bearer ${options.token}`;
220
+ }
221
+
222
+ await ensureDir(destinationDir);
223
+
224
+ logger.info?.(`Downloading Graphonomous release asset: ${assetUrl}`);
225
+ await streamRequestToFile(assetUrl, archivePath, {
226
+ timeoutMs: options.timeoutMs,
227
+ maxRedirects: options.maxRedirects,
228
+ headers
229
+ });
230
+
231
+ logger.info?.(`Extracting release asset into: ${destinationDir}`);
232
+ await runTarExtract({
233
+ archivePath,
234
+ destinationDir,
235
+ stripComponents
236
+ });
237
+
238
+ let executablePath = null;
239
+ if (options.executableName) {
240
+ executablePath = path.join(destinationDir, options.executableName);
241
+ const exists = await fileExists(executablePath);
242
+ if (!exists) {
243
+ throw new Error(
244
+ `Expected executable "${options.executableName}" not found after extraction at ${executablePath}.`
245
+ );
246
+ }
247
+
248
+ // Ensure executable bit is present on unix-like systems
249
+ await fsp.chmod(executablePath, 0o755);
250
+ }
251
+
252
+ if (!options.keepArchive) {
253
+ try {
254
+ await fsp.unlink(archivePath);
255
+ } catch {
256
+ // best-effort cleanup
257
+ }
258
+ }
259
+
260
+ return {
261
+ assetUrl,
262
+ archivePath,
263
+ destinationDir,
264
+ executablePath
265
+ };
266
+ }
267
+
268
+ module.exports = {
269
+ normalizeTag,
270
+ buildGitHubReleaseAssetUrl,
271
+ streamRequestToFile,
272
+ runTarExtract,
273
+ downloadAndExtractReleaseAsset
274
+ };
@@ -0,0 +1,221 @@
1
+ // npm postinstall script for Graphonomous.
2
+ // Downloads the platform-specific Graphonomous release binary into npm/vendor/<target>/.
3
+
4
+ "use strict";
5
+
6
+ const fs = require("fs");
7
+ const fsp = require("fs/promises");
8
+ const path = require("path");
9
+ const { resolvePlatform, isSupportedPlatform } = require("./resolve-platform");
10
+ const { downloadAndExtractReleaseAsset } = require("./download-release-asset");
11
+
12
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
13
+ const VENDOR_ROOT = path.join(PACKAGE_ROOT, "vendor");
14
+
15
+ function readPackageJson() {
16
+ const pkgPath = path.join(PACKAGE_ROOT, "package.json");
17
+ const raw = fs.readFileSync(pkgPath, "utf8");
18
+ return JSON.parse(raw);
19
+ }
20
+
21
+ function envFlag(name, defaultValue = false) {
22
+ const raw = process.env[name];
23
+ if (raw == null) return defaultValue;
24
+ const normalized = String(raw).trim().toLowerCase();
25
+ return ["1", "true", "yes", "on"].includes(normalized);
26
+ }
27
+
28
+ function getConfig(pkg) {
29
+ const owner = process.env.GRAPHONOMOUS_GITHUB_OWNER || "ampersandbox";
30
+ const repo = process.env.GRAPHONOMOUS_GITHUB_REPO || "graphonomous";
31
+ const version = process.env.GRAPHONOMOUS_VERSION || pkg.version;
32
+ const tag =
33
+ process.env.GRAPHONOMOUS_RELEASE_TAG || `v${version.replace(/^v/, "")}`;
34
+ const token =
35
+ process.env.GRAPHONOMOUS_GITHUB_TOKEN || process.env.GITHUB_TOKEN || null;
36
+ const force = envFlag("GRAPHONOMOUS_FORCE_DOWNLOAD", false);
37
+ const skip = envFlag("GRAPHONOMOUS_SKIP_DOWNLOAD", false);
38
+ const timeoutMs = Number(
39
+ process.env.GRAPHONOMOUS_DOWNLOAD_TIMEOUT_MS || 60_000,
40
+ );
41
+ const maxRedirects = Number(
42
+ process.env.GRAPHONOMOUS_DOWNLOAD_MAX_REDIRECTS || 5,
43
+ );
44
+ const releaseBaseUrl = process.env.GRAPHONOMOUS_RELEASE_BASE_URL || null;
45
+
46
+ return {
47
+ owner,
48
+ repo,
49
+ version: version.replace(/^v/, ""),
50
+ tag,
51
+ token,
52
+ force,
53
+ skip,
54
+ timeoutMs: Number.isFinite(timeoutMs) ? timeoutMs : 60_000,
55
+ maxRedirects: Number.isFinite(maxRedirects) ? maxRedirects : 5,
56
+ releaseBaseUrl,
57
+ };
58
+ }
59
+
60
+ async function exists(filePath) {
61
+ try {
62
+ await fsp.access(filePath, fs.constants.F_OK);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ function buildAssetName(version, target) {
70
+ return `graphonomous-v${version}-${target}.tar.gz`;
71
+ }
72
+
73
+ function buildCustomAssetUrl(baseUrl, assetName) {
74
+ const trimmed = String(baseUrl).replace(/\/+$/, "");
75
+ return `${trimmed}/${assetName}`;
76
+ }
77
+
78
+ async function resolveInstalledCommandPath(targetDir, exeName) {
79
+ const direct = path.join(targetDir, exeName);
80
+ if (await exists(direct)) return direct;
81
+
82
+ const otpDirect = path.join(targetDir, "bin", exeName);
83
+ if (await exists(otpDirect)) return otpDirect;
84
+
85
+ const entries = await fsp.readdir(targetDir, { withFileTypes: true });
86
+ for (const entry of entries) {
87
+ if (!entry.isDirectory()) continue;
88
+ const nested = path.join(targetDir, entry.name, "bin", exeName);
89
+ if (await exists(nested)) return nested;
90
+ }
91
+
92
+ throw new Error(
93
+ `Could not locate installed Graphonomous command after extraction. ` +
94
+ `Expected one of: ${direct}, ${otpDirect}, or <release>/bin/${exeName}.`,
95
+ );
96
+ }
97
+
98
+ async function installCommandShim({ shimPath, sourceCommandPath }) {
99
+ const shimDir = path.dirname(shimPath);
100
+ const relativeTarget = path
101
+ .relative(shimDir, sourceCommandPath)
102
+ .replace(/\\/g, "/");
103
+
104
+ const script = `#!/usr/bin/env sh
105
+ set -eu
106
+ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
107
+ exec "$SCRIPT_DIR/${relativeTarget}" "$@"
108
+ `;
109
+
110
+ await fsp.writeFile(shimPath, script, { encoding: "utf8", mode: 0o755 });
111
+ await fsp.chmod(shimPath, 0o755);
112
+ }
113
+
114
+ async function writeInstallMetadata(targetDir, metadata) {
115
+ const metadataPath = path.join(targetDir, ".install-metadata.json");
116
+ await fsp.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
117
+ }
118
+
119
+ async function main() {
120
+ const pkg = readPackageJson();
121
+ const cfg = getConfig(pkg);
122
+
123
+ if (cfg.skip) {
124
+ console.log(
125
+ "[graphonomous] postinstall skipped (GRAPHONOMOUS_SKIP_DOWNLOAD is set).",
126
+ );
127
+ return;
128
+ }
129
+
130
+ if (!isSupportedPlatform(process.platform, process.arch)) {
131
+ console.warn(
132
+ `[graphonomous] Unsupported platform for prebuilt binaries: ${process.platform}-${process.arch}.`,
133
+ );
134
+ console.warn(
135
+ "[graphonomous] Install continues without binary. You can build Graphonomous manually from source.",
136
+ );
137
+ return;
138
+ }
139
+
140
+ const platformInfo = resolvePlatform({
141
+ platform: process.platform,
142
+ arch: process.arch,
143
+ version: cfg.version,
144
+ });
145
+
146
+ const targetDir = path.join(VENDOR_ROOT, platformInfo.target);
147
+ const binaryPath = path.join(targetDir, platformInfo.exeName);
148
+ const assetName = buildAssetName(cfg.version, platformInfo.target);
149
+
150
+ if (!cfg.force && (await exists(binaryPath))) {
151
+ console.log(
152
+ `[graphonomous] Binary already present at ${binaryPath}; skipping download.`,
153
+ );
154
+ return;
155
+ }
156
+
157
+ const useCustomBaseUrl = Boolean(cfg.releaseBaseUrl);
158
+ const directUrl = useCustomBaseUrl
159
+ ? buildCustomAssetUrl(cfg.releaseBaseUrl, assetName)
160
+ : null;
161
+
162
+ console.log(
163
+ `[graphonomous] Installing Graphonomous binary for ${platformInfo.target} (version ${cfg.version})...`,
164
+ );
165
+
166
+ await downloadAndExtractReleaseAsset({
167
+ url: directUrl || undefined,
168
+ owner: cfg.owner,
169
+ repo: cfg.repo,
170
+ tag: cfg.tag,
171
+ assetName,
172
+ destinationDir: targetDir,
173
+ stripComponents: 0,
174
+ timeoutMs: cfg.timeoutMs,
175
+ maxRedirects: cfg.maxRedirects,
176
+ token: cfg.token,
177
+ keepArchive: false,
178
+ logger: console,
179
+ });
180
+
181
+ const installedCommandPath = await resolveInstalledCommandPath(
182
+ targetDir,
183
+ platformInfo.exeName,
184
+ );
185
+
186
+ if (installedCommandPath !== binaryPath) {
187
+ await installCommandShim({
188
+ shimPath: binaryPath,
189
+ sourceCommandPath: installedCommandPath,
190
+ });
191
+ } else {
192
+ await fsp.chmod(binaryPath, 0o755);
193
+ }
194
+
195
+ await writeInstallMetadata(targetDir, {
196
+ packageName: pkg.name,
197
+ packageVersion: pkg.version,
198
+ resolvedVersion: cfg.version,
199
+ tag: cfg.tag,
200
+ target: platformInfo.target,
201
+ binaryPath,
202
+ installedCommandPath,
203
+ installedAt: new Date().toISOString(),
204
+ source: useCustomBaseUrl
205
+ ? "custom_release_base_url"
206
+ : `github:${cfg.owner}/${cfg.repo}`,
207
+ });
208
+
209
+ console.log(`[graphonomous] Installed binary: ${binaryPath}`);
210
+ if (installedCommandPath !== binaryPath) {
211
+ console.log(
212
+ `[graphonomous] Installed command source: ${installedCommandPath}`,
213
+ );
214
+ }
215
+ }
216
+
217
+ main().catch((err) => {
218
+ console.error("[graphonomous] postinstall failed.");
219
+ console.error(err && err.stack ? err.stack : err);
220
+ process.exitCode = 1;
221
+ });
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Resolve Node.js runtime platform/architecture into Graphonomous binary artifact metadata.
5
+ *
6
+ * Supported targets:
7
+ * - darwin-x64
8
+ * - darwin-arm64
9
+ * - linux-x64
10
+ * - linux-arm64
11
+ */
12
+
13
+ const PLATFORM_MAP = {
14
+ darwin: "darwin",
15
+ linux: "linux"
16
+ };
17
+
18
+ const ARCH_MAP = {
19
+ x64: "x64",
20
+ arm64: "arm64"
21
+ };
22
+
23
+ function normalizeVersion(version) {
24
+ if (!version || typeof version !== "string") {
25
+ throw new Error("A package version string is required.");
26
+ }
27
+
28
+ return version.startsWith("v") ? version.slice(1) : version;
29
+ }
30
+
31
+ function resolvePlatform(opts = {}) {
32
+ const rawPlatform = opts.platform || process.platform;
33
+ const rawArch = opts.arch || process.arch;
34
+ const version = opts.version ? normalizeVersion(opts.version) : null;
35
+
36
+ const platform = PLATFORM_MAP[rawPlatform];
37
+ const arch = ARCH_MAP[rawArch];
38
+
39
+ if (!platform) {
40
+ throw new Error(
41
+ `Unsupported platform "${rawPlatform}". Supported platforms: ${Object.keys(PLATFORM_MAP).join(", ")}.`
42
+ );
43
+ }
44
+
45
+ if (!arch) {
46
+ throw new Error(
47
+ `Unsupported architecture "${rawArch}". Supported architectures: ${Object.keys(ARCH_MAP).join(", ")}.`
48
+ );
49
+ }
50
+
51
+ const target = `${platform}-${arch}`;
52
+
53
+ const result = {
54
+ platform,
55
+ arch,
56
+ target,
57
+ exeName: platform === "windows" ? "graphonomous.exe" : "graphonomous",
58
+ archiveExt: "tar.gz"
59
+ };
60
+
61
+ if (version) {
62
+ result.version = version;
63
+ result.tag = `v${version}`;
64
+ result.archiveName = `graphonomous-v${version}-${target}.${result.archiveExt}`;
65
+ }
66
+
67
+ return result;
68
+ }
69
+
70
+ function isSupportedPlatform(platform = process.platform, arch = process.arch) {
71
+ return Boolean(PLATFORM_MAP[platform] && ARCH_MAP[arch]);
72
+ }
73
+
74
+ module.exports = {
75
+ PLATFORM_MAP,
76
+ ARCH_MAP,
77
+ resolvePlatform,
78
+ isSupportedPlatform
79
+ };
File without changes