mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
fix: address pr comments
This commit is contained in:
@@ -12,12 +12,22 @@ const featureLoader = new FeatureLoader();
|
|||||||
export function createApplyHandler() {
|
export function createApplyHandler() {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, plan, branchName } = req.body as {
|
const {
|
||||||
|
projectPath,
|
||||||
|
plan,
|
||||||
|
branchName: rawBranchName,
|
||||||
|
} = req.body as {
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
plan: BacklogPlanResult;
|
plan: BacklogPlanResult;
|
||||||
branchName?: string;
|
branchName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Validate branchName: must be undefined or a non-empty trimmed string
|
||||||
|
const branchName =
|
||||||
|
typeof rawBranchName === 'string' && rawBranchName.trim().length > 0
|
||||||
|
? rawBranchName.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
res.status(400).json({ success: false, error: 'projectPath required' });
|
res.status(400).json({ success: false, error: 'projectPath required' });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch';
|
import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
|
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||||
|
|
||||||
interface WorktreesSectionProps {
|
interface WorktreesSectionProps {
|
||||||
useWorktrees: boolean;
|
useWorktrees: boolean;
|
||||||
@@ -217,9 +218,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id="show-init-script-indicator"
|
id="show-init-script-indicator"
|
||||||
checked={showIndicator}
|
checked={showIndicator}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={async (checked) => {
|
||||||
if (currentProject?.path) {
|
if (currentProject?.path) {
|
||||||
setShowInitScriptIndicator(currentProject.path, checked === true);
|
const value = checked === true;
|
||||||
|
setShowInitScriptIndicator(currentProject.path, value);
|
||||||
|
// Persist to server
|
||||||
|
try {
|
||||||
|
const httpClient = getHttpApiClient();
|
||||||
|
await httpClient.settings.updateProject(currentProject.path, {
|
||||||
|
showInitScriptIndicator: value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to persist showInitScriptIndicator:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
@@ -246,9 +257,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id="auto-dismiss-indicator"
|
id="auto-dismiss-indicator"
|
||||||
checked={autoDismiss}
|
checked={autoDismiss}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={async (checked) => {
|
||||||
if (currentProject?.path) {
|
if (currentProject?.path) {
|
||||||
setAutoDismissInitScriptIndicator(currentProject.path, checked === true);
|
const value = checked === true;
|
||||||
|
setAutoDismissInitScriptIndicator(currentProject.path, value);
|
||||||
|
// Persist to server
|
||||||
|
try {
|
||||||
|
const httpClient = getHttpApiClient();
|
||||||
|
await httpClient.settings.updateProject(currentProject.path, {
|
||||||
|
autoDismissInitScriptIndicator: value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to persist autoDismissInitScriptIndicator:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
@@ -273,9 +294,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id="default-delete-branch"
|
id="default-delete-branch"
|
||||||
checked={defaultDeleteBranch}
|
checked={defaultDeleteBranch}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={async (checked) => {
|
||||||
if (currentProject?.path) {
|
if (currentProject?.path) {
|
||||||
setDefaultDeleteBranch(currentProject.path, checked === true);
|
const value = checked === true;
|
||||||
|
setDefaultDeleteBranch(currentProject.path, value);
|
||||||
|
// Persist to server
|
||||||
|
try {
|
||||||
|
const httpClient = getHttpApiClient();
|
||||||
|
await httpClient.settings.updateProject(currentProject.path, {
|
||||||
|
defaultDeleteBranch: value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to persist defaultDeleteBranch:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export function useProjectSettingsLoader() {
|
|||||||
const setCardBorderOpacity = useAppStore((state) => state.setCardBorderOpacity);
|
const setCardBorderOpacity = useAppStore((state) => state.setCardBorderOpacity);
|
||||||
const setHideScrollbar = useAppStore((state) => state.setHideScrollbar);
|
const setHideScrollbar = useAppStore((state) => state.setHideScrollbar);
|
||||||
const setWorktreePanelVisible = useAppStore((state) => state.setWorktreePanelVisible);
|
const setWorktreePanelVisible = useAppStore((state) => state.setWorktreePanelVisible);
|
||||||
|
const setShowInitScriptIndicator = useAppStore((state) => state.setShowInitScriptIndicator);
|
||||||
|
const setDefaultDeleteBranch = useAppStore((state) => state.setDefaultDeleteBranch);
|
||||||
|
const setAutoDismissInitScriptIndicator = useAppStore(
|
||||||
|
(state) => state.setAutoDismissInitScriptIndicator
|
||||||
|
);
|
||||||
|
|
||||||
const loadingRef = useRef<string | null>(null);
|
const loadingRef = useRef<string | null>(null);
|
||||||
const currentProjectRef = useRef<string | null>(null);
|
const currentProjectRef = useRef<string | null>(null);
|
||||||
@@ -78,6 +83,27 @@ export function useProjectSettingsLoader() {
|
|||||||
if (result.settings.worktreePanelVisible !== undefined) {
|
if (result.settings.worktreePanelVisible !== undefined) {
|
||||||
setWorktreePanelVisible(requestedProjectPath, result.settings.worktreePanelVisible);
|
setWorktreePanelVisible(requestedProjectPath, result.settings.worktreePanelVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply showInitScriptIndicator if present
|
||||||
|
if (result.settings.showInitScriptIndicator !== undefined) {
|
||||||
|
setShowInitScriptIndicator(
|
||||||
|
requestedProjectPath,
|
||||||
|
result.settings.showInitScriptIndicator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply defaultDeleteBranch if present
|
||||||
|
if (result.settings.defaultDeleteBranch !== undefined) {
|
||||||
|
setDefaultDeleteBranch(requestedProjectPath, result.settings.defaultDeleteBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply autoDismissInitScriptIndicator if present
|
||||||
|
if (result.settings.autoDismissInitScriptIndicator !== undefined) {
|
||||||
|
setAutoDismissInitScriptIndicator(
|
||||||
|
requestedProjectPath,
|
||||||
|
result.settings.autoDismissInitScriptIndicator
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load project settings:', error);
|
console.error('Failed to load project settings:', error);
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ export type ThemeMode =
|
|||||||
// LocalStorage key for theme persistence (fallback when server settings aren't available)
|
// LocalStorage key for theme persistence (fallback when server settings aren't available)
|
||||||
export const THEME_STORAGE_KEY = 'automaker:theme';
|
export const THEME_STORAGE_KEY = 'automaker:theme';
|
||||||
|
|
||||||
|
// Maximum number of output lines to keep in init script state (prevents unbounded memory growth)
|
||||||
|
export const MAX_INIT_OUTPUT_LINES = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the theme from localStorage as a fallback
|
* Get the theme from localStorage as a fallback
|
||||||
* Used before server settings are loaded (e.g., on login/setup pages)
|
* Used before server settings are loaded (e.g., on login/setup pages)
|
||||||
@@ -1823,16 +1826,28 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
await syncSettingsToServer();
|
await syncSettingsToServer();
|
||||||
},
|
},
|
||||||
setPlanUseSelectedWorktreeBranch: async (enabled) => {
|
setPlanUseSelectedWorktreeBranch: async (enabled) => {
|
||||||
|
const previous = get().planUseSelectedWorktreeBranch;
|
||||||
set({ planUseSelectedWorktreeBranch: enabled });
|
set({ planUseSelectedWorktreeBranch: enabled });
|
||||||
// Sync to server settings file
|
// Sync to server settings file
|
||||||
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
||||||
await syncSettingsToServer();
|
const ok = await syncSettingsToServer();
|
||||||
|
if (!ok) {
|
||||||
|
logger.error('Failed to sync planUseSelectedWorktreeBranch setting to server - reverting');
|
||||||
|
set({ planUseSelectedWorktreeBranch: previous });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setAddFeatureUseSelectedWorktreeBranch: async (enabled) => {
|
setAddFeatureUseSelectedWorktreeBranch: async (enabled) => {
|
||||||
|
const previous = get().addFeatureUseSelectedWorktreeBranch;
|
||||||
set({ addFeatureUseSelectedWorktreeBranch: enabled });
|
set({ addFeatureUseSelectedWorktreeBranch: enabled });
|
||||||
// Sync to server settings file
|
// Sync to server settings file
|
||||||
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
||||||
await syncSettingsToServer();
|
const ok = await syncSettingsToServer();
|
||||||
|
if (!ok) {
|
||||||
|
logger.error(
|
||||||
|
'Failed to sync addFeatureUseSelectedWorktreeBranch setting to server - reverting'
|
||||||
|
);
|
||||||
|
set({ addFeatureUseSelectedWorktreeBranch: previous });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Worktree Settings actions
|
// Worktree Settings actions
|
||||||
@@ -3282,14 +3297,20 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
|
|||||||
|
|
||||||
appendInitScriptOutput: (projectPath, branch, content) => {
|
appendInitScriptOutput: (projectPath, branch, content) => {
|
||||||
const key = `${projectPath}::${branch}`;
|
const key = `${projectPath}::${branch}`;
|
||||||
const current = get().initScriptState[key];
|
// Initialize state if absent to avoid dropping output due to event-order races
|
||||||
if (!current) return;
|
const current = get().initScriptState[key] || {
|
||||||
|
status: 'idle' as const,
|
||||||
|
branch,
|
||||||
|
output: [],
|
||||||
|
};
|
||||||
|
// Append new content and enforce fixed-size buffer to prevent memory bloat
|
||||||
|
const newOutput = [...current.output, content].slice(-MAX_INIT_OUTPUT_LINES);
|
||||||
set({
|
set({
|
||||||
initScriptState: {
|
initScriptState: {
|
||||||
...get().initScriptState,
|
...get().initScriptState,
|
||||||
[key]: {
|
[key]: {
|
||||||
...current,
|
...current,
|
||||||
output: [...current.output, content],
|
output: newOutput,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,8 +14,12 @@ echo ""
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
echo "[1/1] Installing npm dependencies..."
|
echo "[1/1] Installing npm dependencies..."
|
||||||
if [ -f "package.json" ]; then
|
if [ -f "package.json" ]; then
|
||||||
npm install
|
if npm install; then
|
||||||
echo "Dependencies installed successfully!"
|
echo "Dependencies installed successfully!"
|
||||||
|
else
|
||||||
|
echo "ERROR: npm install failed with exit code $?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "No package.json found, skipping npm install"
|
echo "No package.json found, skipping npm install"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -232,6 +232,27 @@ export function getClaudeProjectsDir(): string {
|
|||||||
return path.join(getClaudeConfigDir(), 'projects');
|
return path.join(getClaudeConfigDir(), 'projects');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerate directories matching a prefix pattern and return full paths
|
||||||
|
* Used to resolve dynamic directory names like version numbers
|
||||||
|
*/
|
||||||
|
function enumerateMatchingPaths(
|
||||||
|
parentDir: string,
|
||||||
|
prefix: string,
|
||||||
|
...subPathParts: string[]
|
||||||
|
): string[] {
|
||||||
|
try {
|
||||||
|
if (!fsSync.existsSync(parentDir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const entries = fsSync.readdirSync(parentDir);
|
||||||
|
const matching = entries.filter((entry) => entry.startsWith(prefix));
|
||||||
|
return matching.map((entry) => path.join(parentDir, entry, ...subPathParts));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get common Git Bash installation paths on Windows
|
* Get common Git Bash installation paths on Windows
|
||||||
* Git Bash is needed for running shell scripts cross-platform
|
* Git Bash is needed for running shell scripts cross-platform
|
||||||
@@ -242,12 +263,38 @@ export function getGitBashPaths(): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
|
const localAppData = process.env.LOCALAPPDATA || '';
|
||||||
|
|
||||||
|
// Dynamic paths that require directory enumeration
|
||||||
|
// winget installs to: LocalAppData\Microsoft\WinGet\Packages\Git.Git_<hash>\bin\bash.exe
|
||||||
|
const wingetGitPaths = localAppData
|
||||||
|
? enumerateMatchingPaths(
|
||||||
|
path.join(localAppData, 'Microsoft', 'WinGet', 'Packages'),
|
||||||
|
'Git.Git_',
|
||||||
|
'bin',
|
||||||
|
'bash.exe'
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// GitHub Desktop bundles Git at: LocalAppData\GitHubDesktop\app-<version>\resources\app\git\cmd\bash.exe
|
||||||
|
const githubDesktopPaths = localAppData
|
||||||
|
? enumerateMatchingPaths(
|
||||||
|
path.join(localAppData, 'GitHubDesktop'),
|
||||||
|
'app-',
|
||||||
|
'resources',
|
||||||
|
'app',
|
||||||
|
'git',
|
||||||
|
'cmd',
|
||||||
|
'bash.exe'
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// Standard Git for Windows installations
|
// Standard Git for Windows installations
|
||||||
'C:\\Program Files\\Git\\bin\\bash.exe',
|
'C:\\Program Files\\Git\\bin\\bash.exe',
|
||||||
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
||||||
// User-local installations
|
// User-local installations
|
||||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'bin', 'bash.exe'),
|
path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),
|
||||||
// Scoop package manager
|
// Scoop package manager
|
||||||
path.join(homeDir, 'scoop', 'apps', 'git', 'current', 'bin', 'bash.exe'),
|
path.join(homeDir, 'scoop', 'apps', 'git', 'current', 'bin', 'bash.exe'),
|
||||||
// Chocolatey
|
// Chocolatey
|
||||||
@@ -259,27 +306,10 @@ export function getGitBashPaths(): string[] {
|
|||||||
'bin',
|
'bin',
|
||||||
'bash.exe'
|
'bash.exe'
|
||||||
),
|
),
|
||||||
// winget typical location
|
// winget installations (dynamically resolved)
|
||||||
path.join(
|
...wingetGitPaths,
|
||||||
process.env.LOCALAPPDATA || '',
|
// GitHub Desktop bundled Git (dynamically resolved)
|
||||||
'Microsoft',
|
...githubDesktopPaths,
|
||||||
'WinGet',
|
|
||||||
'Packages',
|
|
||||||
'Git.Git_*',
|
|
||||||
'bin',
|
|
||||||
'bash.exe'
|
|
||||||
),
|
|
||||||
// GitHub Desktop bundled Git
|
|
||||||
path.join(
|
|
||||||
process.env.LOCALAPPDATA || '',
|
|
||||||
'GitHubDesktop',
|
|
||||||
'app-*',
|
|
||||||
'resources',
|
|
||||||
'app',
|
|
||||||
'git',
|
|
||||||
'cmd',
|
|
||||||
'bash.exe'
|
|
||||||
),
|
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user