workforest 0.5.2__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. workforest-0.6.0/.claude/rules/architecture.md +68 -0
  2. workforest-0.6.0/.github/workflows/assets.yml +42 -0
  3. workforest-0.6.0/.github/workflows/binaries.yml +56 -0
  4. workforest-0.6.0/.github/workflows/ci.yml +162 -0
  5. workforest-0.6.0/.github/workflows/publish_idea.yml +100 -0
  6. workforest-0.6.0/.github/workflows/publish_vscode.yml +96 -0
  7. {workforest-0.5.2 → workforest-0.6.0}/.gitignore +10 -0
  8. workforest-0.6.0/Makefile +147 -0
  9. {workforest-0.5.2 → workforest-0.6.0}/PKG-INFO +141 -21
  10. {workforest-0.5.2 → workforest-0.6.0}/README.md +140 -20
  11. workforest-0.6.0/assets/generate +228 -0
  12. {workforest-0.5.2 → workforest-0.6.0}/assets/logo-dark.svg +2 -0
  13. workforest-0.6.0/assets/logo-icon.svg +5 -0
  14. {workforest-0.5.2 → workforest-0.6.0}/assets/logo-light.svg +2 -0
  15. workforest-0.6.0/assets/logo.svg +5 -0
  16. workforest-0.6.0/assets/src/logo-icon.svg +3 -0
  17. workforest-0.6.0/editors/idea/.gitignore +4 -0
  18. workforest-0.6.0/editors/idea/README.md +262 -0
  19. workforest-0.6.0/editors/idea/build.gradle.kts +96 -0
  20. workforest-0.6.0/editors/idea/gradle/wrapper/gradle-wrapper.jar +0 -0
  21. workforest-0.6.0/editors/idea/gradle/wrapper/gradle-wrapper.properties +9 -0
  22. workforest-0.6.0/editors/idea/gradle.properties +8 -0
  23. workforest-0.6.0/editors/idea/gradlew +248 -0
  24. workforest-0.6.0/editors/idea/gradlew.bat +82 -0
  25. workforest-0.6.0/editors/idea/settings.gradle.kts +7 -0
  26. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Actions.kt +433 -0
  27. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/ActionsUtil.kt +33 -0
  28. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Bundled.kt +31 -0
  29. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/CreateWorktreeDialog.kt +53 -0
  30. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Projects.kt +72 -0
  31. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Protocol.kt +122 -0
  32. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Recency.kt +59 -0
  33. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/Running.kt +54 -0
  34. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorkforestCli.kt +129 -0
  35. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorkforestNotifications.kt +107 -0
  36. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorkforestSettings.kt +53 -0
  37. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorkforestStatusBar.kt +66 -0
  38. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorkforestToolWindow.kt +423 -0
  39. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/WorktreeService.kt +214 -0
  40. workforest-0.6.0/editors/idea/src/main/kotlin/pro/buryakov/workforest/idea/terminal/RunScriptAction.kt +47 -0
  41. workforest-0.6.0/editors/idea/src/main/resources/META-INF/plugin.xml +163 -0
  42. workforest-0.6.0/editors/idea/src/main/resources/META-INF/pluginIcon.svg +5 -0
  43. workforest-0.6.0/editors/idea/src/main/resources/META-INF/pluginIcon_dark.svg +5 -0
  44. workforest-0.6.0/editors/idea/src/main/resources/META-INF/workforest-terminal.xml +18 -0
  45. workforest-0.6.0/editors/idea/src/main/resources/icons/toolWindow.svg +5 -0
  46. workforest-0.6.0/editors/idea/src/main/resources/icons/toolWindow_dark.svg +5 -0
  47. workforest-0.6.0/editors/idea/src/test/kotlin/pro/buryakov/workforest/idea/BundledTest.kt +34 -0
  48. workforest-0.6.0/editors/idea/src/test/kotlin/pro/buryakov/workforest/idea/ProtocolTest.kt +127 -0
  49. workforest-0.6.0/editors/idea/src/test/kotlin/pro/buryakov/workforest/idea/RecencyTest.kt +25 -0
  50. workforest-0.6.0/editors/idea/src/test/kotlin/pro/buryakov/workforest/idea/RunningTest.kt +45 -0
  51. workforest-0.6.0/editors/vscode/.vscodeignore +11 -0
  52. workforest-0.6.0/editors/vscode/CHANGELOG.md +33 -0
  53. workforest-0.6.0/editors/vscode/LICENSE +21 -0
  54. workforest-0.6.0/editors/vscode/README.md +130 -0
  55. workforest-0.6.0/editors/vscode/media/icon.png +0 -0
  56. workforest-0.6.0/editors/vscode/media/icon.svg +10 -0
  57. workforest-0.6.0/editors/vscode/media/workforest.svg +5 -0
  58. workforest-0.6.0/editors/vscode/package-lock.json +3865 -0
  59. workforest-0.6.0/editors/vscode/package.json +393 -0
  60. workforest-0.6.0/editors/vscode/scripts/smoke.sh +32 -0
  61. workforest-0.6.0/editors/vscode/src/cli.ts +169 -0
  62. workforest-0.6.0/editors/vscode/src/commands.ts +568 -0
  63. workforest-0.6.0/editors/vscode/src/extension.ts +97 -0
  64. workforest-0.6.0/editors/vscode/src/forest.ts +261 -0
  65. workforest-0.6.0/editors/vscode/src/model.ts +277 -0
  66. workforest-0.6.0/editors/vscode/src/recency.ts +45 -0
  67. workforest-0.6.0/editors/vscode/src/smoke/index.ts +84 -0
  68. workforest-0.6.0/editors/vscode/src/test/forest.test.ts +196 -0
  69. workforest-0.6.0/editors/vscode/src/tree.ts +216 -0
  70. workforest-0.6.0/editors/vscode/tsconfig.json +20 -0
  71. {workforest-0.5.2 → workforest-0.6.0}/man/workforest.1 +14 -9
  72. {workforest-0.5.2 → workforest-0.6.0}/man/workforest.5 +24 -8
  73. workforest-0.6.0/packaging/binary/build.sh +37 -0
  74. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/__init__.py +1 -1
  75. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/cli.py +2 -2
  76. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/commands.py +12 -4
  77. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/completions.py +1 -1
  78. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/config.py +9 -6
  79. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/examples/.workforest.yaml +7 -2
  80. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/hooks.py +56 -37
  81. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/jobs.py +63 -10
  82. {workforest-0.5.2 → workforest-0.6.0}/tests/test_commands.py +36 -0
  83. {workforest-0.5.2 → workforest-0.6.0}/tests/test_config.py +7 -0
  84. {workforest-0.5.2 → workforest-0.6.0}/tests/test_hooks.py +106 -46
  85. {workforest-0.5.2 → workforest-0.6.0}/tests/test_jobs.py +70 -9
  86. {workforest-0.5.2 → workforest-0.6.0}/tests/test_shell.py +2 -1
  87. workforest-0.5.2/.claude/rules/architecture.md +0 -33
  88. workforest-0.5.2/.github/workflows/ci.yml +0 -55
  89. workforest-0.5.2/Makefile +0 -33
  90. {workforest-0.5.2 → workforest-0.6.0}/.claude/rules/conventions.md +0 -0
  91. {workforest-0.5.2 → workforest-0.6.0}/.claude/rules/tests.md +0 -0
  92. {workforest-0.5.2 → workforest-0.6.0}/.github/workflows/publish_aur.yml +0 -0
  93. {workforest-0.5.2 → workforest-0.6.0}/.github/workflows/publish_homebrew.yml +0 -0
  94. {workforest-0.5.2 → workforest-0.6.0}/.github/workflows/publish_pypi.yml +0 -0
  95. {workforest-0.5.2 → workforest-0.6.0}/.github/workflows/release.yml +0 -0
  96. {workforest-0.5.2 → workforest-0.6.0}/LICENSE +0 -0
  97. {workforest-0.5.2/assets → workforest-0.6.0/assets/src}/logo.svg +0 -0
  98. {workforest-0.5.2 → workforest-0.6.0}/completions/_workforest +0 -0
  99. {workforest-0.5.2 → workforest-0.6.0}/man/wf.1 +0 -0
  100. {workforest-0.5.2 → workforest-0.6.0}/man/wf.5 +0 -0
  101. {workforest-0.5.2 → workforest-0.6.0}/packaging/AUR/PKGBUILD.template +0 -0
  102. {workforest-0.5.2 → workforest-0.6.0}/packaging/homebrew/workforest.rb.template +0 -0
  103. {workforest-0.5.2 → workforest-0.6.0}/pyproject.toml +0 -0
  104. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/__main__.py +0 -0
  105. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/errors.py +0 -0
  106. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/examples/config.yaml +0 -0
  107. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/gitutil.py +0 -0
  108. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/integrations/__init__.py +0 -0
  109. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/integrations/claude.py +0 -0
  110. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/launch.py +0 -0
  111. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/output.py +0 -0
  112. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/shell/completion.bash +0 -0
  113. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/shell/completion.zsh +0 -0
  114. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/shell/workforest.sh +0 -0
  115. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/shellinit.py +0 -0
  116. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/templates/project.yaml +0 -0
  117. {workforest-0.5.2 → workforest-0.6.0}/src/workforest/tui.py +0 -0
  118. {workforest-0.5.2 → workforest-0.6.0}/tests/__init__.py +0 -0
  119. {workforest-0.5.2 → workforest-0.6.0}/tests/conftest.py +0 -0
  120. {workforest-0.5.2 → workforest-0.6.0}/tests/test_claude.py +0 -0
  121. {workforest-0.5.2 → workforest-0.6.0}/tests/test_cli.py +0 -0
  122. {workforest-0.5.2 → workforest-0.6.0}/tests/test_gitutil.py +0 -0
  123. {workforest-0.5.2 → workforest-0.6.0}/tests/test_harness.py +0 -0
  124. {workforest-0.5.2 → workforest-0.6.0}/tests/test_launch.py +0 -0
  125. {workforest-0.5.2 → workforest-0.6.0}/tests/test_man.py +0 -0
  126. {workforest-0.5.2 → workforest-0.6.0}/tests/test_output.py +0 -0
  127. {workforest-0.5.2 → workforest-0.6.0}/tests/test_tui.py +0 -0
  128. {workforest-0.5.2 → workforest-0.6.0}/uv.lock +0 -0
@@ -0,0 +1,68 @@
1
+ # Architecture invariants
2
+
3
+ - `gitutil.py` is the only module that spawns git. Consumers get typed
4
+ results; worktree data comes from `--porcelain -z` output, never from
5
+ parsing the human-readable form.
6
+ - `cli.py` is the sole stdout writer. Commands return
7
+ `ShellAction | str | None`; stdout carries the shell-wrapper cd protocol,
8
+ so nothing else may print there (hook/script stdout is diverted to stderr).
9
+ - `tui.py`: everything except the fzf subprocess is pure and unit-tested;
10
+ fzf is the only sanctioned external tool there.
11
+ - `completions.py` must never break the shell: any error yields an empty
12
+ candidate list, and output stays plain `NAME<TAB>ANNOTATION` lines.
13
+ - `integrations/claude.py` is experimental: it reads Claude Code's private
14
+ on-disk state. Session lines are rewritten by JSON parsing, never by
15
+ string substitution.
16
+ - The editor plugins ship the CLI they drive: `packaging/binary/build.sh`
17
+ freezes the wheel into one self-contained executable (PyInstaller) per
18
+ platform, which `make vscode-build`/`make idea-build` (this machine's
19
+ platform) and CI (all four) put in `editors/vscode/bin/workforest` (one per
20
+ platform-specific `.vsix`) and `editors/idea/bin/<os>-<arch>/workforest`
21
+ (all of them in one plugin zip). Both trees build fine without it, and
22
+ both run the bundled copy when there is one — it was built with the
23
+ client, so the two always match; only where the package carries none do
24
+ they fall back to `PATH` and the usual install directories. Neither
25
+ client offers a setting for the path, and neither may add a second way to
26
+ obtain the CLI — no downloading, no installing on the user's behalf.
27
+ - `editors/vscode/` (the VS Code extension) is a thin client: it only ever
28
+ spawns `workforest` — never git — and reads its machine output
29
+ (`list --json`, `config --json`, `--complete` lines). Terminal prompts are
30
+ replaced by VS Code dialogs plus `--force`/`--keep-branch`/`--delete-branch`.
31
+ Parsing lives in `forest.ts`, which never imports `vscode` and is
32
+ unit-tested with `node:test`; `cli.ts` is the only module that spawns.
33
+ Verify with `npm run check` there (compile, tests, `vsce package`).
34
+ - README.md is the project reference; there is no separate design document.
35
+ The man pages under `man/` (`workforest.1`, `workforest.5`) are its
36
+ installed counterpart: any change to commands, options, config keys,
37
+ environment variables, or exit codes updates both README.md and the
38
+ affected page in the same change. `tests/test_man.py` catches drift from
39
+ `cli.py`, not from prose — keep the wording in sync by hand.
40
+ - Script groups (`hooks.py`): a `bulk`/`pipeline` runs under a forked
41
+ supervisor that leads the process group and takes the tty exactly like a
42
+ command, so records, `wf stop`, `exclusive`, `cleanup`, and `-b` need no
43
+ group-specific code. Bulk members must never take the tty (only one
44
+ group can own it); they run in the supervisor's process group so a
45
+ signal to the group reaches them all, write to a pty when stderr is a
46
+ terminal (so their programs keep colors) and to a pipe otherwise, and
47
+ the supervisor relays their lines prefixed. A bulk always waits for
48
+ every member. An outcome of `-SIGINT` or exit 130 is an interruption
49
+ (warning, `wf` dies by SIGINT), never a failure. Forked-child bodies are
50
+ `# pragma: no cover`; the logic lives in tested pure helpers
51
+ (`_Prefixer`, `_pump`, `_bulk_outcome`, `_run_step`).
52
+ - `editors/idea/` (the JetBrains plugin) is a thin client like the VS Code
53
+ one: it only ever spawns `workforest` — never git — and reads its machine
54
+ output (`list --json`, `--complete` lines, the last stderr line as the
55
+ error). Terminal prompts are replaced by IDE dialogs plus
56
+ `--force`/`--keep-branch`/`--delete-branch`. Parsing lives in
57
+ `Protocol.kt`, which is pure and unit-tested; `WorkforestCli.kt` is the
58
+ only file that spawns. `terminal/` is loaded only with the bundled
59
+ Terminal plugin (optional dependency). Verify with `./gradlew build
60
+ buildPlugin` there (JDK 21).
61
+ - Logo assets are generated, never hand-edited: `assets/src/logo.svg`
62
+ (full size) and `assets/src/logo-icon.svg` (adapted for small formats,
63
+ the source of every icon) are the only files touched by hand.
64
+ `assets/generate` writes `assets/logo*.svg` plus the icons vendored into
65
+ `editors/vscode/media/` and `editors/idea/.../resources/`; each carries a
66
+ "do not edit" header. Adding a place that ships the logo means adding a
67
+ target there, not another copy. `.github/workflows/assets.yml` re-runs the
68
+ script on pull requests and commits any difference.
@@ -0,0 +1,42 @@
1
+ name: Assets
2
+
3
+ # Every logo asset is derived from assets/src/ by assets/generate. Re-run it
4
+ # whenever a pull request touches the sources or the script, and commit the
5
+ # result — so a redrawn logo only ever has to be redrawn once. Pushes made
6
+ # with GITHUB_TOKEN do not trigger workflows, so this cannot loop.
7
+
8
+ on:
9
+ pull_request:
10
+ paths:
11
+ - assets/**
12
+ - .github/workflows/assets.yml
13
+
14
+ permissions:
15
+ contents: write
16
+
17
+ jobs:
18
+ generate:
19
+ # Forks cannot be pushed to with this token; leave their PRs alone.
20
+ if: github.event.pull_request.head.repo.full_name == github.repository
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ with:
25
+ ref: ${{ github.head_ref }}
26
+
27
+ - name: Install rsvg-convert
28
+ run: sudo apt-get update && sudo apt-get install -y librsvg2-bin
29
+
30
+ - name: Regenerate assets
31
+ run: assets/generate
32
+
33
+ - name: Commit any difference
34
+ run: |
35
+ if git diff --quiet; then
36
+ echo "assets are up to date"
37
+ exit 0
38
+ fi
39
+ git config user.name "github-actions[bot]"
40
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
41
+ git commit -am "Regenerate assets"
42
+ git push
@@ -0,0 +1,56 @@
1
+ name: Binaries
2
+
3
+ # The self-contained `workforest` executables the editor plugins ship, one
4
+ # per platform (packaging/binary/build.sh). PyInstaller freezes for the
5
+ # machine it runs on, so every target is built on a runner of its own.
6
+ # Artifacts are named workforest-binary-<os>-<arch>, which is both the
7
+ # `vsce package --target` identifier and the bin/<os>-<arch>/ directory the
8
+ # JetBrains plugin looks under.
9
+ #
10
+ # Called by the publish workflows, dispatchable on its own, and run for a
11
+ # pull request that touches the CLI these executables freeze — which is
12
+ # where `make idea-build-full` gets the three platforms a single machine
13
+ # cannot build. A pull request that leaves the CLI alone reuses whatever
14
+ # run built it last: the binaries would be identical anyway.
15
+ #
16
+ # ci.yml builds linux-x64 of its own on every pull request, smoke-tests it,
17
+ # and packages the editor clients around it; this is the full set, only
18
+ # when it can differ.
19
+
20
+ on:
21
+ workflow_call:
22
+ workflow_dispatch:
23
+ pull_request:
24
+ paths:
25
+ - src/**
26
+ - pyproject.toml
27
+ - uv.lock
28
+ - packaging/binary/**
29
+ - .github/workflows/binaries.yml
30
+
31
+ jobs:
32
+ build:
33
+ strategy:
34
+ matrix:
35
+ include:
36
+ - target: linux-x64
37
+ os: ubuntu-latest
38
+ - target: linux-arm64
39
+ os: ubuntu-24.04-arm
40
+ - target: darwin-x64
41
+ os: macos-26-intel # the standard x86_64 macOS runner
42
+ - target: darwin-arm64
43
+ os: macos-latest
44
+ runs-on: ${{ matrix.os }}
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - uses: astral-sh/setup-uv@v5
49
+
50
+ - name: Build the standalone executable
51
+ run: packaging/binary/build.sh
52
+
53
+ - uses: actions/upload-artifact@v4
54
+ with:
55
+ name: workforest-binary-${{ matrix.target }}
56
+ path: dist/binary/workforest
@@ -0,0 +1,162 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ runs-on: ubuntu-latest
11
+ container: archlinux:latest
12
+ steps:
13
+ - name: Install system dependencies
14
+ run: pacman -Syu --noconfirm git python uv make zsh groff
15
+
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Sync dev environment
19
+ run: uv sync
20
+
21
+ - name: Lint, type-check, test
22
+ run: make check
23
+
24
+ package-smoke:
25
+ # Builds the wheel the AUR package would build and installs it into a
26
+ # clean venv — catches missing package data and broken entry points.
27
+ # The full `makepkg -si` + namcap check runs in the release workflow,
28
+ # where a source tarball exists.
29
+ runs-on: ubuntu-latest
30
+ container: archlinux:latest
31
+ steps:
32
+ - name: Install system dependencies
33
+ run: pacman -Syu --noconfirm git python python-build python-installer python-wheel python-hatchling man-db
34
+
35
+ - uses: actions/checkout@v4
36
+
37
+ - name: Build wheel (no isolation, like the PKGBUILD)
38
+ run: python -m build --wheel --no-isolation
39
+
40
+ - name: Install into a clean venv and smoke test
41
+ run: |
42
+ python -m venv /tmp/venv
43
+ /tmp/venv/bin/pip install dist/*.whl
44
+ /tmp/venv/bin/workforest --version
45
+ /tmp/venv/bin/wf --version
46
+ /tmp/venv/bin/workforest shell-init bash | bash -n
47
+ /tmp/venv/bin/workforest shell-init zsh | head -1 | grep -q Workforest
48
+ # man pages ride along in the wheel's share/man data, and shell-init
49
+ # puts that directory on $MANPATH for venv installs
50
+ MANPATH=/tmp/venv/share/man man -w workforest wf 5 workforest wf
51
+ /tmp/venv/bin/workforest shell-init bash | grep -q 'MANPATH=/tmp/venv/share/man'
52
+ # the init starter is package data too
53
+ git init -q /tmp/repo
54
+ (cd /tmp/repo && /tmp/venv/bin/workforest init)
55
+ test -s /tmp/repo/.workforest.yaml
56
+
57
+ binary:
58
+ # The self-contained executable the editor plugins ship
59
+ # (packaging/binary/build.sh). Only linux-x64 here, to keep PRs cheap:
60
+ # the other three platforms are built when publishing.
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+
65
+ - uses: astral-sh/setup-uv@v5
66
+
67
+ - name: Build the standalone executable
68
+ run: packaging/binary/build.sh
69
+
70
+ - name: Smoke test it (no Python, no venv, package data intact)
71
+ run: |
72
+ bin=$PWD/dist/binary/workforest
73
+ "$bin" --version
74
+ "$bin" shell-init bash | bash -n
75
+ git init -q /tmp/repo
76
+ cd /tmp/repo && "$bin" init && test -s .workforest.yaml
77
+
78
+ - uses: actions/upload-artifact@v4
79
+ with:
80
+ name: workforest-binary-linux-x64
81
+ path: dist/binary/workforest
82
+
83
+ vscode-extension:
84
+ # editors/vscode: compile, unit tests (node:test, no VS Code download),
85
+ # and the platform-specific .vsix that carries the CLI in bin/.
86
+ needs: binary
87
+ runs-on: ubuntu-latest
88
+ defaults:
89
+ run:
90
+ working-directory: editors/vscode
91
+ steps:
92
+ - uses: actions/checkout@v4
93
+
94
+ - uses: actions/setup-node@v4
95
+ with:
96
+ node-version: 22
97
+ cache: npm
98
+ cache-dependency-path: editors/vscode/package-lock.json
99
+
100
+ - uses: actions/download-artifact@v4
101
+ with:
102
+ name: workforest-binary-linux-x64
103
+ path: editors/vscode/bin
104
+
105
+ - name: Make the bundled CLI executable
106
+ run: chmod +x bin/workforest
107
+
108
+ - name: Install
109
+ run: npm ci
110
+
111
+ - name: Compile and test
112
+ run: npm test
113
+
114
+ - name: Package for linux-x64
115
+ run: npm run package -- --target linux-x64
116
+
117
+ - name: The .vsix carries the CLI
118
+ run: unzip -l workforest-linux-x64-*.vsix | grep -q 'extension/bin/workforest'
119
+
120
+ - uses: actions/upload-artifact@v4
121
+ with:
122
+ name: workforest-vscode
123
+ path: editors/vscode/*.vsix
124
+
125
+ plugin-idea:
126
+ # The JetBrains plugin under editors/idea: compile, unit tests, and the
127
+ # plugin zip, which carries one CLI per platform under bin/<os>-<arch>/
128
+ # (only linux-x64 here; a published build ships all four).
129
+ # verifyPlugin (downloads IDEs) is run by hand.
130
+ needs: binary
131
+ runs-on: ubuntu-latest
132
+ defaults:
133
+ run:
134
+ working-directory: editors/idea
135
+ steps:
136
+ - uses: actions/checkout@v4
137
+
138
+ - uses: actions/setup-java@v4
139
+ with:
140
+ distribution: temurin
141
+ java-version: 21
142
+
143
+ - uses: gradle/actions/setup-gradle@v4
144
+
145
+ - uses: actions/download-artifact@v4
146
+ with:
147
+ name: workforest-binary-linux-x64
148
+ path: editors/idea/bin/linux-x64
149
+
150
+ - name: Make the bundled CLI executable
151
+ run: chmod +x bin/linux-x64/workforest
152
+
153
+ - name: Compile, test, package
154
+ run: ./gradlew --no-daemon build buildPlugin
155
+
156
+ - name: The plugin zip carries the CLI
157
+ run: unzip -l build/distributions/*.zip | grep -q 'bin/linux-x64/workforest'
158
+
159
+ - uses: actions/upload-artifact@v4
160
+ with:
161
+ name: workforest-idea
162
+ path: editors/idea/build/distributions/*.zip
@@ -0,0 +1,100 @@
1
+ name: Publish JetBrains plugin
2
+
3
+ # Publishes editors/idea to the JetBrains Marketplace when a release is
4
+ # published, or on demand. One zip carrying the `workforest` executable for
5
+ # every platform (packaging/binary/build.sh) under bin/<os>-<arch>/, so
6
+ # installing the plugin is enough — no separate CLI install.
7
+ #
8
+ # The plugin has no version of its own: gradle.properties carries a
9
+ # placeholder and the build is passed the CLI's __version__, so what the
10
+ # Marketplace serves is always the release frozen inside it. A version
11
+ # already on the Marketplace is skipped, which makes a re-run of an old
12
+ # release a no-op. Needs the JB_PUBLISH_TOKEN secret (a
13
+ # Marketplace permanent token, plugins.jetbrains.com/author/me/tokens);
14
+ # without it the publish step is skipped. The environment variable it
15
+ # becomes has to be PUBLISH_TOKEN — that name is the Gradle plugin's, the
16
+ # secret's says which marketplace it opens.
17
+ #
18
+ # Only updates go through here: the Marketplace upload API updates an
19
+ # existing plugin, it cannot create one. pro.buryakov.workforest (id 34090)
20
+ # was uploaded by hand once and reviewed by the Marketplace team.
21
+ #
22
+ # The zip is published unsigned, which makes the IDE warn on install. To
23
+ # sign it, add the CERTIFICATE_CHAIN, PRIVATE_KEY and PRIVATE_KEY_PASSWORD
24
+ # secrets and pass them to the publish step as env — the Gradle plugin
25
+ # reads exactly those names and runs signPlugin before publishPlugin. Set
26
+ # them only once they exist: an empty value is still a value, and signing
27
+ # with one fails.
28
+
29
+ on:
30
+ release:
31
+ types: [published]
32
+ workflow_dispatch:
33
+
34
+ jobs:
35
+ # The CLI the zip ships, one per platform.
36
+ binaries:
37
+ uses: ./.github/workflows/binaries.yml
38
+
39
+ publish:
40
+ needs: binaries
41
+ runs-on: ubuntu-latest
42
+ environment:
43
+ name: jetbrains-marketplace
44
+ url: https://plugins.jetbrains.com/plugin/34090-workforest
45
+ permissions:
46
+ contents: read
47
+ defaults:
48
+ run:
49
+ working-directory: editors/idea
50
+ env:
51
+ PUBLISH_TOKEN: ${{ secrets.JB_PUBLISH_TOKEN }}
52
+ # The Marketplace's own id for pro.buryakov.workforest; the updates
53
+ # API takes that, not the plugin's xml id.
54
+ PLUGIN_ID: "34090"
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - uses: actions/setup-java@v4
59
+ with:
60
+ distribution: temurin
61
+ java-version: 21
62
+
63
+ - uses: gradle/actions/setup-gradle@v4
64
+
65
+ - uses: actions/download-artifact@v4
66
+ with:
67
+ pattern: workforest-binary-*
68
+ path: ${{ github.workspace }}/binaries
69
+
70
+ - name: Lay the executables out the way the plugin resolves them
71
+ run: |
72
+ for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
73
+ mkdir -p "bin/$target"
74
+ cp "$GITHUB_WORKSPACE/binaries/workforest-binary-$target/workforest" "bin/$target/workforest"
75
+ chmod +x "bin/$target/workforest"
76
+ done
77
+ ls -l bin/*
78
+
79
+ - name: Compile, test, package as the version of the CLI it ships
80
+ run: |
81
+ version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' "$GITHUB_WORKSPACE/src/workforest/__init__.py")
82
+ [ -n "$version" ] || { echo "cannot read __version__" >&2; exit 1; }
83
+ echo "PLUGIN_VERSION=$version" >> "$GITHUB_ENV"
84
+ ./gradlew --no-daemon -PpluginVersion="$version" build buildPlugin
85
+
86
+ - name: The zip carries every platform's CLI
87
+ run: |
88
+ for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
89
+ unzip -l build/distributions/*.zip | grep -q "bin/$target/workforest"
90
+ done
91
+
92
+ - name: Publish
93
+ if: env.PUBLISH_TOKEN != ''
94
+ run: |
95
+ if curl -fsS "https://plugins.jetbrains.com/api/plugins/$PLUGIN_ID/updates" \
96
+ | jq -e --arg v "$PLUGIN_VERSION" 'any(.[]; .version == $v)' > /dev/null; then
97
+ echo "workforest $PLUGIN_VERSION is already on the JetBrains Marketplace — nothing to publish."
98
+ else
99
+ ./gradlew --no-daemon -PpluginVersion="$PLUGIN_VERSION" publishPlugin
100
+ fi
@@ -0,0 +1,96 @@
1
+ name: Publish VS Code extension
2
+
3
+ # Publishes editors/vscode to the Visual Studio Marketplace when a release
4
+ # is published, or on demand. One .vsix per platform, each carrying the
5
+ # `workforest` executable for it (packaging/binary/build.sh), so installing
6
+ # the extension is enough — no separate CLI install. Linux and macOS only:
7
+ # that is what workforest supports.
8
+ #
9
+ # The extension has no version of its own: package.json carries a
10
+ # placeholder and the packaging step stamps it with the CLI's __version__,
11
+ # so what the Marketplace serves is always the release frozen inside it. A
12
+ # version already on the Marketplace is skipped, which makes a re-run of an
13
+ # old release a no-op. Needs the VSCE_PAT secret (a
14
+ # Marketplace personal access token for the publisher in package.json);
15
+ # without it the publish step is skipped.
16
+ #
17
+ # That token is an Azure DevOps *global* PAT ("All accessible organizations",
18
+ # scope Marketplace/Manage) — the Marketplace accepts nothing narrower, and
19
+ # global PATs are decommissioned on 2026-12-01. Before then this job has to
20
+ # move to Microsoft Entra (`vsce publish --azure-credential`, a user-assigned
21
+ # managed identity added as a member of the publisher) or to trusted
22
+ # publishing (`vsce publish --oidc`, no secret at all) once that ships — it is
23
+ # in vsce's main branch but not in a release as of 3.9.2.
24
+
25
+ on:
26
+ release:
27
+ types: [published]
28
+ workflow_dispatch:
29
+
30
+ jobs:
31
+ # The CLI each .vsix ships, one per platform.
32
+ binaries:
33
+ uses: ./.github/workflows/binaries.yml
34
+
35
+ publish:
36
+ needs: binaries
37
+ runs-on: ubuntu-latest
38
+ environment:
39
+ name: vscode-marketplace
40
+ url: https://marketplace.visualstudio.com/items?itemName=ArkadyBuryakov.workforest
41
+ permissions:
42
+ contents: read
43
+ defaults:
44
+ run:
45
+ working-directory: editors/vscode
46
+ env:
47
+ VSCE_PAT: ${{ secrets.VSCE_PAT }}
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - uses: actions/setup-node@v4
52
+ with:
53
+ node-version: 22
54
+ cache: npm
55
+ cache-dependency-path: editors/vscode/package-lock.json
56
+
57
+ - uses: actions/download-artifact@v4
58
+ with:
59
+ pattern: workforest-binary-*
60
+ path: ${{ github.workspace }}/binaries
61
+
62
+ - name: Install
63
+ run: npm ci
64
+
65
+ - name: Compile and test
66
+ run: npm test
67
+
68
+ - name: Stamp the extension with the version of the CLI it ships
69
+ run: |
70
+ version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' "$GITHUB_WORKSPACE/src/workforest/__init__.py")
71
+ [ -n "$version" ] || { echo "cannot read __version__" >&2; exit 1; }
72
+ npm pkg set version="$version"
73
+ echo "packaging $version"
74
+
75
+ - name: Package one .vsix per platform, each with its own CLI
76
+ run: |
77
+ for target in linux-x64 linux-arm64 darwin-x64 darwin-arm64; do
78
+ rm -rf bin && mkdir bin
79
+ cp "$GITHUB_WORKSPACE/binaries/workforest-binary-$target/workforest" bin/workforest
80
+ chmod +x bin/workforest
81
+ npx vsce package --no-dependencies --target "$target"
82
+ done
83
+ ls -l ./*.vsix
84
+
85
+ - name: Publish
86
+ if: env.VSCE_PAT != ''
87
+ run: |
88
+ version=$(node -p "require('./package.json').version")
89
+ publisher=$(node -p "require('./package.json').publisher")
90
+ name=$(node -p "require('./package.json').name")
91
+ if npx vsce show "$publisher.$name" --json 2>/dev/null \
92
+ | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>process.exit(JSON.parse(d).versions.some(v=>v.version==='$version')?0:1))"; then
93
+ echo "$publisher.$name@$version is already on the Marketplace — nothing to publish."
94
+ else
95
+ npx vsce publish --no-dependencies --packagePath workforest-*.vsix
96
+ fi
@@ -24,5 +24,15 @@ packaging/AUR/PKGBUILD
24
24
  packaging/AUR/.SRCINFO
25
25
  packaging/homebrew/workforest.rb
26
26
 
27
+ # VS Code extension (editors/vscode)
28
+ node_modules/
29
+ editors/vscode/out/
30
+ *.vsix
31
+
32
+ # Bundled CLI binaries: built by packaging/binary/build.sh at package time
33
+ editors/vscode/bin/
34
+ editors/idea/bin/
35
+
27
36
  # Tooling config
28
37
  /.workforest.yaml
38
+ /Makefile.local