Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
53 linesCodeBlameRaw
1import { Miniflare, convertV4MiniflareOptions } from 'miniflare';
2import { readFileSync, readdirSync } from 'node:fs';
3import { resolve } from 'node:path';
4
5// Local-only integration fixture: real built API/Work Workers and D1.
6// Authentication, repository lookup and event transport are deterministic
7// fixtures. No production credentials or remote service bindings are used.
8const root = resolve(import.meta.dirname, '..');
9const user = { id: 'usr_test', username: 'tester', verified: true, workspaces: [] };
10const repo = { id: 'rep_test', namespace: 'tester', name: 'rehearsal', description: null, isPrivate: false, ownerId: user.id, defaultBranch: 'main', forkOf: null, createdAt: '2026-10-14T00:00:00Z' };
11const stub = `const user=${JSON.stringify(user)}; const repo=${JSON.stringify(repo)};
12export default { async fetch(request) {
13 const method = new URL(request.url).pathname.split('/').pop();
14 const body = await request.json();
15 if (method === 'user_for_access_token') return Response.json(body.token === 'local-rehearsal' ? user : body.token === 'local-other' ? {...user,id:'usr_other',username:'other'} : null);
16 if (method === 'get') return Response.json({ok:true,value:repo});
17 if (method === 'publish') return Response.json(null);
18 if (method === 'audit_record') return Response.json(1);
19 if (method === 'resolve_slug' || method === 'resolve_path') return Response.json(null);
20 if (method === 'display_usernames') return Response.json({});
21 console.error('Unimplemented local fixture RPC: '+method); return new Response('Unimplemented local fixture RPC: '+method,{status:500});
22}};`;
23const serviceNames = ['IDENTITY','REPOS','EVENTS','RUNNER','BILLING','INTEGRATIONS','WEBHOOKS','ACTIONS','SEARCH','SECURITY','PROJECTS','DEPLOYMENTS','TELEMETRY','PACKAGES','ARTIFACTS'];
24const bindings = Object.fromEntries(serviceNames.map(name => [name, 'fixture']));
25const worker = (name, path, extra) => ({ name, modules: [{type:'ESModule',path:resolve(root,path,'build/index.js')},{type:'CompiledWasm',path:resolve(root,path,'build/index_bg.wasm')}], compatibilityDate:'2026-09-26', ...extra });
26export async function startLocal({ port = 8799, persist = false } = {}) {
27 const mf = new Miniflare(convertV4MiniflareOptions({ host:'127.0.0.1', port, resourcePersistencePath: persist || undefined, workers:[
28 worker('api','apps/api',{serviceBindings:{...bindings,WORK:'work'}, bindings:{MCP_URL:`http://127.0.0.1:${port}/mcp`,API_URL:`http://127.0.0.1:${port}`,SITE_URL:`http://127.0.0.1:${port}`}}),
29 worker('work','services/work',{serviceBindings:bindings,d1Databases:{DB:'local-coordination'}}),
30 {name:'fixture',modules:true,script:stub,compatibilityDate:'2026-09-26'}
31 ]}));
32 const ready = await mf.ready;
33 const db = await mf.getD1Database('DB','work');
34 const initialized = await db.prepare("SELECT name FROM sqlite_master WHERE name = 'sessions'").first();
35 if (!initialized) {
36 for (const file of readdirSync(resolve(root,'services/work/migrations')).filter(f=>f.endsWith('.sql')).sort()) {
37 try {
38 // These migrations contain no triggers. Keep each complete statement on
39 // one line for D1.exec, whose API treats newlines as statement boundaries.
40 const sql = readFileSync(resolve(root,'services/work/migrations',file),'utf8')
41 .replace(/--[^\n]*/g,'').replace(/\r?\n/g,' ').split(';').map(s=>s.trim()).filter(Boolean).join(';\n');
42 await db.exec(sql);
43 }
44 catch(error) { await mf.dispose(); throw new Error(`${file}: ${error.message}`); }
45 }
46 }
47 return {mf, db, url:ready.origin};
48}
49if (process.argv[1] && resolve(process.argv[1]) === resolve(import.meta.filename)) {
50 const {mf,url} = await startLocal({persist:resolve(root,'target/submission-audit/local-coordination')});
51 console.log(`Local rehearsal ready: ${url}; MCP: ${url}/mcp; token: local-rehearsal (fixture only)`);
52 for (const signal of ['SIGINT','SIGTERM']) process.on(signal,async()=>{await mf.dispose();process.exit(0);});
53}