pushci 1.4.2 → 1.4.3
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.
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/pushci.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// pushci npm shim.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// pushci npm shim.
|
|
3
|
+
//
|
|
4
|
+
// Resolution order for the Go binary:
|
|
5
|
+
// 0. PUSHCI_BINARY env var — explicit user override (offline
|
|
6
|
+
// sandboxes, air-gapped CI, corporate proxies)
|
|
7
|
+
// 1. Local dev build at <pkg>/pushci (for pnpm link and goreleaser
|
|
8
|
+
// snapshot runs during development)
|
|
9
|
+
// 2. Bundled platform binary shipped in the npm tarball at
|
|
10
|
+
// bin/pushci-<os>-<arch>[.exe] — the canonical prod path
|
|
11
|
+
// 3. Existing pushci on PATH (Homebrew, go install, curl installer)
|
|
12
|
+
// 4. Download from GitHub Releases into os.tmpdir()
|
|
13
|
+
// 5. `go build` from source if Go is installed
|
|
14
|
+
// 6. Last resort: print install help with offline guidance
|
|
15
|
+
//
|
|
16
|
+
// VERSION reads from package.json so the shim never drifts from the
|
|
17
|
+
// npm package version. See CLAUDE.md "Release & Distribution" for
|
|
18
|
+
// the full pipeline.
|
|
7
19
|
|
|
8
20
|
const { execSync, spawn } = require('child_process');
|
|
9
21
|
const fs = require('fs');
|
|
@@ -13,23 +25,53 @@ const os = require('os');
|
|
|
13
25
|
const pkg = require('../package.json');
|
|
14
26
|
const VERSION = pkg.version;
|
|
15
27
|
const REPO = 'finsavvyai/push-ci.dev';
|
|
28
|
+
const GO_MODULE = 'github.com/finsavvyai/push-ci.dev/cmd/pushci';
|
|
16
29
|
|
|
17
30
|
const BINARY_NAME = os.platform() === 'win32' ? 'pushci.exe' : 'pushci';
|
|
18
|
-
|
|
19
31
|
const PLATFORM_MAP = { darwin: 'darwin', linux: 'linux', win32: 'windows' };
|
|
20
32
|
const ARCH_MAP = { x64: 'amd64', arm64: 'arm64' };
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
// Early short-circuit: `pushci version` / `--version` / `-v` should
|
|
35
|
+
// never trigger a 60s binary download. If we have a resolvable
|
|
36
|
+
// binary we delegate to it for the full version string (which
|
|
37
|
+
// includes the Go build ldflags); otherwise we print just the npm
|
|
38
|
+
// shim's VERSION and exit. Fixes the sandbox UX where asking for
|
|
39
|
+
// the version used to hang installing the binary.
|
|
40
|
+
function handleVersionShortCircuit() {
|
|
41
|
+
const first = process.argv[2];
|
|
42
|
+
if (first !== 'version' && first !== '--version' && first !== '-v') {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const binary = tryResolveBinary();
|
|
46
|
+
if (binary) {
|
|
47
|
+
const child = spawn(binary, [first], { stdio: 'inherit' });
|
|
48
|
+
child.on('error', () => printShimVersion());
|
|
49
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
printShimVersion();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printShimVersion() {
|
|
57
|
+
console.log(`pushci ${VERSION} (npm shim — no binary installed)`);
|
|
58
|
+
console.log('Install the native binary with:');
|
|
59
|
+
console.log(` brew install finsavvyai/tap/pushci # macOS / Linux`);
|
|
60
|
+
console.log(` go install ${GO_MODULE}@latest # anywhere with Go`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// tryResolveBinary is the non-download portion of getBinaryPath —
|
|
65
|
+
// used by the version short-circuit so we never kick off a network
|
|
66
|
+
// fetch just to print a version string.
|
|
67
|
+
function tryResolveBinary() {
|
|
68
|
+
if (process.env.PUSHCI_BINARY) {
|
|
69
|
+
const override = process.env.PUSHCI_BINARY;
|
|
70
|
+
if (fs.existsSync(override) && isValidBinary(override)) return override;
|
|
71
|
+
}
|
|
25
72
|
const local = path.join(__dirname, '..', BINARY_NAME);
|
|
26
73
|
if (fs.existsSync(local) && isValidBinary(local)) return local;
|
|
27
74
|
|
|
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
75
|
const plat = PLATFORM_MAP[os.platform()];
|
|
34
76
|
const arch = ARCH_MAP[os.arch()];
|
|
35
77
|
if (plat && arch) {
|
|
@@ -38,16 +80,18 @@ function getBinaryPath() {
|
|
|
38
80
|
if (fs.existsSync(bundled) && isValidBinary(bundled)) return bundled;
|
|
39
81
|
}
|
|
40
82
|
|
|
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.
|
|
44
83
|
try {
|
|
45
84
|
const cmd = os.platform() === 'win32' ? 'where' : 'which';
|
|
46
85
|
const found = execSync(`${cmd} pushci`, { encoding: 'utf8' }).trim();
|
|
47
86
|
if (found && found !== __filename && isValidBinary(found)) return found;
|
|
48
87
|
} catch (_) {}
|
|
49
88
|
|
|
50
|
-
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getBinaryPath() {
|
|
93
|
+
const resolved = tryResolveBinary();
|
|
94
|
+
if (resolved) return resolved;
|
|
51
95
|
return downloadOrBuild();
|
|
52
96
|
}
|
|
53
97
|
|
|
@@ -58,62 +102,50 @@ function downloadOrBuild() {
|
|
|
58
102
|
const target = path.join(os.tmpdir(), `pushci-${VERSION}${ext}`);
|
|
59
103
|
|
|
60
104
|
if (fs.existsSync(target) && isValidBinary(target)) return target;
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return target;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// If invoked from a git hook, don't block the push.
|
|
105
|
+
if (plat && arch && downloadFromReleases(plat, arch, target)) return target;
|
|
106
|
+
if (buildFromSource(target)) return target;
|
|
107
|
+
|
|
108
|
+
// If invoked from a git hook, warn visibly then let the push
|
|
109
|
+
// through. Earlier versions exited 0 with a one-liner that was
|
|
110
|
+
// easy to miss — users got the false impression that the
|
|
111
|
+
// pre-push hook had actually run their checks. Now we print a
|
|
112
|
+
// loud multi-line warning so the miss is obvious.
|
|
73
113
|
if (process.env.GIT_DIR) {
|
|
74
|
-
console.error('
|
|
114
|
+
console.error('');
|
|
115
|
+
console.error(' ⚠ pushci: BINARY UNAVAILABLE — pre-push checks SKIPPED');
|
|
116
|
+
console.error(' ⚠ This push is going through without running any CI.');
|
|
117
|
+
console.error(' ⚠ Fix: re-run `npm i -g pushci` OR set PUSHCI_BINARY=<path>');
|
|
118
|
+
console.error(' ⚠ Silence: export PUSHCI_SKIP_HOOK=1');
|
|
119
|
+
console.error('');
|
|
75
120
|
process.exit(0);
|
|
76
121
|
}
|
|
77
122
|
printInstallHelp();
|
|
78
123
|
process.exit(1);
|
|
79
124
|
}
|
|
80
125
|
|
|
81
|
-
// downloadFromReleases pulls the goreleaser archive
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
126
|
+
// downloadFromReleases pulls the goreleaser archive from GitHub
|
|
127
|
+
// Releases, extracts the binary, and moves it into place. Returns
|
|
128
|
+
// true on success, false on any failure so the caller can fall
|
|
129
|
+
// through to buildFromSource.
|
|
85
130
|
function downloadFromReleases(plat, arch, target) {
|
|
86
131
|
const isWin = os.platform() === 'win32';
|
|
87
132
|
const archiveExt = isWin ? 'zip' : 'tar.gz';
|
|
88
133
|
const archiveName = `pushci_${VERSION}_${plat}_${arch}.${archiveExt}`;
|
|
89
134
|
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
90
|
-
|
|
91
135
|
const scratchDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pushci-install-'));
|
|
92
136
|
const archivePath = path.join(scratchDir, archiveName);
|
|
93
137
|
|
|
94
138
|
console.log(`Downloading pushci v${VERSION} from GitHub Releases...`);
|
|
95
139
|
try {
|
|
96
|
-
execSync(
|
|
97
|
-
|
|
98
|
-
{ timeout: 60000 },
|
|
99
|
-
);
|
|
140
|
+
execSync(`curl -sfL --retry 2 --retry-delay 1 -o "${archivePath}" "${url}"`,
|
|
141
|
+
{ timeout: 60000 });
|
|
100
142
|
} catch (_) {
|
|
101
143
|
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
102
144
|
return false;
|
|
103
145
|
}
|
|
104
|
-
|
|
105
146
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
}
|
|
147
|
+
execSync(`tar -${isWin ? 'x' : 'xz'}f "${archivePath}" -C "${scratchDir}"`,
|
|
148
|
+
{ timeout: 30000 });
|
|
117
149
|
const extracted = path.join(scratchDir, BINARY_NAME);
|
|
118
150
|
if (!fs.existsSync(extracted) || !isValidBinary(extracted)) {
|
|
119
151
|
fs.rmSync(scratchDir, { recursive: true, force: true });
|
|
@@ -129,8 +161,9 @@ function downloadFromReleases(plat, arch, target) {
|
|
|
129
161
|
}
|
|
130
162
|
}
|
|
131
163
|
|
|
132
|
-
// buildFromSource is the Go-install fallback. Only runs when
|
|
133
|
-
// fails AND the user has Go on PATH. Useful for
|
|
164
|
+
// buildFromSource is the Go-install fallback. Only runs when
|
|
165
|
+
// download fails AND the user has Go on PATH. Useful for
|
|
166
|
+
// air-gapped dev setups.
|
|
134
167
|
function buildFromSource(target) {
|
|
135
168
|
try {
|
|
136
169
|
execSync('go version', { stdio: 'ignore' });
|
|
@@ -151,8 +184,7 @@ function buildFromSource(target) {
|
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
// isValidBinary sanity-checks the magic bytes so a half-downloaded
|
|
154
|
-
// archive doesn't pass for a real binary.
|
|
155
|
-
// (darwin little- and big-endian), PE (Windows).
|
|
187
|
+
// archive doesn't pass for a real binary.
|
|
156
188
|
function isValidBinary(filepath) {
|
|
157
189
|
try {
|
|
158
190
|
const stat = fs.statSync(filepath);
|
|
@@ -162,8 +194,8 @@ function isValidBinary(filepath) {
|
|
|
162
194
|
fs.readSync(fd, buf, 0, 4, 0);
|
|
163
195
|
fs.closeSync(fd);
|
|
164
196
|
if (buf[0] === 0x7f && buf[1] === 0x45) return true; // ELF
|
|
165
|
-
if (buf[0] === 0xcf && buf[1] === 0xfa) return true; // Mach-O
|
|
166
|
-
if (buf[0] === 0xfe && buf[1] === 0xed) return true; // Mach-O
|
|
197
|
+
if (buf[0] === 0xcf && buf[1] === 0xfa) return true; // Mach-O 64-bit LE
|
|
198
|
+
if (buf[0] === 0xfe && buf[1] === 0xed) return true; // Mach-O 32-bit BE
|
|
167
199
|
if (buf[0] === 0x4d && buf[1] === 0x5a) return true; // PE (MZ)
|
|
168
200
|
return false;
|
|
169
201
|
} catch (_) {
|
|
@@ -173,27 +205,36 @@ function isValidBinary(filepath) {
|
|
|
173
205
|
|
|
174
206
|
function printInstallHelp() {
|
|
175
207
|
console.error('');
|
|
176
|
-
console.error('pushci: could not
|
|
208
|
+
console.error('pushci: could not locate a working binary.');
|
|
177
209
|
console.error('');
|
|
178
|
-
console.error('
|
|
210
|
+
console.error('Online install paths:');
|
|
179
211
|
console.error(' curl -sSL https://pushci.dev/install | bash');
|
|
180
212
|
console.error(' brew install finsavvyai/tap/pushci');
|
|
181
|
-
console.error(
|
|
213
|
+
console.error(` go install ${GO_MODULE}@latest`);
|
|
214
|
+
console.error('');
|
|
215
|
+
console.error('Offline / sandbox / air-gapped path:');
|
|
216
|
+
console.error(' 1. Download the tarball for your platform from:');
|
|
217
|
+
console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
|
|
218
|
+
console.error(' 2. Extract it: tar -xzf pushci_<version>_<os>_<arch>.tar.gz');
|
|
219
|
+
console.error(' 3. Point the shim at the extracted binary:');
|
|
220
|
+
console.error(' export PUSHCI_BINARY=/path/to/pushci');
|
|
182
221
|
console.error('');
|
|
183
222
|
console.error("Don't have Node installed?");
|
|
184
223
|
console.error(' macOS: brew install node');
|
|
185
|
-
console.error(' Linux:
|
|
224
|
+
console.error(' Linux: https://nodejs.org/en/download/package-manager');
|
|
186
225
|
console.error(' Windows: https://nodejs.org');
|
|
187
|
-
console.error('');
|
|
188
|
-
console.error('Offline? Install Go and the shim will build from source:');
|
|
189
|
-
console.error(' https://go.dev/dl');
|
|
190
226
|
}
|
|
191
227
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
228
|
+
function main() {
|
|
229
|
+
if (handleVersionShortCircuit()) return;
|
|
230
|
+
const binary = getBinaryPath();
|
|
231
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
232
|
+
child.on('error', (err) => {
|
|
233
|
+
console.error(`pushci: failed to start — ${err.message}`);
|
|
234
|
+
printInstallHelp();
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
|
237
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushci",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "AI-native CI/CD that runs on your machine. Zero config, zero cost. 33 languages, 69 skills, Tailscale mesh, blast radius analysis.",
|
|
3
|
+
"version": "1.4.3",
|
|
4
|
+
"description": "AI-native CI/CD that runs on your machine. Zero config, zero cost. Works inside AI agent sandboxes (Claude, Cursor, Windsurf). 33 languages, 69 skills, Tailscale mesh, blast radius analysis.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pushci": "bin/pushci.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/pushci.js",
|
|
10
|
+
"bin/pushci-darwin-amd64",
|
|
11
|
+
"bin/pushci-darwin-arm64",
|
|
12
|
+
"bin/pushci-linux-amd64",
|
|
13
|
+
"bin/pushci-linux-arm64",
|
|
14
|
+
"bin/pushci-windows-amd64.exe",
|
|
15
|
+
"bin/pushci-windows-arm64.exe",
|
|
10
16
|
"README.md",
|
|
11
17
|
"LICENSE"
|
|
12
18
|
],
|
|
13
19
|
"scripts": {
|
|
20
|
+
"prepack": "bash scripts/build-bundled-binaries.sh",
|
|
14
21
|
"prepublishOnly": "echo 'Publishing pushci v'${npm_package_version}",
|
|
15
22
|
"postpublish": "bash scripts/submit-registries.sh"
|
|
16
23
|
},
|