Skip to content

Commit 1dcf493

Browse files
committed
[#74316] add dry-run JSON WP mutations
Add parent-aware create dry-runs and schema-driven update dry-runs for work packages, both with machine-readable JSON output. Keep the legacy typed update flags intact while adding --set, --parent, --dry-run, and --json as the agent workflow surface. https://community.openproject.org/projects/cli/work_packages/74316
1 parent bef5727 commit 1dcf493

21 files changed

Lines changed: 1923 additions & 68 deletions

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ op update workpackage 42 --subject 'The new subject' --status 'In Progress' --ty
174174

175175
# Uploading an attachment to a work package
176176
op update workpackage 42 --attach ./Downloads/Report.pdf
177+
178+
# Resolving and validating field updates as JSON before applying them
179+
# This first slice supports schema-resolved custom fields via --set.
180+
op update workpackage 74316 --set 'Votes=3' --dry-run --json
177181
```
178182

179183
#### Inspecting
@@ -182,6 +186,9 @@ op update workpackage 42 --attach ./Downloads/Report.pdf
182186
# Inspecting a work package with more details,
183187
# then in the work package list command
184188
op inspect workpackage 42
189+
190+
# Inspecting a work package and its direct children as machine-readable JSON
191+
op inspect workpackage 74316 --children --json
185192
```
186193

187194
## Creating a release

cmd/create/create.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ func init() {
1616
0,
1717
"Project ID to create the work package in",
1818
)
19-
_ = createWorkPackageCmd.MarkFlagRequired("project")
2019

2120
createWorkPackageCmd.Flags().BoolVarP(
2221
&shouldOpenWorkPackageInBrowser,
@@ -34,5 +33,26 @@ func init() {
3433
"Change the work package type",
3534
)
3635

36+
createWorkPackageCmd.Flags().Uint64Var(
37+
&parentWorkPackageID,
38+
"parent",
39+
0,
40+
"Create the work package as a child of an existing work package",
41+
)
42+
43+
createWorkPackageCmd.Flags().BoolVar(
44+
&printCreatedWorkPackageAsJSON,
45+
"json",
46+
false,
47+
"Print machine-readable JSON output",
48+
)
49+
50+
createWorkPackageCmd.Flags().BoolVar(
51+
&dryRunCreateWorkPackage,
52+
"dry-run",
53+
false,
54+
"Resolve and validate without persisting the work package",
55+
)
56+
3757
RootCmd.AddCommand(createWorkPackageCmd)
3858
}

cmd/create/work_package.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ import (
55

66
"github.com/spf13/cobra"
77

8+
componentErrors "github.com/opf/openproject-cli/components/errors"
89
"github.com/opf/openproject-cli/components/launch"
10+
"github.com/opf/openproject-cli/components/presenter"
911
"github.com/opf/openproject-cli/components/printer"
1012
"github.com/opf/openproject-cli/components/resources/work_packages"
1113
"github.com/opf/openproject-cli/components/routes"
1214
)
1315

1416
var projectId uint64
17+
var parentWorkPackageID uint64
1518
var shouldOpenWorkPackageInBrowser bool
19+
var printCreatedWorkPackageAsJSON bool
20+
var dryRunCreateWorkPackage bool
1621
var typeFlag string
1722

1823
var createWorkPackageCmd = &cobra.Command{
@@ -24,14 +29,55 @@ var createWorkPackageCmd = &cobra.Command{
2429

2530
func createWorkPackage(_ *cobra.Command, args []string) {
2631
if len(args) != 1 {
27-
printer.ErrorText(fmt.Sprintf("Expected 1 argument [subject], but got %d", len(args)))
32+
printCreateError("invalid_argument", fmt.Sprintf("Expected 1 argument [subject], but got %d", len(args)))
33+
return
34+
}
35+
36+
if err := validateCreateWorkPackageFlags(); err != nil {
37+
printCreateError("conflicting_arguments", err.Error())
2838
return
2939
}
3040

3141
subject := args[0]
32-
workPackage, err := work_packages.Create(projectId, createOptions(subject))
42+
options := createOptions(subject)
43+
44+
if dryRunCreateWorkPackage {
45+
plan, err := work_packages.DryRunCreate(projectId, options)
46+
if err != nil {
47+
printCreateError(createErrorCode(err), err.Error())
48+
return
49+
}
50+
51+
data, err := presenter.MarshalJSON(plan)
52+
if err != nil {
53+
printer.Error(err)
54+
return
55+
}
56+
57+
printer.Info(string(data))
58+
return
59+
}
60+
61+
workPackage, err := work_packages.Create(projectId, options)
3362
if err != nil {
34-
printer.Error(err)
63+
printCreateError(createErrorCode(err), err.Error())
64+
return
65+
}
66+
67+
if printCreatedWorkPackageAsJSON {
68+
payload, err := work_packages.Inspect(workPackage.Id)
69+
if err != nil {
70+
printCreateError("post_apply_inspect_failed", err.Error())
71+
return
72+
}
73+
74+
data, err := presenter.MarshalJSON(payload)
75+
if err != nil {
76+
printer.Error(err)
77+
return
78+
}
79+
80+
printer.Info(string(data))
3581
return
3682
}
3783

@@ -54,5 +100,44 @@ func createOptions(subject string) map[work_packages.CreateOption]string {
54100
options[work_packages.CreateType] = typeFlag
55101
}
56102

103+
if parentWorkPackageID > 0 {
104+
options[work_packages.CreateParent] = fmt.Sprintf("%d", parentWorkPackageID)
105+
}
106+
57107
return options
58108
}
109+
110+
func validateCreateWorkPackageFlags() error {
111+
if shouldOpenWorkPackageInBrowser && printCreatedWorkPackageAsJSON {
112+
return fmt.Errorf("cannot use --open together with --json")
113+
}
114+
115+
if dryRunCreateWorkPackage && !printCreatedWorkPackageAsJSON {
116+
return fmt.Errorf("cannot use --dry-run without --json")
117+
}
118+
119+
return nil
120+
}
121+
122+
func printCreateError(code, message string) {
123+
if !printCreatedWorkPackageAsJSON {
124+
printer.ErrorText(message)
125+
return
126+
}
127+
128+
data, err := presenter.MarshalError(code, message)
129+
if err != nil {
130+
printer.Error(err)
131+
return
132+
}
133+
134+
printer.Info(string(data))
135+
}
136+
137+
func createErrorCode(err error) string {
138+
if componentErrors.IsCustom(err) {
139+
return "invalid_argument"
140+
}
141+
142+
return "api_error"
143+
}

0 commit comments

Comments
 (0)