pushci 1.3.1 → 1.4.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/bin/pushci.js +126 -40
- package/package.json +1 -1
package/bin/pushci.js
CHANGED
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// pushci npm shim. Resolution order: local dev build → bundled
|
|
3
|
+
// bin/pushci-<os>-<arch> → pushci on PATH → download from GitHub
|
|
4
|
+
// Releases → go build → install help. VERSION reads from package.json
|
|
5
|
+
// so the shim and npm package never drift. See CLAUDE.md
|
|
6
|
+
// "Release & Distribution" for the full pipeline.
|
|
7
|
+
|
|
2
8
|
const { execSync, spawn } = require('child_process');
|
|
3
9
|
const fs = require('fs');
|
|
4
10
|
const path = require('path');
|
|
5
11
|
const os = require('os');
|
|
6
12
|
|
|
7
|
-
const
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
const VERSION = pkg.version;
|
|
15
|
+
const REPO = 'finsavvyai/push-ci.dev';
|
|
16
|
+
|
|
8
17
|
const BINARY_NAME = os.platform() === 'win32' ? 'pushci.exe' : 'pushci';
|
|
9
|
-
const CDN = 'https://pushci-releases.broad-dew-49ad.workers.dev';
|
|
10
18
|
|
|
11
|
-
const PLATFORM_MAP = {
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
const ARCH_MAP = {
|
|
15
|
-
x64: 'amd64', arm64: 'arm64',
|
|
16
|
-
};
|
|
19
|
+
const PLATFORM_MAP = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
|
|
20
|
+
const ARCH_MAP = { x64: 'amd64', arm64: 'arm64' };
|
|
17
21
|
|
|
18
22
|
function getBinaryPath() {
|
|
23
|
+
// 1. Local dev build at the package root (set by `go build` during
|
|
24
|
+
// development or by goreleaser-like bundling).
|
|
19
25
|
const local = path.join(__dirname, '..', BINARY_NAME);
|
|
20
|
-
if (fs.existsSync(local)) return local;
|
|
26
|
+
if (fs.existsSync(local) && isValidBinary(local)) return local;
|
|
27
|
+
|
|
28
|
+
// 2. Bundled platform-specific binary shipped inside the npm
|
|
29
|
+
// tarball at bin/pushci-<os>-<arch>[.exe]. This is the
|
|
30
|
+
// fastest path — zero network, works offline, deterministic.
|
|
31
|
+
// It's also the only reliable path for users who don't have
|
|
32
|
+
// GitHub Releases access (corporate proxies, air-gapped CI).
|
|
33
|
+
const plat = PLATFORM_MAP[os.platform()];
|
|
34
|
+
const arch = ARCH_MAP[os.arch()];
|
|
35
|
+
if (plat && arch) {
|
|
36
|
+
const winExt = os.platform() === 'win32' ? '.exe' : '';
|
|
37
|
+
const bundled = path.join(__dirname, `pushci-${plat}-${arch}${winExt}`);
|
|
38
|
+
if (fs.existsSync(bundled) && isValidBinary(bundled)) return bundled;
|
|
39
|
+
}
|
|
21
40
|
|
|
41
|
+
// 3. Existing install on PATH (Homebrew, go install, curl
|
|
42
|
+
// installer). The `which` result may be this same shim, so
|
|
43
|
+
// validate it's a real binary before trusting it.
|
|
22
44
|
try {
|
|
23
45
|
const cmd = os.platform() === 'win32' ? 'where' : 'which';
|
|
24
46
|
const found = execSync(`${cmd} pushci`, { encoding: 'utf8' }).trim();
|
|
25
|
-
if (found) return found;
|
|
47
|
+
if (found && found !== __filename && isValidBinary(found)) return found;
|
|
26
48
|
} catch (_) {}
|
|
27
49
|
|
|
50
|
+
// 4-6. Download, build, or give up.
|
|
28
51
|
return downloadOrBuild();
|
|
29
52
|
}
|
|
30
53
|
|
|
@@ -37,31 +60,16 @@ function downloadOrBuild() {
|
|
|
37
60
|
if (fs.existsSync(target) && isValidBinary(target)) return target;
|
|
38
61
|
|
|
39
62
|
if (plat && arch) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
execSync(`curl -sfL --retry 2 -o "${target}" "${url}"`, { timeout: 30000 });
|
|
44
|
-
if (isValidBinary(target)) {
|
|
45
|
-
fs.chmodSync(target, 0o755);
|
|
46
|
-
return target;
|
|
47
|
-
}
|
|
48
|
-
try { fs.unlinkSync(target); } catch (_) {}
|
|
49
|
-
} catch (_) {}
|
|
63
|
+
if (downloadFromReleases(plat, arch, target)) {
|
|
64
|
+
return target;
|
|
65
|
+
}
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const out = path.join(os.tmpdir(), BINARY_NAME);
|
|
56
|
-
execSync(`go build -o "${out}" ./cmd/pushci`, {
|
|
57
|
-
cwd: path.join(__dirname, '..'),
|
|
58
|
-
stdio: 'inherit',
|
|
59
|
-
timeout: 120000,
|
|
60
|
-
});
|
|
61
|
-
if (fs.existsSync(out)) return out;
|
|
62
|
-
} catch (_) {}
|
|
68
|
+
if (buildFromSource(target)) {
|
|
69
|
+
return target;
|
|
70
|
+
}
|
|
63
71
|
|
|
64
|
-
// If invoked from a git hook, don't block the push
|
|
72
|
+
// If invoked from a git hook, don't block the push.
|
|
65
73
|
if (process.env.GIT_DIR) {
|
|
66
74
|
console.error('pushci: binary unavailable, skipping checks.');
|
|
67
75
|
process.exit(0);
|
|
@@ -70,6 +78,81 @@ function downloadOrBuild() {
|
|
|
70
78
|
process.exit(1);
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
// downloadFromReleases pulls the goreleaser archive for the current
|
|
82
|
+
// platform from GitHub Releases, extracts the binary, and moves it
|
|
83
|
+
// into place. Returns true on success, false on any failure so the
|
|
84
|
+
// caller can fall through to buildFromSource.
|
|
85
|
+
function downloadFromReleases(plat, arch, target) {
|
|
86
|
+
const isWin = os.platform() === 'win32';
|
|
87
|
+
const archiveExt = isWin ? 'zip' : 'tar.gz';
|
|
88
|
+
const archiveName = `pushci_${VERSION}_${plat}_${arch}.${archiveExt}`;
|
|
89
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
90
|
+
|
|
91
|
+
const scratchDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pushci-install-'));
|
|
92
|
+
const archivePath = path.join(scratchDir, archiveName);
|
|
93
|
+
|
|
94
|
+
console.log(`Downloading pushci v${VERSION} from GitHub Releases...`);
|
|
95
|
+
try {
|
|
96
|
+
execSync(
|
|
97
|
+
`curl -sfL --retry 2 --retry-delay 1 -o "${archivePath}" "${url}"`,
|
|
98
|
+
{ timeout: 60000 },
|
|
99
|
+
);
|
|
100
|
+
} catch (_) {
|
|
101
|
+
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (isWin) {
|
|
107
|
+
// tar has shipped with Windows 10 (1803+) — `tar -xf` handles
|
|
108
|
+
// .zip the same as .tar.gz so we avoid a PowerShell branch.
|
|
109
|
+
execSync(`tar -xf "${archivePath}" -C "${scratchDir}"`, {
|
|
110
|
+
timeout: 30000,
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
execSync(`tar -xzf "${archivePath}" -C "${scratchDir}"`, {
|
|
114
|
+
timeout: 30000,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const extracted = path.join(scratchDir, BINARY_NAME);
|
|
118
|
+
if (!fs.existsSync(extracted) || !isValidBinary(extracted)) {
|
|
119
|
+
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
fs.copyFileSync(extracted, target);
|
|
123
|
+
if (!isWin) fs.chmodSync(target, 0o755);
|
|
124
|
+
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
125
|
+
return true;
|
|
126
|
+
} catch (_) {
|
|
127
|
+
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// buildFromSource is the Go-install fallback. Only runs when download
|
|
133
|
+
// fails AND the user has Go on PATH. Useful for air-gapped dev setups.
|
|
134
|
+
function buildFromSource(target) {
|
|
135
|
+
try {
|
|
136
|
+
execSync('go version', { stdio: 'ignore' });
|
|
137
|
+
} catch (_) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
console.log('Downloading failed, building pushci from source...');
|
|
141
|
+
try {
|
|
142
|
+
execSync(`go build -o "${target}" ./cmd/pushci`, {
|
|
143
|
+
cwd: path.join(__dirname, '..'),
|
|
144
|
+
stdio: 'inherit',
|
|
145
|
+
timeout: 180000,
|
|
146
|
+
});
|
|
147
|
+
return fs.existsSync(target) && isValidBinary(target);
|
|
148
|
+
} catch (_) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// isValidBinary sanity-checks the magic bytes so a half-downloaded
|
|
154
|
+
// archive doesn't pass for a real binary. ELF (Linux), Mach-O
|
|
155
|
+
// (darwin little- and big-endian), PE (Windows).
|
|
73
156
|
function isValidBinary(filepath) {
|
|
74
157
|
try {
|
|
75
158
|
const stat = fs.statSync(filepath);
|
|
@@ -78,12 +161,14 @@ function isValidBinary(filepath) {
|
|
|
78
161
|
const fd = fs.openSync(filepath, 'r');
|
|
79
162
|
fs.readSync(fd, buf, 0, 4, 0);
|
|
80
163
|
fs.closeSync(fd);
|
|
81
|
-
if (buf[0] === 0x7f && buf[1] === 0x45) return true;
|
|
82
|
-
if (buf[0] === 0xcf && buf[1] === 0xfa) return true;
|
|
83
|
-
if (buf[0] === 0xfe && buf[1] === 0xed) return true;
|
|
84
|
-
if (buf[0] === 0x4d && buf[1] === 0x5a) return true;
|
|
164
|
+
if (buf[0] === 0x7f && buf[1] === 0x45) return true; // ELF
|
|
165
|
+
if (buf[0] === 0xcf && buf[1] === 0xfa) return true; // Mach-O (64-bit LE)
|
|
166
|
+
if (buf[0] === 0xfe && buf[1] === 0xed) return true; // Mach-O (32-bit BE)
|
|
167
|
+
if (buf[0] === 0x4d && buf[1] === 0x5a) return true; // PE (MZ)
|
|
85
168
|
return false;
|
|
86
|
-
} catch (_) {
|
|
169
|
+
} catch (_) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
87
172
|
}
|
|
88
173
|
|
|
89
174
|
function printInstallHelp() {
|
|
@@ -95,12 +180,13 @@ function printInstallHelp() {
|
|
|
95
180
|
console.error(' brew install finsavvyai/tap/pushci');
|
|
96
181
|
console.error(' go install github.com/finsavvyai/pushci/cmd/pushci@latest');
|
|
97
182
|
console.error('');
|
|
98
|
-
console.error("Don't have
|
|
183
|
+
console.error("Don't have Node installed?");
|
|
99
184
|
console.error(' macOS: brew install node');
|
|
100
|
-
console.error(' Linux:
|
|
185
|
+
console.error(' Linux: see https://nodejs.org/en/download/package-manager');
|
|
101
186
|
console.error(' Windows: https://nodejs.org');
|
|
102
187
|
console.error('');
|
|
103
|
-
console.error('
|
|
188
|
+
console.error('Offline? Install Go and the shim will build from source:');
|
|
189
|
+
console.error(' https://go.dev/dl');
|
|
104
190
|
}
|
|
105
191
|
|
|
106
192
|
const binary = getBinaryPath();
|
package/package.json
CHANGED