archgate 0.26.0 → 0.26.2

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/archgate.cjs CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { execFileSync } = require("child_process");
4
+ const { execFileSync, execSync } = require("child_process");
5
5
  const https = require("https");
6
6
  const zlib = require("zlib");
7
7
  const path = require("path");
8
8
  const fs = require("fs");
9
+ const os = require("os");
9
10
 
10
- function getPlatformPackageName() {
11
+ function getArtifactName() {
11
12
  const { platform, arch } = process;
12
13
  if (platform === "darwin" && arch === "arm64") return "archgate-darwin-arm64";
13
14
  if (platform === "linux" && arch === "x64") return "archgate-linux-x64";
@@ -21,6 +22,10 @@ function getBinaryName() {
21
22
  return process.platform === "win32" ? "archgate.exe" : "archgate";
22
23
  }
23
24
 
25
+ function getCacheDir() {
26
+ return path.join(os.homedir(), ".archgate", "bin");
27
+ }
28
+
24
29
  function getPackageVersion() {
25
30
  const pkg = JSON.parse(
26
31
  fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")
@@ -29,34 +34,20 @@ function getPackageVersion() {
29
34
  }
30
35
 
31
36
  function getBinaryPath() {
32
- const pkgName = getPlatformPackageName();
33
37
  const binaryName = getBinaryName();
34
-
35
- // 1. Try the platform-specific optional dependency (normal path).
36
- try {
37
- const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
38
- const binaryPath = path.join(pkgDir, "bin", binaryName);
39
- if (fs.existsSync(binaryPath)) return binaryPath;
40
- } catch {
41
- /* platform package not installed */
42
- }
43
-
44
- // 2. Fallback: binary downloaded into our own bin/ by postinstall or a
45
- // previous on-demand download.
46
- const fallbackPath = path.join(__dirname, binaryName);
47
- if (fs.existsSync(fallbackPath)) return fallbackPath;
48
-
38
+ const cachePath = path.join(getCacheDir(), binaryName);
39
+ if (fs.existsSync(cachePath)) return cachePath;
49
40
  return null;
50
41
  }
51
42
 
52
43
  // ---------------------------------------------------------------------------
53
- // On-demand download from the npm registry
44
+ // On-demand download from GitHub Releases
54
45
  // ---------------------------------------------------------------------------
55
46
 
56
47
  function fetchWithRedirects(url) {
57
48
  return new Promise((resolve, reject) => {
58
49
  https
59
- .get(url, (res) => {
50
+ .get(url, { headers: { "User-Agent": "archgate-cli" } }, (res) => {
60
51
  if (
61
52
  res.statusCode >= 300 &&
62
53
  res.statusCode < 400 &&
@@ -82,74 +73,106 @@ function stripNulls(str) {
82
73
  }
83
74
 
84
75
  /**
85
- * Download the platform-specific npm package tarball and extract the binary.
76
+ * Download the platform binary from GitHub Releases and cache it.
86
77
  * Returns the path to the downloaded binary.
87
78
  */
88
79
  async function downloadBinary() {
89
- const pkgName = getPlatformPackageName();
80
+ const artifactName = getArtifactName();
90
81
  const version = getPackageVersion();
91
82
  const binaryName = getBinaryName();
83
+ const isWin = process.platform === "win32";
84
+ const ext = isWin ? "zip" : "tar.gz";
92
85
 
93
- const url = `https://registry.npmjs.org/${pkgName}/-/${pkgName}-${version}.tgz`;
94
- console.error(
95
- `archgate: binary not found, downloading ${pkgName}@${version}...`
96
- );
97
-
98
- const res = await fetchWithRedirects(url);
86
+ const url = `https://github.com/archgate/cli/releases/download/v${version}/${artifactName}.${ext}`;
87
+ console.error(`archgate: binary not found, downloading v${version}...`);
99
88
 
100
- const binDir = __dirname;
101
- const destPath = path.join(binDir, binaryName);
89
+ const cacheDir = getCacheDir();
90
+ fs.mkdirSync(cacheDir, { recursive: true });
91
+ const destPath = path.join(cacheDir, binaryName);
102
92
 
103
- return new Promise((resolve, reject) => {
104
- const gunzip = zlib.createGunzip();
93
+ if (isWin) {
94
+ // Download zip to temp file, extract with PowerShell
95
+ const res = await fetchWithRedirects(url);
105
96
  const chunks = [];
106
- const expectedSuffix = `bin/${binaryName}`;
107
- let found = false;
108
-
109
- res.pipe(gunzip);
110
- gunzip.on("data", (chunk) => chunks.push(chunk));
111
- gunzip.on("end", () => {
112
- const data = Buffer.concat(chunks);
113
- let offset = 0;
114
-
115
- while (offset + 512 <= data.length) {
116
- const header = data.subarray(offset, offset + 512);
117
- offset += 512;
118
-
119
- // Empty header block signals end of archive
120
- if (header.every((b) => b === 0)) break;
121
-
122
- let name = stripNulls(header.subarray(0, 100).toString("utf8"));
123
- const prefix = stripNulls(header.subarray(345, 500).toString("utf8"));
124
- if (prefix) name = `${prefix}/${name}`;
125
-
126
- const sizeStr = stripNulls(
127
- header.subarray(124, 136).toString("utf8")
128
- ).trim();
129
- const size = parseInt(sizeStr, 8) || 0;
130
- const blocks = Math.ceil(size / 512);
131
- const fileData = data.subarray(offset, offset + size);
132
- offset += blocks * 512;
133
-
134
- if (name.endsWith(expectedSuffix)) {
135
- fs.writeFileSync(destPath, fileData, { mode: 0o755 });
136
- found = true;
137
- break;
138
- }
97
+ await new Promise((resolve, reject) => {
98
+ res.on("data", (chunk) => chunks.push(chunk));
99
+ res.on("end", resolve);
100
+ res.on("error", reject);
101
+ });
102
+ const tmpZip = path.join(cacheDir, "archgate-download.zip");
103
+ const tmpExtract = path.join(cacheDir, "archgate-extract");
104
+ fs.writeFileSync(tmpZip, Buffer.concat(chunks));
105
+ try {
106
+ fs.mkdirSync(tmpExtract, { recursive: true });
107
+ execSync(
108
+ `powershell -NoProfile -Command "Expand-Archive -Path '${tmpZip}' -DestinationPath '${tmpExtract}' -Force"`,
109
+ { stdio: "pipe" }
110
+ );
111
+ const extractedBinary = path.join(tmpExtract, binaryName);
112
+ if (!fs.existsSync(extractedBinary)) {
113
+ throw new Error(`Binary ${binaryName} not found in zip archive`);
139
114
  }
115
+ fs.copyFileSync(extractedBinary, destPath);
116
+ } finally {
117
+ try { fs.unlinkSync(tmpZip); } catch { /* cleanup */ }
118
+ try { fs.rmSync(tmpExtract, { recursive: true, force: true }); } catch { /* cleanup */ }
119
+ }
120
+ } else {
121
+ // Download tar.gz, extract binary using inline tar parser
122
+ const res = await fetchWithRedirects(url);
123
+ await new Promise((resolve, reject) => {
124
+ const gunzip = zlib.createGunzip();
125
+ const chunks = [];
126
+
127
+ res.pipe(gunzip);
128
+ gunzip.on("data", (chunk) => chunks.push(chunk));
129
+ gunzip.on("end", () => {
130
+ const data = Buffer.concat(chunks);
131
+ let offset = 0;
132
+ let found = false;
133
+
134
+ while (offset + 512 <= data.length) {
135
+ const header = data.subarray(offset, offset + 512);
136
+ offset += 512;
137
+
138
+ if (header.every((b) => b === 0)) break;
139
+
140
+ let name = stripNulls(header.subarray(0, 100).toString("utf8"));
141
+ const prefix = stripNulls(
142
+ header.subarray(345, 500).toString("utf8")
143
+ );
144
+ if (prefix) name = `${prefix}/${name}`;
145
+
146
+ const sizeStr = stripNulls(
147
+ header.subarray(124, 136).toString("utf8")
148
+ ).trim();
149
+ const size = parseInt(sizeStr, 8) || 0;
150
+ const blocks = Math.ceil(size / 512);
151
+ const fileData = data.subarray(offset, offset + size);
152
+ offset += blocks * 512;
153
+
154
+ if (name === binaryName || name.endsWith(`/${binaryName}`)) {
155
+ fs.writeFileSync(destPath, fileData, { mode: 0o755 });
156
+ found = true;
157
+ break;
158
+ }
159
+ }
140
160
 
141
- if (found) {
142
- console.error(`archgate: binary downloaded successfully.`);
143
- resolve(destPath);
144
- } else {
145
- reject(
146
- new Error(`Could not find ${expectedSuffix} in tarball from ${url}`)
147
- );
148
- }
161
+ if (found) {
162
+ resolve();
163
+ } else {
164
+ reject(
165
+ new Error(`Could not find ${binaryName} in archive from ${url}`)
166
+ );
167
+ }
168
+ });
169
+
170
+ gunzip.on("error", reject);
149
171
  });
172
+ }
150
173
 
151
- gunzip.on("error", reject);
152
- });
174
+ console.error(`archgate: binary downloaded successfully.`);
175
+ return destPath;
153
176
  }
154
177
 
155
178
  // ---------------------------------------------------------------------------
@@ -159,15 +182,14 @@ async function downloadBinary() {
159
182
  async function main() {
160
183
  let binary = getBinaryPath();
161
184
 
162
- // If the binary is missing (optional dep skipped AND postinstall blocked),
163
- // download it on-demand from the npm registry.
185
+ // If the binary is missing, download it on-demand from GitHub Releases.
164
186
  if (!binary) {
165
187
  try {
166
188
  binary = await downloadBinary();
167
189
  } catch (err) {
168
190
  console.error(
169
191
  `archgate: failed to download binary: ${err.message}\n` +
170
- `Try reinstalling: npm install archgate`
192
+ `Visit https://cli.archgate.dev/getting-started/installation/ for alternative install methods.`
171
193
  );
172
194
  process.exit(2);
173
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgate",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "Enforce Architecture Decision Records as executable rules — for both humans and AI agents",
5
5
  "keywords": [
6
6
  "adr",
@@ -75,10 +75,5 @@
75
75
  "peerDependencies": {
76
76
  "typescript": "^5 || ^6.0.0"
77
77
  },
78
- "optionalDependencies": {
79
- "archgate-darwin-arm64": "0.26.0",
80
- "archgate-linux-x64": "0.26.0",
81
- "archgate-win32-x64": "0.26.0"
82
- },
83
78
  "readme": "README.md"
84
79
  }
@@ -8,32 +8,12 @@
8
8
 
9
9
  const path = require("path");
10
10
  const fs = require("fs");
11
-
12
- const PACKAGE_NAME_MAP = {
13
- "darwin-arm64": "archgate-darwin-arm64",
14
- "linux-x64": "archgate-linux-x64",
15
- "win32-x64": "archgate-win32-x64",
16
- };
11
+ const os = require("os");
17
12
 
18
13
  function isBinaryPresent() {
19
- const key = `${process.platform}-${process.arch}`;
20
- const pkgName = PACKAGE_NAME_MAP[key];
21
- if (!pkgName) return true; // unsupported platform — skip silently
22
-
23
14
  const binaryName = process.platform === "win32" ? "archgate.exe" : "archgate";
24
-
25
- // Check optional dependency
26
- try {
27
- const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
28
- if (fs.existsSync(path.join(pkgDir, "bin", binaryName))) return true;
29
- } catch {
30
- /* not installed */
31
- }
32
-
33
- // Check local fallback
34
- if (fs.existsSync(path.join(__dirname, "..", "bin", binaryName))) return true;
35
-
36
- return false;
15
+ const cachePath = path.join(os.homedir(), ".archgate", "bin", binaryName);
16
+ return fs.existsSync(cachePath);
37
17
  }
38
18
 
39
19
  if (!isBinaryPresent()) {