Skip to content

Commit 81145d9

Browse files
committed
feat: 2-phase install flow with automatic dep detection
Phase 1 - collect install methods for workspace packages: - startStartupCheck now finds Installed && !LocalSource (not just !InPackageJson) so workspace:* packages that aren't yet git-cloned are always prompted - getInstallMethods hides npm option when package is already in package.json (workspace:* configured → only offer GIT SSH / GIT HTTPS clone options) - applyQueueChangesCmd, triggerInstall, triggerForceReinstall now save justInstalledAdds for the post-install phase 2 dep check Phase 1 execution: - RunSetupTask clones all packages, runs pnpm install + prepare:modules - 500ms pause before taskFinishedMsg so user sees 100% progress bar Phase 2 - automatic dependency detection: - After setup completes, checkPostInstallDeps reads the package.json of each just-installed package (local file, fallback: npm registry) - Finds @owdproject/* deps whose shortname starts with module- or kit- - Filters: already locally available, already in catalog as LocalSource - If any new deps found: opens install method modals automatically - After user confirms methods: runs a second RunSetupTask for those deps Result: app-about + app-todo + theme-nova are cloned in phase 1. After cloning, if theme-nova has kit-primevue/kit-tailwind in its deps, those are proposed and cloned in phase 2 without user needing to know.
1 parent fcc7503 commit 81145d9

5 files changed

Lines changed: 128 additions & 3 deletions

File tree

go/tui/handlers.go

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ func (m *TuiModel) triggerInstall(pkg *bridge.CatalogEntry, method string) (tea.
401401
m.activeTask = TaskSetup
402402
m.statusMsg = fmt.Sprintf("Installing %s via %s…", pkg.ShortName, method)
403403
m.addLog(fmt.Sprintf(">>> Installing %s via %s", pkg.Name, method))
404+
m.justInstalledAdds = map[string]string{pkg.Name: method}
404405

405406
payload := &bridge.WritePayload{
406407
Config: &bridge.Config{Theme: m.ctx.Config.Theme, Apps: m.ctx.Config.Apps, Modules: m.ctx.Config.Modules},
@@ -492,6 +493,7 @@ func (m *TuiModel) triggerForceReinstall(pkg *bridge.CatalogEntry) (tea.Model, t
492493
}
493494
}
494495

496+
m.justInstalledAdds = map[string]string{pkg.Name: method}
495497
m.RunSetupTask(map[string]string{pkg.Name: method})
496498
return m, m.listenToChannel()
497499
}
@@ -563,7 +565,8 @@ func (m *TuiModel) startStartupCheck() tea.Cmd {
563565
queued := map[string]bool{}
564566

565567
for _, entry := range m.catalog.Entries {
566-
if entry.Installed && !entry.LocalSource && !entry.InPackageJson {
568+
// Package is in config but not yet locally cloned → needs setup
569+
if entry.Installed && !entry.LocalSource {
567570
m.promptQueue = append(m.promptQueue, pendingDecision{
568571
PkgName: entry.Name,
569572
ShortName: entry.ShortName,
@@ -697,6 +700,11 @@ func (m *TuiModel) applyQueueChangesCmd() tea.Cmd {
697700
m.activeTask = TaskSetup
698701
m.statusMsg = "Applying queued package changes…"
699702
m.addLog(">>> Applying queued package configuration changes…")
703+
// Save adds for post-install dependency checking (phase 2)
704+
m.justInstalledAdds = make(map[string]string)
705+
for k, v := range m.finalizedAdds {
706+
m.justInstalledAdds[k] = v
707+
}
700708

701709
payload := &bridge.WritePayload{
702710
Config: &bridge.Config{Theme: m.ctx.Config.Theme, Apps: m.ctx.Config.Apps, Modules: m.ctx.Config.Modules},
@@ -993,3 +1001,105 @@ func (m *TuiModel) updateSettingsFocus() {
9931001
m.settingsUserInput.Blur()
9941002
}
9951003
}
1004+
1005+
// checkPostInstallDeps checks the package.json of every just-installed package for
1006+
// @owdproject/* dependencies whose shortname starts with "module-" or "kit-".
1007+
// If any are found that aren't already locally available, it builds a new prompt queue
1008+
// and returns the first decision cmd (phase 2 of the install flow).
1009+
func (m *TuiModel) checkPostInstallDeps(justInstalled map[string]string) tea.Cmd {
1010+
if m.catalog == nil || len(justInstalled) == 0 {
1011+
return nil
1012+
}
1013+
1014+
nonInstallable := map[string]bool{
1015+
"@owdproject/core": true,
1016+
"@owdproject/cli": true,
1017+
"@owdproject/nx": true,
1018+
}
1019+
1020+
// Build set of packages already locally cloned
1021+
alreadyHave := map[string]bool{}
1022+
for _, e := range m.catalog.Entries {
1023+
if e.LocalSource {
1024+
alreadyHave[e.Name] = true
1025+
}
1026+
}
1027+
1028+
// Reset queue for phase 2
1029+
m.promptQueue = []pendingDecision{}
1030+
m.promptQueueIndex = 0
1031+
m.finalizedAdds = make(map[string]string)
1032+
m.finalizedRemoves = []string{}
1033+
m.finalizedTheme = nil
1034+
1035+
queued := map[string]bool{}
1036+
1037+
for pkgName := range justInstalled {
1038+
// Find catalog entry to get shortName and kind
1039+
var srcEntry *bridge.CatalogEntry
1040+
for i := range m.catalog.Entries {
1041+
if m.catalog.Entries[i].Name == pkgName {
1042+
srcEntry = &m.catalog.Entries[i]
1043+
break
1044+
}
1045+
}
1046+
if srcEntry == nil {
1047+
continue
1048+
}
1049+
1050+
// Read deps from local package.json (just cloned or already present)
1051+
deps, found := getOwdDepsLocal(m.workspaceRoot, srcEntry.ShortName, srcEntry.Kind)
1052+
if !found {
1053+
// Fallback: fetch from npm registry (e.g. if installed via npm)
1054+
deps, _ = getOwdDepsFromNpm(pkgName)
1055+
}
1056+
1057+
for _, dep := range deps {
1058+
if nonInstallable[dep] || alreadyHave[dep] || queued[dep] {
1059+
continue
1060+
}
1061+
short := dep
1062+
if idx := strings.LastIndex(dep, "/"); idx >= 0 {
1063+
short = dep[idx+1:]
1064+
}
1065+
if isLocallyAvailable(m.workspaceRoot, short) {
1066+
continue
1067+
}
1068+
1069+
// Find in catalog
1070+
var depEntry bridge.CatalogEntry
1071+
found := false
1072+
for _, e := range m.catalog.Entries {
1073+
if e.Name == dep {
1074+
depEntry = e
1075+
found = true
1076+
break
1077+
}
1078+
}
1079+
if !found {
1080+
depEntry = bridge.CatalogEntry{
1081+
Name: dep,
1082+
ShortName: short,
1083+
Kind: "module",
1084+
}
1085+
}
1086+
1087+
m.promptQueue = append(m.promptQueue, pendingDecision{
1088+
PkgName: dep,
1089+
ShortName: short,
1090+
Action: "install",
1091+
Kind: depEntry.Kind,
1092+
Entry: depEntry,
1093+
})
1094+
queued[dep] = true
1095+
}
1096+
}
1097+
1098+
if len(m.promptQueue) == 0 {
1099+
return nil
1100+
}
1101+
1102+
m.addLog(fmt.Sprintf(">>> Found %d additional dependenc%s to install.",
1103+
len(m.promptQueue), map[bool]string{true: "y", false: "ies"}[len(m.promptQueue) == 1]))
1104+
return tea.Batch(m.processNextQueueDecision(), m.listenToChannel())
1105+
}

go/tui/helpers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,8 +806,8 @@ func (m *TuiModel) getInstallMethods(pkg *bridge.CatalogEntry) []InstallMethod {
806806
})
807807
}
808808

809-
// 1. NPM Registry
810-
if pkg.SourcesMeta != nil && pkg.SourcesMeta.Npm != nil {
809+
// 1. NPM Registry — only when NOT already configured as workspace:* in package.json
810+
if !pkg.InPackageJson && pkg.SourcesMeta != nil && pkg.SourcesMeta.Npm != nil {
811811
methods = append(methods, InstallMethod{
812812
Name: "npm",
813813
Label: "NPM Registry",

go/tui/tasks.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ func (m *TuiModel) RunSetupTask(adds map[string]string) {
173173
return
174174
}
175175

176+
// Small pause so the user sees the progress bar reach 100% before it closes
177+
time.Sleep(500 * time.Millisecond)
176178
msgChan <- taskFinishedMsg{Success: true}
177179
}()
178180
}

go/tui/tui.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func NewModel(root string) TuiModel {
4545
activeTask: TaskNone,
4646
workspaceBranch: "—",
4747
workspaceChanges: "clean",
48+
justInstalledAdds: make(map[string]string),
4849
}
4950
}
5051

@@ -488,6 +489,17 @@ func (m *TuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
488489
if msg.Success {
489490
m.statusMsg = "Task completed."
490491
m.addLog(">>> Task completed successfully.")
492+
493+
// Phase 2: check deps of newly installed packages
494+
if len(m.justInstalledAdds) > 0 {
495+
justInstalled := m.justInstalledAdds
496+
m.justInstalledAdds = make(map[string]string)
497+
if depCmd := m.checkPostInstallDeps(justInstalled); depCmd != nil {
498+
m.statusMsg = "Found additional dependencies. Choose install method…"
499+
return m, tea.Batch(depCmd, m.loadContextCmd(), m.listenToChannel())
500+
}
501+
}
502+
491503
if m.startServerAfterSetup {
492504
m.startServerAfterSetup = false
493505
m.statusMsg = "Starting dev server…"

go/tui/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,5 @@ type TuiModel struct {
234234
setupLabel string
235235
workspaceBranch string
236236
workspaceChanges string
237+
justInstalledAdds map[string]string // pkgName → method, for post-install dep check
237238
}

0 commit comments

Comments
 (0)