Add a web extension and debian package compilation (#5)
All checks were successful
Check go code / build-and-release (push) Successful in 1m9s
All checks were successful
Check go code / build-and-release (push) Successful in 1m9s
Reviewed-on: #5 Co-authored-by: Julien Fastré <julien.fastre@champs-libres.coop> Co-committed-by: Julien Fastré <julien.fastre@champs-libres.coop>
This commit was merged in pull request #5.
This commit is contained in:
@@ -24,3 +24,9 @@ newlines:
|
|||||||
beforeChangelogVersion: 1
|
beforeChangelogVersion: 1
|
||||||
endOfVersion: 1
|
endOfVersion: 1
|
||||||
envPrefix: CHANGIE_
|
envPrefix: CHANGIE_
|
||||||
|
|
||||||
|
# update the version in the cargo file, on release
|
||||||
|
replacements:
|
||||||
|
- path: Cargo.toml
|
||||||
|
find: '^version = ".*"'
|
||||||
|
replace: 'version = "{{.VersionNoPrefix}}"'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Release binary for cl-cli
|
name: Release binary and debian package for cl-cli
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -6,28 +6,79 @@ on:
|
|||||||
- v**
|
- v**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
webext-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: node:24
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: https://github.com/actions/checkout@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd web-extension/cl && npm install
|
||||||
|
- name: Build and sign web extension
|
||||||
|
env:
|
||||||
|
AMO_API_KEY: ${{ secrets.AMO_API_KEY }}
|
||||||
|
AMO_API_SECRET: ${{ secrets.AMO_API_SECRET }}
|
||||||
|
run: cd web-extension/cl && npm run sign
|
||||||
|
- name: Upload web extension artifact
|
||||||
|
uses: https://github.com/actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: webext-xpi
|
||||||
|
path: web-extension/cl/web-ext-artifacts/*.xpi
|
||||||
|
retention-days: 7
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
build-and-release:
|
build-and-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: [webext-build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: https://github.com/actions/checkout@v4
|
uses: https://github.com/actions/checkout@v4
|
||||||
- name: Install rust toolchain
|
- name: Install rust toolchain
|
||||||
uses: https://github.com/dtolnay/rust-toolchain@stable
|
uses: https://github.com/dtolnay/rust-toolchain@stable
|
||||||
- name: Build binaries
|
- name: Install cargo-deb
|
||||||
run: cargo build --release
|
run: cargo install --quiet cargo-deb
|
||||||
- name: Read release content
|
- name: Build Debian package
|
||||||
uses: https://github.com/jaywcjlove/github-action-read-file@main
|
run: cargo deb --quiet
|
||||||
id: read_release
|
- name: Upload Debian package to Gitea registry
|
||||||
|
env:
|
||||||
|
GITEA_OWNER: ${{ vars.PUBLISH_USERNAME }}
|
||||||
|
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||||
|
GITEA_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||||
|
DEB_DISTRIBUTIONS: noble,plucky,questing
|
||||||
|
DEB_COMPONENT: main
|
||||||
|
DEB_ARCH: amd64
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
DEB_FILE=$(ls target/debian/*.deb | head -n1)
|
||||||
|
if [ -z "${DEB_FILE}" ]; then
|
||||||
|
echo "No .deb file found in target/debian" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# comma-separated in DEB_DISTRIBUTIONS, e.g. "noble,plucky,questing"
|
||||||
|
IFS=',' read -r -a DISTROS <<< "$DEB_DISTRIBUTIONS"
|
||||||
|
for DISTRO in "${DISTROS[@]}"; do
|
||||||
|
echo "Uploading ${DEB_FILE} to ${GITEA_SERVER_URL} for owner ${GITEA_OWNER}"
|
||||||
|
curl -sSf -X PUT \
|
||||||
|
--user "${GITEA_OWNER}:${GITEA_TOKEN}" \
|
||||||
|
--upload-file "${DEB_FILE}" \
|
||||||
|
"${GITEA_SERVER_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DISTRO}/main/upload"
|
||||||
|
done
|
||||||
|
- name: Download web extension artifact
|
||||||
|
uses: https://github.com/actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: .changes/${{ github.ref_name }}.md
|
name: webext-xpi
|
||||||
|
path: web-extension/cl/web-ext-artifacts
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: https://gitea.com/akkuman/gitea-release-action@v1
|
uses: https://gitea.com/akkuman/gitea-release-action@v1
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||||
with:
|
with:
|
||||||
files: |-
|
files: |-
|
||||||
target/release/cl-cli
|
|
||||||
config.toml.dist
|
config.toml.dist
|
||||||
|
web-extension/cl/web-ext-artifacts/*.xpi
|
||||||
md5sum: true
|
md5sum: true
|
||||||
sha256sum: true
|
sha256sum: true
|
||||||
body_path: .changes/${{ github.ref_name }}.md
|
body: Test release
|
||||||
|
prerelease: true
|
||||||
|
#body_path: .changes/${{ github.ref_name }}.md
|
||||||
|
|||||||
130
.junie/guidelines.md
Normal file
130
.junie/guidelines.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# cl-cli — Project-specific development guidelines
|
||||||
|
|
||||||
|
This document captures project-specific knowledge to help advanced contributors work efficiently on this repository. It focuses on practical, non-obvious details (build, configuration, testing, and development tips) specific to this codebase.
|
||||||
|
|
||||||
|
## 1. Build and configuration
|
||||||
|
|
||||||
|
- Rust workspace/binaries
|
||||||
|
- Single crate with two binaries defined in `Cargo.toml`:
|
||||||
|
- `cl-cli` → `src/main.rs`: the CLI entrypoint.
|
||||||
|
- `webext` → `src/webext.rs`: a Native Messaging host used by the browser extension.
|
||||||
|
- Library module tree under `src/lib.rs` exposes common code to both binaries.
|
||||||
|
- Toolchain: stable Rust; CI uses dtolnay/rust-toolchain@stable.
|
||||||
|
- Dependencies and async runtime
|
||||||
|
- Async via `tokio` (features: `rt`, `rt-multi-thread`, `macros`). Both binaries use `#[tokio::main]`.
|
||||||
|
- HTTP via `reqwest`; serialization via `serde`/`serde_json`; configuration via `toml`.
|
||||||
|
- Build
|
||||||
|
- Standard build works without extra flags:
|
||||||
|
- `cargo build` (debug) or `cargo build --release` (optimized).
|
||||||
|
- Individual binaries:
|
||||||
|
- `cargo build --bin cl-cli`
|
||||||
|
- `cargo build --bin webext`
|
||||||
|
- The web extension host (`webext`) is a regular Rust binary; it is not bundled by NPM tooling.
|
||||||
|
- Configuration file
|
||||||
|
- Expected location by default: `$HOME/.config/cl-cli/config.toml`.
|
||||||
|
- The CLI allows overriding the path via `--config <PATH>`; the webext host always reads the default path.
|
||||||
|
- Template: `config.toml.dist`. Minimal fields:
|
||||||
|
- `[[gitlab]] { token, domain }`
|
||||||
|
- `[[gitea]] { token, domain }`
|
||||||
|
- `[openproject] { token, base_url }`
|
||||||
|
- Example of initial setup (copy the dist and edit):
|
||||||
|
```bash
|
||||||
|
mkdir -p "$HOME/.config/cl-cli"
|
||||||
|
cp config.toml.dist "$HOME/.config/cl-cli/config.toml"
|
||||||
|
$EDITOR "$HOME/.config/cl-cli/config.toml"
|
||||||
|
```
|
||||||
|
- Error surface: parsing and IO errors are surfaced as `GeneralError`; the CLI will print and exit non‑zero, the `webext` host will return an error payload.
|
||||||
|
- Local OpenProject for development
|
||||||
|
- `docker-compose.yaml` provides a local OpenProject (13.x) at `http://localhost:8080` with persisted volumes under `.docker-data/`.
|
||||||
|
|
||||||
|
## 2. Testing
|
||||||
|
|
||||||
|
- Test types present
|
||||||
|
- Unit tests live alongside code under `#[cfg(test)]` modules (e.g., `src/gitea/issue.rs`, `src/webext.rs`, `src/planning/utils.rs`).
|
||||||
|
- Integration tests can be placed under `tests/`. The crate exposes internal modules via `src/lib.rs` for integration testing.
|
||||||
|
- Running tests
|
||||||
|
- `cargo test` runs unit and integration tests. CI also runs `cargo test` on every push/PR.
|
||||||
|
- Tests are pure and do not require network or external services; avoid adding network‑bound tests.
|
||||||
|
- Adding new tests
|
||||||
|
- Prefer unit tests near the logic being tested. For integration coverage across modules, place files under `tests/` and use public items from `cl_cli`.
|
||||||
|
- Keep tests deterministic: no real HTTP calls; if needed, mock boundaries at the client layer.
|
||||||
|
- Example integration test (verified locally during preparation)
|
||||||
|
- Minimal example using a pure helper. Create `tests/example_gitea_url.rs`:
|
||||||
|
```rust
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_gitea_issue_html_url_to_api_url() {
|
||||||
|
// Pure transformation; no network
|
||||||
|
let input = Url::parse("https://gitea.champs-libres.be/champs-libres/test/issues/1").unwrap();
|
||||||
|
let out = cl_cli::gitea::issue::issue_html_url_to_api(&input).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
out.as_str(),
|
||||||
|
"https://gitea.champs-libres.be/api/v1/repos/champs-libres/test/issues/1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Run it with `cargo test`. Remove the file if it was only created as a demo.
|
||||||
|
|
||||||
|
## 3. CLI usage tips (project‑specific)
|
||||||
|
|
||||||
|
- `cl-cli planning i2work <gitlab_issue_url> <openproject_project_identifier>` creates an OpenProject work package from a GitLab issue.
|
||||||
|
- The OpenProject project identifier is the short slug in URLs, e.g. `chill` in `.../projects/chill/...`.
|
||||||
|
- The command uses tokens from the configuration file to authenticate to GitLab and OpenProject.
|
||||||
|
- Example:
|
||||||
|
```bash
|
||||||
|
cl-cli planning i2work \
|
||||||
|
https://gitlab.com/Chill-Projet/chill-bundles/-/issues/240 \
|
||||||
|
chill
|
||||||
|
```
|
||||||
|
- `cl-cli test` invokes a debug routine (see `src/debug.rs`) primarily for local diagnostics.
|
||||||
|
|
||||||
|
## 4. Web extension native host (`webext`)
|
||||||
|
|
||||||
|
- Purpose: acts as a Native Messaging host for a browser extension under `web-extension/`.
|
||||||
|
- Protocol: communicates via stdin/stdout using 4‑byte length‑prefixed JSON messages.
|
||||||
|
- Input `serde` enum is tagged with `type`; currently supports `{"type":"Issue2Work", ...}`.
|
||||||
|
- Output is tagged with `result` (`Ok` or `Error`) and contains `data` when successful.
|
||||||
|
- Business flow: the host reads config from `$HOME/.config/cl-cli/config.toml`, translates the input into `Issue2Work` CLI arguments, delegates to `planning::issue2work::handle_issue2work`, and returns the created work package URL.
|
||||||
|
- Testing the transport: see unit tests in `src/webext.rs` that exercise message framing and deserialization.
|
||||||
|
|
||||||
|
## 5. Code style and architectural notes
|
||||||
|
|
||||||
|
- Module layout
|
||||||
|
- Public library facade (`src/lib.rs`) re‑exports internal modules for reuse by both binaries and tests.
|
||||||
|
- Planning features under `src/planning/` with a trait (`Issue2WorkActionTrait`) and concrete actions per platform.
|
||||||
|
- Git providers (`src/gitlab`, `src/gitea`) and OpenProject client live in dedicated modules.
|
||||||
|
- Error handling
|
||||||
|
- Unified lightweight `GeneralError` encapsulates user‑facing messages; conversions from `reqwest::Error` and header errors are provided.
|
||||||
|
- Data parsing
|
||||||
|
- Input/Output types rely on `serde` with explicit tagging for clarity and forward compatibility.
|
||||||
|
- Concurrency
|
||||||
|
- All async entrypoints are `#[tokio::main]`; downstream async functions return `impl Future` to stay generic and testable.
|
||||||
|
- External calls
|
||||||
|
- Wrap external service interactions behind client modules; this eases mocking in tests and keeps pure helpers (like URL mappers) easily testable.
|
||||||
|
|
||||||
|
## 6. Continuous Integration
|
||||||
|
|
||||||
|
- `.gitea/workflows/release/check.yaml` performs:
|
||||||
|
- Checkout → install stable toolchain → `cargo build` → `cargo test`.
|
||||||
|
- Release job (see `.gitea/workflows/release/build-and-release.yaml`) builds release artifacts for distribution.
|
||||||
|
|
||||||
|
## 7. Troubleshooting / gotchas (project‑specific)
|
||||||
|
|
||||||
|
- Config path
|
||||||
|
- The CLI accepts `--config`, but the webext host reads the default path only. Ensure `$HOME/.config/cl-cli/config.toml` exists when using the browser integration.
|
||||||
|
- Tokens scope
|
||||||
|
- Ensure personal access tokens for GitLab and OpenProject have sufficient scopes for reading issues and creating work packages respectively.
|
||||||
|
- Locale/URLs
|
||||||
|
- `Issue2Work` requires the OpenProject project identifier (slug), not its display name.
|
||||||
|
- HTTP environment
|
||||||
|
- `reqwest` honors proxy vars (`HTTP_PROXY`, `HTTPS_PROXY`) if present; be aware for corporate environments.
|
||||||
|
|
||||||
|
## 8. Commands quick reference
|
||||||
|
|
||||||
|
- Build: `cargo build --release`
|
||||||
|
- Run CLI: `cargo run --bin cl-cli -- <args>`
|
||||||
|
- Run webext host directly for dev: `cargo run --bin webext`
|
||||||
|
- Tests: `cargo test`
|
||||||
|
- Linting: `cargo clippy` (not enforced in CI but recommended)
|
||||||
|
|
||||||
286
Cargo.lock
generated
286
Cargo.lock
generated
@@ -111,7 +111,7 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -169,12 +169,12 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cl-cli"
|
name = "cl-cli"
|
||||||
version = "0.1.0"
|
version = "0.5.0-alpha15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"gitlab",
|
"gitlab",
|
||||||
@@ -182,6 +182,7 @@ dependencies = [
|
|||||||
"open",
|
"open",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"simple-home-dir",
|
"simple-home-dir",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
@@ -244,16 +245,6 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -369,22 +360,6 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "errno"
|
|
||||||
version = "0.3.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastrand"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -397,21 +372,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -705,22 +665,6 @@ dependencies = [
|
|||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-tls"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-util",
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
@@ -740,11 +684,9 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"system-configuration",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-registry",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -972,12 +914,6 @@ version = "0.2.177"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -1028,23 +964,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1086,50 +1005,6 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.74"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.108",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.110"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1154,12 +1029,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@@ -1304,12 +1173,10 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
"hyper-tls",
|
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"native-tls",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
@@ -1320,7 +1187,6 @@ dependencies = [
|
|||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -1358,19 +1224,6 @@ version = "2.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.34"
|
version = "0.23.34"
|
||||||
@@ -1418,38 +1271,6 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "2.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"core-foundation",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -1608,40 +1429,6 @@ dependencies = [
|
|||||||
"syn 2.0.108",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"core-foundation",
|
|
||||||
"system-configuration-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration-sys"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.23.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
|
||||||
dependencies = [
|
|
||||||
"fastrand",
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -1733,16 +1520,6 @@ dependencies = [
|
|||||||
"syn 2.0.108",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-native-tls"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
|
||||||
dependencies = [
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
@@ -1911,12 +1688,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -2050,9 +1821,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
"windows-result 0.4.1",
|
"windows-result",
|
||||||
"windows-strings 0.5.1",
|
"windows-strings",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2077,54 +1848,19 @@ dependencies = [
|
|||||||
"syn 2.0.108",
|
"syn 2.0.108",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-registry"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
"windows-result 0.3.4",
|
|
||||||
"windows-strings 0.4.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-result"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-strings"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.1.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2133,7 +1869,7 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2160,7 +1896,7 @@ version = "0.61.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2185,7 +1921,7 @@ version = "0.53.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link",
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
"windows_aarch64_msvc 0.53.1",
|
"windows_aarch64_msvc 0.53.1",
|
||||||
"windows_i686_gnu 0.53.1",
|
"windows_i686_gnu 0.53.1",
|
||||||
|
|||||||
27
Cargo.toml
27
Cargo.toml
@@ -1,14 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cl-cli"
|
name = "cl-cli"
|
||||||
version = "0.1.0"
|
version = "0.5.0-alpha15"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "GPLv3"
|
||||||
|
description = "Some helpers scripts for Champs-Libres"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name="cl-cli"
|
||||||
|
path="src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name="webext"
|
||||||
|
path="src/webext.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1.13", features = ["derive"] }
|
clap = { version = "4.1.13", features = ["derive"] }
|
||||||
gitlab = "0.1804.0"
|
gitlab = "0.1804.0"
|
||||||
reqwest = "0.12.24"
|
reqwest = { version = "0.12.24", default-features = false, features = ["rustls-tls", "http2", "charset"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
url = "2.5.7"
|
url = "2.5.7"
|
||||||
@@ -16,3 +26,16 @@ tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] }
|
|||||||
simple-home-dir = "0.5.2"
|
simple-home-dir = "0.5.2"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
|
||||||
|
[package.metadata.deb]
|
||||||
|
maintainer = "Julien Fastré <julien.fastre@champs-libres.coop>"
|
||||||
|
copyright = "AGPLv3"
|
||||||
|
extended-description = """Helpers for Champs-Libres"""
|
||||||
|
depends = "$auto"
|
||||||
|
section = "utility"
|
||||||
|
priority = "optional"
|
||||||
|
assets = [
|
||||||
|
["target/release/cl-cli", "usr/bin/cl", "755"],
|
||||||
|
["target/release/webext", "usr/bin/", "755"],
|
||||||
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||||||
use clap::{Args, Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub(crate) struct Cli {
|
pub struct Cli {
|
||||||
#[arg(short, long, value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
@@ -12,19 +12,19 @@ pub(crate) struct Cli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum Commands {
|
pub enum Commands {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Planning(Planning),
|
Planning(Planning),
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(crate) enum Planning {
|
pub enum Planning {
|
||||||
I2work(Issue2Work),
|
I2work(Issue2Work),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub(crate) struct Issue2Work {
|
pub struct Issue2Work {
|
||||||
pub issue_url: String,
|
pub issue_url: String,
|
||||||
pub project_id: String,
|
pub project_id: String,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
|||||||
@@ -1,26 +1,59 @@
|
|||||||
|
use crate::error::GeneralError;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct Config {
|
pub struct Config {
|
||||||
pub gitlab: Vec<GitlabConfig>,
|
pub gitlab: Vec<GitlabConfig>,
|
||||||
pub gitea: Vec<GiteaConfig>,
|
pub gitea: Vec<GiteaConfig>,
|
||||||
pub openproject: OpenProjectConfig,
|
pub openproject: OpenProjectConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct GitlabConfig {
|
pub struct GitlabConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct GiteaConfig {
|
pub struct GiteaConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub(crate) struct OpenProjectConfig {
|
pub struct OpenProjectConfig {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BuildConfigError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<GeneralError> for BuildConfigError {
|
||||||
|
fn into(self) -> GeneralError {
|
||||||
|
GeneralError {
|
||||||
|
description: format!("Could not build config: {}", self.msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_config(config_path: &Path) -> Result<Config, BuildConfigError> {
|
||||||
|
let config_path_content = match fs::read_to_string(config_path) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(BuildConfigError {
|
||||||
|
msg: format!(
|
||||||
|
"Could not read a config file at {:?}, error: {}",
|
||||||
|
config_path, e
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config: Config = toml::from_str(&*config_path_content).expect("Could not parse config");
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::error::GeneralError;
|
|||||||
use crate::gitea::issue::Issue;
|
use crate::gitea::issue::Issue;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) async fn debug(config: Config) -> Result<(), GeneralError> {
|
pub async fn debug(config: Config) -> Result<(), GeneralError> {
|
||||||
println!("test");
|
println!("test");
|
||||||
|
|
||||||
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use reqwest::Error;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GeneralError {
|
pub struct GeneralError {
|
||||||
pub(crate) description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<InvalidHeaderValue> for GeneralError {
|
impl From<InvalidHeaderValue> for GeneralError {
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ use crate::openproject::user::{GetMe, User};
|
|||||||
use crate::openproject::work::WorkPackageWriter;
|
use crate::openproject::work::WorkPackageWriter;
|
||||||
use crate::planning::utils::{append_related_issues, IssueRelated};
|
use crate::planning::utils::{append_related_issues, IssueRelated};
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
|
use crate::planning::Issue2WorkResult;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) struct GiteaAction {}
|
pub(crate) struct GiteaAction {}
|
||||||
|
|
||||||
impl Issue2WorkActionTrait for GiteaAction {
|
impl Issue2WorkActionTrait for GiteaAction {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
async fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
let gitea_client = crate::gitea::client::Client::from_config(config.gitea.first().unwrap());
|
||||||
let issue: Issue = gitea_client.get(issue_html_url_to_api(url)?).await?;
|
let issue: Issue = gitea_client.get(issue_html_url_to_api(url)?).await?;
|
||||||
let open_project_client =
|
let open_project_client =
|
||||||
@@ -49,16 +55,10 @@ impl Issue2WorkActionTrait for GiteaAction {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!(
|
Ok(Issue2WorkResult {
|
||||||
"new work package created: {:?}, edit at {}",
|
work_package_url: url_wp,
|
||||||
work_package.subject, url_wp
|
subject: work_package.subject,
|
||||||
);
|
})
|
||||||
|
|
||||||
if let Err(e) = open::that(url_wp) {
|
|
||||||
println!("failed to open work package in browser: {}", e);
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::openproject::client::Client;
|
|||||||
use crate::openproject::user::{GetMe, User};
|
use crate::openproject::user::{GetMe, User};
|
||||||
use crate::openproject::work::WorkPackageWriterAssignee;
|
use crate::openproject::work::WorkPackageWriterAssignee;
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::Issue2WorkActionTrait;
|
||||||
|
use crate::planning::Issue2WorkResult;
|
||||||
use gitlab::api::{issues, projects, AsyncQuery};
|
use gitlab::api::{issues, projects, AsyncQuery};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -26,7 +27,12 @@ struct Issue2WorkPackageDTO {
|
|||||||
pub(crate) struct GitlabAction {}
|
pub(crate) struct GitlabAction {}
|
||||||
|
|
||||||
impl Issue2WorkActionTrait for GitlabAction {
|
impl Issue2WorkActionTrait for GitlabAction {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
async fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let client = client_for_url(&url, &config).await?;
|
let client = client_for_url(&url, &config).await?;
|
||||||
let data = extract_issue_info(&url).unwrap();
|
let data = extract_issue_info(&url).unwrap();
|
||||||
|
|
||||||
@@ -65,12 +71,15 @@ impl Issue2WorkActionTrait for GitlabAction {
|
|||||||
.create_work_package(&(&dto).into(), &args.project_id)
|
.create_work_package(&(&dto).into(), &args.project_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
println!(
|
let result = Issue2WorkResult {
|
||||||
"new work package created: {:?}, edit at {}/projects/{}/work_packages/{}",
|
work_package_url: format!(
|
||||||
work_package.subject, config.openproject.base_url, args.project_id, work_package.id
|
"{}/projects/{}/work_packages/{}",
|
||||||
);
|
config.openproject.base_url, args.project_id, work_package.id
|
||||||
|
),
|
||||||
|
subject: work_package.subject,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
fn supports(&self, url: &Url, config: &Config, _args: &Issue2Work) -> bool {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub struct Issue {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub web_url: String,
|
pub web_url: String,
|
||||||
pub project_id: u64,
|
pub project_id: u64,
|
||||||
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
|||||||
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Regroupe la logique commune accessible par les deux binaires
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
|
pub mod debug;
|
||||||
|
pub mod error;
|
||||||
|
pub mod gitea;
|
||||||
|
pub mod gitlab;
|
||||||
|
pub mod openproject;
|
||||||
|
pub mod planning;
|
||||||
37
src/main.rs
37
src/main.rs
@@ -3,22 +3,11 @@ extern crate reqwest;
|
|||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate simple_home_dir;
|
extern crate simple_home_dir;
|
||||||
|
|
||||||
mod cli;
|
use cl_cli::cli::Cli;
|
||||||
mod config;
|
use cl_cli::cli::Commands::{Planning, Test};
|
||||||
mod debug;
|
use cl_cli::cli::Planning::I2work;
|
||||||
mod error;
|
use cl_cli::error::GeneralError;
|
||||||
mod gitea;
|
|
||||||
mod gitlab;
|
|
||||||
mod openproject;
|
|
||||||
mod planning;
|
|
||||||
|
|
||||||
use crate::cli::Commands::{Planning, Test};
|
|
||||||
use crate::cli::Planning::I2work;
|
|
||||||
use crate::error::GeneralError;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::Cli;
|
|
||||||
use config::Config;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
@@ -34,22 +23,20 @@ async fn main() {
|
|||||||
None => &default_config_path,
|
None => &default_config_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_path_content = match fs::read_to_string(config_path) {
|
let config = match cl_cli::config::build_config(config_path) {
|
||||||
Ok(content) => content,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
let general_error: GeneralError = e.into();
|
||||||
"Could not read config file at {:?}, error: {}",
|
println!("{}", general_error.description);
|
||||||
config_path, e
|
|
||||||
);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let config: Config = toml::from_str(&*config_path_content).expect("Could not parse config");
|
|
||||||
|
|
||||||
let result = match cli.command {
|
let result = match cli.command {
|
||||||
Some(Planning(I2work(args))) => planning::issue2work::issue2work(config, &args).await,
|
Some(Planning(I2work(args))) => {
|
||||||
Some(Test) => debug::debug(config).await,
|
cl_cli::planning::issue2work::issue2work_cli(config, &args).await
|
||||||
|
}
|
||||||
|
Some(Test) => cl_cli::debug::debug(config).await,
|
||||||
None => Err(GeneralError {
|
None => Err(GeneralError {
|
||||||
description: "No command launched".to_string(),
|
description: "No command launched".to_string(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::config::Config;
|
|||||||
use crate::error::GeneralError;
|
use crate::error::GeneralError;
|
||||||
use crate::gitea::action::GiteaAction;
|
use crate::gitea::action::GiteaAction;
|
||||||
use crate::gitlab::action::GitlabAction;
|
use crate::gitlab::action::GitlabAction;
|
||||||
use crate::planning::Issue2WorkActionTrait;
|
use crate::planning::{Issue2WorkActionTrait, Issue2WorkResult};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
@@ -11,20 +11,44 @@ struct App {
|
|||||||
gitea_issue2work_action: GiteaAction,
|
gitea_issue2work_action: GiteaAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn issue2work(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
pub async fn handle_issue2work(
|
||||||
|
config: Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> Result<Issue2WorkResult, GeneralError> {
|
||||||
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
let url = Url::parse(&*args.issue_url).expect("issue_url is not valid");
|
||||||
let app = App {
|
let app = App {
|
||||||
gitlab_issue2work_action: GitlabAction {},
|
gitlab_issue2work_action: GitlabAction {},
|
||||||
gitea_issue2work_action: GiteaAction {},
|
gitea_issue2work_action: GiteaAction {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let result: Issue2WorkResult;
|
||||||
if app.gitlab_issue2work_action.supports(&url, &config, args) {
|
if app.gitlab_issue2work_action.supports(&url, &config, args) {
|
||||||
app.gitlab_issue2work_action.run(&url, &config, args).await
|
result = app
|
||||||
|
.gitlab_issue2work_action
|
||||||
|
.run(&url, &config, args)
|
||||||
|
.await?;
|
||||||
} else if app.gitea_issue2work_action.supports(&url, &config, args) {
|
} else if app.gitea_issue2work_action.supports(&url, &config, args) {
|
||||||
app.gitea_issue2work_action.run(&url, &config, args).await
|
result = app.gitea_issue2work_action.run(&url, &config, args).await?
|
||||||
} else {
|
} else {
|
||||||
Err(GeneralError {
|
return Err(GeneralError {
|
||||||
description: format!("This action is not supported for this url: {}", url),
|
description: format!("This action is not supported for this url: {}", url),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn issue2work_cli(config: Config, args: &Issue2Work) -> Result<(), GeneralError> {
|
||||||
|
let result = handle_issue2work(config, args).await?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"new work package created: {:?}, edit at {}",
|
||||||
|
result.subject, result.work_package_url
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = open::that(result.work_package_url.as_str()) {
|
||||||
|
println!("failed to open work package in browser: {}", e);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,21 @@ use crate::config::Config;
|
|||||||
use crate::error::GeneralError;
|
use crate::error::GeneralError;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) mod issue2work;
|
pub mod issue2work;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub trait Issue2WorkActionTrait {
|
pub trait Issue2WorkActionTrait {
|
||||||
async fn run(&self, url: &Url, config: &Config, args: &Issue2Work) -> Result<(), GeneralError>;
|
fn run(
|
||||||
|
&self,
|
||||||
|
url: &Url,
|
||||||
|
config: &Config,
|
||||||
|
args: &Issue2Work,
|
||||||
|
) -> impl std::future::Future<Output = Result<Issue2WorkResult, GeneralError>> + Send;
|
||||||
|
|
||||||
fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool;
|
fn supports(&self, url: &Url, config: &Config, args: &Issue2Work) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Issue2WorkResult {
|
||||||
|
pub work_package_url: String,
|
||||||
|
pub subject: String,
|
||||||
|
}
|
||||||
|
|||||||
223
src/webext.rs
Normal file
223
src/webext.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use cl_cli::cli::Issue2Work as Issue2WorkCli;
|
||||||
|
use cl_cli::config::build_config;
|
||||||
|
use cl_cli::error::GeneralError;
|
||||||
|
use cl_cli::planning::issue2work::handle_issue2work;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum InputMessage {
|
||||||
|
Issue2Work(Issue2WorkWebExtMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum OutputContent {
|
||||||
|
Issue2Work(OutputIssue2Work),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "result", content = "data")]
|
||||||
|
enum OutputMessage {
|
||||||
|
Ok(OutputContent),
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input message for creating an issue.
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Issue2WorkWebExtMessage {
|
||||||
|
url: String,
|
||||||
|
project: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output message returned to the web extension.
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct OutputIssue2Work {
|
||||||
|
created: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a *raw* message from stdin (Native Messaging format).
|
||||||
|
/// - first reads 4 bytes (length)
|
||||||
|
/// - then reads exactly `len` bytes into a Vec<u8>
|
||||||
|
/// Returns Ok(None) if EOF (no more data).
|
||||||
|
fn read_message_bytes(stdin: &mut io::StdinLock<'_>) -> io::Result<Option<Vec<u8>>> {
|
||||||
|
let mut len_buf = [0u8; 4];
|
||||||
|
|
||||||
|
// Lire les 4 octets de longueur
|
||||||
|
match stdin.read_exact(&mut len_buf) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
|
||||||
|
// Plus de données : fin normale
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Longueur annoncée (endianness native, comme dans la spec)
|
||||||
|
let len = u32::from_ne_bytes(len_buf) as usize;
|
||||||
|
|
||||||
|
// Ici, on peut recevoir un *nombre arbitraire d'octets* (dans la limite de u32).
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
stdin.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a JSON message to stdout with a header of 4 bytes.
|
||||||
|
fn write_json_message<T: serde::Serialize>(
|
||||||
|
stdout: &mut io::StdoutLock<'_>,
|
||||||
|
msg: &T,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let data =
|
||||||
|
serde_json::to_vec(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||||
|
|
||||||
|
let len = data.len() as u32;
|
||||||
|
let len_buf = len.to_ne_bytes();
|
||||||
|
|
||||||
|
stdout.write_all(&len_buf)?;
|
||||||
|
stdout.write_all(&data)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Logique métier" : construit une URL "created" à partir de l'entrée.
|
||||||
|
async fn handle_business_logic(input: InputMessage) -> Result<OutputContent, GeneralError> {
|
||||||
|
let mut default_config_path = PathBuf::new();
|
||||||
|
default_config_path.push(simple_home_dir::home_dir().unwrap());
|
||||||
|
default_config_path.push(".config/cl-cli/config.toml");
|
||||||
|
// during dev: default_config_path.push("/tmp/cl-cli/config.toml");
|
||||||
|
let config = match build_config(&default_config_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = match input {
|
||||||
|
InputMessage::Issue2Work(i2w) => {
|
||||||
|
let arg = Issue2WorkCli {
|
||||||
|
issue_url: i2w.url,
|
||||||
|
project_id: i2w.project,
|
||||||
|
assign_to_me: false,
|
||||||
|
};
|
||||||
|
let result = handle_issue2work(config, &arg).await?;
|
||||||
|
|
||||||
|
OutputContent::Issue2Work(OutputIssue2Work {
|
||||||
|
created: result.work_package_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
eprintln!("Native host Rust démarré.");
|
||||||
|
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut stdin_lock = stdin.lock();
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut stdout_lock = stdout.lock();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match read_message_bytes(&mut stdin_lock) {
|
||||||
|
Ok(Some(raw_bytes)) => {
|
||||||
|
eprintln!("Reçu {} octets depuis l'extension.", raw_bytes.len());
|
||||||
|
|
||||||
|
// On parse le JSON *après* avoir lu tous les octets.
|
||||||
|
let input: InputMessage = match serde_json::from_slice(&raw_bytes) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("JSON invalide reçu: {e}");
|
||||||
|
// Option : renvoyer une erreur JSON au lieu d'abandonner
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Logique métier -> OutputMessage
|
||||||
|
let output = match handle_business_logic(input).await {
|
||||||
|
Ok(v) => OutputMessage::Ok(v),
|
||||||
|
Err(e) => OutputMessage::Error(e.description),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = write_json_message(&mut stdout_lock, &output) {
|
||||||
|
eprintln!("Erreur lors de l'écriture de la réponse: {e}");
|
||||||
|
// Si on ne sait plus écrire, on termine.
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
eprintln!("EOF sur stdin, arrêt du host.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Erreur de lecture: {e}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserializes_issue2work_variant() {
|
||||||
|
// Given: a JSON with the external tag "type" matching the enum variant name
|
||||||
|
let payload = json!({
|
||||||
|
"type": "Issue2Work",
|
||||||
|
"url": "https://example.com/issues/123",
|
||||||
|
"project": "test"
|
||||||
|
});
|
||||||
|
|
||||||
|
// When: deserializing to InputMessage
|
||||||
|
let msg: InputMessage = serde_json::from_value(payload).expect("valid enum JSON");
|
||||||
|
|
||||||
|
// Then: we get the correct variant and fields
|
||||||
|
match msg {
|
||||||
|
InputMessage::Issue2Work(inner) => {
|
||||||
|
assert_eq!(inner.url, "https://example.com/issues/123");
|
||||||
|
assert_eq!(inner.project, "test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_on_unknown_type_tag() {
|
||||||
|
let payload = json!({
|
||||||
|
"type": "Unknown",
|
||||||
|
"url": "https://example.com/issues/123",
|
||||||
|
"project": "test"
|
||||||
|
});
|
||||||
|
|
||||||
|
let err = serde_json::from_value::<InputMessage>(payload).unwrap_err();
|
||||||
|
// Basic sanity check that it's a data error mentioning the unrecognized variant
|
||||||
|
let msg = err.to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains("unknown variant") || msg.contains("unknown") || msg.contains("expected"),
|
||||||
|
"unexpected error message: {msg}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_when_missing_required_fields() {
|
||||||
|
// Missing "url" and "project"
|
||||||
|
let payload = json!({
|
||||||
|
"type": "Issue2Work"
|
||||||
|
});
|
||||||
|
|
||||||
|
let err = serde_json::from_value::<InputMessage>(payload).unwrap_err();
|
||||||
|
let msg = err.to_string();
|
||||||
|
assert!(
|
||||||
|
msg.contains("missing field"),
|
||||||
|
"unexpected error message: {msg}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
web-extension/.idea/.gitignore
generated
vendored
Normal file
8
web-extension/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
8
web-extension/.idea/modules.xml
generated
Normal file
8
web-extension/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/web-extension.iml" filepath="$PROJECT_DIR$/.idea/web-extension.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
web-extension/.idea/vcs.xml
generated
Normal file
6
web-extension/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
web-extension/.idea/web-extension.iml
generated
Normal file
12
web-extension/.idea/web-extension.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
1
web-extension/.nvmrc
Normal file
1
web-extension/.nvmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
24
|
||||||
3
web-extension/cl/.gitignore
vendored
Normal file
3
web-extension/cl/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/*
|
||||||
|
dist/*
|
||||||
|
web-ext-artifacts/*
|
||||||
3
web-extension/cl/.web-extension-id
Normal file
3
web-extension/cl/.web-extension-id
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This file was created by https://github.com/mozilla/web-ext
|
||||||
|
# Your auto-generated extension ID for addons.mozilla.org is:
|
||||||
|
helper@champs-libres-coop
|
||||||
BIN
web-extension/cl/icons/img-48.png
Normal file
BIN
web-extension/cl/icons/img-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 B |
29
web-extension/cl/manifest.json
Normal file
29
web-extension/cl/manifest.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Champs-Libres Helper",
|
||||||
|
"version": "0.4",
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "helper@champs-libres-coop",
|
||||||
|
"strict_min_version": "143.0",
|
||||||
|
"data_collection_permissions": {
|
||||||
|
"required": ["none"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"description": "Aide pour les champs-libres",
|
||||||
|
|
||||||
|
"icons": {
|
||||||
|
"48": "icons/img-48.png"
|
||||||
|
},
|
||||||
|
|
||||||
|
"permissions": ["menus", "nativeMessaging", "tabs"],
|
||||||
|
"host_permissions": [
|
||||||
|
"*://*/*"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["dist/background/background.js"],
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
|
}
|
||||||
6194
web-extension/cl/package-lock.json
generated
Normal file
6194
web-extension/cl/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
web-extension/cl/package.json
Normal file
22
web-extension/cl/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "web-extension",
|
||||||
|
"version": "0.4.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"check": "tsc --noEmit",
|
||||||
|
"build": "tsc",
|
||||||
|
"package": "tsc && web-ext build --overwrite-dest",
|
||||||
|
"sign": "tsc && web-ext build --overwrite-dest && web-ext sign --use-submission-api --api-key $AMO_API_KEY --api-secret $AMO_API_SECRET --channel unlisted"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/firefox-webext-browser": "^143.0.0",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"web-ext": "^7.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
68
web-extension/cl/src/background/background.ts
Normal file
68
web-extension/cl/src/background/background.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {isOutputMessageError, isOutputMessageSuccess} from "./types.js";
|
||||||
|
|
||||||
|
const projects: { name: string, slug: string }[] = [
|
||||||
|
//{name: "test (dev only)", slug: "test"},
|
||||||
|
{name: "Adminsys", slug: "champs-libres-adminsys"},
|
||||||
|
{name: "Be-Arbres", slug: "be-arbres"},
|
||||||
|
{name: "Chill", slug: "chill"},
|
||||||
|
{name: "Chill > AMLI", slug: "amli"},
|
||||||
|
{name: "Chill > Haute-Vienne", slug: "haute-vienne"},
|
||||||
|
{name: "Chill > Petits Chills", slug: "petits-chill"},
|
||||||
|
{name: "Chill > Samu Social", slug: "samu-social"},
|
||||||
|
{name: "Chill > Vendée", slug: "vendee"},
|
||||||
|
{name: "Osiris", slug: "osiris"},
|
||||||
|
{name: "Raponmap", slug: "raponmap"}
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const project of projects) {
|
||||||
|
browser.menus.create(
|
||||||
|
{
|
||||||
|
id: "cl-issue-2-wp-" + project.slug,
|
||||||
|
title: project.name,
|
||||||
|
contexts: ["link"],
|
||||||
|
type: "radio"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.menus.onClicked.addListener(async function(info, tab) {
|
||||||
|
console.log("info", info);
|
||||||
|
|
||||||
|
if (typeof info.menuItemId === "string") {
|
||||||
|
console.log("menuItemId", info.menuItemId);
|
||||||
|
if (info.menuItemId.startsWith("cl-issue-2-wp-")) {
|
||||||
|
console.log("start with cl-issue-2-wp-");
|
||||||
|
const slug = info.menuItemId.replace("cl-issue-2-wp-", "");
|
||||||
|
console.log("slug", slug);
|
||||||
|
if (typeof info.linkUrl === "string") {
|
||||||
|
await convertToWorkPackage(slug, info.linkUrl);
|
||||||
|
} else {
|
||||||
|
console.error("linkUrl is not a string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function convertToWorkPackage(slug: string, url: string): Promise<void> {
|
||||||
|
const message = {
|
||||||
|
type: "Issue2Work",
|
||||||
|
url: url,
|
||||||
|
project: slug,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const sending: unknown = await browser.runtime.sendNativeMessage("cl_cli", message);
|
||||||
|
if (isOutputMessageError(sending)) {
|
||||||
|
console.error("error while handling message", sending.message);
|
||||||
|
} else if (isOutputMessageSuccess(sending)) {
|
||||||
|
try {
|
||||||
|
await browser.tabs.create({
|
||||||
|
url: sending.data.created,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error while creating new tab", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error while handling message", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
web-extension/cl/src/background/types.ts
Normal file
44
web-extension/cl/src/background/types.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
export type OutputMessageError = {
|
||||||
|
result: "Error",
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutputIssue2Work = {
|
||||||
|
type: "Issue2Work";
|
||||||
|
created: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutputMessageSuccess = {
|
||||||
|
result: 'Ok'
|
||||||
|
data: OutputIssue2Work;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutputMessage = OutputMessageError | OutputMessageSuccess;
|
||||||
|
|
||||||
|
export function isOutputMessage(value: unknown): value is OutputMessage {
|
||||||
|
return isOutputMessageError(value) || isOutputMessageSuccess(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOutputMessageError(value: unknown): value is OutputMessageError {
|
||||||
|
return (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'result' in value &&
|
||||||
|
value.result === 'Error' &&
|
||||||
|
'message' in value &&
|
||||||
|
typeof value.message === 'string'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOutputMessageSuccess(value: unknown): value is OutputMessageSuccess {
|
||||||
|
return (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'result' in value &&
|
||||||
|
value.result === 'Ok' &&
|
||||||
|
'data' in value &&
|
||||||
|
typeof value.data === 'object' &&
|
||||||
|
value.data !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
19
web-extension/cl/tsconfig.json
Normal file
19
web-extension/cl/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES6",
|
||||||
|
"module": "ESNext",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": false,
|
||||||
|
"types": [
|
||||||
|
"firefox-webext-browser"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts"],
|
||||||
|
"exclude": ["./node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user