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
2 changes: 2 additions & 0 deletions .changeset/wide-snails-feel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
13 changes: 12 additions & 1 deletion .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ module.exports = {
pathNot: "^packages/(hunk-git|hunk-vcs)/src/|^packages/hunk/src/extension-api/",
},
},
{
name: "hunk-jj-stays-on-vcs-contract",
comment:
"@hunk/jj owns the Jujutsu provider and may reach only its local modules, the public extension contract, and explicit dependency-bottom @hunk/vcs leaves.",
severity: "error",
from: { path: "^packages/hunk-jj/src/" },
to: {
path: "^packages/",
pathNot: "^packages/(hunk-jj|hunk-vcs)/src/|^packages/hunk/src/extension-api/",
},
},
{
name: "lib-is-a-leaf",
comment:
Expand Down Expand Up @@ -192,7 +203,7 @@ module.exports = {
severity: "error",
from: {
path: "^packages/(?!hunk/)",
pathNot: "^packages/hunk-git/",
pathNot: "^packages/hunk-(git|jj)/",
},
to: { path: "^packages/hunk/src/" },
},
Expand Down
11 changes: 11 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
"entry": ["src/**/*.test.ts"],
"project": ["src/**/*.ts"]
},
"packages/hunk-jj": {
"entry": ["src/**/*.test.ts"],
"project": ["src/**/*.ts"],
"ignoreExportsUsedInFile": true
},
"packages/hunk-vcs": {
"entry": ["src/**/*.test.ts"],
"project": ["src/**/*.ts"]
Expand Down
1 change: 1 addition & 0 deletions nix/bun.lock.nix
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
hash = "sha512-vo/MwCnpJAVHBkGzWjCJ28wF45fYHAfbPZcH2rodZODHtch2GHA94KtMfusmVycTUtsLAsaNsHhtY6P8X3RQsA==";
};
"@hunk/git" = copyPathToStore ../packages/hunk-git;
"@hunk/jj" = copyPathToStore ../packages/hunk-jj;
"@hunk/session-broker" = copyPathToStore ../packages/session-broker;
"@hunk/session-broker-bun" = copyPathToStore ../packages/session-broker-bun;
"@hunk/session-broker-core" = copyPathToStore ../packages/session-broker-core;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"devDependencies": {
"@changesets/changelog-github": "^0.7.0",
"@hunk/git": "workspace:*",
"@hunk/jj": "workspace:*",
"@hunk/session-broker": "workspace:*",
"@hunk/session-broker-bun": "workspace:*",
"@hunk/session-broker-core": "workspace:*",
Expand Down
26 changes: 26 additions & 0 deletions packages/hunk-jj/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@hunk/jj",
"version": "0.0.0",
"private": true,
"description": "Private statically bundled Jujutsu provider for Hunk.",
"license": "MIT",
"files": [
"src"
],
"type": "module",
"sideEffects": false,
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"dependencies": {
"@hunk/vcs": "workspace:*",
"hunkdiff": "workspace:*"
},
"engines": {
"bun": ">=1.3.14",
"node": ">=22"
}
}
2 changes: 1 addition & 1 deletion packages/hunk/src/extensions/default/vcs/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import gitExtension from "@hunk/git";
import jjExtension from "./jujutsu";
import jjExtension from "@hunk/jj";
import slExtension from "./sapling";
import { runExtensionFactory } from "../../runExtension";
import {
Expand Down
54 changes: 54 additions & 0 deletions scripts/hunk-jj-package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, test } from "bun:test";
import jjExtension, { JjVcsAdapter } from "@hunk/jj";
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";

const REPO_ROOT = resolve(import.meta.dir, "..");
const PACKAGE_ROOT = join(REPO_ROOT, "packages", "hunk-jj");

describe("@hunk/jj package boundary", () => {
test("exports only the explicit Jujutsu provider entry", () => {
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8")) as {
name: string;
private: boolean;
files: string[];
exports: Record<string, unknown>;
dependencies: Record<string, string>;
};

expect(manifest.name).toBe("@hunk/jj");
expect(manifest.private).toBe(true);
expect(manifest.files).toEqual(["src"]);
expect(manifest.exports).toEqual({
".": {
types: "./src/index.ts",
import: "./src/index.ts",
},
});
expect(manifest.dependencies).toEqual({
"@hunk/vcs": "workspace:*",
hunkdiff: "workspace:*",
});
});

test("registers the private workspace without leaking it into hunkdiff", () => {
const rootManifest = JSON.parse(readFileSync(join(REPO_ROOT, "package.json"), "utf8")) as {
devDependencies: Record<string, string>;
};
const hunkManifest = readFileSync(join(REPO_ROOT, "packages", "hunk", "package.json"), "utf8");
const bunLock = readFileSync(join(REPO_ROOT, "bun.lock"), "utf8");
const nixLock = readFileSync(join(REPO_ROOT, "nix", "bun.lock.nix"), "utf8");

expect(rootManifest.devDependencies["@hunk/jj"]).toBe("workspace:*");
expect(hunkManifest).not.toContain("@hunk/jj");
expect(hunkManifest).not.toContain("workspace:");
expect(bunLock).toContain('"packages/hunk-jj": {');
expect(bunLock).toContain('"@hunk/jj@workspace:packages/hunk-jj"');
expect(nixLock).toContain('"@hunk/jj" = copyPathToStore ../packages/hunk-jj;');
});

test("loads its provider entrypoint through the workspace", () => {
expect(typeof jjExtension).toBe("function");
expect(JjVcsAdapter.id).toBe("jj");
});
});
9 changes: 7 additions & 2 deletions scripts/source-boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const CORE_ROOT = join(SRC_ROOT, "core");
const EXTENSIONS_ROOT = join(SRC_ROOT, "extensions");
const BUNDLED_PROVIDER_ROOT = join(EXTENSIONS_ROOT, "default", "vcs");
const GIT_PACKAGE_ROOT = join(REPO_ROOT, "packages", "hunk-git", "src");
const JJ_PACKAGE_ROOT = join(REPO_ROOT, "packages", "hunk-jj", "src");
const VCS_PACKAGE_ROOT = join(REPO_ROOT, "packages", "hunk-vcs", "src");
const REVIEW_MODEL_ROOT = join(CORE_ROOT, "review");
// The published extension contract, which the review model may name for the annotation shapes
Expand Down Expand Up @@ -167,7 +168,7 @@ function unexpectedProviderImports() {
"@hunk/vcs/path",
"@hunk/vcs/source",
]);
return [BUNDLED_PROVIDER_ROOT, GIT_PACKAGE_ROOT].flatMap((providerRoot) =>
return [BUNDLED_PROVIDER_ROOT, GIT_PACKAGE_ROOT, JJ_PACKAGE_ROOT].flatMap((providerRoot) =>
sourceFiles(providerRoot).flatMap((path) =>
importSpecifiers(path)
// The lightweight scanner can match prose ending in `from "…"`; module specifiers
Expand All @@ -182,7 +183,10 @@ function unexpectedProviderImports() {
) {
return false;
}
return !(path === join(BUNDLED_PROVIDER_ROOT, "index.ts") && specifier === "@hunk/git");
return !(
path === join(BUNDLED_PROVIDER_ROOT, "index.ts") &&
(specifier === "@hunk/git" || specifier === "@hunk/jj")
);
})
.map((specifier) => `${repoPath(path)} -> ${specifier}`),
),
Expand Down Expand Up @@ -312,6 +316,7 @@ describe("source architecture boundaries", () => {
test("keeps bundled providers on the public contract and explicit VCS helper leaves", () => {
expect(forbiddenImports(BUNDLED_PROVIDER_ROOT, CORE_ROOT)).toEqual([]);
expect(escapingImports(GIT_PACKAGE_ROOT, [GIT_PACKAGE_ROOT])).toEqual([]);
expect(escapingImports(JJ_PACKAGE_ROOT, [JJ_PACKAGE_ROOT])).toEqual([]);
expect(unexpectedProviderImports()).toEqual([]);
});

Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"baseUrl": ".",
"paths": {
"@hunk/git": ["packages/hunk-git/src/index.ts"],
"@hunk/jj": ["packages/hunk-jj/src/index.ts"],
"@hunk/session-broker": ["packages/session-broker/src/index.ts"],
"@hunk/session-broker-bun": ["packages/session-broker-bun/src/index.ts"],
"@hunk/session-broker-core": ["packages/session-broker-core/src/index.ts"],
Expand Down
Loading