diff --git a/.config/mise/config.toml b/.config/mise/config.toml index 9afe466d69e..29b54cac135 100644 --- a/.config/mise/config.toml +++ b/.config/mise/config.toml @@ -17,7 +17,7 @@ minimum_release_age = "7d" [tools] # Basic tools node = { version = "22.21.1", postinstall = "corepack enable" } -"npm:turbo" = "2.10.12" +"npm:turbo" = "2.11.2" # Helper to install tools cargo-binstall = "1.19.1" diff --git a/.config/mise/mise.lock b/.config/mise/mise.lock index 68504749887..d49d1a0f84e 100644 --- a/.config/mise/mise.lock +++ b/.config/mise/mise.lock @@ -222,7 +222,7 @@ backend = "npm:renovate" trust_policy_excludes = '["@yarnpkg/libzip@3.2.2"]' [[tools."npm:turbo"]] -version = "2.10.12" +version = "2.11.2" backend = "npm:turbo" [[tools.oxfmt]] diff --git a/.github/scripts/affected-packages.sh b/.github/scripts/affected-packages.sh index 48257d15430..3c464a45a62 100755 --- a/.github/scripts/affected-packages.sh +++ b/.github/scripts/affected-packages.sh @@ -6,8 +6,9 @@ # `tasks` seeds the `affectedTasks` query rather than filtering it, so the # result also carries the tasks needed to run the requested ones; only items # with a requested name count. A task wired via `dependsOn` materializes a -# node in every package, so a package makes the matrix only for tasks it has -# a script for. Each task key holds the reason the package was selected. +# node in every package, so a package makes the matrix only for tasks turbo +# resolves a command for, whether from a `package.json` script or a `command` +# in a `turbo.json`. Each task key holds the reason the package was selected. set -euo pipefail if [[ $# -eq 0 ]]; then @@ -27,7 +28,7 @@ done TASKS=$(printf '%s\n' "$@" | jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))') -QUERY="query { affectedTasks(base: \"HEAD^\", tasks: $TASKS) { items { name script package { name path } reason { __typename ... on TaskFileChanged { filePath } ... on TaskDependencyTaskChanged { taskName packageName } ... on TaskPackageDependencyChanged { packageName } ... on TaskGlobalFileChanged { filePath } ... on TaskGlobalDepsChanged { filePath } ... on TaskAllChanged { description } } } } }" +QUERY="query { affectedTasks(base: \"HEAD^\", tasks: $TASKS) { items { name command package { name path } reason { __typename ... on TaskFileChanged { filePath } ... on TaskDependencyTaskChanged { taskName packageName } ... on TaskPackageDependencyChanged { packageName } ... on TaskGlobalFileChanged { filePath } ... on TaskGlobalDepsChanged { filePath } ... on TaskAllChanged { description } } } } }" # `$tasks` is a jq variable, not shell. # shellcheck disable=SC2016 @@ -39,7 +40,7 @@ turbo query "$QUERY" | jq --compact-output --argjson tasks "$TASKS" ' else "\(.__typename): \(.description // "")" end; [.data.affectedTasks.items[] - | select((.name | IN($tasks[])) and .script != null and .package.name != "//") + | select((.name | IN($tasks[])) and .command != null and .package.name != "//") | { task: .name, name: .package.name, path: .package.path, why: why }] | group_by(.name) | map({ name: .[0].name, path: .[0].path } + (map({ (.task): .why }) | add)) diff --git a/.github/scripts/prune-scopes.sh b/.github/scripts/prune-scopes.sh deleted file mode 100755 index 138768b5a22..00000000000 --- a/.github/scripts/prune-scopes.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -# Prints the given package plus every package reachable through its package -# and task graphs, one per line, for use as `turbo prune` scopes. -# -# `turbo prune` follows the package graph only, while task dependencies -# (`dependsOn: ["pkg#task"]`) cross it — including on packages the package -# graph pulls in, so the closure is a fixpoint over both graphs. Requires -# `turbo` and `yq`. -# -# The closure is resolved before anything is printed: a failing query exits -# without output, so a caller substituting the result into `turbo prune` -# passes no scopes at all rather than a partial list. -set -euo pipefail - -if [[ $# -ne 1 ]]; then - echo "usage: $0 " >&2 - exit 64 -fi -PACKAGE="$1" - -QUERY='query { packages { items { name allDependencies { items { name } } tasks { items { allDependencies { items { package { name } } } } } } } }' - -# One "\t" line per package- and task-graph edge. -# `$n` is a yq variable, not shell. -# shellcheck disable=SC2016 -EDGES=$( - turbo query "$QUERY" \ - | yq -p json '.data.packages.items[] | .name as $n | ((.allDependencies.items[].name, .tasks.items[].allDependencies.items[].package.name) | $n + "\t" + .)' -) - -awk -F'\t' -v start="$PACKAGE" ' - { edges[$1] = edges[$1] "\t" $2 } - END { - queue[1] = start; seen[start] = 1; head = 1; tail = 1 - while (head <= tail) { - n = split(edges[queue[head]], deps, "\t") - for (i = 1; i <= n; i++) - if (deps[i] != "" && !(deps[i] in seen)) { seen[deps[i]] = 1; queue[++tail] = deps[i] } - head++ - } - for (p in seen) if (p != "//") print p - }' <<<"$EDGES" | sort diff --git a/apps/brunch-agent/docker/Dockerfile b/apps/brunch-agent/docker/Dockerfile index 7ea947c075b..7b3230fd1bf 100644 --- a/apps/brunch-agent/docker/Dockerfile +++ b/apps/brunch-agent/docker/Dockerfile @@ -34,7 +34,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/brunch-agent') --docker && \ + turbo prune '@apps/brunch-agent' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/repo/out/full/$1" ] || ( \ mkdir -p "/repo/out/full/$(dirname "$1")/src" && \ diff --git a/apps/hash-ai-worker-ts/docker/Dockerfile b/apps/hash-ai-worker-ts/docker/Dockerfile index 844218d7d3b..d4cdfacfbf8 100644 --- a/apps/hash-ai-worker-ts/docker/Dockerfile +++ b/apps/hash-ai-worker-ts/docker/Dockerfile @@ -31,7 +31,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/hash-ai-worker-ts') --docker && \ + turbo prune '@apps/hash-ai-worker-ts' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/apps/hash-api/docker/Dockerfile b/apps/hash-api/docker/Dockerfile index a240aad583e..8be000168a9 100644 --- a/apps/hash-api/docker/Dockerfile +++ b/apps/hash-api/docker/Dockerfile @@ -31,7 +31,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/hash-api') --docker && \ + turbo prune '@apps/hash-api' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/apps/hash-frontend/docker/Dockerfile b/apps/hash-frontend/docker/Dockerfile index 3726ff0b700..cc7631f41aa 100644 --- a/apps/hash-frontend/docker/Dockerfile +++ b/apps/hash-frontend/docker/Dockerfile @@ -28,7 +28,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/hash-frontend') --docker && \ + turbo prune '@apps/hash-frontend' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/apps/hash-graph/docker/Dockerfile b/apps/hash-graph/docker/Dockerfile index e6d31c0ba0a..285ea1074e5 100644 --- a/apps/hash-graph/docker/Dockerfile +++ b/apps/hash-graph/docker/Dockerfile @@ -32,7 +32,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/hash-graph') --docker && \ + turbo prune '@apps/hash-graph' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/apps/hash-integration-worker/docker/Dockerfile b/apps/hash-integration-worker/docker/Dockerfile index 93e6f48ba62..3a40accbf8d 100644 --- a/apps/hash-integration-worker/docker/Dockerfile +++ b/apps/hash-integration-worker/docker/Dockerfile @@ -31,7 +31,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@apps/hash-integration-worker') --docker && \ + turbo prune '@apps/hash-integration-worker' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/apps/petrinaut-opt/docker/Dockerfile b/apps/petrinaut-opt/docker/Dockerfile index b2ed8cd0f11..f640b6aae5f 100644 --- a/apps/petrinaut-opt/docker/Dockerfile +++ b/apps/petrinaut-opt/docker/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update && \ /etc/mise/install.sh && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ - mise install --locked node npm:turbo yq + mise install --locked node npm:turbo FROM bindings-tools AS bindings-pruner @@ -33,7 +33,7 @@ WORKDIR /repo COPY . . RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@local/petrinaut-python') --docker + turbo prune '@local/petrinaut-python' --docker FROM bindings-tools AS bindings-builder diff --git a/libs/@local/graph/atlas/docker/Dockerfile b/libs/@local/graph/atlas/docker/Dockerfile index f253aa2351e..6d37ca7ad0e 100644 --- a/libs/@local/graph/atlas/docker/Dockerfile +++ b/libs/@local/graph/atlas/docker/Dockerfile @@ -35,7 +35,7 @@ RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \ COPY . . # `turbo prune` does not include Cargo workspaces, so we create dummy projects for each workspace member RUN mise trust && \ - turbo prune $(.github/scripts/prune-scopes.sh '@rust/hash-graph-atlas') --docker && \ + turbo prune '@rust/hash-graph-atlas' --docker && \ find $(yq '.workspace.members' -o tsv Cargo.toml | tr '*' ' ') -maxdepth 2 -name Cargo.toml -exec sh -c ' \ [ -f "/app/out/full/$1" ] || ( \ mkdir -p "/app/out/full/$(dirname "$1")/src" && \ diff --git a/libs/@local/graph/postgres-store/turbo.json b/libs/@local/graph/postgres-store/turbo.json index dc22f111287..4ebf397bba7 100644 --- a/libs/@local/graph/postgres-store/turbo.json +++ b/libs/@local/graph/postgres-store/turbo.json @@ -1,11 +1,6 @@ { "extends": ["//"], "tasks": { - "dev": { - "cache": false, - "persistent": true, - "dependsOn": ["@apps/hash-graph#start:test:migrate"] - }, "test:integration": { "dependsOn": ["^manifest", "@rust/hash-graph-migrations#start:migrate:up"] } diff --git a/libs/@local/petrinaut-arch-docs/turbo.json b/libs/@local/petrinaut-arch-docs/turbo.json index 5d800233f1e..6aea204aabb 100644 --- a/libs/@local/petrinaut-arch-docs/turbo.json +++ b/libs/@local/petrinaut-arch-docs/turbo.json @@ -2,13 +2,11 @@ "extends": ["//"], "tasks": { "doc:architecture": { - // Not cached. Turborepo hashes a task's own package plus its dependencies' - // task *outputs*, and the annotations this reads are plain source comments - // in `@hashintel/petrinaut`, `petrinaut-core`, and `petrinaut-cli`, which - // are not any task's output. - // A cached result would therefore survive an annotation change and go - // quietly stale, which is the exact failure this package exists to prevent. - // The run takes a few seconds; correctness is worth more here. + // The annotations this reads are source comments in the packages it depends + // on; `^manifest` carries their sources into the hash. + "dependsOn": ["^manifest"], + // Not cached: with `PETRINAUT_ARCH_DOCS_DIFF_BASE` the build annotates the + // difference to a git ref whose content the hash cannot see. "cache": false, // Declared so consumers can depend on this task rather than on the // directory existing, and so `turbo run` prunes it on a clean. diff --git a/tests/hash-playwright/turbo.json b/tests/hash-playwright/turbo.json index 1860f9a68af..dc242eedd6e 100644 --- a/tests/hash-playwright/turbo.json +++ b/tests/hash-playwright/turbo.json @@ -36,9 +36,12 @@ "@blockprotocol/graph#build" ] }, + // The tests drive the running frontend and API. `^start:test:healthcheck` only waits + // for them; `^manifest` carries their sources. "test:integration": { "dependsOn": [ "codegen", + "^manifest", "@apps/plugin-browser#build:test", "^start:test:healthcheck" ], diff --git a/turbo.json b/turbo.json index 3bf37f19a83..c83cb1fd863 100644 --- a/turbo.json +++ b/turbo.json @@ -165,5 +165,8 @@ "CARGO_TERM_PROGRESS_WHEN", "RUSTC_WRAPPER", "CARGO_INCREMENTAL" - ] + ], + "futureFlags": { + "affectedUsingTaskInputs": true + } }