progressをわかりやすくする
This commit is contained in:
parent
79ee353990
commit
3fa99ae0f7
@ -18,6 +18,7 @@ vi.mock('../shared/ui/index.js', () => ({
|
||||
info: vi.fn(),
|
||||
blankLine: vi.fn(),
|
||||
error: vi.fn(),
|
||||
withProgress: vi.fn(async (_start, _done, operation) => operation()),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
|
||||
|
||||
@ -11,6 +11,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
withProgress: vi.fn(async (_start, _done, operation) => operation()),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
|
||||
|
||||
@ -28,14 +28,23 @@ vi.mock('../infra/task/summarize.js', () => ({
|
||||
summarizeTaskName: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
header: vi.fn(),
|
||||
status: vi.fn(),
|
||||
setLogLevel: vi.fn(),
|
||||
}));
|
||||
vi.mock('../shared/ui/index.js', () => {
|
||||
const info = vi.fn();
|
||||
return {
|
||||
info,
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
header: vi.fn(),
|
||||
status: vi.fn(),
|
||||
setLogLevel: vi.fn(),
|
||||
withProgress: vi.fn(async (start, done, operation) => {
|
||||
info(start);
|
||||
const result = await operation();
|
||||
info(typeof done === 'function' ? done(result) : done);
|
||||
return result;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
|
||||
@ -40,14 +40,23 @@ vi.mock('../infra/task/summarize.js', async (importOriginal) => ({
|
||||
summarizeTaskName: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
header: vi.fn(),
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
status: vi.fn(),
|
||||
blankLine: vi.fn(),
|
||||
}));
|
||||
vi.mock('../shared/ui/index.js', () => {
|
||||
const info = vi.fn();
|
||||
return {
|
||||
header: vi.fn(),
|
||||
info,
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
status: vi.fn(),
|
||||
blankLine: vi.fn(),
|
||||
withProgress: vi.fn(async (start, done, operation) => {
|
||||
info(start);
|
||||
const result = await operation();
|
||||
info(typeof done === 'function' ? done(result) : done);
|
||||
return result;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../shared/utils/index.js', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* pipeline mode, or interactive mode.
|
||||
*/
|
||||
|
||||
import { info, error } from '../../shared/ui/index.js';
|
||||
import { info, error, withProgress } from '../../shared/ui/index.js';
|
||||
import { getErrorMessage } from '../../shared/utils/index.js';
|
||||
import { getLabel } from '../../shared/i18n/index.js';
|
||||
import { fetchIssue, formatIssueAsTask, checkGhCli, parseIssueNumbers, type GitHubIssue } from '../../infra/github/index.js';
|
||||
@ -35,23 +35,24 @@ import { resolveAgentOverrides, parseCreateWorktreeOption, isDirectTask } from '
|
||||
* Returns resolved issues and the formatted task text for interactive mode.
|
||||
* Throws on gh CLI unavailability or fetch failure.
|
||||
*/
|
||||
function resolveIssueInput(
|
||||
async function resolveIssueInput(
|
||||
issueOption: number | undefined,
|
||||
task: string | undefined,
|
||||
): { issues: GitHubIssue[]; initialInput: string } | null {
|
||||
): Promise<{ issues: GitHubIssue[]; initialInput: string } | null> {
|
||||
if (issueOption) {
|
||||
info('Fetching GitHub Issue...');
|
||||
const ghStatus = checkGhCli();
|
||||
if (!ghStatus.available) {
|
||||
throw new Error(ghStatus.error);
|
||||
}
|
||||
const issue = fetchIssue(issueOption);
|
||||
info(`GitHub Issue fetched: #${issue.number} ${issue.title}`);
|
||||
const issue = await withProgress(
|
||||
'Fetching GitHub Issue...',
|
||||
(fetchedIssue) => `GitHub Issue fetched: #${fetchedIssue.number} ${fetchedIssue.title}`,
|
||||
async () => fetchIssue(issueOption),
|
||||
);
|
||||
return { issues: [issue], initialInput: formatIssueAsTask(issue) };
|
||||
}
|
||||
|
||||
if (task && isDirectTask(task)) {
|
||||
info('Fetching GitHub Issue...');
|
||||
const ghStatus = checkGhCli();
|
||||
if (!ghStatus.available) {
|
||||
throw new Error(ghStatus.error);
|
||||
@ -61,8 +62,11 @@ function resolveIssueInput(
|
||||
if (issueNumbers.length === 0) {
|
||||
throw new Error(`Invalid issue reference: ${task}`);
|
||||
}
|
||||
const issues = issueNumbers.map((n) => fetchIssue(n));
|
||||
info(`GitHub Issues fetched: ${issues.map((issue) => `#${issue.number}`).join(', ')}`);
|
||||
const issues = await withProgress(
|
||||
'Fetching GitHub Issue...',
|
||||
(fetchedIssues) => `GitHub Issues fetched: ${fetchedIssues.map((issue) => `#${issue.number}`).join(', ')}`,
|
||||
async () => issueNumbers.map((n) => fetchIssue(n)),
|
||||
);
|
||||
return { issues, initialInput: issues.map(formatIssueAsTask).join('\n\n---\n\n') };
|
||||
}
|
||||
|
||||
@ -118,7 +122,7 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
let initialInput: string | undefined = task;
|
||||
|
||||
try {
|
||||
const issueResult = resolveIssueInput(opts.issue as number | undefined, task);
|
||||
const issueResult = await resolveIssueInput(opts.issue as number | undefined, task);
|
||||
if (issueResult) {
|
||||
selectOptions.issues = issueResult.issues;
|
||||
initialInput = issueResult.initialInput;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
import { promptInput, confirm } from '../../../shared/prompt/index.js';
|
||||
import { success, info, error } from '../../../shared/ui/index.js';
|
||||
import { success, info, error, withProgress } from '../../../shared/ui/index.js';
|
||||
import { TaskRunner, type TaskFileData } from '../../../infra/task/index.js';
|
||||
import { determinePiece } from '../execute/selectAndExecute.js';
|
||||
import { createLogger, getErrorMessage, generateReportDir } from '../../../shared/utils/index.js';
|
||||
@ -177,13 +177,16 @@ export async function addTask(cwd: string, task?: string): Promise<void> {
|
||||
|
||||
if (isIssueReference(trimmedTask)) {
|
||||
// Issue reference: fetch issue and use directly as task content
|
||||
info('Fetching GitHub Issue...');
|
||||
try {
|
||||
taskContent = resolveIssueTask(trimmedTask);
|
||||
const numbers = parseIssueNumbers([trimmedTask]);
|
||||
const primaryIssueNumber = numbers[0];
|
||||
taskContent = await withProgress(
|
||||
'Fetching GitHub Issue...',
|
||||
primaryIssueNumber ? `GitHub Issue fetched: #${primaryIssueNumber}` : 'GitHub Issue fetched',
|
||||
async () => resolveIssueTask(trimmedTask),
|
||||
);
|
||||
if (numbers.length > 0) {
|
||||
issueNumber = numbers[0];
|
||||
info(`GitHub Issue fetched: #${issueNumber}`);
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = getErrorMessage(e);
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { loadGlobalConfig } from '../../../infra/config/index.js';
|
||||
import { type TaskInfo, createSharedClone, summarizeTaskName, getCurrentBranch } from '../../../infra/task/index.js';
|
||||
import { info } from '../../../shared/ui/index.js';
|
||||
import { info, withProgress } from '../../../shared/ui/index.js';
|
||||
import { getTaskSlugFromTaskDir } from '../../../shared/utils/taskPaths.js';
|
||||
|
||||
export interface ResolvedTaskExecution {
|
||||
@ -60,23 +60,27 @@ export async function resolveTaskExecution(
|
||||
if (data.worktree) {
|
||||
throwIfAborted(abortSignal);
|
||||
baseBranch = getCurrentBranch(defaultCwd);
|
||||
info('Generating branch name...');
|
||||
const taskSlug = await summarizeTaskName(task.content, { cwd: defaultCwd });
|
||||
info(`Branch name generated: ${taskSlug}`);
|
||||
const taskSlug = await withProgress(
|
||||
'Generating branch name...',
|
||||
(slug) => `Branch name generated: ${slug}`,
|
||||
() => summarizeTaskName(task.content, { cwd: defaultCwd }),
|
||||
);
|
||||
|
||||
throwIfAborted(abortSignal);
|
||||
info('Creating clone...');
|
||||
const result = createSharedClone(defaultCwd, {
|
||||
worktree: data.worktree,
|
||||
branch: data.branch,
|
||||
taskSlug,
|
||||
issueNumber: data.issue,
|
||||
});
|
||||
const result = await withProgress(
|
||||
'Creating clone...',
|
||||
(cloneResult) => `Clone created: ${cloneResult.path} (branch: ${cloneResult.branch})`,
|
||||
async () => createSharedClone(defaultCwd, {
|
||||
worktree: data.worktree,
|
||||
branch: data.branch,
|
||||
taskSlug,
|
||||
issueNumber: data.issue,
|
||||
}),
|
||||
);
|
||||
throwIfAborted(abortSignal);
|
||||
execCwd = result.path;
|
||||
branch = result.branch;
|
||||
isWorktree = true;
|
||||
info(`Clone created: ${result.path} (branch: ${result.branch})`);
|
||||
}
|
||||
|
||||
const execPiece = data.piece || defaultPiece;
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
import { confirm } from '../../../shared/prompt/index.js';
|
||||
import { createSharedClone, autoCommitAndPush, summarizeTaskName, getCurrentBranch } from '../../../infra/task/index.js';
|
||||
import { DEFAULT_PIECE_NAME } from '../../../shared/constants.js';
|
||||
import { info, error, success } from '../../../shared/ui/index.js';
|
||||
import { info, error, success, withProgress } from '../../../shared/ui/index.js';
|
||||
import { createLogger } from '../../../shared/utils/index.js';
|
||||
import { createPullRequest, buildPrBody, pushBranch } from '../../../infra/github/index.js';
|
||||
import { executeTask } from './taskExecution.js';
|
||||
@ -113,16 +113,20 @@ export async function confirmAndCreateWorktree(
|
||||
|
||||
const baseBranch = getCurrentBranch(cwd);
|
||||
|
||||
info('Generating branch name...');
|
||||
const taskSlug = await summarizeTaskName(task, { cwd });
|
||||
info(`Branch name generated: ${taskSlug}`);
|
||||
const taskSlug = await withProgress(
|
||||
'Generating branch name...',
|
||||
(slug) => `Branch name generated: ${slug}`,
|
||||
() => summarizeTaskName(task, { cwd }),
|
||||
);
|
||||
|
||||
info('Creating clone...');
|
||||
const result = createSharedClone(cwd, {
|
||||
worktree: true,
|
||||
taskSlug,
|
||||
});
|
||||
info(`Clone created: ${result.path} (branch: ${result.branch})`);
|
||||
const result = await withProgress(
|
||||
'Creating clone...',
|
||||
(cloneResult) => `Clone created: ${cloneResult.path} (branch: ${cloneResult.branch})`,
|
||||
async () => createSharedClone(cwd, {
|
||||
worktree: true,
|
||||
taskSlug,
|
||||
}),
|
||||
);
|
||||
|
||||
return { execCwd: result.path, isWorktree: true, branch: result.branch, baseBranch };
|
||||
}
|
||||
|
||||
17
src/shared/ui/Progress.ts
Normal file
17
src/shared/ui/Progress.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { info } from './LogManager.js';
|
||||
|
||||
export type ProgressCompletionMessage<T> = string | ((result: T) => string);
|
||||
|
||||
export async function withProgress<T>(
|
||||
startMessage: string,
|
||||
completionMessage: ProgressCompletionMessage<T>,
|
||||
operation: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
info(startMessage);
|
||||
const result = await operation();
|
||||
const message = typeof completionMessage === 'function'
|
||||
? completionMessage(result)
|
||||
: completionMessage;
|
||||
info(message);
|
||||
return result;
|
||||
}
|
||||
@ -31,3 +31,5 @@ export { Spinner } from './Spinner.js';
|
||||
export { StreamDisplay, type ProgressInfo } from './StreamDisplay.js';
|
||||
|
||||
export { TaskPrefixWriter } from './TaskPrefixWriter.js';
|
||||
|
||||
export { withProgress, type ProgressCompletionMessage } from './Progress.js';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user