Skip to content

Commit add4147

Browse files
committed
Refactored how commands are handled. Allows for greater flexibility.
1 parent dae7277 commit add4147

File tree

7 files changed

+241
-46
lines changed

7 files changed

+241
-46
lines changed

codify-imports/neal.codify.jsonc

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
[
2+
// Installs homebrew and packages
3+
{
4+
"type": "homebrew",
5+
"formulae": [
6+
"dopplerhq/cli/doppler",
7+
"gnupg",
8+
"hookdeck/hookdeck/hookdeck",
9+
"libpq",
10+
"temporal"
11+
],
12+
"casks": [
13+
"google-chrome",
14+
"slack",
15+
"sublime-text"
16+
]
17+
},
18+
19+
// Sets up SSH keys and helps setup github SSH
20+
{
21+
"type": "ssh-config",
22+
"hosts": [
23+
{
24+
"Host": "github.com",
25+
"AddKeysToAgent": true,
26+
"UseKeychain": true,
27+
"IgnoreUnknown": "UseKeychain",
28+
"IdentityFile": "~/.ssh/id_ed25519"
29+
}
30+
]
31+
},
32+
{
33+
"type": "ssh-key",
34+
"fileName": "id_ed25519",
35+
"passphrase": "my_temp_pass",
36+
"keyType": "ed25519"
37+
},
38+
{
39+
"type": "ssh-add",
40+
"path": "~/.ssh/id_ed25519",
41+
"appleUseKeychain": true
42+
},
43+
{ "type": "wait-github-ssh-key" },
44+
45+
// Configs global git username and email
46+
{
47+
"type": "git",
48+
"email": "chandra.neal@gmail.com",
49+
"username": "neal"
50+
},
51+
52+
// Setup node
53+
{
54+
"type": "nvm",
55+
"global": "23",
56+
"nodeVersions": [
57+
"23"
58+
]
59+
},
60+
{ "type": "pnpm" },
61+
62+
// Docker. This will install docker, docker engine, docker compose docker cli
63+
{ "type": "docker" },
64+
65+
// You can add your git repos here to clone
66+
// {
67+
// "type": "git-repository",
68+
// "directory": "~/Projects/example-project",
69+
// "repository": "git@github.com:kevinwang5658/codify-homebrew-plugin.git"
70+
// },
71+
// {
72+
// "type": "git-repository",
73+
// "directory": "~/Projects/example-project",
74+
// "repository": "git@github.com:my_repo"
75+
// },
76+
77+
78+
// Actions are custom commands you can run
79+
{
80+
"type": "action",
81+
"action": "brew link --force libpq",
82+
"dependsOn": [
83+
"homebrew"
84+
]
85+
},
86+
{
87+
"type": "action",
88+
"action": "doppler login && doppler setup",
89+
"dependsOn": [
90+
"homebrew"
91+
]
92+
},
93+
{
94+
"type": "action",
95+
"action": "hookdeck login"
96+
}
97+
]
98+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
2+
import { ConfigFileSchema } from 'codify-schemas';
3+
import fs from 'node:fs/promises';
4+
import os from 'node:os';
5+
import path from 'node:path';
6+
import { WebSocket } from 'ws';
7+
8+
import { ConnectOrchestrator } from '../../../orchestrators/connect.js';
9+
import { ajv } from '../../../utils/ajv.js';
10+
import { Session } from '../../socket-server.js';
11+
import { ConnectCommand, createCommandHandler } from './create-command.js';
12+
13+
const validator = ajv.compile(ConfigFileSchema);
14+
15+
export function applyHandler() {
16+
const spawnCommand = async (body: Record<string, unknown>, ws: WebSocket, session: Session) => {
17+
const codifyConfig = body.config;
18+
if (!codifyConfig) {
19+
throw new Error('Unable to parse codify config');
20+
}
21+
22+
if (!validator(codifyConfig)) {
23+
throw new Error('Invalid codify config');
24+
}
25+
26+
const tmpDir = await fs.mkdtemp(os.tmpdir());
27+
const filePath = path.join(tmpDir, 'codify.jsonc');
28+
await fs.writeFile(filePath, JSON.stringify(codifyConfig, null, 2));
29+
30+
session.additionalData.filePath = filePath;
31+
32+
return spawn('zsh', ['-c', `${ConnectOrchestrator.rootCommand} apply -p ${filePath}`], {
33+
name: 'xterm-color',
34+
cols: 80,
35+
rows: 30,
36+
cwd: process.env.HOME,
37+
env: process.env
38+
});
39+
}
40+
41+
const onExit = async (exitCode: number, ws: WebSocket, session: Session) => {
42+
if (session.additionalData.filePath) {
43+
await fs.rm(session.additionalData.filePath as string, { recursive: true, force: true });
44+
}
45+
}
46+
47+
return createCommandHandler({
48+
name: ConnectCommand.APPLY,
49+
spawnCommand,
50+
onExit
51+
});
52+
}

src/connect/http-routes/handlers/create-command.ts

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
1+
import { IPty, spawn } from '@homebridge/node-pty-prebuilt-multiarch';
22
import chalk from 'chalk';
33
import { Router } from 'express';
44

55
import { ConnectOrchestrator } from '../../../orchestrators/connect.js';
6-
import { SocketServer } from '../../socket-server.js';
6+
import { Session, SocketServer } from '../../socket-server.js';
7+
import WebSocket from 'ws';
78

89
export enum ConnectCommand {
910
TERMINAL = 'terminal',
@@ -12,33 +13,20 @@ export enum ConnectCommand {
1213
IMPORT = 'import'
1314
}
1415

15-
const CommandInfo = {
16-
[ConnectCommand.TERMINAL]: {
17-
command: () => [],
18-
requiresDocumentId: false,
19-
},
20-
[ConnectCommand.APPLY]: {
21-
command: (args) => ['-c', `${ConnectOrchestrator.rootCommand} apply ${args}`],
22-
requiresDocumentId: true,
23-
},
24-
[ConnectCommand.PLAN]: {
25-
command: (args) => ['-c', `${ConnectOrchestrator.rootCommand} plan ${args}`],
26-
requiresDocumentId: true,
27-
},
28-
[ConnectCommand.IMPORT]: {
29-
command: (args) => ['-c', `${ConnectOrchestrator.rootCommand} import -p ${args}`],
30-
requiresDocumentId: true,
31-
}
16+
interface Params {
17+
name: ConnectCommand;
18+
command?: string[];
19+
spawnCommand?: (body: Record<string, unknown>, ws: WebSocket, session: Session) => IPty | Promise<IPty>;
20+
onExit?: (exitCode: number, ws: WebSocket, session: Session) => Promise<void> | void;
3221
}
3322

34-
export function createCommandHandler(command: ConnectCommand): Router {
35-
if (!Object.values(ConnectCommand).includes(command)) {
36-
throw new Error(`Unknown command ${command}. Please check code`);
23+
export function createCommandHandler({ name, command, spawnCommand, onExit }: Params): Router {
24+
if (!Object.values(ConnectCommand).includes(name)) {
25+
throw new Error(`Unknown command ${name}. Please check code`);
3726
}
3827

39-
const commandInfo = CommandInfo[command];
40-
if (!commandInfo) {
41-
throw new Error(`Command info not provided for ${command}. Please check code`);
28+
if (!command && !spawnCommand) {
29+
throw new Error('One of command or spawnCommand must be provided to createCommandHandler');
4230
}
4331

4432
const router = Router({
@@ -47,17 +35,12 @@ export function createCommandHandler(command: ConnectCommand): Router {
4735

4836
router.post('/:sessionId/start', async (req, res) => {
4937
const { sessionId } = req.params;
50-
const { documentId } = req.body;
51-
console.log(`Received request to ${command}, sessionId: ${sessionId}`)
38+
console.log(`Received request to ${name}, sessionId: ${sessionId}`)
5239

5340
if (!sessionId) {
5441
return res.status(400).json({ error: 'SessionId must be provided' });
5542
}
5643

57-
if (commandInfo.requiresDocumentId && !documentId) {
58-
return res.status(400).json({ error: 'Document id must be provided' });
59-
}
60-
6144
const manager = SocketServer.get();
6245
const session = manager.getSession(sessionId);
6346
if (!session) {
@@ -73,8 +56,9 @@ export function createCommandHandler(command: ConnectCommand): Router {
7356
return res.status(304).json({ status: 'Already started' })
7457
}
7558

76-
console.log('Running command:', commandInfo.command(documentId))
77-
const pty = spawn('zsh', commandInfo.command(documentId), {
59+
console.log(req.body);
60+
61+
const pty = spawnCommand ? await spawnCommand(req.body, ws, session) : spawn('zsh', command!, {
7862
name: 'xterm-color',
7963
cols: 80,
8064
rows: 30,
@@ -92,10 +76,12 @@ export function createCommandHandler(command: ConnectCommand): Router {
9276
pty.write(message.toString('utf8'));
9377
})
9478

95-
pty.onExit(({ exitCode, signal }) => {
96-
console.log('pty exit', exitCode, signal);
79+
pty.onExit(async ({ exitCode, signal }) => {
80+
console.log(`Command ${name} exited with exit code`, exitCode);
9781
ws.send(Buffer.from(chalk.blue(`Session ended exit code ${exitCode}`), 'utf8'))
9882

83+
await onExit?.(exitCode, ws, session)
84+
9985
ws.terminate();
10086
server.close();
10187
})
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { spawn } from '@homebridge/node-pty-prebuilt-multiarch';
2+
import { ConfigFileSchema } from 'codify-schemas';
3+
import fs from 'node:fs/promises';
4+
import os from 'node:os';
5+
import path from 'node:path';
6+
import { WebSocket } from 'ws';
7+
8+
import { ConnectOrchestrator } from '../../../orchestrators/connect.js';
9+
import { ajv } from '../../../utils/ajv.js';
10+
import { Session } from '../../socket-server.js';
11+
import { ConnectCommand, createCommandHandler } from './create-command.js';
12+
13+
const validator = ajv.compile(ConfigFileSchema);
14+
15+
export function planHandler() {
16+
const spawnCommand = async (body: Record<string, unknown>, ws: WebSocket, session: Session) => {
17+
const codifyConfig = body.config;
18+
if (!codifyConfig) {
19+
throw new Error('Unable to parse codify config');
20+
}
21+
22+
if (!validator(codifyConfig)) {
23+
throw new Error('Invalid codify config');
24+
}
25+
26+
const tmpDir = await fs.mkdtemp(os.tmpdir());
27+
const filePath = path.join(tmpDir, 'codify.jsonc');
28+
await fs.writeFile(filePath, JSON.stringify(codifyConfig, null, 2));
29+
30+
session.additionalData.filePath = filePath;
31+
32+
return spawn('zsh', ['-c', `${ConnectOrchestrator.rootCommand} plan -p ${filePath}`], {
33+
name: 'xterm-color',
34+
cols: 80,
35+
rows: 30,
36+
cwd: process.env.HOME,
37+
env: process.env
38+
});
39+
}
40+
41+
const onExit = async (exitCode: number, ws: WebSocket, session: Session) => {
42+
if (session.additionalData.filePath) {
43+
await fs.rm(session.additionalData.filePath as string, { recursive: true, force: true });
44+
}
45+
}
46+
47+
return createCommandHandler({
48+
name: ConnectCommand.APPLY,
49+
spawnCommand,
50+
onExit
51+
});
52+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ConnectCommand, createCommandHandler } from './create-command.js';
2+
3+
export function terminalHandler() {
4+
return createCommandHandler({
5+
name: ConnectCommand.TERMINAL,
6+
command: [],
7+
})
8+
}

src/connect/http-routes/router.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { Router } from 'express';
22

33
import { ConnectCommand, createCommandHandler } from './handlers/create-command.js';
44
import defaultHandler from './handlers/index.js';
5+
import { terminalHandler } from './handlers/terminal-handler.js';
6+
import { applyHandler } from './handlers/apply-handler.js';
7+
import { planHandler } from './handlers/plan-handler.js';
58

69
const router = Router();
710

811
router.use('/', defaultHandler);
9-
router.use('/apply', createCommandHandler(ConnectCommand.APPLY));
10-
router.use('/plan', createCommandHandler(ConnectCommand.PLAN))
11-
router.use('/import', createCommandHandler(ConnectCommand.IMPORT));
12-
router.use('/terminal', createCommandHandler(ConnectCommand.TERMINAL));
12+
router.use('/apply', applyHandler());
13+
router.use('/plan', planHandler())
14+
// router.use('/import', createCommandHandler(ConnectCommand.IMPORT));
15+
router.use('/terminal', terminalHandler());
1316

14-
export default router;
17+
export default router;

src/connect/socket-server.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface Session {
99
server: WebSocketServer;
1010
ws?: WebSocket;
1111
pty?: IPty;
12+
additionalData: Record<string, unknown>;
1213
}
1314

1415
let instance: SocketServer | undefined;
@@ -45,14 +46,9 @@ export class SocketServer {
4546
}
4647

4748
addSession(id: string): void {
48-
// this.io.of(`/ws/session/${id}`).on('connection', (socket) => {
49-
// console.log(`Session ${id} connected!!`);
50-
// handler?.(this.io, socket);
51-
// })
52-
5349
this.sessions.set(
5450
id,
55-
{ server: this.createWssServer() }
51+
{ server: this.createWssServer(), additionalData: {} }
5652
)
5753
}
5854

0 commit comments

Comments
 (0)