procoder-cli 0.1.0
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.
- package/AGENTS.md +122 -0
- package/CONTRIBUTORS.md +79 -0
- package/Makefile +82 -0
- package/README.md +125 -0
- package/bin/procoder.js +28 -0
- package/cmd/procoder/main.go +15 -0
- package/cmd/procoder-return/main.go +67 -0
- package/cmd/procoder-return/main_test.go +77 -0
- package/go.mod +3 -0
- package/internal/app/app.go +224 -0
- package/internal/app/app_test.go +253 -0
- package/internal/app/roundtrip_test.go +215 -0
- package/internal/apply/apply.go +1069 -0
- package/internal/apply/apply_test.go +794 -0
- package/internal/errs/errors.go +114 -0
- package/internal/exchange/exchange_test.go +165 -0
- package/internal/exchange/id.go +66 -0
- package/internal/exchange/json.go +64 -0
- package/internal/exchange/types.go +55 -0
- package/internal/gitx/gitx.go +105 -0
- package/internal/gitx/gitx_test.go +51 -0
- package/internal/output/errors.go +49 -0
- package/internal/output/errors_test.go +41 -0
- package/internal/prepare/prepare.go +788 -0
- package/internal/prepare/prepare_test.go +416 -0
- package/internal/returnpkg/returnpkg.go +589 -0
- package/internal/returnpkg/returnpkg_test.go +489 -0
- package/internal/testutil/gitrepo/repo.go +113 -0
- package/package.json +47 -0
- package/scripts/postinstall.js +263 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for coding agents working in `procoder`.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This repo builds `procoder`, a Go CLI for an offline Git exchange workflow between:
|
|
8
|
+
|
|
9
|
+
- a local developer repository
|
|
10
|
+
- a locked-down ChatGPT coding container
|
|
11
|
+
|
|
12
|
+
The main user flow is:
|
|
13
|
+
|
|
14
|
+
- `procoder prepare`
|
|
15
|
+
- remote work inside the exported repo
|
|
16
|
+
- `./procoder-return`
|
|
17
|
+
- `procoder apply <return-package.zip>`
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
- `cmd/procoder/main.go`: process entrypoint, error handling, exits non-zero on failure.
|
|
22
|
+
- `cmd/procoder-return/main.go`: remote helper entrypoint used inside prepared task packages.
|
|
23
|
+
- `internal/app/app.go`: command parser + top-level CLI handlers.
|
|
24
|
+
- `internal/exchange/`: exchange IDs, task-ref helpers, and JSON models for `exchange.json` / `procoder-return.json`.
|
|
25
|
+
- `internal/gitx/`: Git command runner with structured output and typed failures.
|
|
26
|
+
- `internal/prepare/`: `procoder prepare` implementation.
|
|
27
|
+
- `internal/returnpkg/`: `./procoder-return` implementation.
|
|
28
|
+
- `internal/apply/`: `procoder apply` implementation.
|
|
29
|
+
- `bin/procoder.js`: npm shim that invokes packaged native binary.
|
|
30
|
+
- `scripts/postinstall.js`: downloads or builds both the host CLI binary and the packaged `linux/amd64` helper.
|
|
31
|
+
- `.github/workflows/release.yml`: tag-driven release pipeline.
|
|
32
|
+
|
|
33
|
+
## Exchange Model
|
|
34
|
+
|
|
35
|
+
The current Git-valid task-family shape is:
|
|
36
|
+
|
|
37
|
+
- default prepared task branch: `refs/heads/procoder/<exchange-id>/task`
|
|
38
|
+
- writable task-family prefix: `refs/heads/procoder/<exchange-id>`
|
|
39
|
+
- allowed returned refs: `refs/heads/procoder/<exchange-id>/*`
|
|
40
|
+
|
|
41
|
+
Important:
|
|
42
|
+
|
|
43
|
+
- do not reintroduce the invalid older shape where both `refs/heads/procoder/<exchange-id>` and `refs/heads/procoder/<exchange-id>/*` exist at the same time
|
|
44
|
+
- Git cannot store both of those refs because one path would need to be both a ref and a directory
|
|
45
|
+
|
|
46
|
+
Machine-owned metadata lives under `.git/procoder/`.
|
|
47
|
+
|
|
48
|
+
- local exchange record: `.git/procoder/exchanges/<exchange-id>/exchange.json`
|
|
49
|
+
- exported repo exchange record: `.git/procoder/exchange.json`
|
|
50
|
+
|
|
51
|
+
User-facing artifacts live at repo root by default:
|
|
52
|
+
|
|
53
|
+
- task package: `./procoder-task-<exchange-id>.zip`
|
|
54
|
+
- return package: `./procoder-return-<exchange-id>.zip`
|
|
55
|
+
|
|
56
|
+
When changing exchange behavior, keep `gg/agent-outputs/procoder-handoff-v1-product-spec.md` and `gg/agent-outputs/procoder-exchange-v1-internal-spec.md` aligned with the code.
|
|
57
|
+
|
|
58
|
+
## Local commands
|
|
59
|
+
|
|
60
|
+
Use `make` targets:
|
|
61
|
+
|
|
62
|
+
- `make fmt`
|
|
63
|
+
- `make test`
|
|
64
|
+
- `make vet`
|
|
65
|
+
- `make lint`
|
|
66
|
+
- `make check`
|
|
67
|
+
- `make build`
|
|
68
|
+
- `make build-helper`
|
|
69
|
+
- `make build-all`
|
|
70
|
+
- `make install-local`
|
|
71
|
+
|
|
72
|
+
Direct commands:
|
|
73
|
+
|
|
74
|
+
- `go test ./...`
|
|
75
|
+
- `go vet ./...`
|
|
76
|
+
- `npm run lint`
|
|
77
|
+
|
|
78
|
+
Phase-oriented validation:
|
|
79
|
+
|
|
80
|
+
- `procoder prepare` changes should be covered by integration tests under `internal/prepare/`
|
|
81
|
+
- `procoder-return` changes should be covered by integration tests under `internal/returnpkg/`
|
|
82
|
+
- `procoder apply` changes should be covered by integration tests under `internal/apply/`
|
|
83
|
+
|
|
84
|
+
## How to customize safely
|
|
85
|
+
|
|
86
|
+
1. Rename CLI command consistently in all places:
|
|
87
|
+
- directory `cmd/procoder`
|
|
88
|
+
- `package.json` values (`bin`, `config.cliBinaryName`)
|
|
89
|
+
- `bin/procoder.js`
|
|
90
|
+
- workflow env `CLI_BINARY`
|
|
91
|
+
- `Makefile` `BIN_NAME`
|
|
92
|
+
|
|
93
|
+
2. Keep binary naming convention unchanged unless you also update postinstall/workflow:
|
|
94
|
+
- release assets: `<cli>_<goos>_<goarch>[.exe]`
|
|
95
|
+
- packaged helper asset: `procoder-return_linux_amd64`
|
|
96
|
+
- npm-installed binary path: `bin/<cli>-bin` (or `.exe` on Windows)
|
|
97
|
+
|
|
98
|
+
3. If adding dependencies, commit `go.sum` and optionally enable Go cache in workflow.
|
|
99
|
+
|
|
100
|
+
4. Keep help output expressive and command-local (`<command> --help` should explain examples).
|
|
101
|
+
|
|
102
|
+
5. If you change exchange filenames, helper asset names, or task-family ref naming, update:
|
|
103
|
+
- code
|
|
104
|
+
- tests
|
|
105
|
+
- `README.md`
|
|
106
|
+
- `AGENTS.md`
|
|
107
|
+
- both spec docs under `gg/agent-outputs/`
|
|
108
|
+
|
|
109
|
+
## Release contract
|
|
110
|
+
|
|
111
|
+
Release pipeline triggers on `v*` tags and expects:
|
|
112
|
+
|
|
113
|
+
- `NPM_TOKEN` GitHub secret present.
|
|
114
|
+
- npm package name in `package.json` is publishable under your account/org.
|
|
115
|
+
- repository URL matches the release origin used by `scripts/postinstall.js`.
|
|
116
|
+
|
|
117
|
+
## Guardrails
|
|
118
|
+
|
|
119
|
+
- Prefer additive changes; do not break the release asset naming contract unintentionally.
|
|
120
|
+
- If you change release artifacts or CLI binary name, update both workflow and postinstall script in the same PR.
|
|
121
|
+
- Keep agent-facing failures specific and actionable, especially for `procoder-return`.
|
|
122
|
+
- Favor integration tests for real Git behavior over mocked unit-only coverage for exchange flows.
|
package/CONTRIBUTORS.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CONTRIBUTORS.md
|
|
2
|
+
|
|
3
|
+
Maintainer notes for this template repository.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Go `1.26+`
|
|
8
|
+
- Node `18+`
|
|
9
|
+
- npm account with publish rights for the package name in `package.json`
|
|
10
|
+
- GitHub repo admin access
|
|
11
|
+
|
|
12
|
+
## Local development
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
make check
|
|
16
|
+
make build
|
|
17
|
+
make build-helper
|
|
18
|
+
./dist/procoder --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Install command locally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
make install-local
|
|
25
|
+
procoder --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`make install-local` installs both `procoder` and `procoder-return_linux_amd64` into `~/.local/bin`.
|
|
29
|
+
|
|
30
|
+
## Release process
|
|
31
|
+
|
|
32
|
+
1. Ensure `main` is green:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
make check
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Prepare release tag:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
make release-tag VERSION=0.1.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
3. GitHub Actions `release` workflow runs automatically:
|
|
45
|
+
- quality checks
|
|
46
|
+
- cross-platform `procoder` binary build
|
|
47
|
+
- packaged `procoder-return_linux_amd64` helper build
|
|
48
|
+
- GitHub release publish
|
|
49
|
+
- npm publish
|
|
50
|
+
|
|
51
|
+
## Required GitHub secret
|
|
52
|
+
|
|
53
|
+
- `NPM_TOKEN`: npm automation token with publish rights for your package.
|
|
54
|
+
|
|
55
|
+
Set via GitHub CLI:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gh secret set NPM_TOKEN --repo amxv/procoder
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## npm token setup
|
|
62
|
+
|
|
63
|
+
Create token at npm:
|
|
64
|
+
|
|
65
|
+
- Profile -> Access Tokens -> Create New Token
|
|
66
|
+
- Use an automation/granular token scoped to required package/org
|
|
67
|
+
|
|
68
|
+
Validate auth locally:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm whoami
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Notes on package naming
|
|
75
|
+
|
|
76
|
+
Before first publish, set a package name you control in `package.json`.
|
|
77
|
+
|
|
78
|
+
- Example unscoped: `"name": "your-cli-name"`
|
|
79
|
+
- Example scoped: `"name": "@your-scope/your-cli-name"`
|
package/Makefile
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
SHELL := /bin/bash
|
|
2
|
+
|
|
3
|
+
GO ?= go
|
|
4
|
+
GOFMT ?= gofmt
|
|
5
|
+
BIN_NAME ?= procoder
|
|
6
|
+
CMD_PATH ?= ./cmd/$(BIN_NAME)
|
|
7
|
+
HELPER_NAME ?= procoder-return
|
|
8
|
+
HELPER_GOOS ?= linux
|
|
9
|
+
HELPER_GOARCH ?= amd64
|
|
10
|
+
HELPER_ASSET ?= $(HELPER_NAME)_$(HELPER_GOOS)_$(HELPER_GOARCH)
|
|
11
|
+
HELPER_CMD_PATH ?= ./cmd/$(HELPER_NAME)
|
|
12
|
+
DIST_DIR ?= dist
|
|
13
|
+
BIN_PATH ?= $(DIST_DIR)/$(BIN_NAME)
|
|
14
|
+
HELPER_PATH ?= $(DIST_DIR)/$(HELPER_ASSET)
|
|
15
|
+
LDFLAGS ?= -s -w
|
|
16
|
+
|
|
17
|
+
.PHONY: help fmt test vet lint check build build-helper build-all install-local clean release-tag
|
|
18
|
+
|
|
19
|
+
help:
|
|
20
|
+
@echo "procoder command runner"
|
|
21
|
+
@echo ""
|
|
22
|
+
@echo "Targets:"
|
|
23
|
+
@echo " make fmt - format Go files"
|
|
24
|
+
@echo " make test - run go test ./..."
|
|
25
|
+
@echo " make vet - run go vet ./..."
|
|
26
|
+
@echo " make lint - run Node script checks"
|
|
27
|
+
@echo " make check - fmt + test + vet + lint"
|
|
28
|
+
@echo " make build - build local binary to dist/procoder"
|
|
29
|
+
@echo " make build-helper - build the linux/amd64 procoder-return helper asset"
|
|
30
|
+
@echo " make build-all - build release binaries for 5 target platforms plus helper asset"
|
|
31
|
+
@echo " make install-local - install CLI and helper to ~/.local/bin"
|
|
32
|
+
@echo " make clean - remove dist artifacts"
|
|
33
|
+
@echo " make release-tag - create and push git tag (requires VERSION=x.y.z)"
|
|
34
|
+
|
|
35
|
+
fmt:
|
|
36
|
+
@$(GOFMT) -w $$(find . -type f -name '*.go' -not -path './dist/*')
|
|
37
|
+
|
|
38
|
+
test:
|
|
39
|
+
@$(GO) test ./...
|
|
40
|
+
|
|
41
|
+
vet:
|
|
42
|
+
@$(GO) vet ./...
|
|
43
|
+
|
|
44
|
+
lint:
|
|
45
|
+
@npm run lint
|
|
46
|
+
|
|
47
|
+
check: fmt test vet lint
|
|
48
|
+
|
|
49
|
+
build:
|
|
50
|
+
@mkdir -p $(DIST_DIR)
|
|
51
|
+
@$(GO) build -trimpath -ldflags="$(LDFLAGS)" -o $(BIN_PATH) $(CMD_PATH)
|
|
52
|
+
|
|
53
|
+
build-helper:
|
|
54
|
+
@mkdir -p $(DIST_DIR)
|
|
55
|
+
@CGO_ENABLED=0 GOOS=$(HELPER_GOOS) GOARCH=$(HELPER_GOARCH) \
|
|
56
|
+
$(GO) build -trimpath -ldflags="$(LDFLAGS)" -o $(HELPER_PATH) $(HELPER_CMD_PATH)
|
|
57
|
+
|
|
58
|
+
build-all: build-helper
|
|
59
|
+
@mkdir -p $(DIST_DIR)
|
|
60
|
+
@for target in "darwin amd64" "darwin arm64" "linux amd64" "linux arm64" "windows amd64"; do \
|
|
61
|
+
set -- $$target; \
|
|
62
|
+
GOOS=$$1; GOARCH=$$2; \
|
|
63
|
+
EXT=""; \
|
|
64
|
+
if [ "$$GOOS" = "windows" ]; then EXT=".exe"; fi; \
|
|
65
|
+
echo "Building $(BIN_NAME) for $$GOOS/$$GOARCH"; \
|
|
66
|
+
CGO_ENABLED=0 GOOS=$$GOOS GOARCH=$$GOARCH $(GO) build -trimpath -ldflags="$(LDFLAGS)" -o "$(DIST_DIR)/$(BIN_NAME)_$$GOOS_$$GOARCH$$EXT" $(CMD_PATH); \
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
install-local: build build-helper
|
|
70
|
+
@mkdir -p $$HOME/.local/bin
|
|
71
|
+
@install -m 755 $(BIN_PATH) $$HOME/.local/bin/$(BIN_NAME)
|
|
72
|
+
@install -m 755 $(HELPER_PATH) $$HOME/.local/bin/$(HELPER_ASSET)
|
|
73
|
+
@echo "Installed $(BIN_NAME) to $$HOME/.local/bin/$(BIN_NAME)"
|
|
74
|
+
@echo "Installed $(HELPER_ASSET) to $$HOME/.local/bin/$(HELPER_ASSET)"
|
|
75
|
+
|
|
76
|
+
clean:
|
|
77
|
+
@rm -rf $(DIST_DIR)
|
|
78
|
+
|
|
79
|
+
release-tag:
|
|
80
|
+
@test -n "$(VERSION)" || (echo "Usage: make release-tag VERSION=x.y.z" && exit 1)
|
|
81
|
+
@git tag "v$(VERSION)"
|
|
82
|
+
@git push origin "v$(VERSION)"
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# procoder
|
|
2
|
+
|
|
3
|
+
`procoder` is a Go CLI for an offline Git exchange workflow between:
|
|
4
|
+
|
|
5
|
+
- a local developer repository
|
|
6
|
+
- a locked-down ChatGPT coding container
|
|
7
|
+
|
|
8
|
+
The default round trip is:
|
|
9
|
+
|
|
10
|
+
1. Run `procoder prepare` in a clean local repository.
|
|
11
|
+
2. Upload `./procoder-task-<exchange-id>.zip` to ChatGPT.
|
|
12
|
+
3. Work inside the exported repo, commit on the prepared task branch, and run `./procoder-return`.
|
|
13
|
+
4. Download `./procoder-return-<exchange-id>.zip`.
|
|
14
|
+
5. Run `procoder apply <return-package.zip>` in the source repository.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Global install via npm:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i -g procoder-cli
|
|
22
|
+
procoder --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The installer fetches two release assets when available:
|
|
26
|
+
|
|
27
|
+
- the host `procoder` binary
|
|
28
|
+
- the packaged `linux/amd64` `procoder-return` helper used inside prepared task packages
|
|
29
|
+
|
|
30
|
+
If those release assets are unavailable, `scripts/postinstall.js` falls back to local Go builds for both binaries.
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
### `procoder prepare`
|
|
35
|
+
|
|
36
|
+
Run `procoder prepare` from the root of a clean Git worktree:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
procoder prepare
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`prepare` does the following:
|
|
43
|
+
|
|
44
|
+
- validates that the repo is clean and has no untracked files, submodules, or Git LFS usage
|
|
45
|
+
- creates a local task branch at `refs/heads/procoder/<exchange-id>/task`
|
|
46
|
+
- writes the local exchange record to `.git/procoder/exchanges/<exchange-id>/exchange.json`
|
|
47
|
+
- builds a sanitized export repo that includes local heads and tags for read-only context
|
|
48
|
+
- injects the `procoder-return` helper at the exported repo root
|
|
49
|
+
- writes `./procoder-task-<exchange-id>.zip` in the source repo root
|
|
50
|
+
|
|
51
|
+
The current checkout is left unchanged. On success the command prints the task branch ref and the absolute task package path.
|
|
52
|
+
|
|
53
|
+
### `./procoder-return`
|
|
54
|
+
|
|
55
|
+
After ChatGPT has made one or more commits inside the exported repo, run the helper from that prepared task package:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
./procoder-return
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The helper:
|
|
62
|
+
|
|
63
|
+
- verifies that it is running inside a prepared task package
|
|
64
|
+
- rejects dirty worktrees, tag changes, and branch changes outside `refs/heads/procoder/<exchange-id>/*`
|
|
65
|
+
- bundles only task-family refs that descend from the prepared base commit
|
|
66
|
+
- writes `./procoder-return-<exchange-id>.zip` at the repo root
|
|
67
|
+
|
|
68
|
+
On success it prints the absolute zip path and a `sandbox:` hint that can be pasted back to the local user.
|
|
69
|
+
|
|
70
|
+
### `procoder apply`
|
|
71
|
+
|
|
72
|
+
Apply the returned work from the original source repo:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
procoder apply procoder-return-<exchange-id>.zip
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Supported flags:
|
|
79
|
+
|
|
80
|
+
- `--dry-run`: verify the return package and print the ref update plan without mutating refs
|
|
81
|
+
- `--namespace <prefix>`: import returned refs under `refs/heads/<prefix>/<exchange-id>/...`
|
|
82
|
+
- `--checkout`: check out the updated default task branch after a successful apply
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
procoder apply procoder-return-<exchange-id>.zip
|
|
88
|
+
procoder apply procoder-return-<exchange-id>.zip --dry-run
|
|
89
|
+
procoder apply procoder-return-<exchange-id>.zip --namespace procoder-import
|
|
90
|
+
procoder apply procoder-return-<exchange-id>.zip --checkout
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Default apply behavior is "update when safe, otherwise fail." If the prepared task branch moved locally, `procoder apply` fails with a `BRANCH_MOVED` error and suggests retrying with `--namespace`.
|
|
94
|
+
|
|
95
|
+
## Command Summary
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
procoder --help
|
|
99
|
+
procoder prepare
|
|
100
|
+
procoder apply <return-package.zip>
|
|
101
|
+
procoder apply <return-package.zip> --dry-run
|
|
102
|
+
procoder apply <return-package.zip> --namespace <prefix>
|
|
103
|
+
procoder apply <return-package.zip> --checkout
|
|
104
|
+
procoder version
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
Common local commands:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
make fmt
|
|
113
|
+
make test
|
|
114
|
+
make vet
|
|
115
|
+
make lint
|
|
116
|
+
make check
|
|
117
|
+
make build
|
|
118
|
+
make build-helper
|
|
119
|
+
make build-all
|
|
120
|
+
make install-local
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`make build-all` produces the release binaries for the host CLI targets plus the packaged helper asset `procoder-return_linux_amd64`.
|
|
124
|
+
|
|
125
|
+
See [AGENTS.md](AGENTS.md) and [CONTRIBUTORS.md](CONTRIBUTORS.md) for implementation and release details.
|
package/bin/procoder.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const pkg = require("../package.json");
|
|
8
|
+
const cliName = pkg.config?.cliBinaryName || "procoder";
|
|
9
|
+
const executableName = process.platform === "win32" ? `${cliName}.exe` : `${cliName}-bin`;
|
|
10
|
+
const executablePath = path.join(__dirname, executableName);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(executablePath)) {
|
|
13
|
+
console.error(`${cliName} binary is not installed. Re-run: npm rebuild -g ${pkg.name}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const child = spawnSync(executablePath, process.argv.slice(2), { stdio: "inherit" });
|
|
18
|
+
|
|
19
|
+
if (child.error) {
|
|
20
|
+
console.error(child.error.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (child.signal) {
|
|
25
|
+
process.kill(process.pid, child.signal);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.exit(child.status ?? 1);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
|
|
6
|
+
"github.com/amxv/procoder/internal/app"
|
|
7
|
+
"github.com/amxv/procoder/internal/output"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func main() {
|
|
11
|
+
if err := app.Run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
|
|
12
|
+
output.WriteError(os.Stderr, err)
|
|
13
|
+
os.Exit(1)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"os"
|
|
7
|
+
|
|
8
|
+
"github.com/amxv/procoder/internal/errs"
|
|
9
|
+
"github.com/amxv/procoder/internal/output"
|
|
10
|
+
"github.com/amxv/procoder/internal/returnpkg"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var version = "dev"
|
|
14
|
+
var runReturn = returnpkg.Run
|
|
15
|
+
|
|
16
|
+
func main() {
|
|
17
|
+
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
|
|
18
|
+
output.WriteError(os.Stderr, err)
|
|
19
|
+
os.Exit(1)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func run(args []string, stdout, stderr io.Writer) error {
|
|
24
|
+
_ = stderr
|
|
25
|
+
|
|
26
|
+
if len(args) > 0 && isHelpArg(args[0]) {
|
|
27
|
+
printHelp(stdout)
|
|
28
|
+
return nil
|
|
29
|
+
}
|
|
30
|
+
if len(args) > 0 {
|
|
31
|
+
return errs.New(
|
|
32
|
+
errs.CodeUnknownCommand,
|
|
33
|
+
fmt.Sprintf("unknown argument %q for `procoder-return`", args[0]),
|
|
34
|
+
errs.WithHint("run `procoder-return --help`"),
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
result, err := runReturn(returnpkg.Options{ToolVersion: version})
|
|
39
|
+
if err != nil {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
_, _ = fmt.Fprintln(stdout, returnpkg.FormatSuccess(result))
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func printHelp(w io.Writer) {
|
|
47
|
+
lines := []string{
|
|
48
|
+
"procoder-return - create a return package",
|
|
49
|
+
"",
|
|
50
|
+
"Usage:",
|
|
51
|
+
" procoder-return",
|
|
52
|
+
"",
|
|
53
|
+
"This command creates a return package inside a prepared task package.",
|
|
54
|
+
}
|
|
55
|
+
for _, line := range lines {
|
|
56
|
+
_, _ = io.WriteString(w, line+"\n")
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func isHelpArg(v string) bool {
|
|
61
|
+
switch v {
|
|
62
|
+
case "-h", "--help", "help":
|
|
63
|
+
return true
|
|
64
|
+
default:
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
|
|
8
|
+
"github.com/amxv/procoder/internal/errs"
|
|
9
|
+
"github.com/amxv/procoder/internal/returnpkg"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestPrintHelpTerminology(t *testing.T) {
|
|
13
|
+
var out bytes.Buffer
|
|
14
|
+
printHelp(&out)
|
|
15
|
+
|
|
16
|
+
got := out.String()
|
|
17
|
+
if !strings.Contains(got, "return package") {
|
|
18
|
+
t.Fatalf("expected return package terminology, got: %q", got)
|
|
19
|
+
}
|
|
20
|
+
if !strings.Contains(got, "prepared task package") {
|
|
21
|
+
t.Fatalf("expected prepared task package terminology, got: %q", got)
|
|
22
|
+
}
|
|
23
|
+
if strings.Contains(got, "remote handoff flow") {
|
|
24
|
+
t.Fatalf("unexpected stale wording in help output, got: %q", got)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func TestRunSuccessOutputIncludesSandboxHint(t *testing.T) {
|
|
29
|
+
originalRunReturn := runReturn
|
|
30
|
+
t.Cleanup(func() {
|
|
31
|
+
runReturn = originalRunReturn
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
runReturn = func(opts returnpkg.Options) (returnpkg.Result, error) {
|
|
35
|
+
if opts.ToolVersion == "" {
|
|
36
|
+
t.Fatal("expected tool version in returnpkg options")
|
|
37
|
+
}
|
|
38
|
+
return returnpkg.Result{
|
|
39
|
+
ExchangeID: "20260320-120000-a1b2c3",
|
|
40
|
+
ReturnPackagePath: "/tmp/procoder-return-20260320-120000-a1b2c3.zip",
|
|
41
|
+
}, nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var out bytes.Buffer
|
|
45
|
+
var errBuf bytes.Buffer
|
|
46
|
+
|
|
47
|
+
if err := run(nil, &out, &errBuf); err != nil {
|
|
48
|
+
t.Fatalf("run returned error: %v", err)
|
|
49
|
+
}
|
|
50
|
+
got := out.String()
|
|
51
|
+
if !strings.Contains(got, "Created return package.") {
|
|
52
|
+
t.Fatalf("expected success header, got %q", got)
|
|
53
|
+
}
|
|
54
|
+
if !strings.Contains(got, "/tmp/procoder-return-20260320-120000-a1b2c3.zip") {
|
|
55
|
+
t.Fatalf("expected absolute return package path, got %q", got)
|
|
56
|
+
}
|
|
57
|
+
if !strings.Contains(got, "sandbox:/tmp/procoder-return-20260320-120000-a1b2c3.zip") {
|
|
58
|
+
t.Fatalf("expected sandbox hint output, got %q", got)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func TestRunUnknownArgument(t *testing.T) {
|
|
63
|
+
var out bytes.Buffer
|
|
64
|
+
var errBuf bytes.Buffer
|
|
65
|
+
|
|
66
|
+
err := run([]string{"unexpected"}, &out, &errBuf)
|
|
67
|
+
if err == nil {
|
|
68
|
+
t.Fatal("expected error")
|
|
69
|
+
}
|
|
70
|
+
typed, ok := errs.As(err)
|
|
71
|
+
if !ok {
|
|
72
|
+
t.Fatalf("expected typed error, got %T", err)
|
|
73
|
+
}
|
|
74
|
+
if typed.Code != errs.CodeUnknownCommand {
|
|
75
|
+
t.Fatalf("unexpected code: got %s want %s", typed.Code, errs.CodeUnknownCommand)
|
|
76
|
+
}
|
|
77
|
+
}
|
package/go.mod
ADDED