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
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

on:
pull_request:
workflow_call:

permissions:
contents: read
Expand Down Expand Up @@ -30,6 +31,8 @@ jobs:
run: cargo install taplo-cli --version 0.10.0 --locked
- name: Check TOML formatting and alphabetical key order
run: taplo fmt --check
- name: Validate Cargo manifest schema
run: taplo lint Cargo.toml

check:
name: Check, Clippy, tests, and binary (${{ matrix.os }})
Expand Down Expand Up @@ -62,8 +65,19 @@ jobs:
run: cargo build --locked --release
- name: Smoke test
run: ./target/release/filetrail --help
- name: Verify crates.io package without publishing
run: cargo publish --dry-run --locked --registry crates-io
- name: Package standalone binary
shell: bash
run: |
version=$(cargo metadata --locked --no-deps --format-version 1 | python3 -c 'import json, sys; print(json.load(sys.stdin)["packages"][0]["version"])')
target=$(rustc -vV | sed -n 's/^host: //p')
package="filetrail-v${version}-${target}"
mkdir -p "target/dist/$package" target/artifacts
cp target/release/filetrail LICENSE README.md README_zh.md "target/dist/$package/"
tar -czf "target/artifacts/$package.tar.gz" -C target/dist "$package"
- uses: actions/upload-artifact@v4
with:
name: filetrail-${{ runner.os }}-${{ runner.arch }}
path: target/release/filetrail
path: target/artifacts/*.tar.gz
if-no-files-found: error
101 changes: 101 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Publish

on:
push:
tags: ['v*']

permissions:
contents: read

concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
ci:
name: Release checks
uses: ./.github/workflows/ci.yml

publish:
name: Publish to crates.io
needs: ci
runs-on: ubuntu-latest
environment: crates-io
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install pinned toolchain
run: rustup show
- name: Check tag matches Cargo.toml version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import tomllib

with open("Cargo.toml", "rb") as manifest:
version = tomllib.load(manifest)["package"]["version"]
expected = f"v{version}"
actual = os.environ["RELEASE_TAG"]
if actual != expected:
raise SystemExit(f"Tag {actual!r} does not match Cargo.toml version: expected {expected!r}")
print(f"Publishing filetrail {version} from {actual}")
PY
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo '::error::Set CARGO_REGISTRY_TOKEN in the crates-io GitHub environment.'
exit 1
fi
cargo publish --locked --registry crates-io

release:
name: Prepare draft GitHub Release
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: filetrail-*
merge-multiple: true
path: artifacts
- name: Generate and verify SHA-256 checksums
working-directory: artifacts
shell: bash
run: |
shopt -s nullglob
linux_archives=(filetrail-*-unknown-linux-gnu.tar.gz)
macos_archives=(filetrail-*-apple-darwin.tar.gz)
if (( ${#linux_archives[@]} == 0 || ${#macos_archives[@]} == 0 )); then
echo '::error::Both Linux and macOS release archives are required.'
exit 1
fi
sha256sum -- filetrail-*.tar.gz > SHA256SUMS
sha256sum --check SHA256SUMS
- name: Create or update draft release
working-directory: artifacts
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
run: |
if gh release view "$RELEASE_TAG" --json isDraft --jq '.isDraft' > "$RUNNER_TEMP/filetrail-release-draft"; then
if [ "$(cat "$RUNNER_TEMP/filetrail-release-draft")" != true ]; then
echo '::error::The release is already published; refusing to change it.'
exit 1
fi
gh release upload "$RELEASE_TAG" filetrail-*.tar.gz SHA256SUMS --clobber
else
gh release create "$RELEASE_TAG" filetrail-*.tar.gz SHA256SUMS \
--draft --verify-tag --title "FileTrail $RELEASE_TAG" --generate-notes
fi
20 changes: 20 additions & 0 deletions .schemas/cargo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$comment": "Taplo 0.10.0 does not load external references hidden inside anyOf. Validate Cargo lints explicitly so its schema is loaded before validating the complete manifest. Both validations use the official SchemaStore schemas.",
"allOf": [
{
"properties": {
"lints": {
"properties": {
"cargo": {
"$ref": "https://www.schemastore.org/cargo-lints-cargo.json"
}
}
}
}
},
{
"$ref": "https://www.schemastore.org/cargo.json"
}
]
}
6 changes: 6 additions & 0 deletions .taplo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ include = ["**/*.toml"]
column_width = 100
reorder_inline_tables = true
reorder_keys = true

[[rule]]
include = ["Cargo.toml"]

[rule.schema]
path = ".schemas/cargo.json"
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ one executable for macOS and Linux. Read README.md before changing its behavior.
`cargo test --locked --all-targets`, and `cargo test --locked --doc`.
- Completion integration tests require Bash, Zsh, and Fish on PATH.
- Run `taplo fmt` and `taplo fmt --check` with taplo-cli 0.10.0.
- Run `taplo lint Cargo.toml` to validate the manifest schema. `.taplo.toml` selects
`.schemas/cargo.json`, which works around Taplo's loading of external references
inside `anyOf` while preserving the complete official Cargo schema validation.
- Every Rust import must be its own `use` statement. Do not use grouped braces.
rustfmt's `imports_granularity = "Item"` enforces this on the pinned nightly.
- Keep TOML keys alphabetically sorted, particularly `[package]` and dependency
Expand All @@ -24,6 +27,39 @@ one executable for macOS and Linux. Read README.md before changing its behavior.
- Keep OS service installation out of automated tests. Test rendered definitions.
- Do not introduce a dependency on an external `git` executable for core commands.

## Publishing to crates.io

- PR CI runs `cargo publish --dry-run --locked --registry crates-io` on macOS and
Linux without credentials. The final `aborting upload due to dry run` warning is
Cargo's expected confirmation that nothing was uploaded.
- `.github/workflows/publish.yml` runs on pushed `v*` tags and reuses the complete
CI workflow. Only after all checks pass does it require an exact
`v<package.version>` match (for example, `v0.1.0`), then publish to crates.io.
- After crates.io publishing succeeds, the workflow creates a draft GitHub
Release with generated notes. It attaches the tested macOS and Linux binaries
as `filetrail-v<version>-<Rust target>.tar.gz` archives, including both READMEs
and the license, plus a `SHA256SUMS` file. Archives preserve executable permissions.
Publish the GitHub Release manually after reviewing it. A rerun can replace
assets on an existing draft but refuses to modify a published release. If this
job fails after crates.io publishing, rerun only failed jobs to avoid publishing
the same crate version again.
- In GitHub repository Settings → Environments, create `crates-io`, allow release
tags matching `v*`, and add an environment secret named `CARGO_REGISTRY_TOKEN`.
Its value must be a crates.io API token, not a GitHub personal access token.
No extra GitHub credentials are needed. Only the draft Release job grants the
built-in `GITHUB_TOKEN` `contents: write`; other jobs use `contents: read`.
- The crates.io account must have a verified email and permission to publish
`filetrail`. The token needs permission to publish new crates for the first
release, and publish new versions for later releases. Scope it to `filetrail`
where supported and set an expiration date. Create tokens at
<https://crates.io/settings/tokens>.
- Update `Cargo.toml` and the root package version in `Cargo.lock` together,
commit the release changes, then push the matching tag. For version `0.1.0`:
`git tag v0.1.0` followed by `git push origin v0.1.0`.
A published version cannot be overwritten; each new release needs a new version.
- For local verification of uncommitted changes only, add `--allow-dirty` to the
dry-run command. CI and actual publishing deliberately require a clean checkout.

## Architecture and invariants

Core modules: `config.rs` manages mappings, `state.rs` persists sync state,
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[package]
description = "Watch files, sync them into a Git repository, and commit on your terms."
edition = "2024"
license = "MIT"
name = "filetrail"
repository = "https://github.com/RinChanNOWWW/FileTrail"
version = "0.1.0"

[dependencies]
Expand All @@ -22,6 +24,10 @@ tempfile = "3.20"
toml = "0.9"
walkdir = "2.5"

[lints.cargo]
# Cargo explicitly inserts readme into the packaged manifest on the pinned nightly.
manual_readme = "allow"

[profile.release]
codegen-units = 1
lto = "thin"
Expand Down
Loading