diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91b0927..e2decb8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: CI Build Flatpak on: push: - branches: ['**'] + branches: ['main'] pull_request: workflow_dispatch: jobs: @@ -18,6 +18,8 @@ jobs: runs-on: ${{ matrix.variant.runner }} steps: - uses: actions/checkout@v5 + - name: Check launcher defaults + run: bash tests/check-launcher.sh - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 with: bundle: t3code.flatpak @@ -27,6 +29,20 @@ jobs: arch: ${{ matrix.variant.arch }} verbose: true + - name: Create checksum + run: sha256sum t3code.flatpak > t3code.flatpak.sha256 + + - name: Upload Flatpak and checksum + uses: actions/upload-artifact@v7 + with: + name: t3code-flatpak + path: | + t3code.flatpak + t3code.flatpak.sha256 + if-no-files-found: error + compression-level: 0 + retention-days: 3 + - name: Check installed SSH boundaries with a disposable key and agent shell: bash env: diff --git a/.github/workflows/pages-flatpak-repo.yml b/.github/workflows/pages-flatpak-repo.yml index 7d1198f..015e8ae 100644 --- a/.github/workflows/pages-flatpak-repo.yml +++ b/.github/workflows/pages-flatpak-repo.yml @@ -162,9 +162,8 @@ jobs: - name: Download unsigned architecture repository uses: actions/download-artifact@v8 with: - pattern: t3code-ostree-* - path: inputs - merge-multiple: false + name: t3code-ostree-x86_64 + path: inputs/t3code-ostree-x86_64 - name: Restore previous final repository id: repo-cache @@ -196,8 +195,8 @@ jobs: ref_file="$artifact_dir/t3code-ref-${arch}.txt" expected_ref="app/${APP_ID}/${arch}/master" - test -s "$archive" - test -s "$ref_file" + test -s "$archive" || { echo "Missing repository archive: $archive" >&2; exit 1; } + test -s "$ref_file" || { echo "Missing application ref: $ref_file" >&2; exit 1; } test "$(<"$ref_file")" = "$expected_ref" extract_dir="$RUNNER_TEMP/t3code-${arch}" @@ -338,7 +337,10 @@ jobs: grep -Eq '^RuntimeRepo=https://flathub\.org/repo/flathub\.flatpakrepo$' site/t3code.flatpakref grep -Eq '^IsRuntime=false$' site/t3code.flatpakref grep -Eq '^GPGKey=[A-Za-z0-9+/=]+$' site/t3code.flatpakref - ! grep -Eq '@[A-Z_]+@' site/t3code.flatpakrepo site/t3code.flatpakref + if grep -Eq '@[A-Z_]+@' site/t3code.flatpakrepo site/t3code.flatpakref; then + echo "Unexpanded placeholders remain in repository descriptors." >&2 + exit 1 + fi grep -Fq 't3code.flatpakrepo' site/index.html grep -Fq 't3code.flatpakref' site/index.html grep -Fq 'flatpak install --user t3code com.t3tools.t3code' site/index.html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 133e129..fdf8020 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: git config --global --add safe.directory "$GITHUB_WORKSPACE" - commit="$(git rev-parse --verify HEAD^{commit})" + commit="$(git rev-parse --verify 'HEAD^{commit}')" source_date_epoch="$(git show -s --format=%ct "$commit")" if [[ ! "$source_date_epoch" =~ ^[0-9]+$ ]]; then @@ -151,8 +151,8 @@ jobs: run: | set -euo pipefail - sha256sum -c *.sha256 - sha256sum *.flatpak > SHA256SUMS + sha256sum -c ./*.sha256 + sha256sum ./*.flatpak > SHA256SUMS - name: Create or update release env: diff --git a/.github/workflows/update-bundled-runtime-pins.yml b/.github/workflows/update-bundled-runtime-pins.yml index 549fe94..1f7a0be 100644 --- a/.github/workflows/update-bundled-runtime-pins.yml +++ b/.github/workflows/update-bundled-runtime-pins.yml @@ -15,6 +15,10 @@ permissions: contents: write pull-requests: write +concurrency: + group: update-bundled-runtime-pins + cancel-in-progress: false + jobs: update: name: Check bundled runtime pins @@ -59,6 +63,11 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # Refresh the remote-tracking ref before a leased update of an + # existing automation branch. + if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + git fetch origin "refs/heads/$branch:refs/remotes/origin/$branch" + fi git switch -c "$branch" git add com.t3tools.t3code.yml git commit -m "chore: update bundled runtime pins" @@ -66,14 +75,16 @@ jobs: body="$(printf 'Updates bundled runtime source pins for the T3 Code Flatpak.\n\n- Node.js: %s\n- Git: %s\n- OpenSSH: %s\n\nThis workflow keeps updates separate from upstream T3 Code AppImage repinning.' "$NODE_VERSION" "$GIT_VERSION" "$OPENSSH_VERSION")" - if gh pr view "$branch" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh pr edit "$branch" --repo "$GITHUB_REPOSITORY" \ - --title "chore: update bundled runtime pins" \ - --body "$body" + # Use the repository endpoint explicitly, including in contributor + # forks where PR discovery must not select the parent repository. + owner="${GITHUB_REPOSITORY%%/*}" + pr_number="$(gh api --method GET "repos/$GITHUB_REPOSITORY/pulls" \ + -f state=open -f base=main -f "head=$owner:$branch" --jq '.[0].number // empty')" + if [ -n "$pr_number" ]; then + gh api --method PATCH "repos/$GITHUB_REPOSITORY/pulls/$pr_number" \ + -f title="chore: update bundled runtime pins" -f body="$body" else - gh pr create --repo "$GITHUB_REPOSITORY" \ - --base main \ - --head "$branch" \ - --title "chore: update bundled runtime pins" \ - --body "$body" + gh api --method POST "repos/$GITHUB_REPOSITORY/pulls" \ + -f base=main -f "head=$owner:$branch" \ + -f title="chore: update bundled runtime pins" -f body="$body" fi diff --git a/.github/workflows/update-t3code-appimage.yml b/.github/workflows/update-t3code-appimage.yml index d276fdb..9c91755 100644 --- a/.github/workflows/update-t3code-appimage.yml +++ b/.github/workflows/update-t3code-appimage.yml @@ -7,26 +7,22 @@ on: permissions: contents: write + actions: write + +concurrency: + group: update-t3code-appimage + cancel-in-progress: false jobs: update: name: Check latest upstream AppImage runs-on: ubuntu-24.04 steps: - - name: Verify downstream workflow token - env: - DOWNSTREAM_WORKFLOW_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - if [ -z "${DOWNSTREAM_WORKFLOW_TOKEN:-}" ]; then - echo "Set the GH_TOKEN secret so updater pushes trigger CI, release, and Pages workflows." >&2 - exit 1 - fi - - uses: actions/checkout@v5 with: ref: main fetch-depth: 0 - token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.GH_TOKEN || github.token }} - uses: actions/setup-node@v5 with: @@ -39,6 +35,7 @@ jobs: run: node scripts/update-t3code-appimage.js - name: Commit update and tag release + id: publish env: VERSION: ${{ steps.update.outputs.version }} run: | @@ -49,6 +46,7 @@ jobs: *) tag="t3code-v${VERSION}" ;; esac + echo "tag=$tag" >> "$GITHUB_OUTPUT" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" @@ -61,6 +59,7 @@ jobs: echo "Manifest already points at T3 Code ${VERSION}; creating missing release tag ${tag}." git tag -a "$tag" -m "T3 Code ${VERSION}" git push origin "$tag" + echo "release=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -74,3 +73,24 @@ jobs: git tag -a "$tag" -m "T3 Code ${VERSION}" git push origin HEAD:main git push origin "$tag" + echo "release=true" >> "$GITHUB_OUTPUT" + echo "updated=true" >> "$GITHUB_OUTPUT" + + - name: Dispatch downstream workflows for the default token + if: steps.publish.outputs.release == 'true' + env: + GH_TOKEN: ${{ github.token }} + CUSTOM_TOKEN: ${{ secrets.GH_TOKEN }} + RELEASE_TAG: ${{ steps.publish.outputs.tag }} + UPDATED: ${{ steps.publish.outputs.updated }} + run: | + set -euo pipefail + # PAT pushes trigger workflows automatically; GITHUB_TOKEN pushes do not. + if [ -n "$CUSTOM_TOKEN" ]; then + exit 0 + fi + gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref main -f tag="$RELEASE_TAG" + if [ "$UPDATED" = true ]; then + gh workflow run build.yml --repo "$GITHUB_REPOSITORY" --ref main + gh workflow run pages-flatpak-repo.yml --repo "$GITHUB_REPOSITORY" --ref main + fi diff --git a/README.md b/README.md index d4568d5..e3d9576 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,16 @@ socket. The default sandbox grants display, IPC, network, portal-based file picking, and cursor theme access. It does not grant access to the full home directory, SSH agent, host command execution, host private keys, or broad host filesystems. -SSH-agent access is available through an explicit user override described below. +SSH-agent and GPG-agent access are available through explicit user overrides +described below. These are optional authentication permissions, not requirements +for launching T3 Code or using every provider. Provider CLIs can be installed into app data with the sandbox-local helper below. Project directory access still requires an explicit per-path override. ## Sandbox runtime tools -The Flatpak bundles sandbox-local `node`, `npm`, `npx`, `corepack`, `git`, OpenSSH client tools, and the `gh`, `glab`, and `tea` hosting CLIs. These are real binaries inside `/app`, not host wrappers. The launcher does not add host `~/.local/bin` to `PATH`. The manifest does not grant host command execution, home access, or SSH-agent access. +The Flatpak bundles sandbox-local `node`, `npm`, `npx`, `corepack`, `git`, GNU make, OpenSSH client tools, and the `gh`, `glab`, and `gt` hosting CLIs. `gt` is [Gitea CLI](https://gitea.com/gitea/cli), not Graphite. These are real binaries inside `/app`, not host wrappers. Before export, the build checks that all three hosting CLIs exist in `/app/bin` and can print their versions. Managed commands precede optional extension commands in `PATH`, without adding host `~/.local/bin`. The manifest does not grant host command execution, home access, or SSH-agent access. The launcher configures npm globals and cache under T3 Code app data: @@ -34,6 +36,40 @@ PATH=$NPM_CONFIG_PREFIX/bin:/app/bin:$PATH When installed normally, those resolve under `~/.var/app/com.t3tools.t3code/data/npm-global` and `~/.var/app/com.t3tools.t3code/cache/npm`. +## Optional tool extensions + +Separately maintained tool sets can use the `com.t3tools.t3code.Tools` +extension point. Extensions are installed explicitly; installing or updating +T3 Code does not automatically download them. + +For a separately maintained toolset, configure its remote and install its +matching branch. Replace the example remote and extension name below with +those supplied by its maintainer: + +```bash +flatpak install --user com.t3tools.t3code.Tools.Example//26.08 +``` + +The extension branch must match T3 Code's Freedesktop runtime version, currently +`26.08`. A runtime upgrade requires a matching extension build. Restart T3 Code +after installing, updating, or removing an extension. + +Extensions mount at `/app/tools/`. The launcher adds their existing `bin` +directories to `PATH` in glob order, after npm globals and `/app/bin`, +but before the inherited runtime paths. Flatpak registers each extension's +`lib` directory with the dynamic loader. Tool-specific configuration belongs +in the extension. Extension loading adds no sandbox permissions, but extension +commands run with the application's existing access to app data, network, and +mounted projects. Only install extensions from maintainers you trust. + +For a [local integration check](tests/README.md), build and install the app into an isolated +Flatpak installation, then build and install +`tests/extension-fixture/com.t3tools.t3code.Tools.Test.yml` into the same +installation. Run `tests/check-tool-extension.sh installed` in the app sandbox +with the test directory mounted read-only. After uninstalling the fixture, +run it with `absent`. The fixture command depends on an extension library and +has no rpath, so it checks library loading as well as command discovery. + ## Provider CLIs Install supported provider CLIs from inside the Flatpak sandbox: @@ -68,7 +104,7 @@ that app directory: | `~/.claude` | `~/.var/app/com.t3tools.t3code/.claude` | Claude Code config, including `.claude.json` | `--persist=.claude` + `CLAUDE_CONFIG_DIR` | | `~/.ssh` | `~/.var/app/com.t3tools.t3code/.ssh` | sandbox-local SSH configuration and verified `known_hosts`; not the host SSH directory | `--persist=.ssh` | | `$XDG_CONFIG_HOME/git/config` | `~/.var/app/com.t3tools.t3code/config/git/config` | `git config --global` settings | launcher creates the file so git prefers it over `~/.gitconfig` | -| `$HISTFILE` | `~/.var/app/com.t3tools.t3code/.local/state/bash/history` | terminal shell history | launcher sets `HISTFILE` | +| `$HISTFILE` | `~/.var/app/com.t3tools.t3code/data/state/bash/history` by default | terminal shell history | launcher sets persistent `XDG_STATE_HOME` and `HISTFILE` defaults | | `$XDG_CONFIG_HOME`, `$XDG_DATA_HOME`, `$XDG_CACHE_HOME` | `~/.var/app/com.t3tools.t3code/{config,data,cache}` | Electron profile, OpenCode, npm globals and cache | Flatpak default | The app-specific state is separate from the host's `~/.t3`, `~/.codex`, @@ -95,6 +131,13 @@ Restart T3 Code. On the host, ensure the agent contains the intended key. Inside T3 Code's terminal, `ssh-add -l` lists the available agent identities. Git then uses the bundled SSH client for authentication. +The package already includes `--persist=.ssh`, so current builds do not need a +separate persistence override. For an older build without it, use: + +```bash +flatpak override --user --persist=.ssh --socket=ssh-auth com.t3tools.t3code +``` + Every provider, extension, and project subprocess inherits agent access. An agent does not normally disclose private-key material, but those processes can request authentication with its loaded keys while access lasts. Prefer a @@ -127,6 +170,37 @@ These revocations cannot undo earlier credential disclosure. Agent access is a separate permission and does not require any of those directory or D-Bus grants. See [the disposable SSH boundary checks](tests/ssh-boundary.md) for runtime validation. +## Optional GPG-agent access + +If your workflow needs the host GPG agent, for example for GPG-signed Git +commits, grant access and preserve sandbox-local GnuPG state with: + +```bash +flatpak override --user --socket=gpg-agent --persist=.gnupg com.t3tools.t3code +``` + +Restart T3 Code after changing permissions. This preserves the sandbox's own +`~/.gnupg` under `~/.var/app/com.t3tools.t3code/.gnupg`; it does not expose or +import the host's `~/.gnupg`, public keyring, or configuration. The socket grant +alone does not install a GPG client or configure Git signing. Check that `gpg` +is available inside T3 Code's terminal and configure the required public keys +and signing identity there. This package does not explicitly bundle GnuPG, and +the SSH boundary test does not validate GPG signing. + +All sandbox processes gain access to the agent socket, including providers, +extensions, and project commands. They can request operations permitted by the +agent; do not assume that every request requires fresh confirmation. Only +enable this permission if you trust those processes with that access. + +To revoke GPG-agent access: + +```bash +flatpak override --user --nosocket=gpg-agent com.t3tools.t3code +``` + +Restart T3 Code after revoking access. Revocation does not delete the persisted +sandbox-local GnuPG state. + ## Project access T3 Code's private app data lives under `~/.var/app/com.t3tools.t3code` (see @@ -139,12 +213,34 @@ For persistent access to one project directory, add a narrow user override: flatpak override --user --filesystem=/path/to/project:rw com.t3tools.t3code ``` +If a project is a symlink whose destination lies outside the granted directory, +grant access to the resolved destination as well. Access to the directory +containing a symlink does not expose an otherwise inaccessible target: + +```bash +realpath "$HOME/projects/example" +flatpak override --user --filesystem="$(realpath "$HOME/projects/example"):rw" com.t3tools.t3code +``` + +Here, `$HOME/projects/example` is the symlink. Keep access to its containing +directory if you want to open the project through that link. The additional +grant covers the resolved destination at the time of the command. If the link +is changed to point elsewhere, review and grant its new destination separately. +Do not grant `home` or `host` just to expose arbitrary symlink targets. + To revoke that project later: ```bash flatpak override --user --nofilesystem=/path/to/project com.t3tools.t3code ``` +For a symlink target grant, revoke the actual destination path that was granted, +even if the link has since changed or been removed: + +```bash +flatpak override --user --nofilesystem=/actual/path/to/project com.t3tools.t3code +``` + Inspect active permissions with: ```bash @@ -184,12 +280,12 @@ The container needs network access to Flathub and the upstream source hosts. `-- This repository publishes a signed x86_64 Flatpak repository at: - + Add the remote and install T3 Code with: ```bash -flatpak remote-add --user --if-not-exists t3code https://0cwa.github.io/t3-flatpak/t3code.flatpakrepo +flatpak remote-add --user --if-not-exists t3code https://openSUSE-Python.github.io/t3-flatpak/t3code.flatpakrepo flatpak install --user t3code com.t3tools.t3code ``` @@ -203,7 +299,7 @@ flatpak update --user com.t3tools.t3code The standalone app reference is also available for one-click installation: ```bash -flatpak install --user https://0cwa.github.io/t3-flatpak/t3code.flatpakref +flatpak install --user https://openSUSE-Python.github.io/t3-flatpak/t3code.flatpakref ``` This package intentionally supports x86_64 only. The manifest and Pages @@ -224,10 +320,13 @@ Before the first publish: key securely; users trust its public key from the generated descriptors. 2. Add `FLATPAK_GPG_PRIVATE_KEY` as an ASCII-armored GitHub Actions secret. 3. Add `FLATPAK_GPG_KEY_ID` as the signing key ID or fingerprint secret. -4. Add `GH_TOKEN` as a token authorized to push commits and tags. The updater - uses it because pushes made with the default `github.token` do not trigger - downstream CI, release, and Pages workflows. The updater fails before - changing anything when this secret is absent. +4. The AppImage updater uses the default `github.token` to push commits and + tags, then explicitly dispatches downstream CI, release, and Pages workflows. + Optionally add `GH_TOKEN` as a token authorized to push commits and tags; + its pushes trigger downstream workflows automatically instead. Allow GitHub + Actions to create pull requests for the bundled-runtime updater. + Set the repository's default branch to `main` so scheduled workflows use + the same maintained branch as builds and publishing. 5. Set the public repository variable `PUBLISH_FLATPAK_REPO` to `true` after the signing secrets are ready. Automatic publication is restricted to `refs/heads/main`; manual dispatch is also restricted to that ref. @@ -235,7 +334,7 @@ Before the first publish: the trailing slash or `/repo/`. The workflow appends `/repo/` when it generates repository metadata. To keep the documented URL when the GitHub repository slug is not `t3-flatpak`, set it to - `https://0cwa.github.io/t3-flatpak`. + `https://openSUSE-Python.github.io/t3-flatpak`. 7. In repository Settings → Pages, set the source to **GitHub Actions**. A branch/folder Pages source will not deploy this workflow's artifact. diff --git a/com.t3tools.t3code.yml b/com.t3tools.t3code.yml index 0a3b17b..008fbcf 100644 --- a/com.t3tools.t3code.yml +++ b/com.t3tools.t3code.yml @@ -11,6 +11,24 @@ command: startt3code branch: master separate-locales: false +# Check the finished /app tree before exporting any repository or bundle. +cleanup-commands: + - | + set -eu + for cli in gh glab gt; do + test -x "/app/bin/$cli" + "/app/bin/$cli" --version + done + +add-extensions: + com.t3tools.t3code.Tools: + directory: tools + version: *runtime-version + subdirectories: true + no-autodownload: true + autodelete: true + add-ld-path: lib + finish-args: - --socket=wayland - --socket=fallback-x11 @@ -38,8 +56,8 @@ modules: - ln -s ../lib/nodejs/bin/corepack /app/bin/corepack sources: - type: archive - url: https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz - sha256: fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6 + url: https://nodejs.org/dist/v24.22.2/node-v24.22.2-linux-x64.tar.xz + sha256: 48cc0e21e048790e54c0cf5749c2725005313d652651c40b7a21feb9697aaf75 only-arches: [x86_64] - name: git @@ -52,6 +70,17 @@ modules: url: https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.56.0.tar.xz sha256: 26c56c296b38c0695b26fa95f475f1d01704d2d38e73465ca30b0b2f5dc789d3 + - name: make + buildsystem: simple + build-commands: + - ./configure --prefix=/app + - make + - make install + sources: + - type: archive + url: https://ftp.gnu.org/gnu/make/make-4.4.1.tar.gz + sha256: dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3 + - name: gh buildsystem: simple build-commands: @@ -72,15 +101,15 @@ modules: sha256: 4e6c59de9f7ed2f304bf93aad01ea8f8a69584f0450ce90ad696ef81f69c69aa only-arches: [x86_64] - - name: tea + - name: gt buildsystem: simple build-commands: - - install -Dm755 tea-0.16.0-linux-amd64 /app/bin/tea + - install -Dm755 gitea-cli-0.2.0-linux-amd64 /app/bin/gt sources: - type: file - url: https://gitea.com/gitea/tea/releases/download/v0.16.0/tea-0.16.0-linux-amd64 - dest-filename: tea-0.16.0-linux-amd64 - sha256: 9c142c8a0b31485eea2387031c1c404f12b7141075eb4ffbd7ced49441590636 + url: https://gitea.com/gitea/cli/releases/download/v0.2.0/gitea-cli-0.2.0-linux-amd64 + dest-filename: gitea-cli-0.2.0-linux-amd64 + sha256: 220f329cbc08f55f623aa0814000eabab2fe76271c57ffb12fdb6311ab358e36 only-arches: [x86_64] - name: openssh @@ -93,8 +122,8 @@ modules: - install -m644 ssh_config /app/etc/ssh/ssh_config sources: - type: archive - url: https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.5p1.tar.gz - sha256: d44d28a839ea9daf969cc69150fde59910b2b39361dad81a3bd6cbd19218db11 + url: https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.6p1.tar.gz + sha256: a9dc9565dffe8640f64d863cd29a32bc4a3dbdec0566a7fc44c5d6ee767d5f39 - name: t3code buildsystem: simple @@ -146,6 +175,7 @@ modules: done - install -D squashfs-root/t3code.desktop /app/share/applications/com.t3tools.t3code.desktop - install -Dm755 startt3code /app/bin/startt3code + - install -d /app/tools - install -Dm755 t3code-install-provider /app/bin/t3code-install-provider - install -D com.t3tools.t3code.metainfo.xml -t /app/share/metainfo/ - mv squashfs-root /app/bin/t3code @@ -160,9 +190,9 @@ modules: path: com.t3tools.t3code.metainfo.xml - type: file - url: https://github.com/pingdotgg/t3code/releases/download/v0.1.2/T3-Code-0.1.2-x86_64.AppImage + url: https://github.com/pingdotgg/t3code/releases/download/v0.1.6/T3-Code-0.1.6-x86_64.AppImage dest-filename: T3-Code.AppImage - sha256: cf01e2d167eff9d01c4c1004d31b2c0da9e2522cc94262e41644d1cb15dfdb5c + sha256: 09e2507208c779309b396be846aea1d945144bd780606899c2f481c96ddf34d0 only-arches: [x86_64] x-checker-data: type: electron-updater diff --git a/startt3code b/startt3code index 68db8d7..8c1a08e 100755 --- a/startt3code +++ b/startt3code @@ -3,7 +3,13 @@ export TMPDIR="$XDG_RUNTIME_DIR/app/${FLATPAK_ID:-com.t3tools.t3code}" export NPM_CONFIG_PREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/npm-global" export NPM_CONFIG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/npm" -export PATH="$NPM_CONFIG_PREFIX/bin:/app/bin:$PATH" +# Keep managed tools ahead of optional extension commands. +tools_path="" +for tool_bin in /app/tools/*/bin; do + [[ -d "$tool_bin" ]] && tools_path+=":$tool_bin" +done +export PATH="$NPM_CONFIG_PREFIX/bin:/app/bin$tools_path:$PATH" +unset tools_path tool_bin mkdir -p "$NPM_CONFIG_PREFIX" "$NPM_CONFIG_CACHE" @@ -24,7 +30,8 @@ mkdir -p "$(dirname -- "$git_xdg_config")" [[ -e "$git_xdg_config" ]] || : > "$git_xdg_config" # Shell history for T3 Code terminals. -export HISTFILE="${HISTFILE:-${XDG_STATE_HOME:-$HOME/.local/state}/bash/history}" +export XDG_STATE_HOME="${XDG_STATE_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/state}" +export HISTFILE="${HISTFILE:-$XDG_STATE_HOME/bash/history}" mkdir -p "$(dirname -- "$HISTFILE")" declare -a FLAGS=() diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..44cd139 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,61 @@ +# Launcher and tool extension checks + +Run `bash tests/check-launcher.sh` for an isolated launcher check without +Flatpak or Electron. It checks managed-tool precedence, persistent state +paths, and preservation of existing Git configuration. CI runs this check +before building the application. + +## Tool extension integration check + +Run these commands on a host with Flatpak and flatpak-builder, from the +repository root. Use a disposable installation so the running T3 Code app is +not replaced. The initial build downloads the SDK, runtime, and Electron base. + +```bash +test_root=$(mktemp -d) +export FLATPAK_USER_DIR="$test_root/installation" +flatpak remote-add --user --if-not-exists flathub \ + https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak-builder --user --force-clean --install-deps-from=flathub \ + --disable-rofiles-fuse --state-dir="$test_root/state" \ + --repo="$test_root/repo" "$test_root/app" com.t3tools.t3code.yml +flatpak remote-add --user --no-gpg-verify local "$test_root/repo" +flatpak install --user -y local com.t3tools.t3code//master + +flatpak run --user --filesystem="$PWD/tests:ro" --command=bash \ + com.t3tools.t3code "$PWD/tests/check-tool-extension.sh" absent + +flatpak-builder --user --force-clean --disable-rofiles-fuse \ + --state-dir="$test_root/state" --repo="$test_root/repo" \ + "$test_root/extension" tests/extension-fixture/com.t3tools.t3code.Tools.Test.yml +``` + +```bash +flatpak install --user -y local com.t3tools.t3code.Tools.Test//26.08 +flatpak run --user --command=sh com.t3tools.t3code -ec \ + '/app/tools/Test/bin/t3-tool-test; ldconfig -p | grep libt3tooltest' +flatpak run --user --filesystem="$PWD/tests:ro" --command=bash \ + com.t3tools.t3code "$PWD/tests/check-tool-extension.sh" installed + +flatpak uninstall --user -y com.t3tools.t3code.Tools.Test//26.08 +flatpak run --user --filesystem="$PWD/tests:ro" --command=bash \ + com.t3tools.t3code "$PWD/tests/check-tool-extension.sh" absent +``` + +The launcher check intercepts the Electron handoff and runs a fresh child +shell. It verifies command lookup, PATH precedence, and library loading without +opening the GUI. The C fixture deliberately has no rpath. + +For an end-to-end UI check, install the fixture again, start the app from this +installation, and run `command -v t3-tool-test` and `t3-tool-test` in a terminal +and an agent session. Remove it again and verify the app starts normally. + +Inspect app metadata with `flatpak info --user --show-metadata com.t3tools.t3code`. +It should contain the `com.t3tools.t3code.Tools` extension point with branch +`26.08` and `no-autodownload=true`. The app-only installation should contain +no Tools extension. Compare sandbox permissions with the parent revision; +this change does not alter `finish-args`. + +Remove the disposable installation and build directory when finished, and +unset `FLATPAK_USER_DIR`. Update the fixture's branch and SDK when changing the +app's Freedesktop runtime version. diff --git a/tests/check-launcher.sh b/tests/check-launcher.sh new file mode 100644 index 0000000..429e38a --- /dev/null +++ b/tests/check-launcher.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Check launcher defaults without starting Electron or modifying user data. +set -euo pipefail + +root=$(cd -- "$(dirname -- "$0")/.." && pwd) +test_root=$(mktemp -d) +trap 'rm -rf -- "$test_root"' EXIT + +export HOME="$test_root/home" +export XDG_CONFIG_HOME="$test_root/config" +export XDG_DATA_HOME="$test_root/data" +export XDG_CACHE_HOME="$test_root/cache" +export XDG_RUNTIME_DIR="$test_root/run" +export FLATPAK_ID=com.t3tools.t3code +export WAYLAND_DISPLAY="" +unset XDG_STATE_HOME HISTFILE CLAUDE_CONFIG_DIR + +zypak-wrapper() { + [[ "$1" == /app/bin/t3code/t3code ]] + [[ "$NPM_CONFIG_PREFIX" == "$XDG_DATA_HOME/npm-global" ]] + [[ "$NPM_CONFIG_CACHE" == "$XDG_CACHE_HOME/npm" ]] + [[ "$CLAUDE_CONFIG_DIR" == "$HOME/.claude" ]] + [[ "$XDG_STATE_HOME" == "$XDG_DATA_HOME/state" ]] + [[ "$HISTFILE" == "$XDG_DATA_HOME/state/bash/history" ]] + [[ "$PATH" == "$NPM_CONFIG_PREFIX/bin:/app/bin:"* ]] + [[ -d "$(dirname -- "$HISTFILE")" ]] + [[ -f "$XDG_CONFIG_HOME/git/config" ]] +} + +# First run creates persistent state locations; second run keeps user config. +source "$root/startt3code" +printf '%s\n' '# user configuration' > "$XDG_CONFIG_HOME/git/config" +source "$root/startt3code" +grep -Fxq '# user configuration' "$XDG_CONFIG_HOME/git/config" +printf '%s\n' 'Launcher defaults and existing Git config checks passed.' diff --git a/tests/check-tool-extension.sh b/tests/check-tool-extension.sh new file mode 100644 index 0000000..6674753 --- /dev/null +++ b/tests/check-tool-extension.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Run inside the installed app sandbox, with the fixture installed or absent. +set -euo pipefail + +case "${1:-}" in + installed|absent) export TEST_EXTENSION_STATE="$1" ;; + *) printf 'Usage: bash %s installed|absent\n' "$0" >&2; exit 2 ;; +esac + +# Capture the launcher's environment at the Electron handoff. A fresh child +# process checks the exported PATH seen by terminals and agent subprocesses. +zypak-wrapper() { + bash -euc ' + if [[ "$TEST_EXTENSION_STATE" == installed ]]; then + [[ "$(command -v t3-tool-test)" == /app/tools/Test/bin/t3-tool-test ]] + [[ "$PATH" == *:/app/bin:/app/tools/Test/bin:* ]] + t3-tool-test + else + if command -v t3-tool-test; then + echo "Unexpected t3-tool-test command without the fixture." >&2 + exit 1 + fi + [[ "$PATH" != *"/app/tools/"* ]] + fi + ' +} + +# The launcher accepts unset optional display/environment variables. +set +u +source /app/bin/startt3code +printf 'Tool extension check passed: %s\n' "$TEST_EXTENSION_STATE" diff --git a/tests/extension-fixture/com.t3tools.t3code.Tools.Test.yml b/tests/extension-fixture/com.t3tools.t3code.Tools.Test.yml new file mode 100644 index 0000000..14d486c --- /dev/null +++ b/tests/extension-fixture/com.t3tools.t3code.Tools.Test.yml @@ -0,0 +1,22 @@ +id: com.t3tools.t3code.Tools.Test +branch: '26.08' +runtime: com.t3tools.t3code +runtime-version: master +sdk: org.freedesktop.Sdk//26.08 +build-extension: true +separate-locales: false +appstream-compose: false +build-options: + prefix: /app/tools/Test +modules: + - name: t3-tool-test + buildsystem: simple + build-commands: + - install -d ${FLATPAK_DEST}/bin ${FLATPAK_DEST}/lib + - cc -shared -fPIC -o ${FLATPAK_DEST}/lib/libt3tooltest.so lib.c + - cc -o ${FLATPAK_DEST}/bin/t3-tool-test main.c -L${FLATPAK_DEST}/lib -lt3tooltest + sources: + - type: file + path: lib.c + - type: file + path: main.c diff --git a/tests/extension-fixture/lib.c b/tests/extension-fixture/lib.c new file mode 100644 index 0000000..a42b9b3 --- /dev/null +++ b/tests/extension-fixture/lib.c @@ -0,0 +1,4 @@ +int t3_tool_test(void) +{ + return 42; +} diff --git a/tests/extension-fixture/main.c b/tests/extension-fixture/main.c new file mode 100644 index 0000000..7de775a --- /dev/null +++ b/tests/extension-fixture/main.c @@ -0,0 +1,6 @@ +int t3_tool_test(void); + +int main(void) +{ + return t3_tool_test() == 42 ? 0 : 1; +}