Skip to content

feat(commands): add non-interactive mode and Discover function#263

Open
pjcdawkins wants to merge 2 commits intorefactor/abstract-fs-3-discoveryfrom
refactor/abstract-fs-4-noninteractive
Open

feat(commands): add non-interactive mode and Discover function#263
pjcdawkins wants to merge 2 commits intorefactor/abstract-fs-3-discoveryfrom
refactor/abstract-fs-4-noninteractive

Conversation

@pjcdawkins
Copy link
Contributor

Summary

Stacked on #262.

Add --no-interaction flag and a Discover() function for programmatic callers like source-integration-apps.

  • Discover(ctx, flavor, noInteraction, fileSystem) — new entry point that accepts an fs.FS and returns *UserInput without writing files
  • --no-interaction flag — skip all interactive prompts, use auto-detection via Discoverer
  • FilesOverwrite — accepts a file list and skips in non-interactive mode
  • All question handlers check NoInteraction and use Discoverer for auto-detection
  • WorkingDirectory supports pre-set filesystem from callers and skips git detection

Author: @akalipetis

🤖 Generated with Claude Code

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-interaction into the CLI flow.
  • Updates question handlers to respect NoInteraction and rely more on the new discovery.Discoverer methods.
  • 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 yarn without 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.

Comment on lines +16 to +21
// 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
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +125
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 {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

func (q *WorkingDirectory) Ask(ctx context.Context) error {
_, stderr, ok := colors.FromContext(ctx)
_, stderr, _ := colors.FromContext(ctx)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
_, stderr, _ := colors.FromContext(ctx)
_, stderr, okColors := colors.FromContext(ctx)
if !okColors || stderr == nil {
stderr = os.Stderr
}

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +108
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
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +56
// 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) {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +50
runtime, _ := models.Runtimes.RuntimeByType(typ)

if answers.NoInteraction {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)
}

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +22
environment, err := answers.Discoverer.Environment()
if err != nil {
return err
}

answers.Environment = environment
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Antonis Kalipetis and others added 2 commits March 24, 2026 01:19
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>
@pjcdawkins pjcdawkins force-pushed the refactor/abstract-fs-3-discovery branch from a2655d1 to c8068a9 Compare March 24, 2026 01:20
@pjcdawkins pjcdawkins force-pushed the refactor/abstract-fs-4-noninteractive branch from 26dc918 to 1c50d81 Compare March 24, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants