Merge branch 'main' into feature/shared-packages

This commit is contained in:
Test User
2025-12-20 23:55:03 -05:00
76 changed files with 1898 additions and 893 deletions

View File

@@ -4,9 +4,9 @@
*/
import path from "path";
import fs from "fs/promises";
import type { Feature } from "@automaker/types";
import { createLogger } from "@automaker/utils";
import * as secureFs from "../lib/secure-fs.js";
import {
getFeaturesDir,
getFeatureDir,
@@ -39,8 +39,12 @@ export class FeatureLoader {
*/
private async deleteOrphanedImages(
projectPath: string,
oldPaths: Array<string | { path: string; [key: string]: unknown }> | undefined,
newPaths: Array<string | { path: string; [key: string]: unknown }> | undefined
oldPaths:
| Array<string | { path: string; [key: string]: unknown }>
| undefined,
newPaths:
| Array<string | { path: string; [key: string]: unknown }>
| undefined
): Promise<void> {
if (!oldPaths || oldPaths.length === 0) {
return;
@@ -59,8 +63,8 @@ export class FeatureLoader {
if (!newPathSet.has(oldPath)) {
try {
// Paths are now absolute
await fs.unlink(oldPath);
logger.info(`Deleted orphaned image: ${oldPath}`);
await secureFs.unlink(oldPath);
console.log(`[FeatureLoader] Deleted orphaned image: ${oldPath}`);
} catch (error) {
// Ignore errors when deleting (file may already be gone)
logger.warn(
@@ -87,10 +91,11 @@ export class FeatureLoader {
}
const featureImagesDir = this.getFeatureImagesDir(projectPath, featureId);
await fs.mkdir(featureImagesDir, { recursive: true });
await secureFs.mkdir(featureImagesDir, { recursive: true });
const updatedPaths: Array<string | { path: string; [key: string]: unknown }> =
[];
const updatedPaths: Array<
string | { path: string; [key: string]: unknown }
> = [];
for (const imagePath of imagePaths) {
try {
@@ -110,7 +115,7 @@ export class FeatureLoader {
// Check if file exists
try {
await fs.access(fullOriginalPath);
await secureFs.access(fullOriginalPath);
} catch {
logger.warn(
`[FeatureLoader] Image not found, skipping: ${fullOriginalPath}`
@@ -123,14 +128,14 @@ export class FeatureLoader {
const newPath = path.join(featureImagesDir, filename);
// Copy the file
await fs.copyFile(fullOriginalPath, newPath);
logger.info(
await secureFs.copyFile(fullOriginalPath, newPath);
console.log(
`[FeatureLoader] Copied image: ${originalPath} -> ${newPath}`
);
// Try to delete the original temp file
try {
await fs.unlink(fullOriginalPath);
await secureFs.unlink(fullOriginalPath);
} catch {
// Ignore errors when deleting temp file
}
@@ -163,14 +168,20 @@ export class FeatureLoader {
* Get the path to a feature's feature.json file
*/
getFeatureJsonPath(projectPath: string, featureId: string): string {
return path.join(this.getFeatureDir(projectPath, featureId), "feature.json");
return path.join(
this.getFeatureDir(projectPath, featureId),
"feature.json"
);
}
/**
* Get the path to a feature's agent-output.md file
*/
getAgentOutputPath(projectPath: string, featureId: string): string {
return path.join(this.getFeatureDir(projectPath, featureId), "agent-output.md");
return path.join(
this.getFeatureDir(projectPath, featureId),
"agent-output.md"
);
}
/**
@@ -189,13 +200,15 @@ export class FeatureLoader {
// Check if features directory exists
try {
await fs.access(featuresDir);
await secureFs.access(featuresDir);
} catch {
return [];
}
// Read all feature directories
const entries = await fs.readdir(featuresDir, { withFileTypes: true });
const entries = (await secureFs.readdir(featuresDir, {
withFileTypes: true,
})) as any[];
const featureDirs = entries.filter((entry) => entry.isDirectory());
// Load each feature
@@ -205,7 +218,10 @@ export class FeatureLoader {
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
try {
const content = await fs.readFile(featureJsonPath, "utf-8");
const content = (await secureFs.readFile(
featureJsonPath,
"utf-8"
)) as string;
const feature = JSON.parse(content);
if (!feature.id) {
@@ -252,7 +268,10 @@ export class FeatureLoader {
async get(projectPath: string, featureId: string): Promise<Feature | null> {
try {
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
const content = await fs.readFile(featureJsonPath, "utf-8");
const content = (await secureFs.readFile(
featureJsonPath,
"utf-8"
)) as string;
return JSON.parse(content);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -281,7 +300,7 @@ export class FeatureLoader {
await ensureAutomakerDir(projectPath);
// Create feature directory
await fs.mkdir(featureDir, { recursive: true });
await secureFs.mkdir(featureDir, { recursive: true });
// Migrate images from temp directory to feature directory
const migratedImagePaths = await this.migrateImages(
@@ -300,7 +319,7 @@ export class FeatureLoader {
};
// Write feature.json
await fs.writeFile(
await secureFs.writeFile(
featureJsonPath,
JSON.stringify(feature, null, 2),
"utf-8"
@@ -352,7 +371,7 @@ export class FeatureLoader {
// Write back to file
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
await fs.writeFile(
await secureFs.writeFile(
featureJsonPath,
JSON.stringify(updatedFeature, null, 2),
"utf-8"
@@ -368,8 +387,8 @@ export class FeatureLoader {
async delete(projectPath: string, featureId: string): Promise<boolean> {
try {
const featureDir = this.getFeatureDir(projectPath, featureId);
await fs.rm(featureDir, { recursive: true, force: true });
logger.info(`Deleted feature ${featureId}`);
await secureFs.rm(featureDir, { recursive: true, force: true });
console.log(`[FeatureLoader] Deleted feature ${featureId}`);
return true;
} catch (error) {
logger.error(
@@ -389,7 +408,10 @@ export class FeatureLoader {
): Promise<string | null> {
try {
const agentOutputPath = this.getAgentOutputPath(projectPath, featureId);
const content = await fs.readFile(agentOutputPath, "utf-8");
const content = (await secureFs.readFile(
agentOutputPath,
"utf-8"
)) as string;
return content;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -412,10 +434,10 @@ export class FeatureLoader {
content: string
): Promise<void> {
const featureDir = this.getFeatureDir(projectPath, featureId);
await fs.mkdir(featureDir, { recursive: true });
await secureFs.mkdir(featureDir, { recursive: true });
const agentOutputPath = this.getAgentOutputPath(projectPath, featureId);
await fs.writeFile(agentOutputPath, content, "utf-8");
await secureFs.writeFile(agentOutputPath, content, "utf-8");
}
/**
@@ -427,7 +449,7 @@ export class FeatureLoader {
): Promise<void> {
try {
const agentOutputPath = this.getAgentOutputPath(projectPath, featureId);
await fs.unlink(agentOutputPath);
await secureFs.unlink(agentOutputPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
throw error;