Skip to content

Commit aa04148

Browse files
betegonclaude
andcommitted
refactor: add DirEntry type and precomputeDirListing returning DirEntry[] directly
Extract inline entry shape into a named DirEntry type and add precomputeDirListing() that returns DirEntry[] instead of LocalOpResult, so callers get the entries array directly without type assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6ffca44 commit aa04148

File tree

5 files changed

+76
-8
lines changed

5 files changed

+76
-8
lines changed

src/lib/init/local-ops.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "./constants.js";
1616
import type {
1717
ApplyPatchsetPayload,
18+
DirEntry,
1819
FileExistsBatchPayload,
1920
ListDirPayload,
2021
LocalOpPayload,
@@ -218,11 +219,7 @@ function listDir(payload: ListDirPayload): LocalOpResult {
218219
const maxEntries = params.maxEntries ?? 500;
219220
const recursive = params.recursive ?? false;
220221

221-
const entries: Array<{
222-
name: string;
223-
path: string;
224-
type: "file" | "directory";
225-
}> = [];
222+
const entries: DirEntry[] = [];
226223

227224
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: walking the directory tree is a complex operation
228225
function walk(dir: string, depth: number): void {
@@ -470,3 +467,13 @@ function applyPatchset(
470467

471468
return { ok: true, data: { applied } };
472469
}
470+
471+
export function precomputeDirListing(directory: string): DirEntry[] {
472+
const result = listDir({
473+
type: "local-op",
474+
operation: "list-dir",
475+
cwd: directory,
476+
params: { path: ".", recursive: true, maxDepth: 3, maxEntries: 500 },
477+
});
478+
return (result.data as { entries?: DirEntry[] })?.entries ?? [];
479+
}

src/lib/init/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export type DirEntry = {
2+
name: string;
3+
path: string;
4+
type: "file" | "directory";
5+
};
6+
17
export type WizardOptions = {
28
directory: string;
39
force: boolean;

src/lib/init/wizard-runner.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "./constants.js";
2222
import { formatError, formatResult } from "./formatters.js";
2323
import { handleInteractive } from "./interactive.js";
24-
import { handleLocalOp } from "./local-ops.js";
24+
import { handleLocalOp, precomputeDirListing } from "./local-ops.js";
2525
import type {
2626
InteractivePayload,
2727
LocalOpPayload,
@@ -150,13 +150,16 @@ export async function runWizard(options: WizardOptions): Promise<void> {
150150

151151
const spin = spinner();
152152

153+
spin.start("Scanning project...");
154+
const dirListing = precomputeDirListing(directory);
155+
153156
let run: Awaited<ReturnType<typeof workflow.createRun>>;
154157
let result: WorkflowRunResult;
155158
try {
156-
spin.start("Connecting to wizard...");
159+
spin.message("Connecting to wizard...");
157160
run = await workflow.createRun();
158161
result = (await run.startAsync({
159-
inputData: { directory, force, yes, dryRun, features },
162+
inputData: { directory, force, yes, dryRun, features, dirListing },
160163
tracingOptions,
161164
})) as WorkflowRunResult;
162165
} catch (err) {

test/lib/init/local-ops.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs, { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
33
import { join } from "node:path";
44
import {
55
handleLocalOp,
6+
precomputeDirListing,
67
validateCommand,
78
} from "../../../src/lib/init/local-ops.js";
89
import type {
@@ -768,3 +769,49 @@ describe("handleLocalOp", () => {
768769
});
769770
});
770771
});
772+
773+
describe("precomputeDirListing", () => {
774+
let testDir: string;
775+
776+
beforeEach(() => {
777+
testDir = mkdtempSync(join("/tmp", "precompute-test-"));
778+
});
779+
780+
afterEach(() => {
781+
rmSync(testDir, { recursive: true, force: true });
782+
});
783+
784+
test("returns DirEntry[] directly", () => {
785+
writeFileSync(join(testDir, "app.ts"), "x");
786+
mkdirSync(join(testDir, "src"));
787+
788+
const entries = precomputeDirListing(testDir);
789+
790+
expect(Array.isArray(entries)).toBe(true);
791+
expect(entries.length).toBeGreaterThanOrEqual(2);
792+
793+
const names = entries.map((e) => e.name).sort();
794+
expect(names).toContain("app.ts");
795+
expect(names).toContain("src");
796+
797+
const file = entries.find((e) => e.name === "app.ts");
798+
expect(file?.type).toBe("file");
799+
800+
const dir = entries.find((e) => e.name === "src");
801+
expect(dir?.type).toBe("directory");
802+
});
803+
804+
test("returns empty array for non-existent directory", () => {
805+
const entries = precomputeDirListing(join(testDir, "nope"));
806+
expect(entries).toEqual([]);
807+
});
808+
809+
test("recursively lists nested entries", () => {
810+
mkdirSync(join(testDir, "a"));
811+
writeFileSync(join(testDir, "a", "nested.ts"), "x");
812+
813+
const entries = precomputeDirListing(testDir);
814+
const paths = entries.map((e) => e.path);
815+
expect(paths).toContain(join("a", "nested.ts"));
816+
});
817+
});

test/lib/init/wizard-runner.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ let formatBannerSpy: ReturnType<typeof spyOn>;
6666
let formatResultSpy: ReturnType<typeof spyOn>;
6767
let formatErrorSpy: ReturnType<typeof spyOn>;
6868
let handleLocalOpSpy: ReturnType<typeof spyOn>;
69+
let precomputeDirListingSpy: ReturnType<typeof spyOn>;
6970
let handleInteractiveSpy: ReturnType<typeof spyOn>;
7071

7172
// MastraClient
@@ -143,6 +144,9 @@ beforeEach(() => {
143144
ok: true,
144145
data: { results: [] },
145146
});
147+
precomputeDirListingSpy = spyOn(ops, "precomputeDirListing").mockReturnValue(
148+
[]
149+
);
146150
handleInteractiveSpy = spyOn(inter, "handleInteractive").mockResolvedValue({
147151
action: "continue",
148152
});
@@ -169,6 +173,7 @@ afterEach(() => {
169173
formatResultSpy.mockRestore();
170174
formatErrorSpy.mockRestore();
171175
handleLocalOpSpy.mockRestore();
176+
precomputeDirListingSpy.mockRestore();
172177
handleInteractiveSpy.mockRestore();
173178

174179
stderrSpy.mockRestore();

0 commit comments

Comments
 (0)