pushci 1.3.0 → 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/README.md +219 -27
- package/bin/pushci.js +126 -40
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -61,8 +61,9 @@ PushCI includes an MCP server for AI coding agents like **Claude Code**, **Curso
|
|
|
61
61
|
|
|
62
62
|
## Supported
|
|
63
63
|
|
|
64
|
-
**
|
|
65
|
-
Ruby, PHP, Swift, Dart, Elixir, Zig,
|
|
64
|
+
**33 Languages**: Go, Node/TS, Python, Rust, Java, C#,
|
|
65
|
+
Ruby, PHP, Swift, Dart, Elixir, Zig, Scala, Haskell,
|
|
66
|
+
Kotlin, Lua, Perl, R, Julia, OCaml, Nim, Crystal +more
|
|
66
67
|
|
|
67
68
|
**40+ Frameworks**: Next.js, Nuxt, SvelteKit, Django, FastAPI,
|
|
68
69
|
Flask, Spring Boot, Rails, Laravel, Phoenix, Flutter +more
|
|
@@ -103,44 +104,235 @@ Supported platforms:
|
|
|
103
104
|
## Commands
|
|
104
105
|
|
|
105
106
|
```
|
|
106
|
-
pushci init
|
|
107
|
-
pushci run
|
|
108
|
-
pushci deploy
|
|
109
|
-
pushci diagnose
|
|
110
|
-
pushci status
|
|
111
|
-
pushci secret
|
|
112
|
-
pushci heal
|
|
113
|
-
pushci ask
|
|
114
|
-
pushci generate
|
|
115
|
-
pushci migrate
|
|
116
|
-
pushci mcp
|
|
117
|
-
pushci agent
|
|
118
|
-
pushci
|
|
119
|
-
pushci
|
|
120
|
-
pushci
|
|
107
|
+
pushci init Detect stack and generate pushci.yml
|
|
108
|
+
pushci run Execute pipeline checks
|
|
109
|
+
pushci deploy Deploy to target environment
|
|
110
|
+
pushci diagnose AI-diagnose failed runs
|
|
111
|
+
pushci status Show last run results
|
|
112
|
+
pushci secret Manage encrypted secrets
|
|
113
|
+
pushci heal AI self-heal broken pipeline
|
|
114
|
+
pushci ask Natural language CI commands
|
|
115
|
+
pushci generate AI-generate pushci.yml
|
|
116
|
+
pushci migrate Convert GitHub Actions workflow
|
|
117
|
+
pushci mcp Start MCP server for AI agents
|
|
118
|
+
pushci agent Start webhook agent server
|
|
119
|
+
pushci index Build dependency graph for blast radius
|
|
120
|
+
pushci skill Install/list/remove marketplace skills
|
|
121
|
+
pushci login Authenticate with PushCI (Pro)
|
|
122
|
+
pushci logout Remove saved credentials
|
|
123
|
+
pushci doctor Check environment health
|
|
121
124
|
pushci troubleshoot Diagnose issues with actionable fixes
|
|
122
|
-
pushci
|
|
125
|
+
pushci trace View Perfetto performance traces
|
|
126
|
+
pushci release Build & publish release locally ($0)
|
|
127
|
+
pushci promote Register with AI registries
|
|
128
|
+
pushci uninstall Remove hooks, config, and .pushci
|
|
129
|
+
pushci version Print version
|
|
123
130
|
```
|
|
124
131
|
|
|
125
132
|
See [docs/CLI.md](docs/CLI.md) for the full CLI reference with flags, examples, and plan requirements.
|
|
126
133
|
|
|
127
134
|
## Configuration
|
|
128
135
|
|
|
129
|
-
|
|
136
|
+
`pushci.yml` is optional — `pushci init` generates one that works, and
|
|
137
|
+
zero-config mode auto-detects your stack. When you want more control,
|
|
138
|
+
PushCI supports three authoring styles. Full reference is at
|
|
139
|
+
[pushci.dev/docs/pushci-yaml](https://pushci.dev/docs/pushci-yaml).
|
|
140
|
+
|
|
141
|
+
### Simple example
|
|
142
|
+
|
|
143
|
+
Zero-config Node.js app with linear stages, single-target deploy on
|
|
144
|
+
`main`, and Slack notifications. Drop this into your repo as
|
|
145
|
+
`pushci.yml` and run `pushci run`.
|
|
130
146
|
|
|
131
147
|
```yaml
|
|
132
148
|
on: [push, pull_request]
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
|
|
150
|
+
stages:
|
|
151
|
+
- name: install
|
|
152
|
+
checks:
|
|
153
|
+
- name: deps
|
|
154
|
+
run: pnpm install --frozen-lockfile
|
|
155
|
+
|
|
156
|
+
- name: lint
|
|
157
|
+
depends_on: [install]
|
|
158
|
+
checks:
|
|
159
|
+
- name: eslint
|
|
160
|
+
run: pnpm lint
|
|
161
|
+
|
|
162
|
+
- name: test
|
|
163
|
+
depends_on: [install]
|
|
164
|
+
checks:
|
|
165
|
+
- name: vitest
|
|
166
|
+
run: pnpm test
|
|
167
|
+
|
|
168
|
+
- name: build
|
|
169
|
+
depends_on: [install, lint, test]
|
|
170
|
+
checks:
|
|
171
|
+
- name: next-build
|
|
172
|
+
run: pnpm build
|
|
173
|
+
|
|
174
|
+
deploy:
|
|
175
|
+
trigger: push
|
|
176
|
+
only_on: [main]
|
|
177
|
+
run: npx wrangler pages deploy dist --project-name=my-app
|
|
178
|
+
|
|
179
|
+
notify:
|
|
180
|
+
slack: "${{ secrets.SLACK_WEBHOOK }}"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Complex example — every feature
|
|
184
|
+
|
|
185
|
+
Parallel stages, cross-stage DAG, conditional checks, retries,
|
|
186
|
+
timeouts, Docker-isolated steps, multi-environment staged deploy with
|
|
187
|
+
an approval gate on production, and stage-scoped secrets.
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
on: [push, pull_request, workflow_dispatch]
|
|
191
|
+
|
|
192
|
+
stages:
|
|
193
|
+
- name: install
|
|
194
|
+
checks:
|
|
195
|
+
- name: pnpm-install
|
|
196
|
+
run: pnpm install --frozen-lockfile
|
|
197
|
+
retry: 2
|
|
198
|
+
timeout: 3m
|
|
199
|
+
|
|
200
|
+
- name: quality
|
|
201
|
+
depends_on: [install]
|
|
202
|
+
parallel: true # every check runs concurrently
|
|
203
|
+
env:
|
|
204
|
+
NODE_ENV: test
|
|
205
|
+
checks:
|
|
206
|
+
- name: typecheck
|
|
207
|
+
run: pnpm tsc --noEmit
|
|
208
|
+
- name: lint
|
|
209
|
+
run: pnpm lint
|
|
210
|
+
- name: format
|
|
211
|
+
run: pnpm prettier --check .
|
|
212
|
+
- name: audit
|
|
213
|
+
run: pnpm audit --audit-level=high
|
|
214
|
+
on_fail: warn # log but don't fail the stage
|
|
215
|
+
|
|
216
|
+
- name: test
|
|
217
|
+
depends_on: [install]
|
|
218
|
+
parallel: true
|
|
219
|
+
env:
|
|
220
|
+
DATABASE_URL: postgres://test:test@localhost:5432/test
|
|
221
|
+
checks:
|
|
222
|
+
- name: unit
|
|
223
|
+
run: pnpm test:unit --coverage
|
|
224
|
+
- name: integration
|
|
225
|
+
run: pnpm test:integration
|
|
226
|
+
retry: 1
|
|
227
|
+
timeout: 5m
|
|
228
|
+
- name: e2e
|
|
229
|
+
if: branch == 'main' || branch =~ '^release/'
|
|
230
|
+
run: pnpm test:e2e
|
|
231
|
+
docker: mcr.microsoft.com/playwright:v1.49.0-focal
|
|
232
|
+
timeout: 10m
|
|
233
|
+
|
|
234
|
+
- name: security
|
|
235
|
+
depends_on: [install]
|
|
236
|
+
checks:
|
|
237
|
+
- name: secret-scan
|
|
238
|
+
run: npx gitleaks detect --no-git
|
|
239
|
+
- name: sast
|
|
240
|
+
run: pushci scan --engine claude --fail-on high
|
|
241
|
+
|
|
242
|
+
- name: build
|
|
243
|
+
depends_on: [quality, test, security]
|
|
244
|
+
checks:
|
|
245
|
+
- name: next-build
|
|
246
|
+
run: pnpm build
|
|
247
|
+
- name: bundle-size
|
|
248
|
+
run: npx size-limit
|
|
249
|
+
line-limit: 10
|
|
250
|
+
|
|
138
251
|
deploy:
|
|
139
|
-
|
|
140
|
-
|
|
252
|
+
trigger: push
|
|
253
|
+
environments:
|
|
254
|
+
- name: staging
|
|
255
|
+
only_on: [develop]
|
|
256
|
+
run: npx wrangler pages deploy dist --project-name=app-staging
|
|
257
|
+
env:
|
|
258
|
+
CF_API_TOKEN: "${{ secrets.CF_API_TOKEN_STAGING }}"
|
|
259
|
+
- name: production
|
|
260
|
+
only_on: [main]
|
|
261
|
+
approve: true # requires interactive approval
|
|
262
|
+
run: npx wrangler pages deploy dist --project-name=app-prod
|
|
263
|
+
env:
|
|
264
|
+
CF_API_TOKEN: "${{ secrets.CF_API_TOKEN_PROD }}"
|
|
265
|
+
|
|
266
|
+
notify:
|
|
267
|
+
slack: "${{ secrets.SLACK_WEBHOOK }}"
|
|
268
|
+
discord: "${{ secrets.DISCORD_WEBHOOK }}"
|
|
269
|
+
email: oncall@example.com
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### GitHub Actions parity — no rewrite needed
|
|
273
|
+
|
|
274
|
+
PushCI v1.3.1+ runs your existing `.github/workflows/*.yml` files
|
|
275
|
+
end-to-end via the embedded [nektos/act](https://github.com/nektos/act)
|
|
276
|
+
runtime. `actions/checkout@v4`, matrix builds, service containers,
|
|
277
|
+
composite actions, secret masking, `needs.*.outputs.*` — all work.
|
|
278
|
+
Just drop in a workflow and run:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
pushci actions run # runs all .github/workflows/*.yml
|
|
282
|
+
pushci actions run --job test # run one job
|
|
283
|
+
pushci actions run --dry-run # validate without containers
|
|
284
|
+
pushci actions doctor # check act + docker + workflow status
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
A real complex workflow that runs unchanged:
|
|
288
|
+
|
|
289
|
+
```yaml
|
|
290
|
+
name: CI
|
|
291
|
+
on: [push, pull_request]
|
|
292
|
+
|
|
293
|
+
jobs:
|
|
294
|
+
test:
|
|
295
|
+
runs-on: ubuntu-latest
|
|
296
|
+
strategy:
|
|
297
|
+
fail-fast: false
|
|
298
|
+
matrix:
|
|
299
|
+
node: [20, 22]
|
|
300
|
+
services:
|
|
301
|
+
postgres:
|
|
302
|
+
image: postgres:16
|
|
303
|
+
env:
|
|
304
|
+
POSTGRES_PASSWORD: test
|
|
305
|
+
ports: ['5432:5432']
|
|
306
|
+
options: >-
|
|
307
|
+
--health-cmd pg_isready --health-interval 10s
|
|
308
|
+
--health-timeout 5s --health-retries 5
|
|
309
|
+
steps:
|
|
310
|
+
- uses: actions/checkout@v4
|
|
311
|
+
- uses: actions/setup-node@v4
|
|
312
|
+
with:
|
|
313
|
+
node-version: ${{ matrix.node }}
|
|
314
|
+
- run: npm ci
|
|
315
|
+
- run: npm test
|
|
316
|
+
env:
|
|
317
|
+
DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
|
|
318
|
+
- id: coverage
|
|
319
|
+
run: echo "pct=$(npm test --silent)" >> $GITHUB_OUTPUT
|
|
320
|
+
outputs:
|
|
321
|
+
coverage: ${{ steps.coverage.outputs.pct }}
|
|
322
|
+
|
|
323
|
+
deploy:
|
|
324
|
+
needs: test
|
|
325
|
+
runs-on: ubuntu-latest
|
|
326
|
+
if: github.ref == 'refs/heads/main'
|
|
327
|
+
steps:
|
|
328
|
+
- uses: actions/checkout@v4
|
|
329
|
+
- name: Deploy
|
|
330
|
+
run: echo "coverage was ${{ needs.test.outputs.coverage }}"
|
|
331
|
+
env:
|
|
332
|
+
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
|
141
333
|
```
|
|
142
334
|
|
|
143
|
-
Or just run `pushci init` —
|
|
335
|
+
Or just run `pushci init` — PushCI figures everything out automatically.
|
|
144
336
|
|
|
145
337
|
## Git Hook
|
|
146
338
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushci",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI-native CI/CD that runs on your machine. Zero config, zero cost.
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "AI-native CI/CD that runs on your machine. Zero config, zero cost. 33 languages, 69 skills, Tailscale mesh, blast radius analysis.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pushci": "bin/pushci.js"
|
|
7
7
|
},
|