mirror of https://github.com/garrytan/gstack.git
Merge d98b587928 into c43c850cae
This commit is contained in:
commit
55d4275894
|
|
@ -749,6 +749,47 @@ describe('CLI lifecycle', () => {
|
|||
expect(result.stdout).toContain('Status: healthy');
|
||||
expect(result.stderr).toContain('Starting server');
|
||||
}, 20000);
|
||||
|
||||
test('sequential CLI invocations reuse the same daemon and page state', async () => {
|
||||
const stateFile = `/tmp/browse-test-persist-${Date.now()}.json`;
|
||||
const cliPath = path.resolve(__dirname, '../src/cli.ts');
|
||||
const cliEnv: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) {
|
||||
if (v !== undefined) cliEnv[k] = v;
|
||||
}
|
||||
cliEnv.BROWSE_STATE_FILE = stateFile;
|
||||
|
||||
const runCli = (args: string[]) =>
|
||||
new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
|
||||
const proc = spawn('bun', ['run', cliPath, ...args], {
|
||||
timeout: 15000,
|
||||
env: cliEnv,
|
||||
});
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
proc.stdout.on('data', (d) => stdout += d.toString());
|
||||
proc.stderr.on('data', (d) => stderr += d.toString());
|
||||
proc.on('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
||||
});
|
||||
|
||||
const gotoResult = await runCli(['goto', `${baseUrl}/basic.html`]);
|
||||
expect(gotoResult.code).toBe(0);
|
||||
expect(gotoResult.stdout).toContain('Navigated to');
|
||||
|
||||
const pid1 = JSON.parse(fs.readFileSync(stateFile, 'utf-8')).pid;
|
||||
|
||||
const textResult = await runCli(['text']);
|
||||
expect(textResult.code).toBe(0);
|
||||
expect(textResult.stdout).toContain('Hello World');
|
||||
|
||||
const pid2 = JSON.parse(fs.readFileSync(stateFile, 'utf-8')).pid;
|
||||
|
||||
try { fs.unlinkSync(stateFile); } catch {}
|
||||
try { process.kill(pid2, 'SIGTERM'); } catch {}
|
||||
|
||||
expect(pid1).toBe(pid2);
|
||||
expect(textResult.stderr).not.toContain('Starting server');
|
||||
}, 20000);
|
||||
});
|
||||
|
||||
// ─── Buffer bounds ──────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import { describe, test, expect } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
||||
function makeTempDir(prefix: string): string {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
}
|
||||
|
||||
function copyRepoWithoutGit(dest: string): void {
|
||||
fs.cpSync(ROOT, dest, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
preserveTimestamps: true,
|
||||
filter: (src) => {
|
||||
const rel = path.relative(ROOT, src);
|
||||
if (!rel) return true;
|
||||
const top = rel.split(path.sep)[0];
|
||||
return top !== '.git' && top !== 'node_modules' && top !== '.agents';
|
||||
},
|
||||
});
|
||||
|
||||
const nodeModulesSrc = path.join(ROOT, 'node_modules');
|
||||
const nodeModulesDest = path.join(dest, 'node_modules');
|
||||
fs.symlinkSync(
|
||||
nodeModulesSrc,
|
||||
nodeModulesDest,
|
||||
process.platform === 'win32' ? 'junction' : 'dir'
|
||||
);
|
||||
}
|
||||
|
||||
function runSetup(cwd: string, homeDir: string): ReturnType<typeof spawnSync> {
|
||||
return spawnSync('bash', ['./setup', '--host', 'codex'], {
|
||||
cwd,
|
||||
env: { ...process.env, HOME: homeDir },
|
||||
encoding: 'utf-8',
|
||||
timeout: 120_000,
|
||||
});
|
||||
}
|
||||
|
||||
describe('setup --host codex smoke', () => {
|
||||
test('global install creates Codex runtime root and generated skills', () => {
|
||||
const repoDir = makeTempDir('gstack-codex-global-repo-');
|
||||
const homeDir = makeTempDir('gstack-codex-global-home-');
|
||||
|
||||
try {
|
||||
copyRepoWithoutGit(repoDir);
|
||||
const result = runSetup(repoDir, homeDir);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain('gstack ready (codex).');
|
||||
|
||||
const runtimeRoot = path.join(homeDir, '.codex', 'skills', 'gstack');
|
||||
const reviewSkill = path.join(homeDir, '.codex', 'skills', 'gstack-review', 'SKILL.md');
|
||||
|
||||
expect(fs.existsSync(path.join(runtimeRoot, 'SKILL.md'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(runtimeRoot, 'browse', 'dist'))).toBe(true);
|
||||
expect(fs.existsSync(path.join(reviewSkill))).toBe(true);
|
||||
expect(fs.lstatSync(path.join(homeDir, '.codex', 'skills', 'gstack-review')).isSymbolicLink()).toBe(true);
|
||||
} finally {
|
||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 120_000);
|
||||
|
||||
test('repo-local install writes generated skills next to .agents checkout only', () => {
|
||||
const projectDir = makeTempDir('gstack-codex-local-project-');
|
||||
const homeDir = makeTempDir('gstack-codex-local-home-');
|
||||
const repoDir = path.join(projectDir, '.agents', 'skills', 'gstack');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(repoDir), { recursive: true });
|
||||
copyRepoWithoutGit(repoDir);
|
||||
const result = runSetup(repoDir, homeDir);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain('gstack ready (codex).');
|
||||
|
||||
const localSkill = path.join(projectDir, '.agents', 'skills', 'gstack-review', 'SKILL.md');
|
||||
const localSidecar = path.join(projectDir, '.agents', 'skills', 'gstack', 'bin');
|
||||
const globalSkill = path.join(homeDir, '.codex', 'skills', 'gstack-review');
|
||||
|
||||
expect(fs.existsSync(localSkill)).toBe(true);
|
||||
expect(fs.existsSync(localSidecar)).toBe(true);
|
||||
expect(fs.existsSync(globalSkill)).toBe(false);
|
||||
} finally {
|
||||
fs.rmSync(projectDir, { recursive: true, force: true });
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 120_000);
|
||||
});
|
||||
Loading…
Reference in New Issue