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: 1 addition & 1 deletion docs/operations/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Windows investigation while that suite is not a required gate.

### Unused code

`vp run knip:check` checks unused files and dependencies across the repo, then
`vp run knip:check` checks unused files and dependencies across the repo and, at the same time,
unused runtime exports in `apps/server`, `apps/desktop`, `apps/web`, and every internal package under
`packages/`. CI enforces both checks.
Exported types and Effect schemas are allowed without consumers. The schema preprocessor
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"report:renders": "node scripts/render-metrics.ts",
"lint": "vp lint --report-unused-disable-directives",
"knip": "knip --preprocessor ./scripts/knip-schemas.ts",
"knip:check": "knip --include files,dependencies --no-config-hints && knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-acp --workspace packages/provider-acp-registry --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --workspace packages/provider-cursor --workspace packages/provider-fx --workspace packages/provider-grok --workspace packages/provider-kiro --workspace packages/secret-provider-core --workspace packages/secret-provider-definitions --workspace packages/secret-provider-onepassword --workspace packages/source-control-core --workspace packages/source-control-definitions --workspace packages/source-control-azure-devops --workspace packages/source-control-bitbucket --workspace packages/source-control-forgejo --workspace packages/source-control-gitcafe --workspace packages/source-control-github --workspace packages/source-control-gitlab --workspace packages/source-control-testing --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints",
"knip:check": "vp run knip:check:all",
"knip:check:files": "knip --include files,dependencies --no-config-hints",
"knip:check:exports": "knip --workspace apps/server --workspace apps/desktop --workspace apps/web --workspace packages/client-runtime --workspace packages/contracts --workspace packages/effect-acp --workspace packages/effect-codex-app-server --workspace packages/shared --workspace packages/ssh --workspace packages/tailscale --workspace packages/provider-core --workspace packages/provider-acp --workspace packages/provider-acp-registry --workspace packages/provider-testing --workspace packages/provider-pi --workspace packages/provider-muse --workspace packages/provider-opencode --workspace packages/provider-cursor --workspace packages/provider-fx --workspace packages/provider-grok --workspace packages/provider-kiro --workspace packages/secret-provider-core --workspace packages/secret-provider-definitions --workspace packages/secret-provider-onepassword --workspace packages/source-control-core --workspace packages/source-control-definitions --workspace packages/source-control-azure-devops --workspace packages/source-control-bitbucket --workspace packages/source-control-forgejo --workspace packages/source-control-gitcafe --workspace packages/source-control-github --workspace packages/source-control-gitlab --workspace packages/source-control-testing --exports --preprocessor ./scripts/knip-schemas.ts --no-config-hints",
"knip:production": "knip --production --preprocessor ./scripts/knip-schemas.ts",
"lint:mobile": "node scripts/mobile-native-static-check.ts",
"mobile:install": "node scripts/mobile-native-client.ts ensure",
Expand Down
12 changes: 10 additions & 2 deletions scripts/check-changed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,13 @@ function launcherFixture() {
write("pnpm-workspace.yaml", "packages:\n - packages/*\n");
write(
"package.json",
JSON.stringify({ type: "module", scripts: { "knip:check": "knip --include files" } }),
JSON.stringify({
type: "module",
scripts: {
"knip:check:files": "knip --include files",
"knip:check:exports": "knip --exports",
},
}),
);
write("packages/example/package.json", JSON.stringify({ name: "example" }));
const source = "packages/example/space & (draft) %PATH% ! 研究.ts";
Expand Down Expand Up @@ -419,7 +425,9 @@ it("lints an Astro component and runs knip when only it changed", () => {
"--no-error-on-unmatched-pattern",
page,
]);
expect(calls.some(({ args }) => args[0] === "exec" && args[1] === "knip")).toBe(true);
const commands = calls.map(({ args }) => args);
expect(commands).toContainEqual(["exec", "knip", "--include", "files"]);
expect(commands).toContainEqual(["exec", "knip", "--exports"]);
});

it.each(["install", "lint"])("reports a failing %s step with its output", (step) => {
Expand Down
17 changes: 13 additions & 4 deletions scripts/check-changed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,14 @@ const CacheEntry = Schema.fromJsonString(
const decodeCacheEntry = Schema.decodeEffect(CacheEntry);
const encodeCacheEntry = Schema.encodeEffect(CacheEntry);
const decodeRootPackageJson = Schema.decodeEffect(
Schema.fromJsonString(Schema.Struct({ scripts: Schema.Struct({ "knip:check": Schema.String }) })),
Schema.fromJsonString(
Schema.Struct({
scripts: Schema.Struct({
"knip:check:files": Schema.String,
"knip:check:exports": Schema.String,
}),
}),
),
);

const collectBytes = (stream: Stream.Stream<Uint8Array, PlatformError.PlatformError>) =>
Expand Down Expand Up @@ -370,14 +377,16 @@ const checkChanged = Effect.fn("checkChanged")(function* (input: {
const runKnip = files.some(
(file) => CODE.test(file) || path.basename(file) === "package.json" || file === "knip.jsonc",
);
// CI runs knip:check as one script of `knip ... && knip ...` passes. The passes
// CI runs knip:check as a task that depends on one script per pass. The passes
// do not depend on each other, so they run side by side here.
const rootPackage = yield* fs
.readFileString(inRoot("package.json"))
.pipe(Effect.flatMap(decodeRootPackageJson), Effect.orDie);
const knipCheck = rootPackage.scripts["knip:check"];
const knipPasses: Array<Array<string>> = [];
for (const command of knipCheck.split(" && ")) {
for (const command of [
rootPackage.scripts["knip:check:files"],
rootPackage.scripts["knip:check:exports"],
]) {
const [bin, ...args] = command.trim().split(/\s+/);
if (bin !== "knip" || /["'$\\|;&<>]/.test(command)) {
return yield* Effect.fail(`check:changed cannot split knip:check: ${command}`);
Expand Down
11 changes: 10 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,16 @@ const taskCacheConfig = {
export default defineConfig({
run: {
cache: taskCacheConfig,
tasks: cachedTestTasks(process.env.T3_CACHED_TESTS),
tasks: {
...cachedTestTasks(process.env.T3_CACHED_TESTS),
// The two knip passes read the same files and write nothing, so they run side by side.
// The `knip:check` script runs this task, so `vp run knip:check` and `pnpm run knip:check` both use it.
"knip:check:all": {
command: 'node -e ""',
dependsOn: ["knip:check:files", "knip:check:exports"],
cache: false,
},
},
},
resolve: {
alias: {
Expand Down
Loading