Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ For more information, visit: https://docs.codifycli.com/commands/apply
]

async init(): Promise<void> {
ctx.log('Running Codify test...')
console.log('Running Codify test...')
return super.init();
}

Expand Down
4 changes: 2 additions & 2 deletions src/connect/http-routes/handlers/test-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}
Expand Down
80 changes: 79 additions & 1 deletion src/connect/socket-server.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}
});

Expand All @@ -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)

Expand Down
26 changes: 25 additions & 1 deletion src/entities/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<string, ResourceConfig>,
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);
}
}
}
12 changes: 7 additions & 5 deletions src/entities/resource-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
3 changes: 0 additions & 3 deletions src/orchestrators/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}, {
Expand Down
Loading