A bilingual (English / 繁體中文) toolkit for composing a SmokePing Targets file from a shared curated catalogue plus your own patches.
- Web app — pick targets from the tree, edit any entry, export a committable
patch.yamlor share via URL. Runs entirely in the browser. - CLI (
smokeping-config) — a single static binary (Rust, published to crates.io) that renders the same patch against the bundled (or custom) base catalogue from the command line. No Node.js, no runtime deps. Scripts cleanly into Ansible, cron, GitHub Actions.
Both front-ends read and write the same YAML schema, so you can edit interactively and render reproducibly without the two going out of sync.
Before this refactor, every user would export a monolithic Targets file and their edits would drift from the shared curated list as soon as anyone added a new CDN, ISP, or probe. Now the curated list is the base, your edits live in a patch (YAML, git-committable), and render composes them on demand. When upstream evolves, your patch keeps applying — and diff-base tells you which of your paths drifted.
Pick targets at https://hydai.github.io/smokepingconfig/ (or your own deploy), edit as needed, then:
- Download — get the final monolithic
Targetsfile. - Export patch — save a
patch.yamlyou can commit to your own repo. - Import patch — paste or upload an existing patch; the modal previews drift before applying.
- Share — copy a URL that encodes your patch (
#s=...) for quick hand-off.
# install — pick one
cargo install smokeping-config
# or grab a prebuilt static binary from the GitHub Releases page:
# smokeping-config-linux-x86_64, -linux-aarch64,
# -macos-x86_64, -macos-aarch64, -windows-x86_64.exe
smokeping-config init # writes patch.yaml pinned to the bundled catalogue
# ... edit patch.yaml ...
smokeping-config render patch.yaml --out Targets
smokeping-config diff-base patch.yamlBase resolution cascade: --base <file> → --base-url <url> → bundled snapshot.
Drift modes: --on-drift=ignore | warn (default) | error.
Running smokeping-config --version prints both the CLI version and the bundled catalogue's {date, sha} stamp, so CI logs record exactly which base was rendered against.
config.txt curated catalogue, source of truth
│
▼ (prebuild)
packages/core/src/catalog.json stamped with version = { date, sha }
│
├─ @smokepingconf/core pure logic — parser, serializer, patch model,
│ path ↔ id helpers, URL hash v:1/v:2
│
├─ @smokepingconf/web SvelteKit app — tree UI, Import/Export Patch
│ modal, live preview, share URL
│
└─ smokeping-config clap CLI (Rust crate) — render / diff-base /
init, ships as a static single binary with
catalog.json embedded at compile time
web consumes @smokepingconf/core directly; smokeping-config embeds a build-time copy of core's catalog.json via build.rs. Both are swappable front-ends over the same patch schema and merge algorithm.
Two toolchains, split by package:
- Node 24 (see
.nvmrc) forpackages/core/(catalogue + TypeScript library) andpackages/web/(SvelteKit app). - Rust 1.85+ (2024 edition) for
packages/cli-rs/(thesmokeping-configbinary).
First-time setup:
npm install
npx playwright install chromium # one-time, for E2ECommon Node commands (run from the repo root — they orchestrate across workspaces):
npm run dev # SvelteKit dev server on :5173
npm run check # tsc for core + svelte-check for web
npm test # vitest in core and web
npm run build # regenerates catalog.json + builds web
npm run test:e2e # Playwright smoke flow (incl. Import/Export round-trip)Rust CLI (run inside packages/cli-rs/):
cargo build --release # produces target/release/smokeping-config
cargo test --release # assert_cmd-based integration tests
cargo fmt --check
cargo clippy -- -D warningsThe CLI's build.rs copies packages/core/src/catalog.json into the build directory and embeds it via include_str!, so rerun npm -w @smokepingconf/core run prebuild before cargo build --release whenever config.txt changes.
config.txt curated catalogue source
packages/core/
├── src/
│ ├── types.ts Catalog, Node, Probe, CatalogVersion
│ ├── parser.ts / serializer.ts SmokePing Targets ↔ Catalog
│ ├── probes.ts probe metadata + probesFileSnippet
│ ├── tree.ts findNode, freshTree, idToPath, pathToId
│ ├── url-state.ts TreeDiff + encodeTree/decodeTree (v:1)
│ ├── patch.ts Patch + encodePatch/applyPatch + YAML I/O
│ ├── catalog.json generated, version-stamped
│ └── index.ts barrel re-export
├── tests/ parser, serializer, patch, probes,
│ roundtrip, tree, catalog-version
└── scripts/parse-config.ts prebuild: config.txt → catalog.json
packages/web/
├── src/
│ ├── lib/store.ts Svelte writable + exportPatchYaml / previewPatchYaml
│ ├── lib/components/
│ │ ├── Actions.svelte Copy / Download / Share / Export / Import / Reset
│ │ ├── ImportPatchModal.svelte paste / upload + drift preview + apply
│ │ └── … TreeView, TreeNode, EditForm, Preview,
│ │ ProbesNotice, AddButton, LangToggle
│ ├── lib/i18n/ en.json, zh-TW.json, index.ts (loader)
│ └── routes/ +layout.svelte, +layout.ts (SSR off),
│ +page.svelte
├── tests/unit/ store, url-state
└── tests/e2e/flow.spec.ts Playwright
packages/cli-rs/
├── src/
│ ├── main.rs clap entry (init / render / diff-base)
│ ├── base_resolver.rs --base / --base-url / bundled cascade
│ ├── commands/ init.rs · render.rs · diff_base.rs
│ ├── diff.rs · patch.rs drift detection + YAML I/O
│ ├── serializer.rs · tree.rs Targets emission + path helpers
│ └── types.rs Catalog / Node / Probe mirroring core
├── tests/cli_integration.rs assert_cmd + tempfile
├── build.rs embeds catalog.json via include_str!
└── Cargo.toml release profile: strip, lto, opt-level=z
Push to main / master — GitHub Actions builds packages/web/ and deploys to Pages via actions/deploy-pages. The workflow sets BASE_PATH=/<repo-name> so SvelteKit emits hashed assets under the right sub-path.
See CONTRIBUTING.md for how to add curated targets, new probe kinds, defaults, or translations. TL;DR: edit config.txt and run npm run build to regenerate packages/core/src/catalog.json; rerun cargo build --release inside packages/cli-rs/ to re-embed it into the binary.
MIT © 2026 hydai