feat: azd init -t auto-creates project directory like git clone#7290
feat: azd init -t auto-creates project directory like git clone#7290
Conversation
There was a problem hiding this comment.
Pull request overview
Updates azd init -t to behave more like git clone by defaulting template-based initialization into an auto-created project directory, with an optional positional [directory] override and updated outputs/specs/tests.
Changes:
- Add optional
[directory]positional arg toazd initand implement auto-create +chdirflow for template init. - Introduce
templates.DeriveDirectoryName()helper (with intended traversal protection) and add unit tests. - Extend auth status contract/output to include token expiry (
expiresOn) and improve login guidance for a specific auth error.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/pkg/templates/path.go | Adds DeriveDirectoryName() helper used to derive the auto-created project folder name. |
| cli/azd/pkg/templates/path_test.go | Adds test cases for DeriveDirectoryName() across URL/path formats and edge cases. |
| cli/azd/cmd/init.go | Adds [directory] positional arg, directory validation/creation, chdir, and a post-init cd hint. |
| cli/azd/cmd/init_test.go | Adds tests for resolving/validating/creating the target directory for template init. |
| cli/azd/cmd/testdata/TestUsage-azd-init.snap | Updates usage snapshot to include [directory]. |
| cli/azd/cmd/testdata/TestFigSpec.ts | Updates fig completion spec to include optional directory arg for init. |
| cli/azd/pkg/contracts/auth.go | Adds expiresOn field to auth status result contract. |
| cli/azd/pkg/contracts/auth_token_test.go | Adds JSON roundtrip tests for StatusResult with/without expiresOn. |
| cli/azd/cmd/auth_status.go | Populates StatusResult.ExpiresOn from acquired access token. |
| cli/azd/cmd/middleware/login_guard.go | Wraps ErrNoCurrentUser with a suggestion to run azd auth login. |
6f0cbae to
e625f6a
Compare
|
/azp run |
|
You have several pipelines (over 10) configured to build pull requests in this repository. Specify which pipelines you would like to run by using /azp run [pipelines] command. You can specify multiple pipelines using a comma separated list. |
e625f6a to
584d3b2
Compare
Add lessons learned from recent PR reviews (#7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039) as agent instructions to prevent recurring review findings. New sections: - Error handling: ErrorWithSuggestion completeness, telemetry service attribution, scope-agnostic messages - Architecture boundaries: pkg/project target-agnostic, extension docs - Output formatting: shell-safe paths, consistent JSON contracts - Path safety: traversal validation, quoted paths in messages - Testing best practices: test actual rules, extract shared helpers, correct env vars, TypeScript patterns, efficient dir checks - CI/GitHub Actions: permissions, PATH handling, artifact downloads, prefer ADO for secrets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add lessons learned from team and Copilot reviews across PRs #7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run azure-dev - cli |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Add lessons learned from team and Copilot reviews across PRs #7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run azure-dev - cli |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Add lessons learned from team and Copilot reviews across PRs #7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
584d3b2 to
c722542
Compare
Add lessons learned from team and Copilot reviews across PRs #7290, #7251, #7250, #7247, #7236, #7235, #7202, #7039 as agent instructions to prevent recurring review findings. New/expanded sections: - Error handling: ErrorWithSuggestion field completeness, telemetry service attribution, scope-agnostic messages, link/suggestion parity, stale data in polling loops - Architecture boundaries: pkg/project target-agnostic, extension docs separation, env var verification against source code - Output formatting: shell-safe quoted paths, consistent JSON types - Path safety: traversal validation, quoted paths in messages - Code organization: extract shared logic across scopes - Documentation standards: help text consistency, no dead references, PR description accuracy - Testing best practices: test YAML rules e2e, extract shared helpers, correct env vars (AZD_FORCE_TTY, NO_COLOR), TypeScript patterns, reasonable timeouts, cross-platform paths, test new JSON fields - CI / GitHub Actions: permissions blocks, PATH handling, cross-workflow artifacts, prefer ADO for secrets, no placeholder steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c722542 to
bc6cbf5
Compare
|
/azp run azure-dev - cli |
|
Azure Pipelines successfully started running 1 pipeline(s). |
- Remove redundant 'if a.flags.check' branches in auth_token.go that duplicated the same return (Copilot review comment #2) - Add StatusResult JSON serialization tests verifying expiresOn is present when authenticated and omitted when unauthenticated (Copilot review comment #3) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of adding a --check flag to the hidden 'auth token' command, make the existing 'auth status --output json' command agent-friendly: - Exit non-zero when unauthenticated in machine-readable mode, so agents can rely on exit code without parsing output - expiresOn field already added to StatusResult in this PR - Remove --check flag and its tests (net -90 lines) Agents can now validate auth with: azd auth status --output json # exit 0 + JSON with expiresOn = valid # exit 1 + JSON with status:unauthenticated = invalid This is more discoverable than a hidden flag on a hidden command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit 7253f21.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per @JeffreyCA feedback: - Return auth.ErrNoCurrentUser when unauthenticated in both JSON and interactive modes (exit non-zero in all cases) - In JSON mode, format output before returning error to avoid double-print - In interactive mode, show status UX then exit non-zero Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per @vhvb1989 feedback: unauthenticated is a valid result, not a command failure. Non-zero exit should only be for unexpected errors. The expiresOn and LoginGuardMiddleware improvements remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When using azd init -t <template>, automatically create a project directory named after the template and initialize inside it, similar to how git clone creates a directory. Changes: - Add optional [directory] positional argument to azd init - Auto-derive folder name from template path (git clone conventions) - Create directory, os.Chdir into it, run full init pipeline inside - Pass "." to use current directory (preserves existing behavior) - Show cd hint after init so users know how to enter the project - Add DeriveDirectoryName() helper with path traversal protection - Validate target directory: prompt if non-empty, error with --no-prompt Fixes #7289 Related to #4032 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use Readdirnames(1) instead of os.ReadDir for efficient empty check - Update help text/snapshots to reflect template auto-directory behavior - Fix DeriveDirectoryName to return 'new-project' for '.'/'..' inputs - Quote cd hint path when it contains whitespace Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move init-mode validation before directory creation to prevent orphaned directories on conflicting flags - Resolve local template paths to absolute before os.Chdir so that relative paths like ../my-template resolve against original CWD - Reject positional [directory] arg when not in template mode with a clear error message - Export LooksLikeLocalPath for use outside the templates package - Remove duplicate validation code (branch check, mode count) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Test_RunMethodsNoBareErrors test flagged the positional directory argument error as a bare fmt.Errorf without %w wrapping. Wrapped with ErrInvalidFlagCombination sentinel to produce meaningful telemetry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
init -t creates a subdirectory named after the template (like git clone), so azure.yaml lives at targetDir/todo-nodejs-mongo/azure.yaml, not targetDir/azure.yaml. Added templateDir field to test table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace ostest.Chdir (raw os.Chdir) with t.Chdir in detect_confirm_test.go. The t.Chdir API integrates properly with Go 1.26's testing framework for process-wide cwd changes, preventing subtle test state corruption that caused subsequent tests in the same package to fail on CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove t.Parallel() from Test_detectConfirm_confirm which conflicts with t.Chdir() added by this PR. Go 1.26 panics when both are used together, causing all tests in the package to fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… tests - [High] Add deferred cleanup to remove created directory and restore CWD on init failure, matching git clone's behavior - [Medium] Handle Readdirnames error properly instead of discarding it; only treat io.EOF as empty directory - [Low] Log when DeriveDirectoryName falls back to 'new-project' default - [Low] Add test case for DeriveDirectoryName with empty string input Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add missing args parameter to newInitAction call in coverage test - Apply go fix: strings.Split -> strings.SplitSeq in process_darwin.go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
36fbe12 to
73f63de
Compare
Re-record all functional test recordings against live Azure (eastus2, Bicep 0.42.1) to fix stale playback failures caused by Bicep version bump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@spboyer what is the resolution for not breaking existing behavior? |
The test was checking the parent directory for git history and template files, but with the auto-create directory feature, template init now creates a subdirectory derived from the template name. Update assertions to check the cosmos-dotnet-core-todo-app subdirectory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@jongio Good question. Existing behavior is preserved through three mechanisms: 1. azd init --template <url> . # uses current directory (old behavior)
azd init --template <url> # creates subdirectory (new behavior)2. 3. Tests covering backward compatibility
4. Cleanup on failure |
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
hemarina
left a comment
There was a problem hiding this comment.
For breaking change, could we add a warning message to remind users?
| func newInitCmd() *cobra.Command { | ||
| return &cobra.Command{ | ||
| Use: "init", | ||
| Use: "init [directory]", |
There was a problem hiding this comment.
When I typed azd init [dir], it prints out ERROR: positional [directory] argument requires --template: invalid flag combination. This is user confusing. We should keep init in help message.
| Use: "init [directory]", | |
| Use: "init", |
|
|
||
| if i.console.IsNoPromptMode() { | ||
| return fmt.Errorf( | ||
| "directory '%s' already exists and is not empty; "+ |
Deferred Cleanup Edge Cases — init.go defer blockReviewed the cleanup defer in the directory-creation path:
Findings
Suggested Fix`go // ... MkdirAll, Chdir ... defer func() { This addresses: (1) error visibility via logging, (2) not deleting pre-existing directories, and (3) diagnostic output when cleanup fails on Windows. |
jongio
left a comment
There was a problem hiding this comment.
Supplementary review - adding findings not covered by hemarina's or jongio's existing feedback.
The core concern is the breaking behavioral change: azd init -t currently initializes in CWD, and this PR changes the default to create a subdirectory. The UX improvement is real, but the risk to existing automation/scripts is high since there's no opt-out or migration path beyond passing . explicitly. See inline comment for suggested approaches.
Other findings: --filter interactive path doesn't get the auto-directory behavior (inconsistency), absolute path acceptance in the positional arg, and a test coverage gap around failure cleanup.
| if err := i.validateTargetDirectory(ctx, targetDir); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
There was a problem hiding this comment.
[High] Breaking behavioral change for existing automation
Scripts doing azd init -t foo --no-prompt -e env && azd up currently get azure.yaml in CWD. After this change, azure.yaml lands inside a foo/ subdirectory, but azd up still runs in the parent - silent failure.
This is a better UX for interactive users, but the default-on behavior is risky because we don't know what's out there depending on the current in-place behavior. CI pipelines, tutorials, blog posts, and wrapper scripts all assume init puts files in CWD.
Suggestions (pick one):
- Gate behind
--clone-layoutopt-in for v1, then flip the default in a later release after deprecation messaging - Detect
--no-promptand default to CWD (current behavior) unless a positional arg is explicitly passed - At minimum, treat this as a documented breaking change in release notes and bump a minor version so
azd init -t foo .becomes the migration path
| return filepath.Join(wd, dirName), nil | ||
| } | ||
|
|
||
| // Template selected via --filter tags (interactive selection) — use CWD |
There was a problem hiding this comment.
[Medium] --filter interactive selection doesn't get auto-directory
When isTemplateInit is true only because of --filter tags (no --template), this returns CWD since templatePath is empty. Users who pick a template interactively via --filter won't get the new git-clone-style behavior - only --template users do.
Consider deferring directory resolution until after initializeTemplate returns, when the actual template name is known. This would also enable auto-directory for interactively selected templates.
| if dirArg == "." { | ||
| return wd, nil | ||
| } | ||
|
|
There was a problem hiding this comment.
[Low] Accepts absolute paths without validation
A user can pass azd init -t foo /some/absolute/path and this will create/chdir into an arbitrary location. If cleanup-on-failure is added later (per hemarina's comment), that could lead to os.RemoveAll on a directory outside the user's project root.
Consider rejecting absolute paths here, or validating the target is a descendant of the original CWD.
|
|
||
| // Run will panic or error later due to missing template mocks, | ||
| // but the directory should be created before that point. | ||
| _ = runActionSafe(*mockContext.Context, action) |
There was a problem hiding this comment.
[Low] Missing failure-cleanup test coverage
runActionSafe catches panics and returns an error, but the test only asserts DirExists - it doesn't verify what happens when a downstream step fails (non-panic). Once cleanup-on-failure is implemented per hemarina's feedback, add a test that verifies the directory is removed when Run returns a real error.

Summary
When using
azd init -t <template>, automatically create a project directory named after the template and initialize inside it — similar to howgit clonecreates a directory.This addresses user feedback from a getting-started study (#4032):
Fixes #7289
Changes
[directory]positional argument toazd initgit cloneconventionsos.Chdirinto it, run full init pipeline inside.as directory to use current directory (preserves existing behavior)cdhint after init so users know how to enter the projectDeriveDirectoryName()helper with path traversal protection--no-promptUsage
Testing
DeriveDirectoryName()including edge cases (traversal, .git suffix, various URL formats)Files Changed
pkg/templates/path.goDeriveDirectoryName()with traversal protectioncmd/init.gocmd/init_test.gopkg/templates/path_test.gocmd/testdata/*.snap