From fcd26472ebcdc599cc15f2baaf228f5f89094d8b Mon Sep 17 00:00:00 2001 From: kevinwang Date: Sat, 7 Mar 2026 22:07:43 -0500 Subject: [PATCH 1/5] fix: Fix install script --- scripts/install.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 45dd8d0c..5f60db8b 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -30,7 +30,7 @@ if [ "\$ARCH" == "x86_64" ]; then ARCH=x64 elif [[ "\$ARCH" == aarch* ]]; then - ARCH=arm + ARCH=arm64 elif [[ "\$ARCH" == "arm64" ]]; then ARCH=arm64 else @@ -56,9 +56,17 @@ fi echo "Installing CLI from \$URL" if [ \$(command -v curl) ]; then - curl "\$URL" | tar "\$TAR_ARGS" + if [ "\$OS" = "darwin" ]; then + curl "\$URL" | tar "\$TAR_ARGS" + else + curl "\$URL" | tar "\$TAR_ARGS" --warning=no-unknown-keyword + fi else - wget -O- "\$URL" | tar "\$TAR_ARGS" + if [ "\$OS" = "darwin" ]; then + wget -O- "\$URL" | tar "\$TAR_ARGS" + else + wget -O- "\$URL" | tar "\$TAR_ARGS" --warning=no-unknown-keyword + fi fi # delete old codify bin if exists rm -f \$(command -v codify) || true From 09ff8438a76261619e6cc824d94dbf81aac3908c Mon Sep 17 00:00:00 2001 From: kevinwang Date: Mon, 9 Mar 2026 22:43:02 -0400 Subject: [PATCH 2/5] fix: Fix for test command not working --- src/connect/http-routes/handlers/test-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/connect/http-routes/handlers/test-handler.ts b/src/connect/http-routes/handlers/test-handler.ts index 18d07ba9..341bf8c3 100644 --- a/src/connect/http-routes/handlers/test-handler.ts +++ b/src/connect/http-routes/handlers/test-handler.ts @@ -30,11 +30,11 @@ export function testHandler() { session.additionalData.filePath = filePath; - return spawn(ShellUtils.getDefaultShell(), ['-c', `${ConnectOrchestrator.nodeBinary} ${ConnectOrchestrator.rootCommand} test`], { + return spawn(ShellUtils.getDefaultShell(), ['-c', `${ConnectOrchestrator.nodeBinary} ${ConnectOrchestrator.rootCommand} test -p ${filePath}`], { name: 'xterm-color', cols: 80, rows: 30, - cwd: filePath, + cwd: process.env.HOME, env: process.env }); } From bf1c5e0154aed9545fbb23cede7d09cc50b88362 Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 10 Mar 2026 00:02:42 -0400 Subject: [PATCH 3/5] fix: Fix test command not working (local reference found) --- src/commands/test.ts | 2 +- src/orchestrators/test.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/commands/test.ts b/src/commands/test.ts index ab5074c0..47ee4e77 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -48,7 +48,7 @@ For more information, visit: https://docs.codifycli.com/commands/apply ] async init(): Promise { - ctx.log('Running Codify test...') + console.log('Running Codify test...') return super.init(); } diff --git a/src/orchestrators/test.ts b/src/orchestrators/test.ts index 019076db..114dbeb6 100644 --- a/src/orchestrators/test.ts +++ b/src/orchestrators/test.ts @@ -112,9 +112,6 @@ export const TestOrchestrator = { ctx.subprocessStarted(SubProcessName.TEST_CHECKING_VM_INSTALLED); planResult = await PlanOrchestrator.run({ codifyConfigs: [{ - type: 'project', - plugins: { default: '/Users/kevinwang/Projects/codify-homebrew-plugin/src/index.ts' } - }, { type: 'homebrew', formulae: ['sshpass'] }, { From 1436ff11af6aad76971e397c7a109550affbd73f Mon Sep 17 00:00:00 2001 From: kevinwang Date: Tue, 10 Mar 2026 16:21:56 -0400 Subject: [PATCH 4/5] feat: Augmented test to be able to accept live config updates. --- src/connect/socket-server.ts | 80 +++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/connect/socket-server.ts b/src/connect/socket-server.ts index 86a377e5..aedd7c37 100644 --- a/src/connect/socket-server.ts +++ b/src/connect/socket-server.ts @@ -1,5 +1,8 @@ import { IPty } from '@homebridge/node-pty-prebuilt-multiarch'; +import * as fs from 'node:fs/promises'; import { Server as HttpServer, IncomingMessage } from 'node:http'; +import os from 'node:os'; +import path from 'node:path'; import { Duplex } from 'node:stream'; import { v4 as uuid } from 'uuid'; import WebSocket, { WebSocketServer } from 'ws'; @@ -122,10 +125,17 @@ export class SocketServer { this.mainConnections.set(clientId, ws); ws.send(JSON.stringify({ key: 'opened', data: { clientId, startTimestamp: this.startTimestamp.toISOString() } })); - ws.on('message', (message) => { + ws.on('message', async (message) => { const data = JSON.parse(message.toString('utf8')); + if (data.key === 'terminate') { process.exit(0); + return; + } + + if (data.key === 'update-config') { + await this.handleConfigUpdate(ws, data); + return; } }); @@ -134,6 +144,74 @@ export class SocketServer { }) } + private async handleConfigUpdate(ws: WebSocket, data: { sessionId: string; config: string }) { + try { + const { sessionId, config: configContent } = data; + + if (!sessionId || !configContent) { + ws.send(JSON.stringify({ + key: 'update-config-response', + success: false, + sessionId, + error: 'Missing sessionId or config' + })); + return; + } + + const session = this.sessions.get(sessionId); + if (!session) { + ws.send(JSON.stringify({ + key: 'update-config-response', + success: false, + sessionId, + error: 'Session not found' + })); + return; + } + + const filePath = session.additionalData.filePath as string | undefined; + if (!filePath) { + ws.send(JSON.stringify({ + key: 'update-config-response', + success: false, + sessionId, + error: 'File path not found in session' + })); + return; + } + + // Security: Ensure file path is in temp directory + const tmpDir = os.tmpdir(); + const resolvedPath = path.resolve(filePath); + if (!resolvedPath.startsWith(tmpDir)) { + console.error('Security: Attempted to write outside temp directory', filePath); + ws.send(JSON.stringify({ + key: 'update-config-response', + success: false, + sessionId, + error: 'Invalid file path' + })); + return; + } + + await fs.writeFile(filePath, configContent, 'utf8'); + + ws.send(JSON.stringify({ + key: 'update-config-response', + success: true, + sessionId + })); + } catch (error) { + console.error('Error updating config:', error); + ws.send(JSON.stringify({ + key: 'update-config-response', + success: false, + sessionId: data.sessionId, + error: error instanceof Error ? error.message : 'Unknown error' + })); + } + } + private validateOrigin = (origin: string): boolean => config.corsAllowedOrigins.includes(origin) From 4847b5925da1fac858b85dcd3aad875f3d45190d Mon Sep 17 00:00:00 2001 From: kevinwang Date: Wed, 11 Mar 2026 14:36:14 -0400 Subject: [PATCH 5/5] fix: Fix dependsOn requiring a full id (type.name). Instead modify it to only require type and match multiple resources. A full qualified name (type.name) will match only one resource. --- src/entities/project.ts | 26 +++++++++++++++++++++++++- src/entities/resource-config.ts | 12 +++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/entities/project.ts b/src/entities/project.ts index aee93f8b..fc371c91 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -248,7 +248,7 @@ ${JSON.stringify(projectConfigs, null, 2)}`); for (const r of this.resourceConfigs) { // User specified dependencies are hard dependencies. They must be present. - r.addDependenciesFromDependsOn((id) => resourceMap.has(id)); + r.addDependenciesFromDependsOn((idOrType) => this.getMatchingResourceIds(resourceMap, idOrType)); r.addDependenciesBasedOnParameters((id) => resourceMap.has(id)); // Plugin dependencies are soft dependencies. They only activate if the dependent resource is present. @@ -299,4 +299,28 @@ ${JSON.stringify(projectConfigs, null, 2)}`); } } } + + /** + * This function supports both full (type.name) and partial IDs (type) when matching. It's meant + * for the dependsOn field to simplify dependency resolution for. users. + * @param resourceMap + * @param idOrType + * @private + */ + private getMatchingResourceIds( + resourceMap: Map, + idOrType: string + ): string[] { + const hasName = idOrType.includes('.'); + + if (hasName) { + // Full ID (type.name): return exact match or empty array + return resourceMap.has(idOrType) ? [idOrType] : []; + } else { + // Partial ID (type only): return all resources with this type + return [...resourceMap.values()] + .filter((resource) => resource.type === idOrType) + .map((resource) => resource.id); + } + } } diff --git a/src/entities/resource-config.ts b/src/entities/resource-config.ts index b9474999..3d1d2f32 100644 --- a/src/entities/resource-config.ts +++ b/src/entities/resource-config.ts @@ -100,13 +100,15 @@ export class ResourceConfig implements ConfigBlock { this.raw[name] = value; } - addDependenciesFromDependsOn(resourceExists: (id: string) => boolean) { - for (const id of this.dependsOn) { - if (!resourceExists(id)) { - throw new Error(`Reference ${id} is not a valid resource`); + addDependenciesFromDependsOn(getMatchingResourceIds: (idOrType: string) => string[]) { + for (const idOrType of this.dependsOn) { + const matchingIds = getMatchingResourceIds(idOrType); + + if (matchingIds.length === 0) { + throw new Error(`Reference ${idOrType} is not a valid resource`); } - this.dependencyIds.push(id); + this.dependencyIds.push(...matchingIds); } }