Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/apps/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const ManagementGroupID = "management"
func Commands() []*cobra.Command {
return []*cobra.Command{
newInitCmd(),
newManifestCmd(),
newDevRemoteCmd(),
newLogsCommand(),
newRunLocal(),
Expand Down
46 changes: 46 additions & 0 deletions cmd/apps/init_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package apps

import (
"bytes"
"context"
"errors"
"io"
"os"
"path/filepath"
"testing"

"github.com/databricks/cli/libs/apps/manifest"
Expand Down Expand Up @@ -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)
}
113 changes: 113 additions & 0 deletions cmd/apps/manifest.go
Original file line number Diff line number Diff line change
@@ -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 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.

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
}