From b217ab1946e333a5e91f4d1130d6421d64cfb561 Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Tue, 17 Feb 2026 13:16:27 +0100 Subject: [PATCH 1/2] feat: add init manifest flag --- cmd/apps/apps.go | 1 + cmd/apps/init_test.go | 46 +++++++++++++++++ cmd/apps/manifest.go | 113 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 cmd/apps/manifest.go diff --git a/cmd/apps/apps.go b/cmd/apps/apps.go index 73ddd258f5..cd1a02f1b5 100644 --- a/cmd/apps/apps.go +++ b/cmd/apps/apps.go @@ -11,6 +11,7 @@ const ManagementGroupID = "management" func Commands() []*cobra.Command { return []*cobra.Command{ newInitCmd(), + newManifestCmd(), newDevRemoteCmd(), newLogsCommand(), newRunLocal(), diff --git a/cmd/apps/init_test.go b/cmd/apps/init_test.go index 4d3445248c..703e28a326 100644 --- a/cmd/apps/init_test.go +++ b/cmd/apps/init_test.go @@ -1,8 +1,12 @@ package apps import ( + "bytes" "context" "errors" + "io" + "os" + "path/filepath" "testing" "github.com/databricks/cli/libs/apps/manifest" @@ -453,3 +457,45 @@ func TestAppendUniqueNoValues(t *testing.T) { result := appendUnique([]string{"a", "b"}) assert.Equal(t, []string{"a", "b"}, result) } + +func TestRunManifestOnlyFound(t *testing.T) { + dir := t.TempDir() + manifestPath := filepath.Join(dir, manifest.ManifestFileName) + content := `{"$schema":"https://example.com/schema","version":"1.0","plugins":{"analytics":{"name":"analytics","resources":{"required":[],"optional":[]}}}}` + require.NoError(t, os.WriteFile(manifestPath, []byte(content), 0o644)) + + old := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + err = runManifestOnly(context.Background(), dir, "", "") + w.Close() + os.Stdout = old + require.NoError(t, err) + + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + out := buf.String() + assert.Contains(t, out, `"version": "1.0"`) + assert.Contains(t, out, `"analytics"`) +} + +func TestRunManifestOnlyNotFound(t *testing.T) { + dir := t.TempDir() + + old := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + err = runManifestOnly(context.Background(), dir, "", "") + w.Close() + os.Stdout = old + require.NoError(t, err) + + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + out := buf.String() + assert.Equal(t, "No appkit.plugins.json manifest found in this template.\n", out) +} diff --git a/cmd/apps/manifest.go b/cmd/apps/manifest.go new file mode 100644 index 0000000000..01b7b93e92 --- /dev/null +++ b/cmd/apps/manifest.go @@ -0,0 +1,113 @@ +package apps + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/databricks/cli/libs/apps/manifest" + "github.com/spf13/cobra" +) + +// runManifestOnly resolves the template, loads appkit.plugins.json if present, and prints it to stdout (or a message if not found). +func runManifestOnly(ctx context.Context, templatePath, branch, version string) error { + templateSrc := templatePath + if templateSrc == "" { + templateSrc = os.Getenv(templatePathEnvVar) + } + gitRef := branch + usingDefaultTemplate := templateSrc == "" + if usingDefaultTemplate { + switch { + case branch != "": + case version != "": + gitRef = normalizeVersion(version) + default: + gitRef = appkitDefaultBranch + } + templateSrc = appkitRepoURL + } + + branchForClone := branch + subdirForClone := "" + if usingDefaultTemplate { + branchForClone = gitRef + subdirForClone = appkitTemplateDir + } + resolvedPath, cleanup, err := resolveTemplate(ctx, templateSrc, branchForClone, subdirForClone) + if err != nil { + return err + } + if cleanup != nil { + defer cleanup() + } + + templateDir := filepath.Join(resolvedPath, "generic") + if _, err := os.Stat(templateDir); os.IsNotExist(err) { + templateDir = resolvedPath + if _, err := os.Stat(templateDir); os.IsNotExist(err) { + return fmt.Errorf("template not found at %s (also checked %s/generic)", resolvedPath, resolvedPath) + } + } + + if manifest.HasManifest(templateDir) { + m, err := manifest.Load(templateDir) + if err != nil { + return fmt.Errorf("load manifest: %w", err) + } + enc, err := json.MarshalIndent(m, "", " ") + if err != nil { + return fmt.Errorf("encode manifest: %w", err) + } + fmt.Fprintln(os.Stdout, string(enc)) + return nil + } + + fmt.Fprintln(os.Stdout, "No appkit.plugins.json manifest found in this template.") + return nil +} + +func newManifestCmd() *cobra.Command { + var ( + templatePath string + branch string + version string + ) + + cmd := &cobra.Command{ + Use: "manifest", + Short: "Print the template's appkit.plugins.json manifest to stdout", + Hidden: true, + Long: `Resolves a template (default AppKit repo or --template URL), locates appkit.plugins.json, +and prints its contents to stdout. No workspace authentication is required. + +Use the same --template, --branch, and --version flags as "databricks apps init" to target +a specific template. Without --template, uses the default AppKit template (main branch). + +Examples: + # Default template manifest + databricks apps manifest + + # Specific version of default template + databricks apps manifest --version v0.2.0 + + # Manifest from a GitHub repo + databricks apps manifest --template https://github.com/user/repo --branch main`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + if cmd.Flags().Changed("branch") && cmd.Flags().Changed("version") { + return errors.New("--branch and --version are mutually exclusive") + } + return runManifestOnly(ctx, templatePath, branch, version) + }, + } + + cmd.Flags().StringVar(&templatePath, "template", "", "Template path (local directory or GitHub URL)") + cmd.Flags().StringVar(&branch, "branch", "", "Git branch or tag (for GitHub templates, mutually exclusive with --version)") + cmd.Flags().StringVar(&version, "version", "", "AppKit version for default template (default: main, use 'latest' for main branch)") + return cmd +} From fc22f469da374b0de9a6035f141d462a5969281b Mon Sep 17 00:00:00 2001 From: MarioCadenas Date: Tue, 17 Feb 2026 16:56:31 +0100 Subject: [PATCH 2/2] chore: fixup --- cmd/apps/manifest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/apps/manifest.go b/cmd/apps/manifest.go index 01b7b93e92..f4f5c0a4bb 100644 --- a/cmd/apps/manifest.go +++ b/cmd/apps/manifest.go @@ -79,7 +79,7 @@ func newManifestCmd() *cobra.Command { cmd := &cobra.Command{ Use: "manifest", - Short: "Print the template's appkit.plugins.json manifest to stdout", + Short: "Print template manifest with available plugins and required resources", Hidden: true, Long: `Resolves a template (default AppKit repo or --template URL), locates appkit.plugins.json, and prints its contents to stdout. No workspace authentication is required.