createProject creates a GitHub Project V2
(ctx context.Context, ownerId, title string, verbose bool)
| 288 | |
| 289 | // createProject creates a GitHub Project V2 |
| 290 | func createProject(ctx context.Context, ownerId, title string, verbose bool) (map[string]any, error) { |
| 291 | projectLog.Printf("Creating project: ownerId=%s, title=%s", ownerId, title) |
| 292 | console.LogVerbose(verbose, "Creating project with owner ID: "+ownerId) |
| 293 | |
| 294 | mutation := `mutation($ownerId: ID!, $title: String!) { |
| 295 | createProjectV2(input: { ownerId: $ownerId, title: $title }) { |
| 296 | projectV2 { |
| 297 | id |
| 298 | number |
| 299 | title |
| 300 | url |
| 301 | } |
| 302 | } |
| 303 | }` |
| 304 | |
| 305 | output, err := workflow.RunGH("Creating project...", "api", "graphql", |
| 306 | "-f", "query="+mutation, |
| 307 | "-f", "ownerId="+ownerId, |
| 308 | "-f", "title="+title, |
| 309 | ) |
| 310 | if err != nil { |
| 311 | // Check for permission errors |
| 312 | //nolint:errstringmatch // gh CLI GraphQL surfaces missing Projects scope as INSUFFICIENT_SCOPES text. |
| 313 | if strings.Contains(err.Error(), "INSUFFICIENT_SCOPES") || errorutil.IsNotFoundError(err) { |
| 314 | return nil, errors.New("insufficient permissions. You need a PAT with Projects access (classic: 'project' scope, fine-grained: Organization → Projects: Read & Write). Set GH_AW_PROJECT_GITHUB_TOKEN or configure gh CLI with a suitable token") |
| 315 | } |
| 316 | return nil, fmt.Errorf("GraphQL mutation failed: %w", err) |
| 317 | } |
| 318 | |
| 319 | // Parse response |
| 320 | var response map[string]any |
| 321 | if err := json.Unmarshal(output, &response); err != nil { |
| 322 | return nil, fmt.Errorf("failed to parse GraphQL response: %w", err) |
| 323 | } |
| 324 | |
| 325 | // Extract project data |
| 326 | data, ok := response["data"].(map[string]any) |
| 327 | if !ok { |
| 328 | return nil, errors.New("invalid response: missing 'data' field") |
| 329 | } |
| 330 | |
| 331 | createResult, ok := data["createProjectV2"].(map[string]any) |
| 332 | if !ok { |
| 333 | return nil, errors.New("invalid response: missing 'createProjectV2' field") |
| 334 | } |
| 335 | |
| 336 | project, ok := createResult["projectV2"].(map[string]any) |
| 337 | if !ok { |
| 338 | return nil, errors.New("invalid response: missing 'projectV2' field") |
| 339 | } |
| 340 | |
| 341 | console.LogVerbose(verbose, fmt.Sprintf("✓ Project created: #%v", project["number"])) |
| 342 | return project, nil |
| 343 | } |
| 344 | |
| 345 | // linkProjectToRepo links a project to a repository |
| 346 | func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose bool) error { |
no test coverage detected