Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @butttons/dora

## 1.5.0

### Patch Changes

- Fix TypeScript strict errors.

## 1.4.6

### Patch Changes
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@butttons/dora",
"version": "1.4.6",
"version": "1.5.0",
"module": "src/index.ts",
"type": "module",
"private": true,
Expand All @@ -14,6 +14,7 @@
"license": "MIT",
"scripts": {
"test": "bun test ./test/",
"tsc": "tsc",
"build": "bun build src/index.ts --compile --outfile dist/dora",
"generate-proto": "protoc --plugin=./node_modules/.bin/protoc-gen-es --es_out=src/converter --es_opt=target=ts --proto_path=src/proto src/proto/scip.proto",
"biome:format": "biome format --write ./src",
Expand Down
260 changes: 130 additions & 130 deletions src/commands/adventure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,45 @@ import { getDependencies, getReverseDependencies } from "../db/queries.ts";
import type { PathResult } from "../types.ts";
import { CtxError } from "../utils/errors.ts";
import {
DEFAULTS,
outputJson,
resolveAndValidatePath,
setupCommand,
DEFAULTS,
outputJson,
resolveAndValidatePath,
setupCommand,
} from "./shared.ts";

export async function adventure(from: string, to: string): Promise<void> {
const ctx = await setupCommand();

const fromPath = resolveAndValidatePath({ ctx, inputPath: from });
const toPath = resolveAndValidatePath({ ctx, inputPath: to });

// If same file, return direct path
if (fromPath === toPath) {
const result: PathResult = {
from: fromPath,
to: toPath,
path: [fromPath],
distance: 0,
};
outputJson(result);
return;
}

// Use BFS to find shortest path
const foundPath = findShortestPath(ctx.db, fromPath, toPath);

if (!foundPath) {
throw new CtxError(`No path found from ${fromPath} to ${toPath}`);
}

const result: PathResult = {
from: fromPath,
to: toPath,
path: foundPath,
distance: foundPath.length - 1,
};

outputJson(result);
const ctx = await setupCommand();

const fromPath = resolveAndValidatePath({ ctx, inputPath: from });
const toPath = resolveAndValidatePath({ ctx, inputPath: to });

// If same file, return direct path
if (fromPath === toPath) {
const result: PathResult = {
from: fromPath,
to: toPath,
path: [fromPath],
distance: 0,
};
outputJson(result);
return;
}

// Use BFS to find shortest path
const foundPath = findShortestPath(ctx.db, fromPath, toPath);

if (!foundPath) {
throw new CtxError(`No path found from ${fromPath} to ${toPath}`);
}

const result: PathResult = {
from: fromPath,
to: toPath,
path: foundPath,
distance: foundPath.length - 1,
};

outputJson(result);
}

/**
Expand All @@ -54,106 +54,106 @@ export async function adventure(from: string, to: string): Promise<void> {
* This is not a blocker for release as path finding is infrequently used.
*/
function findShortestPath(
db: Database,
from: string,
to: string,
db: Database,
from: string,
to: string
): string[] | null {
// Try increasing depths until we find a path or reach max depth
const maxDepth = DEFAULTS.MAX_PATH_DEPTH;

for (let depth = 1; depth <= maxDepth; depth++) {
// Get dependencies from 'from' file
const forwardDeps = getDependencies(db, from, depth);
const forwardSet = new Set(forwardDeps.map((d) => d.path));

// Check if 'to' is in forward dependencies
if (forwardSet.has(to)) {
// Reconstruct path using BFS
return reconstructPath(db, from, to, depth, true);
}

// Get reverse dependencies from 'to' file
const reverseDeps = getReverseDependencies({ db, relativePath: to, depth });
const reverseSet = new Set(reverseDeps.map((d) => d.path));

// Check if 'from' is in reverse dependencies
if (reverseSet.has(from)) {
// Path exists in reverse direction
return reconstructPath(db, from, to, depth, true);
}

// Check for intersection between forward and reverse
for (const forwardFile of forwardSet) {
if (reverseSet.has(forwardFile)) {
// Found a connecting file
const pathToMiddle = reconstructPath(
db,
from,
forwardFile,
depth,
true,
);
const pathFromMiddle = reconstructPath(
db,
forwardFile,
to,
depth,
true,
);

if (pathToMiddle && pathFromMiddle) {
// Combine paths (remove duplicate middle file)
return [...pathToMiddle, ...pathFromMiddle.slice(1)];
}
}
}
}

return null;
// Try increasing depths until we find a path or reach max depth
const maxDepth = DEFAULTS.MAX_PATH_DEPTH;

for (let depth = 1; depth <= maxDepth; depth++) {
// Get dependencies from 'from' file
const forwardDeps = getDependencies(db, from, depth);
const forwardSet = new Set(forwardDeps.map((d) => d.path));

// Check if 'to' is in forward dependencies
if (forwardSet.has(to)) {
// Reconstruct path using BFS
return reconstructPath(db, from, to, depth, true);
}

// Get reverse dependencies from 'to' file
const reverseDeps = getReverseDependencies(db, to, depth);
const reverseSet = new Set(reverseDeps.map((d) => d.path));

// Check if 'from' is in reverse dependencies
if (reverseSet.has(from)) {
// Path exists in reverse direction
return reconstructPath(db, from, to, depth, true);
}

// Check for intersection between forward and reverse
for (const forwardFile of forwardSet) {
if (reverseSet.has(forwardFile)) {
// Found a connecting file
const pathToMiddle = reconstructPath(
db,
from,
forwardFile,
depth,
true
);
const pathFromMiddle = reconstructPath(
db,
forwardFile,
to,
depth,
true
);

if (pathToMiddle && pathFromMiddle) {
// Combine paths (remove duplicate middle file)
return [...pathToMiddle, ...pathFromMiddle.slice(1)];
}
}
}
}

return null;
}

/**
* Reconstruct path using BFS
*/
function reconstructPath(
db: Database,
from: string,
to: string,
maxDepth: number,
forward: boolean,
db: Database,
from: string,
to: string,
maxDepth: number,
forward: boolean
): string[] | null {
// Simple BFS implementation
const queue: Array<{ file: string; path: string[] }> = [
{ file: from, path: [from] },
];
const visited = new Set<string>([from]);

while (queue.length > 0) {
const current = queue.shift()!;

if (current.file === to) {
return current.path;
}

if (current.path.length > maxDepth) {
continue;
}

// Get neighbors
const neighbors = forward
? getDependencies(db, current.file, 1)
: getReverseDependencies({ db, relativePath: current.file, depth: 1 });

for (const neighbor of neighbors) {
if (!visited.has(neighbor.path)) {
visited.add(neighbor.path);
queue.push({
file: neighbor.path,
path: [...current.path, neighbor.path],
});
}
}
}

return null;
// Simple BFS implementation
const queue: Array<{ file: string; path: string[] }> = [
{ file: from, path: [from] },
];
const visited = new Set<string>([from]);

while (queue.length > 0) {
const current = queue.shift()!;

if (current.file === to) {
return current.path;
}

if (current.path.length > maxDepth) {
continue;
}

// Get neighbors
const neighbors = forward
? getDependencies(db, current.file, 1)
: getReverseDependencies(db, current.file, 1);

for (const neighbor of neighbors) {
if (!visited.has(neighbor.path)) {
visited.add(neighbor.path);
queue.push({
file: neighbor.path,
path: [...current.path, neighbor.path],
});
}
}
}

return null;
}
50 changes: 24 additions & 26 deletions src/commands/changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,34 @@ import { getChangedFiles, isGitRepo } from "../utils/git.ts";
import { DEFAULTS, outputJson, setupCommand } from "./shared.ts";

export async function changes(
ref: string,
_flags: Record<string, string | boolean> = {},
ref: string,
_flags: Record<string, string | boolean> = {}
): Promise<void> {
if (!(await isGitRepo())) {
throw new CtxError("Not a git repository");
}
if (!(await isGitRepo())) {
throw new CtxError("Not a git repository");
}

const ctx = await setupCommand();
const changedFiles = await getChangedFiles(ref);
const ctx = await setupCommand();
const changedFiles = await getChangedFiles(ref);

// For each changed file, get its reverse dependencies (depth 1)
const impacted = new Set<string>();
// For each changed file, get its reverse dependencies (depth 1)
const impacted = new Set<string>();

for (const file of changedFiles) {
try {
const rdeps = getReverseDependencies({
db: ctx.db,
relativePath: file,
depth: DEFAULTS.DEPTH,
});
rdeps.forEach((dep) => impacted.add(dep.path));
} catch {}
}
for (const file of changedFiles) {
try {
const rdeps = getReverseDependencies(ctx.db, file, DEFAULTS.DEPTH);
rdeps.forEach((dep) => {
impacted.add(dep.path);
});
} catch {}
}

const result: ChangesResult = {
ref,
changed: changedFiles,
impacted: Array.from(impacted),
total_impacted: impacted.size,
};
const result: ChangesResult = {
ref,
changed: changedFiles,
impacted: Array.from(impacted),
total_impacted: impacted.size,
};

outputJson(result);
outputJson(result);
}
Loading