diff --git a/.changeset/quiet-tools-rest.md b/.changeset/quiet-tools-rest.md new file mode 100644 index 000000000..06de1397e --- /dev/null +++ b/.changeset/quiet-tools-rest.md @@ -0,0 +1,5 @@ +--- + +--- + +Run T3 worktree dependency preparation asynchronously while retaining portable Git hook setup. diff --git a/.githooks/README.md b/.githooks/README.md index 5952ca25b..147308b75 100644 --- a/.githooks/README.md +++ b/.githooks/README.md @@ -37,6 +37,13 @@ source-identity key. Records package/lockfile state under `node_modules` so T3 handoff does not repeat an install Git already completed. +When T3 launches the configured worktree setup script, it sets +`T3CODE_DEFER_DEPENDENCY_INSTALL=1`. Cleanup still runs synchronously, while the +setup terminal installs dependencies asynchronously with hardlink imports from +the shared pnpm store. Raw Git worktrees still install during checkout. Their +install failure is best-effort; T3 can request the real exit status with +`T3CODE_WORKTREE_PREPARATION_STRICT=1` without removing the created worktree. + ## `pre-commit` / `pre-push` lint-staged on commit; agent ship gate on push (see repo AGENTS.md). diff --git a/.githooks/post-checkout b/.githooks/post-checkout index e1fb69449..e938e7375 100755 --- a/.githooks/post-checkout +++ b/.githooks/post-checkout @@ -63,6 +63,13 @@ clean_ts_project_emit() { clean_ts_project_emit "$wt_root" +# T3 launches the configured worktree setup command in a visible terminal. Keep +# cleanup synchronous, but leave dependency installation to that async setup. +if [ "${T3CODE_DEFER_DEPENDENCY_INSTALL:-0}" = "1" ]; then + echo "post-checkout: dependency installation deferred to T3 workspace initialization" >&2 + exit 0 +fi + # --- pnpm install when lockfile / package.json changed ----------------------- [ -f "$wt_root/pnpm-lock.yaml" ] || exit 0 state_file="$wt_root/node_modules/.pnpm-checkout-state" @@ -78,5 +85,14 @@ command -v pnpm >/dev/null 2>&1 || { export CI=1 echo "post-checkout: pnpm install in $wt_root" >&2 cd "$wt_root" || exit 1 -pnpm install --frozen-lockfile --prefer-offline --config.confirmModulesPurge=false || exit $? -printf '%s\n' "$state" >"$state_file" +if pnpm install --frozen-lockfile --prefer-offline \ + --config.package-import-method=hardlink \ + --config.confirmModulesPurge=false +then + printf '%s\n' "$state" >"$state_file" +else + status=$? + echo "post-checkout: dependency preparation failed (exit $status); worktree remains usable but degraded" >&2 + [ "${T3CODE_WORKTREE_PREPARATION_STRICT:-0}" = "1" ] && exit "$status" + exit 0 +fi diff --git a/scripts/hooks/post-checkout.test.sh b/scripts/hooks/post-checkout.test.sh index f36f7587d..1f5b10b47 100755 --- a/scripts/hooks/post-checkout.test.sh +++ b/scripts/hooks/post-checkout.test.sh @@ -14,7 +14,7 @@ mkdir -p .githooks "$work/fake-home/.local/bin" install -m 0755 "$hook" .githooks/post-checkout printf '{}\n' >package.json printf 'lockfileVersion: 9\n' >pnpm-lock.yaml -printf '#!/bin/sh\nroot=$(git rev-parse --show-toplevel)\nmkdir -p "$root/node_modules"\ntouch "$root/node_modules/.modules.yaml"\nprintf x >>"$root/.pnpm-runs"\n' >"$work/fake-home/.local/bin/pnpm" +printf '#!/bin/sh\nroot=$(git rev-parse --show-toplevel)\nmkdir -p "$root/node_modules"\ntouch "$root/node_modules/.modules.yaml"\nprintf x >>"$root/.pnpm-runs"\nprintf "%%s\\n" "$*" >"$root/.pnpm-args"\n' >"$work/fake-home/.local/bin/pnpm" chmod +x "$work/fake-home/.local/bin/pnpm" git add .githooks package.json pnpm-lock.yaml git commit -q -m init @@ -24,6 +24,7 @@ ref=$(git -C "$work/idempotent-linked" rev-parse HEAD) HOME="$work/fake-home" git -C "$work/idempotent-linked" -c core.hooksPath=.githooks \ hook run post-checkout -- "$ref" "$ref" 1 test "$(wc -c <"$work/idempotent-linked/.pnpm-runs")" = 1 +grep -q -- '--config.package-import-method=hardlink' "$work/idempotent-linked/.pnpm-args" # Branch checkout must wipe packages/*/dist. git init -q "$work/ts-clean-main" @@ -61,4 +62,23 @@ HOME="$work/fake-home" git -c core.hooksPath=.githooks \ hook run post-checkout -- "$ref" "$ref" 0 test -f packages/effect-app/dist/keep.d.ts +# T3 may defer installation to its setup terminal. Normal Git preparation is +# best-effort, while T3's explicit strict pass can still observe the exit code. +printf '#!/bin/sh\ntouch "$(git rev-parse --show-toplevel)/.pnpm-attempt"\nexit 17\n' >"$work/fake-home/.local/bin/pnpm" +chmod +x "$work/fake-home/.local/bin/pnpm" +rm -f node_modules/.pnpm-checkout-state .pnpm-attempt +deferred_out="$(HOME="$work/fake-home" T3CODE_DEFER_DEPENDENCY_INSTALL=1 T3CODE_WORKTREE_PREPARATION_STRICT=1 sh .githooks/post-checkout HEAD HEAD 1 2>&1)" +case "$deferred_out" in + *"dependency installation deferred to T3 workspace initialization"*) ;; + *) exit 1 ;; +esac +test ! -f .pnpm-attempt +HOME="$work/fake-home" sh .githooks/post-checkout HEAD HEAD 1 >/dev/null 2>&1 +test -f .pnpm-attempt +set +e +HOME="$work/fake-home" T3CODE_WORKTREE_PREPARATION_STRICT=1 sh .githooks/post-checkout HEAD HEAD 1 >/dev/null 2>&1 +strict_status=$? +set -e +test "$strict_status" = 17 + echo "portable worktree hooks passed" diff --git a/t3.json b/t3.json new file mode 100644 index 000000000..76f180744 --- /dev/null +++ b/t3.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://t3.codes/schema/t3.json", + "scripts": [ + { + "name": "Setup Worktree", + "command": "pnpm install --frozen-lockfile --prefer-offline --config.package-import-method=hardlink --config.confirmModulesPurge=false", + "runOnWorktreeCreate": true + } + ] +}