feat(commands): add non-interactive mode and Discover function#263
feat(commands): add non-interactive mode and Discover function#263pjcdawkins wants to merge 2 commits intorefactor/abstract-fs-3-discoveryfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a non-interactive execution path and a programmatic discovery entry point so callers can auto-detect project configuration (optionally against a provided fs.FS) without prompts or file writes.
Changes:
- Introduces
Discover(ctx, flavor, noInteraction, fileSystem)and wires--no-interactioninto the CLI flow. - Updates question handlers to respect
NoInteractionand rely more on the newdiscovery.Discoverermethods. - Refactors overwrite confirmation to accept an explicit list of files produced by
Platformify().
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/question/working_directory.go | Allows pre-set filesystem, initializes Discoverer, and skips git detection in non-interactive mode. |
| internal/question/welcome.go | Suppresses welcome output in non-interactive mode. |
| internal/question/type.go | Uses Discoverer.Type() for runtime detection; adds non-interactive behavior. |
| internal/question/stack.go | Adjusts Symfony handling for non-interactive runs. |
| internal/question/services.go | Skips service prompts when NoInteraction is enabled. |
| internal/question/name.go | Adds deterministic default name selection for non-interactive mode. |
| internal/question/models/answer.go | Adds NoInteraction to the answers model. |
| internal/question/files_overwrite.go | Makes overwrite checking configurable via explicit file list; skips in non-interactive mode. |
| internal/question/environment.go | Delegates environment defaults to Discoverer.Environment(). |
| internal/question/dependency_manager.go | Delegates dependency manager detection to Discoverer.DependencyManagers(). |
| internal/question/almost_done.go | Suppresses “almost done” output in non-interactive mode. |
| commands/platformify.go | Adds --no-interaction, introduces Discover(), and moves overwrite prompt after config generation. |
| commands/commands.go | Minor rename/cleanup of root command variable. |
Comments suppressed due to low confidence (1)
internal/question/dependency_manager.go:66
- Dependency managers are now copied from Discoverer, but the previous behavior of populating answers.Dependencies for tools like Yarn/Composer has been removed. This can break builds in cases where the runtime isn’t nodejs but Yarn is detected (BuildSteps will run
yarnwithout ensuring it’s installed). Please restore/tool-install dependency injection (or move it to BuildSteps) for the detected managers.
dependencyManagers, err := answers.Discoverer.DependencyManagers()
if err != nil {
return err
}
answers.DependencyManagers = make([]models.DepManager, 0, len(dependencyManagers))
for _, dm := range dependencyManagers {
answers.DependencyManagers = append(answers.DependencyManagers, models.DepManager(dm))
}
if exists := utils.FileExists(answers.WorkingDirectory, "", bundlerLockFile); exists {
answers.DependencyManagers = append(answers.DependencyManagers, models.Bundler)
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // FilesOverwrite prompts the user to confirm overwriting existing config files. | ||
| // If FilesToCreateUpdate is set, those files are checked instead of the | ||
| // default proprietary files list. | ||
| type FilesOverwrite struct { | ||
| FilesToCreateUpdate []string | ||
| } |
There was a problem hiding this comment.
The field name FilesToCreateUpdate is a bit unclear/grammatically awkward. Consider renaming to something like FilesToCreateOrUpdate (and updating the comment) to better communicate intent to future maintainers.
| filesToCreateUpdate := make([]string, 0, len(configFiles)) | ||
| for file := range configFiles { | ||
| filesToCreateUpdate = append(filesToCreateUpdate, file) | ||
| } | ||
|
|
||
| filesOverwrite := question.FilesOverwrite{FilesToCreateUpdate: filesToCreateUpdate} | ||
| if err := filesOverwrite.Ask(ctx); err != nil { |
There was a problem hiding this comment.
filesToCreateUpdate is built by iterating over a map, so the order is non-deterministic. That can lead to inconsistent prompt output in FilesOverwrite (and makes behavior harder to test/debug). Consider sorting the file list before passing it into FilesOverwrite.
|
|
||
| func (q *WorkingDirectory) Ask(ctx context.Context) error { | ||
| _, stderr, ok := colors.FromContext(ctx) | ||
| _, stderr, _ := colors.FromContext(ctx) |
There was a problem hiding this comment.
colors.FromContext(ctx) can return (nil, nil, false). Ignoring the ok value here means stderr may be nil, and later fmt.Fprintln(stderr, ...) will panic when Discover/AskQuestions is invoked without colors context. Please handle !ok (e.g., return questionnaire.ErrSilent or set stderr to io.Discard/os.Stderr).
| _, stderr, _ := colors.FromContext(ctx) | |
| _, stderr, okColors := colors.FromContext(ctx) | |
| if !okColors || stderr == nil { | |
| stderr = os.Stderr | |
| } |
| ctx = colors.ToContext(ctx, stdout, stderr) | ||
| ctx = models.ToContext(ctx, models.NewAnswers()) | ||
| input, err := Discover(ctx, assets.ConfigFlavor, noInteraction, nil) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
With SilenceErrors=true on the Cobra command, returning an error without printing it results in the CLI exiting with status 1 but no error message. After calling Discover()/Platformify(), errors are now returned directly (no colored output). Please print a user-facing error to stderr in Platformify (or disable SilenceErrors).
| // Discover detects project configuration non-interactively. | ||
| // It is the entry point for programmatic callers like source-integration-apps. | ||
| func Discover( | ||
| ctx context.Context, | ||
| flavor string, | ||
| noInteraction bool, | ||
| fileSystem fs.FS, | ||
| ) (*platformifier.UserInput, error) { |
There was a problem hiding this comment.
The Discover() docstring says it detects configuration “non-interactively”, but the function accepts noInteraction=false and will still run interactive questions. Consider updating the comment to reflect actual behavior (or enforce non-interactive behavior inside Discover).
| runtime, _ := models.Runtimes.RuntimeByType(typ) | ||
|
|
||
| if answers.NoInteraction { |
There was a problem hiding this comment.
RuntimeByType can return a non-nil error when the detected type isn’t in the registry. Currently the error is ignored and non-interactive mode returns a generic "no runtime detected". Please handle/propagate the RuntimeByType error (and include the detected typ in the error message) so callers can diagnose unsupported runtimes.
| runtime, _ := models.Runtimes.RuntimeByType(typ) | |
| if answers.NoInteraction { | |
| runtime, runtimeErr := models.Runtimes.RuntimeByType(typ) | |
| if answers.NoInteraction { | |
| if runtimeErr != nil { | |
| return fmt.Errorf("unsupported runtime type %s: %w", typ, runtimeErr) | |
| } |
| environment, err := answers.Discoverer.Environment() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| answers.Environment = environment |
There was a problem hiding this comment.
Switching to Discoverer.Environment() changes the pinned tool versions (e.g., Poetry/Pipenv) compared to the previous hard-coded values in this question. Please confirm the intended versions and update discovery.Environment() accordingly, otherwise this is a silent behavior regression for generated configs.
Add --no-interaction flag and a Discover() function that can be called programmatically by external services like source-integration-apps. - Add NoInteraction field to Answers (defaults to false for CLI) - Add --no-interaction flag to platformify/upsunify commands - Extract Discover() function from Platformify() — accepts an fs.FS and returns *UserInput without writing files - Platformify() now calls Discover() internally, then writes files - FilesOverwrite accepts a file list and skips in non-interactive mode - All interactive question handlers skip prompts when NoInteraction is true, using Discoverer for auto-detection instead - WorkingDirectory handler skips git detection in non-interactive mode and supports pre-set WorkingDirectory from callers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In answer.go, make([]string, len(...)) pre-fills with empty strings before appending, producing a slice like ["", "", "composer", "pip"]. Use capacity instead of length. In stack.go, the Symfony non-interactive case redundantly sets answers.Stack to GenericStack — it was already set before the switch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a2655d1 to
c8068a9
Compare
26dc918 to
1c50d81
Compare
Summary
Stacked on #262.
Add
--no-interactionflag and aDiscover()function for programmatic callers like source-integration-apps.Discover(ctx, flavor, noInteraction, fileSystem)— new entry point that accepts anfs.FSand returns*UserInputwithout writing files--no-interactionflag — skip all interactive prompts, use auto-detection via DiscovererFilesOverwrite— accepts a file list and skips in non-interactive modeNoInteractionand use Discoverer for auto-detectionWorkingDirectorysupports pre-set filesystem from callers and skips git detectionAuthor: @akalipetis
🤖 Generated with Claude Code