Skip to content

Commit 00ea10d

Browse files
authored
azd init - Combine "Create a minimal project" with "Scan current directory" option and add --minimal flag (#5280)
* Add --minimal flag to init command * Allow user to continue initializing as minimal project when there's no detected service * Merge next-steps.md and next-steps-alpha.md * Do not show "Remove a detected service" when there are no detected resources
1 parent 3b39dda commit 00ea10d

9 files changed

Lines changed: 320 additions & 492 deletions

File tree

cli/azd/cmd/init.go

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"github.com/azure/azure-dev/cli/azd/pkg/tools"
3232
"github.com/azure/azure-dev/cli/azd/pkg/tools/git"
3333
"github.com/azure/azure-dev/cli/azd/pkg/workflow"
34-
"github.com/fatih/color"
3534
"github.com/joho/godotenv"
3635
"github.com/spf13/cobra"
3736
"github.com/spf13/pflag"
@@ -59,6 +58,7 @@ type initFlags struct {
5958
location string
6059
global *internal.GlobalCommandOptions
6160
fromCode bool
61+
minimal bool
6262
up bool
6363
internal.EnvFlag
6464
}
@@ -99,6 +99,13 @@ func (i *initFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOpt
9999
false,
100100
"Initializes a new application from your existing code.",
101101
)
102+
local.BoolVarP(
103+
&i.minimal,
104+
"minimal",
105+
"m",
106+
false,
107+
"Initializes a minimal project.",
108+
)
102109
local.BoolVarP(
103110
&i.up,
104111
"up",
@@ -206,28 +213,35 @@ func (i *initAction) Run(ctx context.Context) (*actions.ActionResult, error) {
206213
return nil, fmt.Errorf("checking if project exists: %w", err)
207214
}
208215

209-
var initTypeSelect initType
216+
var initTypeSelect initType = initUnknown
217+
initTypeCount := 0
210218
if i.flags.templatePath != "" || len(i.flags.templateTags) > 0 {
211-
// an explicit --template passed, always initialize from app template
219+
initTypeCount++
212220
initTypeSelect = initAppTemplate
213221
}
214-
215222
if i.flags.fromCode {
216-
if i.flags.templatePath != "" {
217-
return nil, errors.New("only one of init modes: --template, or --from-code should be set")
218-
}
223+
initTypeCount++
219224
initTypeSelect = initFromApp
220225
}
226+
if i.flags.minimal {
227+
initTypeCount++
228+
initTypeSelect = initFromApp // Minimal now also uses initFromApp path
229+
}
221230

222-
if i.flags.templatePath == "" && !i.flags.fromCode && existingProject {
223-
// only initialize environment when no mode is set explicitly
224-
initTypeSelect = initEnvironment
231+
if initTypeCount > 1 {
232+
return nil, errors.New("only one of init modes: --template, --from-code, or --minimal should be set")
225233
}
226234

227235
if initTypeSelect == initUnknown {
228-
initTypeSelect, err = promptInitType(i.console, ctx)
229-
if err != nil {
230-
return nil, err
236+
if existingProject {
237+
// only initialize environment when no mode is set explicitly
238+
initTypeSelect = initEnvironment
239+
} else {
240+
// Prompt for init type for new projects
241+
initTypeSelect, err = promptInitType(i.console, ctx)
242+
if err != nil {
243+
return nil, err
244+
}
231245
}
232246
}
233247

@@ -277,36 +291,48 @@ func (i *initAction) Run(ctx context.Context) (*actions.ActionResult, error) {
277291

278292
case initFromApp:
279293
tracing.SetUsageAttributes(fields.InitMethod.String("app"))
280-
281294
header = "Your app is ready for the cloud!"
282295
followUp = "Run " + output.WithHighLightFormat("azd up") + " to provision and deploy your app to Azure.\n" +
283296
"Run " + output.WithHighLightFormat("azd add") + " to add new Azure components to your project.\n" +
284297
"Run " + output.WithHighLightFormat("azd infra gen") + " to generate IaC for your project to disk, " +
285298
"allowing you to manually manage it.\n" +
286299
"See " + output.WithHighLightFormat("./next-steps.md") + " for more information on configuring your app."
287-
entries, err := os.ReadDir(azdCtx.ProjectDirectory())
288-
if err != nil {
289-
return nil, fmt.Errorf("reading current directory: %w", err)
300+
301+
envSpecified := i.flags.EnvironmentName != ""
302+
initializeEnv := func() (*environment.Environment, error) {
303+
return i.initializeEnv(ctx, azdCtx, templates.Metadata{})
290304
}
305+
initializeMinimal := func() error {
306+
tracing.SetUsageAttributes(fields.InitMethod.String("project"))
307+
err := i.repoInitializer.InitializeMinimal(ctx, azdCtx)
308+
if err != nil {
309+
return err
310+
}
291311

292-
if len(entries) == 0 {
293-
return nil, &internal.ErrorWithSuggestion{
294-
Err: errors.New("no files found in the current directory"),
295-
Suggestion: "Ensure you're in the directory where your app code is located and try again." +
296-
" If you do not have code and would like to start with an app template, run '" +
297-
output.WithHighLightFormat("azd init") + "' and select the option to " +
298-
color.MagentaString("Use a template") + ".",
312+
// Create env upfront only if the environment name is passed in.
313+
if envSpecified {
314+
_, err := initializeEnv()
315+
if err != nil {
316+
return err
317+
}
299318
}
319+
320+
header = "Generated azure.yaml project file."
321+
followUp = "Run " + output.WithHighLightFormat("azd add") + " to add new Azure components to your project."
322+
return nil
300323
}
301324

302-
err = i.repoInitializer.InitFromApp(
303-
ctx,
304-
azdCtx,
305-
func() (*environment.Environment, error) {
306-
return i.initializeEnv(ctx, azdCtx, templates.Metadata{})
307-
},
308-
i.flags.EnvironmentName != "",
309-
)
325+
if i.flags.minimal {
326+
err = initializeMinimal()
327+
} else {
328+
err = i.repoInitializer.InitFromApp(
329+
ctx,
330+
azdCtx,
331+
initializeEnv,
332+
initializeMinimal,
333+
envSpecified,
334+
)
335+
}
310336
if err != nil {
311337
return nil, err
312338
}
@@ -318,24 +344,6 @@ func (i *initAction) Run(ctx context.Context) (*actions.ActionResult, error) {
318344

319345
header = fmt.Sprintf("Initialized environment %s.", env.Name())
320346
followUp = ""
321-
case initProject:
322-
tracing.SetUsageAttributes(fields.InitMethod.String("project"))
323-
324-
err = i.repoInitializer.InitializeMinimal(ctx, azdCtx)
325-
if err != nil {
326-
return nil, err
327-
}
328-
329-
// Create env upfront only if the environment name is passed in.
330-
if i.flags.EnvironmentName != "" {
331-
_, err := i.initializeEnv(ctx, azdCtx, templates.Metadata{})
332-
if err != nil {
333-
return nil, err
334-
}
335-
}
336-
337-
header = "Generated azure.yaml project file."
338-
followUp = "Run " + output.WithHighLightFormat("azd add") + " to add new Azure components to your project."
339347
default:
340348
panic("unhandled init type")
341349
}
@@ -358,17 +366,15 @@ const (
358366
initUnknown = iota
359367
initFromApp
360368
initAppTemplate
361-
initProject
362369
initEnvironment
363370
)
364371

365372
func promptInitType(console input.Console, ctx context.Context) (initType, error) {
366373
selection, err := console.Select(ctx, input.ConsoleOptions{
367374
Message: "How do you want to initialize your app?",
368375
Options: []string{
369-
"Use code in the current directory",
376+
"Scan current directory", // This now covers minimal project creation too
370377
"Select a template",
371-
"Create a minimal project",
372378
},
373379
})
374380
if err != nil {
@@ -380,8 +386,6 @@ func promptInitType(console input.Console, ctx context.Context) (initType, error
380386
return initFromApp, nil
381387
case 1:
382388
return initAppTemplate, nil
383-
case 2:
384-
return initProject, nil
385389
default:
386390
panic("unhandled selection")
387391
}

cli/azd/cmd/testdata/TestUsage-azd-init.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Flags
1313
-f, --filter strings : The tag(s) used to filter template results. Supports comma-separated values.
1414
--from-code : Initializes a new application from your existing code.
1515
-l, --location string : Azure location for the new environment
16+
-m, --minimal : Initializes a minimal project.
1617
-s, --subscription string : Name or ID of an Azure subscription to use for the new environment
1718
-t, --template string : Initializes a new application from a template. You can use Full URI, <owner>/<repository>, or <repository> if it's part of the azure-samples organization.
1819
--up : Provision and deploy to Azure after initializing the project from a template.

cli/azd/internal/repository/app_init.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func (i *Initializer) InitFromApp(
3535
ctx context.Context,
3636
azdCtx *azdcontext.AzdContext,
3737
initializeEnv func() (*environment.Environment, error),
38+
initializeMinimal func() error,
3839
envSpecified bool) error {
3940
i.console.Message(ctx, "")
4041
title := "Scanning app code in current directory"
@@ -235,6 +236,10 @@ func (i *Initializer) InitFromApp(
235236
tracing.SetUsageAttributes(fields.AppInitLastStep.String("config"))
236237
tracing.SetUsageAttributes(fields.AppInitLastStep.String("generate"))
237238

239+
if len(detect.Services) == 0 && len(detect.Databases) == 0 {
240+
return initializeMinimal()
241+
}
242+
238243
// Defer env initialization until 'azd up', except cases where user explicitly specifies the env name
239244
if envSpecified {
240245
_, err = initializeEnv()
@@ -258,8 +263,7 @@ func (i *Initializer) InitFromApp(
258263
return fmt.Errorf("loading scaffold templates: %w", err)
259264
}
260265

261-
// TODO: Merge next-steps.md and next-steps-alpha.md
262-
err = scaffold.Execute(t, "next-steps-alpha.md", nil, filepath.Join(azdCtx.ProjectDirectory(), "next-steps.md"))
266+
err = scaffold.Execute(t, "next-steps.md", nil, filepath.Join(azdCtx.ProjectDirectory(), "next-steps.md"))
263267
if err != nil {
264268
return err
265269
}

cli/azd/internal/repository/detect_confirm.go

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -106,69 +106,54 @@ func (d *detectConfirm) captureUsage(
106106
// Confirm prompts the user to confirm the detected services and databases,
107107
// providing modifications to the detected services and databases.
108108
func (d *detectConfirm) Confirm(ctx context.Context) error {
109+
const (
110+
optionConfirmAndContinue = "Confirm and continue initializing my app"
111+
optionRemoveService = "Remove a detected service"
112+
optionAddService = "Add an undetected service"
113+
)
114+
109115
for {
110116
if err := d.render(ctx); err != nil {
111117
return err
112118
}
113119

114-
if len(d.Services) == 0 && !d.modified {
115-
confirmAdd, err := d.console.Confirm(ctx, input.ConsoleOptions{
116-
Message: "Add an undetected service?",
117-
DefaultValue: true,
118-
})
119-
if err != nil {
120-
return err
121-
}
122-
123-
if !confirmAdd {
124-
return fmt.Errorf("cancelled")
125-
}
126-
127-
if err := d.add(ctx); err != nil {
128-
return err
129-
}
130-
131-
tracing.IncrementUsageAttribute(fields.AppInitModifyAddCount.Int(1))
132-
continue
120+
options := []string{optionConfirmAndContinue}
121+
if len(d.Services) > 0 || len(d.Databases) > 0 {
122+
options = append(options, optionRemoveService)
133123
}
124+
options = append(options, optionAddService)
134125

135-
d.modified = false
136-
137-
continueOption, err := d.console.Select(ctx, input.ConsoleOptions{
126+
selectedOptionIndex, err := d.console.Select(ctx, input.ConsoleOptions{
138127
Message: "Select an option",
139-
Options: []string{
140-
"Confirm and continue initializing my app",
141-
"Remove a detected service",
142-
"Add an undetected service",
143-
},
128+
Options: options,
144129
})
145130
if err != nil {
146131
return err
147132
}
148133

149-
switch continueOption {
150-
case 0:
134+
selectedOption := options[selectedOptionIndex]
135+
136+
switch selectedOption {
137+
case optionConfirmAndContinue:
151138
d.captureUsage(
152139
fields.AppInitConfirmedDatabases,
153140
fields.AppInitConfirmedServices)
154141
return nil
155-
case 1:
142+
case optionRemoveService:
156143
if err := d.remove(ctx); err != nil {
157144
if errors.Is(err, terminal.InterruptErr) {
158145
continue
159146
}
160147
return err
161148
}
162-
163149
tracing.IncrementUsageAttribute(fields.AppInitModifyRemoveCount.Int(1))
164-
case 2:
150+
case optionAddService:
165151
if err := d.add(ctx); err != nil {
166152
if errors.Is(err, terminal.InterruptErr) {
167153
continue
168154
}
169155
return err
170156
}
171-
172157
tracing.IncrementUsageAttribute(fields.AppInitModifyAddCount.Int(1))
173158
}
174159
}

cli/azd/internal/repository/detect_confirm_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func Test_detectConfirm_confirm(t *testing.T) {
4747
name: "add from empty",
4848
detection: []appdetect.Project{},
4949
interactions: []string{
50-
"y",
50+
"Add an undetected service",
5151
fmt.Sprintf("%s\t%s", appdetect.Java.Display(), "[Language]"),
5252
"java-dir",
5353
"Confirm and continue initializing my app",

0 commit comments

Comments
 (0)