savepoint 1.0.2 → 1.0.4
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/.claude/settings.local.json +12 -1
- package/.github/workflows/ci.yml +20 -0
- package/.golangci.yml +11 -0
- package/.savepoint/Design.md +40 -38
- package/.savepoint/{audit/v1.1/E02-cross-platform-compatibility/proposals.md → releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md} +48 -38
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +195 -0
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +14 -1
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +3 -3
- package/.savepoint/{audit/v1.1/E04-epic-navigation/proposals.md → releases/v1.1/epics/E04-epic-navigation/E04-Audit.md} +65 -54
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +237 -0
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +25 -16
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +17 -6
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +15 -5
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +19 -5
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +11 -1
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +9 -6
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +29 -13
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +56 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +63 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +44 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +35 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +40 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +45 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +26 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +33 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +34 -0
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +33 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +336 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +61 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +37 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +28 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +46 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +27 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +25 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +26 -0
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +26 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +333 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +68 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +26 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +27 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +28 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +27 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +29 -0
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +27 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +207 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +65 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +24 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +28 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +29 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +27 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +28 -0
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +31 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +36 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +25 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +21 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +22 -0
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +29 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +444 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +45 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +35 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +19 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +29 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +25 -0
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +37 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +118 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +73 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +66 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +35 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +60 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +33 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +37 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +40 -0
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +37 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +267 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +54 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +39 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +42 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +33 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +35 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +27 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +29 -0
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +28 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Audit.md +272 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +60 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +31 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +34 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +30 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +27 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +28 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +26 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +33 -0
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T008-ci-and-release-automation.md +46 -0
- package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +25 -0
- package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +27 -0
- package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +27 -0
- package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +29 -0
- package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +31 -0
- package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +34 -0
- package/.savepoint/releases/v1.1/v1.1-PRD.md +67 -7
- package/.savepoint/router.md +10 -17
- package/AGENTS.md +39 -24
- package/Makefile +3 -1
- package/README.md +0 -1
- package/agent-skills/savepoint-audit/SKILL.md +86 -34
- package/agent-skills/savepoint-build-task/SKILL.md +7 -2
- package/agent-skills/savepoint-create-plan/SKILL.md +7 -2
- package/agent-skills/savepoint-create-task/SKILL.md +44 -31
- package/agent-skills/savepoint-draft-prd/SKILL.md +7 -2
- package/agent-skills/savepoint-system-design/SKILL.md +7 -2
- package/agent_skills_test.go +91 -0
- package/cmd/board.go +59 -0
- package/cmd/board_test.go +137 -0
- package/cmd/doctor.go +53 -0
- package/cmd/doctor_test.go +146 -0
- package/cmd/init.go +63 -0
- package/cmd/init_test.go +104 -0
- package/internal/board/board.go +44 -36
- package/internal/board/board_test.go +27 -82
- package/internal/board/card.go +43 -23
- package/internal/board/card_test.go +74 -5
- package/internal/board/column.go +75 -15
- package/internal/board/column_test.go +76 -2
- package/internal/board/debug.go +26 -0
- package/internal/board/debug_test.go +108 -0
- package/internal/board/detail.go +33 -47
- package/internal/board/detail_test.go +48 -0
- package/internal/board/epic_panel.go +120 -22
- package/internal/board/epic_panel_test.go +302 -17
- package/internal/board/help.go +1 -0
- package/internal/board/help_test.go +1 -0
- package/internal/board/integration_test.go +266 -0
- package/internal/board/interfaces.go +65 -0
- package/internal/board/interfaces_test.go +114 -0
- package/internal/board/io.go +93 -0
- package/internal/board/model.go +79 -118
- package/internal/board/plain.go +88 -0
- package/internal/board/plain_test.go +117 -0
- package/internal/board/release.go +1 -9
- package/internal/board/release_test.go +6 -6
- package/internal/board/status.go +4 -4
- package/internal/board/theme.go +24 -0
- package/internal/board/theme_test.go +31 -0
- package/internal/board/transitions.go +113 -88
- package/internal/board/transitions_test.go +164 -141
- package/internal/board/tui.go +32 -0
- package/internal/board/update.go +344 -215
- package/internal/board/update_test.go +326 -18
- package/internal/board/util.go +76 -0
- package/internal/board/view.go +31 -28
- package/internal/board/view_test.go +74 -2
- package/internal/board/watch.go +41 -5
- package/internal/buildtool/main.go +45 -15
- package/internal/buildtool/main_test.go +224 -0
- package/internal/data/config.go +17 -3
- package/internal/data/config_test.go +49 -0
- package/internal/data/discover.go +26 -0
- package/internal/data/discover_test.go +34 -10
- package/internal/data/errors.go +4 -0
- package/internal/data/fuzz_test.go +75 -0
- package/internal/data/lifecycle.go +13 -6
- package/internal/data/lifecycle_test.go +14 -11
- package/internal/data/parser.go +22 -6
- package/internal/data/parser_test.go +31 -7
- package/internal/data/task.go +0 -9
- package/internal/data/testdata/fuzz/FuzzSplitFrontmatterBody/68eb66b0fe91e7e3 +2 -0
- package/internal/data/write.go +88 -11
- package/internal/data/write_test.go +167 -0
- package/internal/doctor/checks.go +567 -0
- package/internal/doctor/checks_test.go +716 -0
- package/internal/doctor/gates.go +193 -0
- package/internal/doctor/gates_test.go +166 -0
- package/internal/doctor/interfaces.go +64 -0
- package/internal/doctor/interfaces_test.go +104 -0
- package/internal/doctor/repairs.go +80 -0
- package/internal/doctor/repairs_test.go +81 -0
- package/internal/doctor/report.go +157 -0
- package/internal/doctor/report_test.go +89 -0
- package/internal/init/clipboard.go +146 -0
- package/internal/init/clipboard_test.go +74 -0
- package/internal/init/install.go +16 -0
- package/internal/init/integration_test.go +197 -0
- package/internal/init/prompt.go +14 -0
- package/internal/init/prompt_test.go +77 -0
- package/internal/init/scaffold.go +59 -0
- package/internal/init/scaffold_test.go +179 -0
- package/internal/init/template_freshness_test.go +56 -0
- package/internal/init/validate.go +85 -0
- package/internal/init/validate_test.go +141 -0
- package/internal/init/write.go +73 -0
- package/internal/init/write_test.go +91 -0
- package/internal/styles/styles_test.go +133 -0
- package/internal/testutil/fixture.go +113 -0
- package/internal/testutil/fs.go +26 -0
- package/main.go +120 -4
- package/package.json +2 -2
- package/project-audit/audit_report_glm_5.1.md +411 -0
- package/project-audit/audit_report_opus_4.6.md +406 -0
- package/project-audit/consolidated-audit-report.md +456 -0
- package/templates/project/.savepoint/Design.md +2 -2
- package/templates/project/.savepoint/router.md +10 -10
- package/templates/project/AGENTS.md +33 -21
- package/templates/project/agent-skills/savepoint-audit/SKILL.md +87 -0
- package/templates/project/agent-skills/savepoint-build-task/SKILL.md +44 -0
- package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +33 -0
- package/templates/project/agent-skills/savepoint-create-task/SKILL.md +44 -0
- package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +37 -0
- package/templates/project/agent-skills/savepoint-system-design/SKILL.md +38 -0
- package/templates/prompts/audit-reconciliation.prompt.md +33 -28
- package/templates/prompts/design.prompt.md +3 -1
- package/.savepoint/audit/v1/E01/proposals.md +0 -168
- package/.savepoint/audit/v1/E01/snapshot.md +0 -78
- package/.savepoint/audit/v1/E01-go-setup/proposals.md +0 -166
- package/.savepoint/audit/v1/E01-go-setup/snapshot.md +0 -71
- package/.savepoint/audit/v1/E01-scaffolding/proposals/AGENTS.md +0 -66
- package/.savepoint/audit/v1/E01-scaffolding/proposals/Design.md +0 -210
- package/.savepoint/audit/v1/E01-scaffolding/proposals/epic-Design.md +0 -117
- package/.savepoint/audit/v1/E01-scaffolding/proposals/quality-review.md +0 -101
- package/.savepoint/audit/v1/E01-scaffolding/snapshot.md +0 -54
- package/.savepoint/audit/v1/E02-data-model/snapshot.md +0 -128
- package/.savepoint/audit/v1/E02-data-readers/proposals.md +0 -123
- package/.savepoint/audit/v1/E02-data-readers/snapshot.md +0 -54
- package/.savepoint/audit/v1/E03-board-tui-core/proposals.md +0 -146
- package/.savepoint/audit/v1/E03-board-tui-core/snapshot.md +0 -57
- package/.savepoint/audit/v1/E03-cli-foundation/snapshot.md +0 -106
- package/.savepoint/audit/v1/E04-board-components/proposals.md +0 -118
- package/.savepoint/audit/v1/E04-board-components/snapshot.md +0 -77
- package/.savepoint/audit/v1/E04-templates-and-prompts/snapshot.md +0 -115
- package/.savepoint/audit/v1/E05-init-command/snapshot.md +0 -125
- package/.savepoint/audit/v1/E05-phase-transitions/proposals.md +0 -83
- package/.savepoint/audit/v1/E05-phase-transitions/snapshot.md +0 -36
- package/.savepoint/audit/v1/E06-atari-noir-layout/proposals.md +0 -130
- package/.savepoint/audit/v1/E06-atari-noir-layout/snapshot.md +0 -84
- package/.savepoint/audit/v1/E06-tui-board/snapshot.md +0 -64
- package/.savepoint/audit/v1/E07-audit-pipeline/snapshot.md +0 -165
- package/.savepoint/audit/v1/E08-board-workflow-cleanup/snapshot.md +0 -65
- package/.savepoint/audit/v1.1/E02-cross-platform-compatibility/snapshot.md +0 -41
- package/.savepoint/audit/v1.1/E04-epic-navigation/snapshot.md +0 -48
- package/ink-cli-ui-design.zip +0 -0
- package/savepoint +0 -0
- package/savepoint.exe +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package main_test
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestBundledSavepointSkillsHaveDiscoveryFrontmatter(t *testing.T) {
|
|
11
|
+
assertSavepointSkillsHaveFrontmatter(t, filepath.Join("agent-skills"))
|
|
12
|
+
assertSavepointSkillsHaveFrontmatter(t, filepath.Join("templates", "project", "agent-skills"))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func TestProjectAgentGuideIncludesLocalSkillFallback(t *testing.T) {
|
|
16
|
+
path := filepath.Join("templates", "project", "AGENTS.md")
|
|
17
|
+
content, err := os.ReadFile(path)
|
|
18
|
+
if err != nil {
|
|
19
|
+
t.Fatalf("ReadFile(%q) error = %v", path, err)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
want := "If the agent says the skill is not found, read `agent-skills/{skill}/SKILL.md` directly"
|
|
23
|
+
if !strings.Contains(string(content), want) {
|
|
24
|
+
t.Fatalf("%s missing local skill fallback instruction", path)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func TestScaffoldedSavepointSkillsMatchBundledSkills(t *testing.T) {
|
|
29
|
+
root := filepath.Join("agent-skills")
|
|
30
|
+
entries, err := os.ReadDir(root)
|
|
31
|
+
if err != nil {
|
|
32
|
+
t.Fatalf("ReadDir(%q) error = %v", root, err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for _, entry := range entries {
|
|
36
|
+
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "savepoint-") {
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sourcePath := filepath.Join(root, entry.Name(), "SKILL.md")
|
|
41
|
+
scaffoldPath := filepath.Join("templates", "project", "agent-skills", entry.Name(), "SKILL.md")
|
|
42
|
+
source, err := os.ReadFile(sourcePath)
|
|
43
|
+
if err != nil {
|
|
44
|
+
t.Fatalf("ReadFile(%q) error = %v", sourcePath, err)
|
|
45
|
+
}
|
|
46
|
+
scaffold, err := os.ReadFile(scaffoldPath)
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatalf("ReadFile(%q) error = %v", scaffoldPath, err)
|
|
49
|
+
}
|
|
50
|
+
if string(scaffold) != string(source) {
|
|
51
|
+
t.Fatalf("%s does not match %s", scaffoldPath, sourcePath)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func assertSavepointSkillsHaveFrontmatter(t *testing.T, root string) {
|
|
57
|
+
t.Helper()
|
|
58
|
+
|
|
59
|
+
entries, err := os.ReadDir(root)
|
|
60
|
+
if err != nil {
|
|
61
|
+
t.Fatalf("ReadDir(%q) error = %v", root, err)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var found int
|
|
65
|
+
for _, entry := range entries {
|
|
66
|
+
if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "savepoint-") {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
found++
|
|
70
|
+
path := filepath.Join(root, entry.Name(), "SKILL.md")
|
|
71
|
+
content, err := os.ReadFile(path)
|
|
72
|
+
if err != nil {
|
|
73
|
+
t.Fatalf("ReadFile(%q) error = %v", path, err)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
text := string(content)
|
|
77
|
+
if !strings.HasPrefix(text, "---\n") {
|
|
78
|
+
t.Fatalf("%s missing YAML frontmatter", path)
|
|
79
|
+
}
|
|
80
|
+
if !strings.Contains(text, "name: "+entry.Name()) {
|
|
81
|
+
t.Fatalf("%s frontmatter name does not match directory", path)
|
|
82
|
+
}
|
|
83
|
+
if !strings.Contains(text, "description:") {
|
|
84
|
+
t.Fatalf("%s missing frontmatter description", path)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if found == 0 {
|
|
89
|
+
t.Fatalf("%s contains %d savepoint skills, want at least 1", root, found)
|
|
90
|
+
}
|
|
91
|
+
}
|
package/cmd/board.go
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
const boardUsage = "Usage: board [--release <release>] [--epic <epic>]"
|
|
10
|
+
|
|
11
|
+
type BoardOptions struct {
|
|
12
|
+
Release string
|
|
13
|
+
Epic string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type BoardRunner func(BoardOptions) error
|
|
17
|
+
|
|
18
|
+
func RunBoard(ctx context.Context, args []string, stdout io.Writer, runner BoardRunner) error {
|
|
19
|
+
options, help, err := ParseBoardArgs(args)
|
|
20
|
+
if help {
|
|
21
|
+
_, writeErr := fmt.Fprintln(stdout, boardUsage)
|
|
22
|
+
return writeErr
|
|
23
|
+
}
|
|
24
|
+
if err != nil {
|
|
25
|
+
return err
|
|
26
|
+
}
|
|
27
|
+
return runner(options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func ParseBoardArgs(args []string) (BoardOptions, bool, error) {
|
|
31
|
+
var options BoardOptions
|
|
32
|
+
|
|
33
|
+
for i := 0; i < len(args); i++ {
|
|
34
|
+
arg := args[i]
|
|
35
|
+
switch arg {
|
|
36
|
+
case "--help":
|
|
37
|
+
return BoardOptions{}, true, nil
|
|
38
|
+
case "--release":
|
|
39
|
+
i++
|
|
40
|
+
if i >= len(args) {
|
|
41
|
+
return BoardOptions{}, false, fmt.Errorf("--release requires a value")
|
|
42
|
+
}
|
|
43
|
+
options.Release = args[i]
|
|
44
|
+
case "--epic":
|
|
45
|
+
i++
|
|
46
|
+
if i >= len(args) {
|
|
47
|
+
return BoardOptions{}, false, fmt.Errorf("--epic requires a value")
|
|
48
|
+
}
|
|
49
|
+
options.Epic = args[i]
|
|
50
|
+
default:
|
|
51
|
+
if len(arg) > 0 && arg[0] == '-' {
|
|
52
|
+
return BoardOptions{}, false, fmt.Errorf("unknown board flag %q", arg)
|
|
53
|
+
}
|
|
54
|
+
return BoardOptions{}, false, fmt.Errorf("board takes no positional arguments, got %q", arg)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return options, false, nil
|
|
59
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"errors"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestRunBoardHelp(t *testing.T) {
|
|
12
|
+
var stdout bytes.Buffer
|
|
13
|
+
called := false
|
|
14
|
+
|
|
15
|
+
err := RunBoard(context.Background(), []string{"--help"}, &stdout, func(BoardOptions) error {
|
|
16
|
+
called = true
|
|
17
|
+
return nil
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if err != nil {
|
|
21
|
+
t.Fatalf("RunBoard() error = %v", err)
|
|
22
|
+
}
|
|
23
|
+
if called {
|
|
24
|
+
t.Fatal("RunBoard() called runner for help")
|
|
25
|
+
}
|
|
26
|
+
if !strings.Contains(stdout.String(), "board [--release <release>] [--epic <epic>]") {
|
|
27
|
+
t.Fatalf("help output = %q", stdout.String())
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func TestRunBoardNoArgs(t *testing.T) {
|
|
32
|
+
got := runBoardOptions(t, nil)
|
|
33
|
+
|
|
34
|
+
if got.Release != "" {
|
|
35
|
+
t.Fatalf("Release = %q, want empty", got.Release)
|
|
36
|
+
}
|
|
37
|
+
if got.Epic != "" {
|
|
38
|
+
t.Fatalf("Epic = %q, want empty", got.Epic)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func TestRunBoardRelease(t *testing.T) {
|
|
43
|
+
got := runBoardOptions(t, []string{"--release", "v1"})
|
|
44
|
+
|
|
45
|
+
if got.Release != "v1" {
|
|
46
|
+
t.Fatalf("Release = %q, want v1", got.Release)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func TestRunBoardEpic(t *testing.T) {
|
|
51
|
+
got := runBoardOptions(t, []string{"--epic", "E03"})
|
|
52
|
+
|
|
53
|
+
if got.Epic != "E03" {
|
|
54
|
+
t.Fatalf("Epic = %q, want E03", got.Epic)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func TestRunBoardReleaseAndEpic(t *testing.T) {
|
|
59
|
+
got := runBoardOptions(t, []string{"--release", "v1", "--epic", "E03"})
|
|
60
|
+
|
|
61
|
+
if got.Release != "v1" {
|
|
62
|
+
t.Fatalf("Release = %q, want v1", got.Release)
|
|
63
|
+
}
|
|
64
|
+
if got.Epic != "E03" {
|
|
65
|
+
t.Fatalf("Epic = %q, want E03", got.Epic)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func TestRunBoardRejectsUnknownFlag(t *testing.T) {
|
|
70
|
+
var stdout bytes.Buffer
|
|
71
|
+
|
|
72
|
+
err := RunBoard(context.Background(), []string{"--bogus"}, &stdout, func(BoardOptions) error {
|
|
73
|
+
return nil
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if err == nil {
|
|
77
|
+
t.Fatal("RunBoard() error = nil, want unknown flag error")
|
|
78
|
+
}
|
|
79
|
+
if !strings.Contains(err.Error(), "unknown board flag") {
|
|
80
|
+
t.Fatalf("error = %q, want unknown flag", err.Error())
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func TestRunBoardRejectsPositionalArgs(t *testing.T) {
|
|
85
|
+
var stdout bytes.Buffer
|
|
86
|
+
|
|
87
|
+
err := RunBoard(context.Background(), []string{"extra"}, &stdout, func(BoardOptions) error {
|
|
88
|
+
return nil
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if err == nil {
|
|
92
|
+
t.Fatal("RunBoard() error = nil, want positional arg error")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func TestRunBoardReleaseMissingValue(t *testing.T) {
|
|
97
|
+
var stdout bytes.Buffer
|
|
98
|
+
|
|
99
|
+
err := RunBoard(context.Background(), []string{"--release"}, &stdout, func(BoardOptions) error {
|
|
100
|
+
return nil
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if err == nil {
|
|
104
|
+
t.Fatal("RunBoard() error = nil, want missing value error")
|
|
105
|
+
}
|
|
106
|
+
if !strings.Contains(err.Error(), "--release requires a value") {
|
|
107
|
+
t.Fatalf("error = %q", err.Error())
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func TestRunBoardReturnsRunnerError(t *testing.T) {
|
|
112
|
+
want := errors.New("runner failed")
|
|
113
|
+
var stdout bytes.Buffer
|
|
114
|
+
|
|
115
|
+
err := RunBoard(context.Background(), nil, &stdout, func(BoardOptions) error {
|
|
116
|
+
return want
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if !errors.Is(err, want) {
|
|
120
|
+
t.Fatalf("RunBoard() error = %v, want %v", err, want)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func runBoardOptions(t *testing.T, args []string) BoardOptions {
|
|
125
|
+
t.Helper()
|
|
126
|
+
|
|
127
|
+
var stdout bytes.Buffer
|
|
128
|
+
var got BoardOptions
|
|
129
|
+
err := RunBoard(context.Background(), args, &stdout, func(options BoardOptions) error {
|
|
130
|
+
got = options
|
|
131
|
+
return nil
|
|
132
|
+
})
|
|
133
|
+
if err != nil {
|
|
134
|
+
t.Fatalf("RunBoard() error = %v", err)
|
|
135
|
+
}
|
|
136
|
+
return got
|
|
137
|
+
}
|
package/cmd/doctor.go
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
const doctorUsage = "Usage: doctor [--epic <epic>]"
|
|
10
|
+
|
|
11
|
+
type DoctorOptions struct {
|
|
12
|
+
Epic string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// DoctorRunner receives parsed options and returns an exit code: 0=clean, 1=problems, 2=internal error.
|
|
16
|
+
type DoctorRunner func(DoctorOptions) (int, error)
|
|
17
|
+
|
|
18
|
+
func RunDoctor(ctx context.Context, args []string, stdout io.Writer, runner DoctorRunner) (int, error) {
|
|
19
|
+
options, help, err := ParseDoctorArgs(args)
|
|
20
|
+
if help {
|
|
21
|
+
_, writeErr := fmt.Fprintln(stdout, doctorUsage)
|
|
22
|
+
return 0, writeErr
|
|
23
|
+
}
|
|
24
|
+
if err != nil {
|
|
25
|
+
return 2, err
|
|
26
|
+
}
|
|
27
|
+
return runner(options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func ParseDoctorArgs(args []string) (DoctorOptions, bool, error) {
|
|
31
|
+
var options DoctorOptions
|
|
32
|
+
|
|
33
|
+
for i := 0; i < len(args); i++ {
|
|
34
|
+
arg := args[i]
|
|
35
|
+
switch arg {
|
|
36
|
+
case "--help":
|
|
37
|
+
return DoctorOptions{}, true, nil
|
|
38
|
+
case "--epic":
|
|
39
|
+
i++
|
|
40
|
+
if i >= len(args) {
|
|
41
|
+
return DoctorOptions{}, false, fmt.Errorf("--epic requires a value")
|
|
42
|
+
}
|
|
43
|
+
options.Epic = args[i]
|
|
44
|
+
default:
|
|
45
|
+
if len(arg) > 0 && arg[0] == '-' {
|
|
46
|
+
return DoctorOptions{}, false, fmt.Errorf("unknown doctor flag %q", arg)
|
|
47
|
+
}
|
|
48
|
+
return DoctorOptions{}, false, fmt.Errorf("doctor takes no positional arguments, got %q", arg)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return options, false, nil
|
|
53
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestRunDoctorHelp(t *testing.T) {
|
|
11
|
+
var stdout bytes.Buffer
|
|
12
|
+
called := false
|
|
13
|
+
|
|
14
|
+
code, err := RunDoctor(context.Background(), []string{"--help"}, &stdout, func(DoctorOptions) (int, error) {
|
|
15
|
+
called = true
|
|
16
|
+
return 0, nil
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if err != nil {
|
|
20
|
+
t.Fatalf("RunDoctor() error = %v", err)
|
|
21
|
+
}
|
|
22
|
+
if called {
|
|
23
|
+
t.Fatal("RunDoctor() called runner for help")
|
|
24
|
+
}
|
|
25
|
+
if code != 0 {
|
|
26
|
+
t.Fatalf("RunDoctor() code = %d, want 0", code)
|
|
27
|
+
}
|
|
28
|
+
if !strings.Contains(stdout.String(), "doctor [--epic <epic>]") {
|
|
29
|
+
t.Fatalf("help output = %q", stdout.String())
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestRunDoctorNoArgs(t *testing.T) {
|
|
34
|
+
got := runDoctorOptions(t, nil)
|
|
35
|
+
|
|
36
|
+
if got.Epic != "" {
|
|
37
|
+
t.Fatalf("Epic = %q, want empty", got.Epic)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func TestRunDoctorEpic(t *testing.T) {
|
|
42
|
+
got := runDoctorOptions(t, []string{"--epic", "E03"})
|
|
43
|
+
|
|
44
|
+
if got.Epic != "E03" {
|
|
45
|
+
t.Fatalf("Epic = %q, want E03", got.Epic)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func TestRunDoctorEpicMissingValue(t *testing.T) {
|
|
50
|
+
var stdout bytes.Buffer
|
|
51
|
+
|
|
52
|
+
code, err := RunDoctor(context.Background(), []string{"--epic"}, &stdout, func(DoctorOptions) (int, error) {
|
|
53
|
+
return 0, nil
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if err == nil {
|
|
57
|
+
t.Fatal("RunDoctor() error = nil, want missing value error")
|
|
58
|
+
}
|
|
59
|
+
if !strings.Contains(err.Error(), "--epic requires a value") {
|
|
60
|
+
t.Fatalf("error = %q", err.Error())
|
|
61
|
+
}
|
|
62
|
+
if code != 2 {
|
|
63
|
+
t.Fatalf("code = %d, want 2", code)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func TestRunDoctorRejectsUnknownFlag(t *testing.T) {
|
|
68
|
+
var stdout bytes.Buffer
|
|
69
|
+
|
|
70
|
+
code, err := RunDoctor(context.Background(), []string{"--bogus"}, &stdout, func(DoctorOptions) (int, error) {
|
|
71
|
+
return 0, nil
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if err == nil {
|
|
75
|
+
t.Fatal("RunDoctor() error = nil, want unknown flag error")
|
|
76
|
+
}
|
|
77
|
+
if !strings.Contains(err.Error(), "unknown doctor flag") {
|
|
78
|
+
t.Fatalf("error = %q", err.Error())
|
|
79
|
+
}
|
|
80
|
+
if code != 2 {
|
|
81
|
+
t.Fatalf("code = %d, want 2", code)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func TestRunDoctorRejectsPositionalArgs(t *testing.T) {
|
|
86
|
+
var stdout bytes.Buffer
|
|
87
|
+
|
|
88
|
+
code, err := RunDoctor(context.Background(), []string{"extra"}, &stdout, func(DoctorOptions) (int, error) {
|
|
89
|
+
return 0, nil
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if err == nil {
|
|
93
|
+
t.Fatal("RunDoctor() error = nil, want positional arg error")
|
|
94
|
+
}
|
|
95
|
+
if code != 2 {
|
|
96
|
+
t.Fatalf("code = %d, want 2", code)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func TestRunDoctorExitCode1(t *testing.T) {
|
|
101
|
+
var stdout bytes.Buffer
|
|
102
|
+
|
|
103
|
+
code, err := RunDoctor(context.Background(), nil, &stdout, func(DoctorOptions) (int, error) {
|
|
104
|
+
return 1, nil
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if err != nil {
|
|
108
|
+
t.Fatalf("RunDoctor() error = %v", err)
|
|
109
|
+
}
|
|
110
|
+
if code != 1 {
|
|
111
|
+
t.Fatalf("code = %d, want 1", code)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func TestRunDoctorExitCode2(t *testing.T) {
|
|
116
|
+
var stdout bytes.Buffer
|
|
117
|
+
|
|
118
|
+
code, err := RunDoctor(context.Background(), nil, &stdout, func(DoctorOptions) (int, error) {
|
|
119
|
+
return 2, nil
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if err != nil {
|
|
123
|
+
t.Fatalf("RunDoctor() error = %v", err)
|
|
124
|
+
}
|
|
125
|
+
if code != 2 {
|
|
126
|
+
t.Fatalf("code = %d, want 2", code)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func runDoctorOptions(t *testing.T, args []string) DoctorOptions {
|
|
131
|
+
t.Helper()
|
|
132
|
+
|
|
133
|
+
var stdout bytes.Buffer
|
|
134
|
+
var got DoctorOptions
|
|
135
|
+
code, err := RunDoctor(context.Background(), args, &stdout, func(options DoctorOptions) (int, error) {
|
|
136
|
+
got = options
|
|
137
|
+
return 0, nil
|
|
138
|
+
})
|
|
139
|
+
if err != nil {
|
|
140
|
+
t.Fatalf("RunDoctor() error = %v", err)
|
|
141
|
+
}
|
|
142
|
+
if code != 0 {
|
|
143
|
+
t.Fatalf("RunDoctor() code = %d, want 0", code)
|
|
144
|
+
}
|
|
145
|
+
return got
|
|
146
|
+
}
|
package/cmd/init.go
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const initUsage = "Usage: init [dir] [--force] [--install]"
|
|
11
|
+
|
|
12
|
+
type InitOptions struct {
|
|
13
|
+
Dir string
|
|
14
|
+
Force bool
|
|
15
|
+
Install bool
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type InitRunner func(context.Context, InitOptions) error
|
|
19
|
+
|
|
20
|
+
var ErrInitNotImplemented = errors.New("init scaffold is not implemented yet")
|
|
21
|
+
|
|
22
|
+
func RunInit(ctx context.Context, args []string, stdout io.Writer, runner InitRunner) error {
|
|
23
|
+
options, help, err := ParseInitArgs(args)
|
|
24
|
+
if help {
|
|
25
|
+
_, writeErr := fmt.Fprintln(stdout, initUsage)
|
|
26
|
+
return writeErr
|
|
27
|
+
}
|
|
28
|
+
if err != nil {
|
|
29
|
+
return err
|
|
30
|
+
}
|
|
31
|
+
return runner(ctx, options)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func ParseInitArgs(args []string) (InitOptions, bool, error) {
|
|
35
|
+
options := InitOptions{Dir: "."}
|
|
36
|
+
var dirSet bool
|
|
37
|
+
|
|
38
|
+
for _, arg := range args {
|
|
39
|
+
switch arg {
|
|
40
|
+
case "--help":
|
|
41
|
+
return InitOptions{}, true, nil
|
|
42
|
+
case "--force":
|
|
43
|
+
options.Force = true
|
|
44
|
+
case "--install":
|
|
45
|
+
options.Install = true
|
|
46
|
+
default:
|
|
47
|
+
if len(arg) > 0 && arg[0] == '-' {
|
|
48
|
+
return InitOptions{}, false, fmt.Errorf("unknown init flag %q", arg)
|
|
49
|
+
}
|
|
50
|
+
if dirSet {
|
|
51
|
+
return InitOptions{}, false, fmt.Errorf("init accepts at most one directory")
|
|
52
|
+
}
|
|
53
|
+
options.Dir = arg
|
|
54
|
+
dirSet = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return options, false, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func InitNotImplemented(context.Context, InitOptions) error {
|
|
62
|
+
return ErrInitNotImplemented
|
|
63
|
+
}
|
package/cmd/init_test.go
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"errors"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestRunInitHelp(t *testing.T) {
|
|
12
|
+
var stdout bytes.Buffer
|
|
13
|
+
called := false
|
|
14
|
+
|
|
15
|
+
err := RunInit(context.Background(), []string{"--help"}, &stdout, func(context.Context, InitOptions) error {
|
|
16
|
+
called = true
|
|
17
|
+
return nil
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if err != nil {
|
|
21
|
+
t.Fatalf("RunInit() error = %v", err)
|
|
22
|
+
}
|
|
23
|
+
if called {
|
|
24
|
+
t.Fatal("RunInit() called runner for help")
|
|
25
|
+
}
|
|
26
|
+
if !strings.Contains(stdout.String(), "Usage: init [dir] [--force] [--install]") {
|
|
27
|
+
t.Fatalf("help output = %q", stdout.String())
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func TestRunInitDefaultsToCurrentDirectory(t *testing.T) {
|
|
32
|
+
got := runInitOptions(t, nil)
|
|
33
|
+
|
|
34
|
+
if got.Dir != "." {
|
|
35
|
+
t.Fatalf("Dir = %q, want .", got.Dir)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func TestRunInitUsesSpecifiedDirectory(t *testing.T) {
|
|
40
|
+
got := runInitOptions(t, []string{"example"})
|
|
41
|
+
|
|
42
|
+
if got.Dir != "example" {
|
|
43
|
+
t.Fatalf("Dir = %q, want example", got.Dir)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func TestRunInitParsesForceAndInstall(t *testing.T) {
|
|
48
|
+
got := runInitOptions(t, []string{"example", "--force", "--install"})
|
|
49
|
+
|
|
50
|
+
if !got.Force {
|
|
51
|
+
t.Fatal("Force = false, want true")
|
|
52
|
+
}
|
|
53
|
+
if !got.Install {
|
|
54
|
+
t.Fatal("Install = false, want true")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func TestRunInitRejectsUnknownFlags(t *testing.T) {
|
|
59
|
+
var stdout bytes.Buffer
|
|
60
|
+
called := false
|
|
61
|
+
|
|
62
|
+
err := RunInit(context.Background(), []string{"--bogus"}, &stdout, func(context.Context, InitOptions) error {
|
|
63
|
+
called = true
|
|
64
|
+
return nil
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if err == nil {
|
|
68
|
+
t.Fatal("RunInit() error = nil, want unknown flag error")
|
|
69
|
+
}
|
|
70
|
+
if called {
|
|
71
|
+
t.Fatal("RunInit() called runner after invalid args")
|
|
72
|
+
}
|
|
73
|
+
if !strings.Contains(err.Error(), "unknown init flag") {
|
|
74
|
+
t.Fatalf("error = %q, want unknown flag", err.Error())
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func TestRunInitReturnsRunnerError(t *testing.T) {
|
|
79
|
+
want := errors.New("runner failed")
|
|
80
|
+
var stdout bytes.Buffer
|
|
81
|
+
|
|
82
|
+
err := RunInit(context.Background(), nil, &stdout, func(context.Context, InitOptions) error {
|
|
83
|
+
return want
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if !errors.Is(err, want) {
|
|
87
|
+
t.Fatalf("RunInit() error = %v, want %v", err, want)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func runInitOptions(t *testing.T, args []string) InitOptions {
|
|
92
|
+
t.Helper()
|
|
93
|
+
|
|
94
|
+
var stdout bytes.Buffer
|
|
95
|
+
var got InitOptions
|
|
96
|
+
err := RunInit(context.Background(), args, &stdout, func(_ context.Context, options InitOptions) error {
|
|
97
|
+
got = options
|
|
98
|
+
return nil
|
|
99
|
+
})
|
|
100
|
+
if err != nil {
|
|
101
|
+
t.Fatalf("RunInit() error = %v", err)
|
|
102
|
+
}
|
|
103
|
+
return got
|
|
104
|
+
}
|