From 275cf31a1a200870604d0434bd6f3e51658f4535 Mon Sep 17 00:00:00 2001 From: Lennart Kats Date: Fri, 9 Oct 2026 12:36:58 +0000 Subject: [PATCH 1/2] Add AppKit template to bundle init --- .nextchanges/bundles/app-appkit.md | 1 + acceptance/bundle/help/bundle-init/output.txt | 9 +- .../bundle/templates/app-appkit/input.json | 5 + .../bundle/templates/app-appkit/out.test.toml | 3 + .../bundle/templates/app-appkit/output.txt | 55 ++++++++ .../output/appkit-bundle-test/app.yaml | 1 + .../appkit-bundle-test/appkit.plugins.json | 16 +++ .../output/appkit-bundle-test/databricks.yml | 17 +++ .../output/appkit-bundle-test/package.json | 13 ++ .../output/appkit-bundle-test/server.js | 5 + acceptance/bundle/templates/app-appkit/script | 19 +++ .../templates/app-appkit/template/app.yaml | 1 + .../app-appkit/template/appkit.plugins.json | 16 +++ .../app-appkit/template/databricks.yml.tmpl | 17 +++ .../app-appkit/template/package.json | 12 ++ .../templates/app-appkit/template/server.js | 5 + .../bundle/templates/app-appkit/test.toml | 1 + cmd/apps/init.go | 5 + cmd/bundle/bundle.go | 4 +- cmd/bundle/init.go | 36 +++-- cmd/bundle/init_appkit.go | 127 ++++++++++++++++++ cmd/cmd.go | 3 +- libs/template/resolver.go | 6 + libs/template/template.go | 8 ++ libs/template/template_test.go | 4 +- 25 files changed, 368 insertions(+), 21 deletions(-) create mode 100644 .nextchanges/bundles/app-appkit.md create mode 100644 acceptance/bundle/templates/app-appkit/input.json create mode 100644 acceptance/bundle/templates/app-appkit/out.test.toml create mode 100644 acceptance/bundle/templates/app-appkit/output.txt create mode 100644 acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/app.yaml create mode 100644 acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/appkit.plugins.json create mode 100644 acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/databricks.yml create mode 100644 acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/package.json create mode 100644 acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/server.js create mode 100644 acceptance/bundle/templates/app-appkit/script create mode 100644 acceptance/bundle/templates/app-appkit/template/app.yaml create mode 100644 acceptance/bundle/templates/app-appkit/template/appkit.plugins.json create mode 100644 acceptance/bundle/templates/app-appkit/template/databricks.yml.tmpl create mode 100644 acceptance/bundle/templates/app-appkit/template/package.json create mode 100644 acceptance/bundle/templates/app-appkit/template/server.js create mode 100644 acceptance/bundle/templates/app-appkit/test.toml create mode 100644 cmd/bundle/init_appkit.go diff --git a/.nextchanges/bundles/app-appkit.md b/.nextchanges/bundles/app-appkit.md new file mode 100644 index 00000000000..7bf1302651f --- /dev/null +++ b/.nextchanges/bundles/app-appkit.md @@ -0,0 +1 @@ +* Add `app-appkit` to `databricks bundle init` to scaffold an AppKit app using the same template as `databricks apps init`. Pass `--config-file` with `project_name` to initialize without prompts. diff --git a/acceptance/bundle/help/bundle-init/output.txt b/acceptance/bundle/help/bundle-init/output.txt index dad5e198e4c..521f69434c3 100644 --- a/acceptance/bundle/help/bundle-init/output.txt +++ b/acceptance/bundle/help/bundle-init/output.txt @@ -10,17 +10,16 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f - dbt-sql: The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks) - mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks) - pydabs: A variant of the 'default-python' template that defines resources in Python instead of YAML +- app-appkit: Databricks App with AppKit (TypeScript) - a local file system path with a template directory - a Git repository URL, e.g. https://github.com/my/repository Examples: - databricks bundle init # Choose from built-in templates - databricks bundle init default-python # Python jobs and notebooks - databricks bundle init dbt-sql # dbt + SQL warehouse project - databricks bundle init --output-dir ./my-project + databricks bundle init default-python + databricks bundle init app-appkit --config-file input.json # {"project_name":"my-app"} After initialization: - databricks bundle deploy --target dev + databricks bundle deploy See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates. diff --git a/acceptance/bundle/templates/app-appkit/input.json b/acceptance/bundle/templates/app-appkit/input.json new file mode 100644 index 00000000000..e6bf923499b --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/input.json @@ -0,0 +1,5 @@ +{ + "project_name": "appkit-bundle-test", + "app_description": "AppKit bundle initialization test", + "skip_install": true +} diff --git a/acceptance/bundle/templates/app-appkit/out.test.toml b/acceptance/bundle/templates/app-appkit/out.test.toml new file mode 100644 index 00000000000..27ec2a7fcd6 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/out.test.toml @@ -0,0 +1,3 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DMS = [""] diff --git a/acceptance/bundle/templates/app-appkit/output.txt b/acceptance/bundle/templates/app-appkit/output.txt new file mode 100644 index 00000000000..782e69c0123 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output.txt @@ -0,0 +1,55 @@ + +=== Missing project name fails without a terminal +>>> musterr [CLI] bundle init app-appkit +Error: app-appkit requires project_name in --config-file in non-interactive mode + +=== Unsupported template directory fails before initialization +>>> musterr [CLI] bundle init app-appkit --template-dir template +Error: --template-dir is not supported for app-appkit; use databricks apps init --template for a custom AppKit template + +=== Initialize AppKit from a config file without a terminal +>>> [CLI] bundle init app-appkit --config-file input.json --output-dir output +✔ Creating project + +✔ Project created successfully! + + Location: [TEST_TMP_DIR]/output/appkit-bundle-test + Files: 5 + + Next steps: + + cd appkit-bundle-test + pnpm install --frozen-lockfile && pnpm run dev + + +>>> [CLI] bundle validate +Name: appkit-bundle-test +Target: default +Workspace: + Host: [DATABRICKS_URL] + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/appkit-bundle-test/default + +Validation OK! + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/appkit-bundle-test/default/files... +✓ Deployment succeeded +Created apps.app +Files: 5 uploaded, 0 deleted +Resources: 1 created, 0 changed, 0 deleted, 0 unchanged + +>>> [CLI] apps get appkit-bundle-test -o json +{ + "name": "appkit-bundle-test", + "compute_state": "ACTIVE", + "deployment_state": "SUCCEEDED" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.apps.app + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/appkit-bundle-test/default + +Destroy: 1 deleted diff --git a/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/app.yaml b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/app.yaml new file mode 100644 index 00000000000..f474313bb13 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/app.yaml @@ -0,0 +1 @@ +command: ["npm", "run", "start"] diff --git a/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/appkit.plugins.json b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/appkit.plugins.json new file mode 100644 index 00000000000..d391f596241 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/appkit.plugins.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "plugins": { + "server": { + "name": "server", + "displayName": "Server", + "description": "HTTP server", + "package": "@databricks/appkit", + "requiredByTemplate": true, + "resources": { + "required": [], + "optional": [] + } + } + } +} diff --git a/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/databricks.yml b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/databricks.yml new file mode 100644 index 00000000000..f0bdad79cdd --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/databricks.yml @@ -0,0 +1,17 @@ +bundle: + name: appkit-bundle-test + +resources: + apps: + app: + name: appkit-bundle-test + description: AppKit bundle initialization test + source_code_path: ./ + lifecycle: + started: true + +targets: + default: + default: true + workspace: + host: [DATABRICKS_URL] diff --git a/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/package.json b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/package.json new file mode 100644 index 00000000000..8f9f012cf8d --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/package.json @@ -0,0 +1,13 @@ +{ + "name": "appkit-bundle-test", + "version": "1.0.0", + "type": "module", + "description": "AppKit bundle initialization test", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "@databricks/appkit": "0.57.0" + }, + "packageManager": "pnpm@11.0.8" +} diff --git a/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/server.js b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/server.js new file mode 100644 index 00000000000..e0a132b230a --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/output/appkit-bundle-test/server.js @@ -0,0 +1,5 @@ +import { createApp, server } from '@databricks/appkit'; + +createApp({ + plugins: [server(),], +}).catch(console.error); diff --git a/acceptance/bundle/templates/app-appkit/script b/acceptance/bundle/templates/app-appkit/script new file mode 100644 index 00000000000..a1b7aa42a60 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/script @@ -0,0 +1,19 @@ +export DATABRICKS_APPKIT_TEMPLATE_PATH="$TESTDIR/template" + +title "Missing project name fails without a terminal" +trace musterr $CLI bundle init app-appkit < /dev/null + +title "Unsupported template directory fails before initialization" +trace musterr $CLI bundle init app-appkit --template-dir template < /dev/null + +title "Initialize AppKit from a config file without a terminal" +trace $CLI bundle init app-appkit --config-file input.json --output-dir output < /dev/null +test ! -d output/appkit-bundle-test/node_modules + +cd output/appkit-bundle-test + +trace $CLI bundle validate < /dev/null +trace $CLI bundle deploy < /dev/null +trace $CLI apps get appkit-bundle-test -o json | jq '{name, compute_state: .compute_status.state, deployment_state: .active_deployment.status.state}' +trace $CLI bundle destroy --auto-approve < /dev/null +rm -r .databricks diff --git a/acceptance/bundle/templates/app-appkit/template/app.yaml b/acceptance/bundle/templates/app-appkit/template/app.yaml new file mode 100644 index 00000000000..f474313bb13 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/template/app.yaml @@ -0,0 +1 @@ +command: ["npm", "run", "start"] diff --git a/acceptance/bundle/templates/app-appkit/template/appkit.plugins.json b/acceptance/bundle/templates/app-appkit/template/appkit.plugins.json new file mode 100644 index 00000000000..d391f596241 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/template/appkit.plugins.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "plugins": { + "server": { + "name": "server", + "displayName": "Server", + "description": "HTTP server", + "package": "@databricks/appkit", + "requiredByTemplate": true, + "resources": { + "required": [], + "optional": [] + } + } + } +} diff --git a/acceptance/bundle/templates/app-appkit/template/databricks.yml.tmpl b/acceptance/bundle/templates/app-appkit/template/databricks.yml.tmpl new file mode 100644 index 00000000000..b7afbfd83d0 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/template/databricks.yml.tmpl @@ -0,0 +1,17 @@ +bundle: + name: {{.projectName}} + +resources: + apps: + app: + name: {{.projectName}} + description: {{.appDescription}} + source_code_path: ./ + lifecycle: + started: true + +targets: + default: + default: true + workspace: + host: {{.workspaceHost}} diff --git a/acceptance/bundle/templates/app-appkit/template/package.json b/acceptance/bundle/templates/app-appkit/template/package.json new file mode 100644 index 00000000000..292e1a0ac95 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/template/package.json @@ -0,0 +1,12 @@ +{ + "name": "{{.projectName}}", + "version": "1.0.0", + "type": "module", + "description": "{{.appDescription}}", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "@databricks/appkit": "0.57.0" + } +} diff --git a/acceptance/bundle/templates/app-appkit/template/server.js b/acceptance/bundle/templates/app-appkit/template/server.js new file mode 100644 index 00000000000..67f9aab20d3 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/template/server.js @@ -0,0 +1,5 @@ +import { createApp{{range $name, $_ := .plugins}}, {{$name}}{{end}} } from '@databricks/appkit'; + +createApp({ + plugins: [{{range $name, $_ := .plugins}}{{$name}}(),{{end}}], +}).catch(console.error); diff --git a/acceptance/bundle/templates/app-appkit/test.toml b/acceptance/bundle/templates/app-appkit/test.toml new file mode 100644 index 00000000000..9609e1af299 --- /dev/null +++ b/acceptance/bundle/templates/app-appkit/test.toml @@ -0,0 +1 @@ +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/cmd/apps/init.go b/cmd/apps/init.go index b93bbf8e86d..7ba36f79999 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -220,6 +220,11 @@ Environment variables: return cmd } +// NewInitCommand creates the AppKit initializer for use by other CLI commands. +func NewInitCommand() *cobra.Command { + return newInitCmd() +} + type createOptions struct { templatePath string branch string diff --git a/cmd/bundle/bundle.go b/cmd/bundle/bundle.go index d8334d38289..ba045180aaf 100644 --- a/cmd/bundle/bundle.go +++ b/cmd/bundle/bundle.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" ) -func New() *cobra.Command { +func New(appKitInit func() *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "bundle", Short: "Declarative Automation Bundles let you express data/AI/analytics projects as code.", @@ -32,7 +32,7 @@ Online documentation: https://docs.databricks.com/en/dev-tools/bundles/index.htm cmd.AddCommand(newSchemaCommand()) cmd.AddCommand(newSyncCommand()) cmd.AddCommand(newValidateCommand()) - cmd.AddCommand(newInitCommand()) + cmd.AddCommand(newInitCommand(appKitInit)) cmd.AddCommand(newSummaryCommand()) cmd.AddCommand(newGenerateCommand()) cmd.AddCommand(newDebugCommand()) diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go index ed235ecc73c..61f2f4acffd 100644 --- a/cmd/bundle/init.go +++ b/cmd/bundle/init.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -func newInitCommand() *cobra.Command { +func newInitCommand(appKitInit func() *cobra.Command) *cobra.Command { cmd := &cobra.Command{ Use: "init [TEMPLATE_PATH]", Short: "Initialize using a bundle template", @@ -24,13 +24,11 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f - a Git repository URL, e.g. https://github.com/my/repository Examples: - databricks bundle init # Choose from built-in templates - databricks bundle init default-python # Python jobs and notebooks - databricks bundle init dbt-sql # dbt + SQL warehouse project - databricks bundle init --output-dir ./my-project + databricks bundle init default-python + databricks bundle init app-appkit --config-file input.json # {"project_name":"my-app"} After initialization: - databricks bundle deploy --target dev + databricks bundle deploy See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, template.HelpDescriptions()), } @@ -51,10 +49,21 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf if tag != "" && branch != "" { return errors.New("only one of --tag or --branch can be specified") } - + ctx := cmd.Context() var templatePathOrUrl string if len(args) > 0 { templatePathOrUrl = args[0] + } else { + selected, err := template.SelectTemplate(ctx) + if errors.Is(err, template.ErrCustomSelected) { + cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.") + cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.") + return nil + } + if err != nil { + return err + } + templatePathOrUrl = string(selected) } r := template.Resolver{ TemplatePathOrUrl: templatePathOrUrl, @@ -65,12 +74,15 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf Branch: branch, } - ctx := cmd.Context() tmpl, err := r.Resolve(ctx) - if errors.Is(err, template.ErrCustomSelected) { - cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.") - cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.") - return nil + if errors.Is(err, template.ErrAppKitSelected) { + return runAppKitInit(cmd, appKitInit, appKitInitOptions{ + configFile: configFile, + outputDir: outputDir, + templateDir: templateDir, + tag: tag, + branch: branch, + }) } if err != nil { return err diff --git a/cmd/bundle/init_appkit.go b/cmd/bundle/init_appkit.go new file mode 100644 index 00000000000..0d8ef9c1b45 --- /dev/null +++ b/cmd/bundle/init_appkit.go @@ -0,0 +1,127 @@ +package bundle + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/spf13/cobra" +) + +type appKitInitOptions struct { + configFile string + outputDir string + templateDir string + tag string + branch string +} + +type appKitInitConfig struct { + ProjectName string `json:"project_name"` + AppDescription string `json:"app_description"` + Features []string `json:"features"` + Set []string `json:"set"` + AuthMode string `json:"auth_mode"` + PackageManager string `json:"package_manager"` + SkipInstall bool `json:"skip_install"` + Version string `json:"version"` +} + +func readAppKitInitConfig(path string) (appKitInitConfig, error) { + var config appKitInitConfig + if path == "" { + return config, nil + } + + file, err := os.Open(path) + if err != nil { + return config, fmt.Errorf("open AppKit config file: %w", err) + } + defer file.Close() + + decoder := json.NewDecoder(file) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&config); err != nil { + return config, fmt.Errorf("read AppKit config file: %w", err) + } + var extra any + if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { + return config, errors.New("AppKit config file must contain one JSON object") + } + return config, nil +} + +func runAppKitInit(cmd *cobra.Command, newAppKitInit func() *cobra.Command, opts appKitInitOptions) error { + if opts.templateDir != "" { + return errors.New("--template-dir is not supported for app-appkit; use databricks apps init --template for a custom AppKit template") + } + if root.OutputType(cmd) == flags.OutputJSON { + return errors.New("-o json is not supported for app-appkit; use text output") + } + + config, err := readAppKitInitConfig(opts.configFile) + if err != nil { + return err + } + if config.Version != "" && (opts.tag != "" || opts.branch != "") { + return errors.New("version in --config-file cannot be combined with --tag or --branch") + } + if config.ProjectName == "" && !cmdio.IsPromptSupported(cmd.Context()) { + return errors.New("app-appkit requires project_name in --config-file in non-interactive mode") + } + + appCmd := newAppKitInit() + appCmd.SetContext(cmd.Context()) + appFlags := appCmd.Flags() + values := map[string]string{ + "name": config.ProjectName, + "description": config.AppDescription, + "output-dir": opts.outputDir, + "auth-mode": config.AuthMode, + "package-manager": config.PackageManager, + } + for flag, value := range values { + if value == "" { + continue + } + if err := appFlags.Set(flag, value); err != nil { + return fmt.Errorf("set AppKit %s: %w", flag, err) + } + } + if len(config.Features) > 0 { + if err := appFlags.Set("features", strings.Join(config.Features, ",")); err != nil { + return fmt.Errorf("set AppKit features: %w", err) + } + } + for _, value := range config.Set { + if err := appFlags.Set("set", value); err != nil { + return fmt.Errorf("set AppKit resource value: %w", err) + } + } + if opts.branch != "" || opts.tag != "" { + ref := opts.branch + if ref == "" { + ref = opts.tag + } + if err := appFlags.Set("branch", ref); err != nil { + return fmt.Errorf("set AppKit template ref: %w", err) + } + } else if config.Version != "" { + if err := appFlags.Set("version", config.Version); err != nil { + return fmt.Errorf("set AppKit version: %w", err) + } + } + if config.SkipInstall { + if err := appFlags.Set("skip-install", "true"); err != nil { + return fmt.Errorf("set AppKit skip-install: %w", err) + } + } + + return appCmd.RunE(appCmd, nil) +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 7888af65068..4f873af0968 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/cmd/account" "github.com/databricks/cli/cmd/api" + "github.com/databricks/cli/cmd/apps" "github.com/databricks/cli/cmd/auth" "github.com/databricks/cli/cmd/bundle" "github.com/databricks/cli/cmd/cache" @@ -111,7 +112,7 @@ func New(ctx context.Context) *cobra.Command { cli.AddCommand(api.New()) cli.AddCommand(auth.New()) cli.AddCommand(completion.New()) - cli.AddCommand(bundle.New()) + cli.AddCommand(bundle.New(apps.NewInitCommand)) cli.AddCommand(cache.New()) cli.AddCommand(experimental.New()) cli.AddCommand(psql.New()) diff --git a/libs/template/resolver.go b/libs/template/resolver.go index 191b47d42fd..ae55da9d428 100644 --- a/libs/template/resolver.go +++ b/libs/template/resolver.go @@ -193,6 +193,9 @@ type Resolver struct { // template. var ErrCustomSelected = errors.New("custom template selected") +// ErrAppKitSelected indicates that AppKit needs its own scaffold renderer. +var ErrAppKitSelected = errors.New("AppKit template selected") + // Configures the reader and the writer for template and returns // a handle to the template. // Prompts the user if needed. @@ -220,6 +223,9 @@ func (r Resolver) Resolve(ctx context.Context) (*Template, error) { } else { templateName = TemplateName(r.TemplatePathOrUrl) } + if templateName == AppKitApp { + return nil, ErrAppKitSelected + } tmpl := GetDatabricksTemplate(templateName) diff --git a/libs/template/template.go b/libs/template/template.go index cb2e1a8e3a2..251b61cb9a2 100644 --- a/libs/template/template.go +++ b/libs/template/template.go @@ -36,10 +36,13 @@ const ( DbtSql TemplateName = "dbt-sql" MlopsStacks TemplateName = "mlops-stacks" Pydabs TemplateName = "pydabs" + AppKitApp TemplateName = "app-appkit" Custom TemplateName = "custom" Default TemplateName = "default" ) +const appKitDescription = "Databricks App with AppKit (TypeScript)" + var databricksTemplates = []Template{ { name: DefaultPython, @@ -116,6 +119,7 @@ func HelpDescriptions() string { lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description)) } } + lines = append(lines, fmt.Sprintf("- %s: %s", AppKitApp, appKitDescription)) return strings.Join(lines, "\n") } @@ -133,6 +137,7 @@ func options() []cmdio.Tuple { } names = append(names, tuple) } + names = append(names, cmdio.Tuple{Name: string(AppKitApp), Id: appKitDescription}) names = append(names, cmdio.Tuple{ Name: "custom...", @@ -153,6 +158,9 @@ func SelectTemplate(ctx context.Context) (TemplateName, error) { if description == customTemplateDescription { return TemplateName(""), ErrCustomSelected } + if description == appKitDescription { + return AppKitApp, nil + } for _, template := range databricksTemplates { if template.description == description { diff --git a/libs/template/template_test.go b/libs/template/template_test.go index b0a5b6c641b..ac096300291 100644 --- a/libs/template/template_test.go +++ b/libs/template/template_test.go @@ -14,7 +14,8 @@ func TestTemplateHelpDescriptions(t *testing.T) { - default-scala: The default Scala template for JAR jobs - dbt-sql: The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks) - mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks) -- pydabs: A variant of the 'default-python' template that defines resources in Python instead of YAML` +- pydabs: A variant of the 'default-python' template that defines resources in Python instead of YAML +- app-appkit: Databricks App with AppKit (TypeScript)` assert.Equal(t, expected, HelpDescriptions()) } @@ -27,6 +28,7 @@ func TestTemplateOptions(t *testing.T) { {Name: "dbt-sql", Id: "The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks)"}, {Name: "mlops-stacks", Id: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)"}, {Name: "pydabs", Id: "A variant of the 'default-python' template that defines resources in Python instead of YAML"}, + {Name: "app-appkit", Id: "Databricks App with AppKit (TypeScript)"}, {Name: "custom...", Id: "Bring your own template"}, } assert.Equal(t, expected, options()) From 902c15161b5aa49cd5e87b4c5d56bfa8a7a8672a Mon Sep 17 00:00:00 2001 From: Lennart Kats Date: Fri, 9 Oct 2026 12:42:18 +0000 Subject: [PATCH 2/2] Link AppKit changelog entry to PR #7004 --- .nextchanges/bundles/app-appkit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nextchanges/bundles/app-appkit.md b/.nextchanges/bundles/app-appkit.md index 7bf1302651f..bbceef80c13 100644 --- a/.nextchanges/bundles/app-appkit.md +++ b/.nextchanges/bundles/app-appkit.md @@ -1 +1 @@ -* Add `app-appkit` to `databricks bundle init` to scaffold an AppKit app using the same template as `databricks apps init`. Pass `--config-file` with `project_name` to initialize without prompts. +* Add `app-appkit` to `databricks bundle init` to scaffold an AppKit app using the same template as `databricks apps init`. Pass `--config-file` with `project_name` to initialize without prompts. ([#7004](https://github.com/databricks/cli/pull/7004))