mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-04 07:47:27 +01:00
fix(windows): every compiled binary answers --version before any side effect
browse --version started a server and exited 1, pdf printed usage and exited 1, gstack-global-discover rejected the flag, and find-browse ran discovery. A launch probe for Smart App Control (#2595) needs one side-effect-free command that exits 0 on a healthy build, so browse, find-browse, make-pdf and gstack-global-discover now answer --version first (design already did). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
67b36f6625
commit
3e62aceae5
5 files changed
+95
-3
No files matched your search
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync, statSync, readFileSync, openSync, readSync, closeSync } from "fs";
|
||||
import { join, basename } from "path";
|
||||
import { join, basename, dirname } from "path";
|
||||
import { execSync } from "child_process";
|
||||
import { homedir } from "os";
|
||||
import { canonicalRemote } from "../lib/remote-identity";
|
||||
@@ -50,12 +50,24 @@ function printUsage(): void {
|
||||
--since <window> Time window: e.g. 7d, 14d, 30d, 24h
|
||||
--format <fmt> Output format: json (default) or summary
|
||||
--help Show this help
|
||||
--version Print the gstack version and exit (no scan)
|
||||
|
||||
Examples:
|
||||
gstack-global-discover --since 7d
|
||||
gstack-global-discover --since 14d --format summary`);
|
||||
}
|
||||
|
||||
function readGstackVersion(): string {
|
||||
// The compiled binary and this source file both live in bin/, one level below VERSION.
|
||||
for (const dir of [dirname(process.execPath), import.meta.dir]) {
|
||||
try {
|
||||
const version = readFileSync(join(dir, "..", "VERSION"), "utf8").trim();
|
||||
if (version) return version;
|
||||
} catch {}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function parseArgs(): { since: string; format: "json" | "summary" } {
|
||||
const args = process.argv.slice(2);
|
||||
let since = "";
|
||||
@@ -65,6 +77,10 @@ function parseArgs(): { since: string; format: "json" | "summary" } {
|
||||
if (args[i] === "--help" || args[i] === "-h") {
|
||||
printUsage();
|
||||
process.exit(0);
|
||||
} else if (args[i] === "--version") {
|
||||
// setup's and gstack-doctor's launch probe (#2595): exits before any scan.
|
||||
console.log(readGstackVersion());
|
||||
process.exit(0);
|
||||
} else if (args[i] === "--since" && args[i + 1]) {
|
||||
since = args[++i];
|
||||
} else if (args[i] === "--format" && args[i + 1]) {
|
||||
|
||||
@@ -1715,6 +1715,12 @@ export function withLoopbackNoProxy(env: Record<string, string | undefined>): st
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// setup's and gstack-doctor's launch probe: proves the binary starts, with no
|
||||
// server, flags or network (Smart App Control detection, #2595).
|
||||
if (process.argv[2] === '--version') {
|
||||
console.log(readVersionHash() ?? 'unknown');
|
||||
process.exit(0);
|
||||
}
|
||||
process.env.NO_PROXY = process.env.no_proxy = withLoopbackNoProxy(process.env);
|
||||
const rawArgs = process.argv.slice(2);
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* Outputs the absolute path to the browse binary on stdout, or exits 1 if not found.
|
||||
*/
|
||||
|
||||
import { accessSync, constants } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { accessSync, constants, readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
// ─── Binary Discovery ───────────────────────────────────────────
|
||||
@@ -91,6 +91,13 @@ export function locateBinary(): string | null {
|
||||
// ─── Main ───────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
// setup's and gstack-doctor's launch probe (#2595): no git, no discovery.
|
||||
if (process.argv[2] === '--version') {
|
||||
let version = 'unknown';
|
||||
try { version = readFileSync(join(dirname(process.execPath), '.version'), 'utf8').trim() || version; } catch {}
|
||||
console.log(version);
|
||||
process.exit(0);
|
||||
}
|
||||
const bin = locateBinary();
|
||||
if (!bin) {
|
||||
process.stderr.write('ERROR: browse binary not found. Run: cd <skill-dir> && ./setup\n');
|
||||
|
||||
@@ -40,6 +40,8 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
||||
printUsage();
|
||||
process.exit(ExitCode.Success);
|
||||
}
|
||||
// setup's and gstack-doctor's launch probe (#2595): the version command needs no browser.
|
||||
if (args[0] === "--version") return { command: "version", positional: [], flags: {} };
|
||||
|
||||
// First non-flag arg is the command.
|
||||
let command = "";
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
// #2595: setup and gstack-doctor detect Smart App Control by launching each
|
||||
// compiled binary with --version. Each entry must answer before any server
|
||||
// start, discovery scan or network call, or the probe would misclassify a
|
||||
// healthy build. Runs the source entries; the compiled binaries embed them.
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const tmps: string[] = [];
|
||||
afterEach(() => { for (const t of tmps.splice(0)) fs.rmSync(t, { recursive: true, force: true }); });
|
||||
|
||||
function runVersion(entry: string) {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-version-probe-'));
|
||||
tmps.push(tmp);
|
||||
const home = path.join(tmp, 'home');
|
||||
fs.mkdirSync(home);
|
||||
const r = spawnSync(process.execPath, ['run', path.join(ROOT, entry), '--version'], {
|
||||
cwd: tmp,
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
BROWSE_STATE_FILE: path.join(tmp, 'state', '.gstack', 'browse.json'),
|
||||
GSTACK_STATE_ROOT: path.join(tmp, 'gstack-state'),
|
||||
HTTPS_PROXY: 'http://127.0.0.1:9',
|
||||
HTTP_PROXY: 'http://127.0.0.1:9',
|
||||
},
|
||||
encoding: 'utf8',
|
||||
timeout: 30_000,
|
||||
});
|
||||
return { ...r, tmp, home };
|
||||
}
|
||||
|
||||
describe('compiled-binary entries answer --version early (#2595)', () => {
|
||||
for (const entry of ['browse/src/cli.ts', 'browse/src/find-browse.ts', 'design/src/cli.ts', 'make-pdf/src/cli.ts', 'bin/gstack-global-discover.ts']) {
|
||||
test(`${entry} --version exits 0 with one line and no side effects`, () => {
|
||||
const r = runVersion(entry);
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout.trim().split('\n')).toHaveLength(1);
|
||||
expect(r.stdout.trim()).not.toBe('');
|
||||
expect(r.stderr).not.toContain('Starting server');
|
||||
expect(r.stderr).not.toMatch(/Unknown|Usage/);
|
||||
expect(fs.existsSync(path.join(r.tmp, 'state'))).toBe(false);
|
||||
expect(fs.existsSync(path.join(r.tmp, 'gstack-state'))).toBe(false);
|
||||
// `bun run` itself may create its runtime cache under the home; the entry writes nothing.
|
||||
expect(fs.readdirSync(r.home).filter(name => name !== '.bun' && name !== 'AppData')).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
test('find-browse --version does not search for browse', () => {
|
||||
expect(runVersion('browse/src/find-browse.ts').stdout).not.toContain('browse/dist/browse');
|
||||
});
|
||||
|
||||
test('gstack-global-discover --version prints the checkout VERSION', () => {
|
||||
const version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf8').trim();
|
||||
expect(runVersion('bin/gstack-global-discover.ts').stdout.trim()).toBe(version);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user