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