Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Validation OK!

>>> [CLI] bundle deploy
Building python_artifact...
Error: build failed python_artifact, error: exit status 127, output: bash: uv: command not found
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be specific output to your local run and will cause tests to fail on CI, could you revert?

Error: build failed python_artifact, error: exit status 127, output: /opt/homebrewbash: uv: command not found



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ To get started, refer to the project README.md file and the documentation at htt
>>> find my_pydabs -mindepth 1 ! -name pyproject.toml ! -regex .*/resources.* -delete

>>> ruff format --quiet --diff --check my_pydabs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, but you can just install ruff and it should succeed

script: line 45: ruff: command not found

>>> yamlcheck.py
Exit code: 127
19 changes: 18 additions & 1 deletion experimental/apps-mcp/lib/prompts/apps.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,35 @@ DATABRICKS APPS DEVELOPMENT

invoke_databricks_cli 'experimental apps-mcp tools init-template --name my-app-name --description "My app description"'

# Local Development

⚠️ After scaffolding, refer to CLAUDE.md in your app directory for operational commands.

Quick reference:
- Local testing: npm run dev
- Validation: invoke_databricks_cli 'experimental apps-mcp tools validate ./your-app'
- Deployment: See "# Deployment" section

For complete guidance including what NOT to do, check CLAUDE.md in your app.

# Validation

⚠️ Always validate your app before deploying to production:
⚠️ Always validate before deploying:

invoke_databricks_cli 'experimental apps-mcp tools validate ./your-app-location'

This runs: npm install → build → typecheck → tests
See CLAUDE.md for details.

# Deployment

⚠️ Use the deploy command which validates, deploys, and runs the app:

invoke_databricks_cli 'experimental apps-mcp tools deploy'

⚠️ IMPORTANT: Always validate first!
See CLAUDE.md for pre-deployment checklist.

# View and manage your app:

invoke_databricks_cli 'bundle summary'
110 changes: 71 additions & 39 deletions experimental/apps-mcp/lib/validation/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package validation

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"

"github.com/databricks/cli/libs/log"
Expand All @@ -11,6 +14,27 @@ import (
// ValidationNodeJs implements validation for Node.js-based projects using build, type check, and tests.
type ValidationNodeJs struct{}

// PackageJSON represents package.json structure
type PackageJSON struct {
Scripts map[string]string `json:"scripts"`
}

// readPackageJSON reads and parses package.json
func readPackageJSON(workDir string) (*PackageJSON, error) {
pkgPath := filepath.Join(workDir, "package.json")
data, err := os.ReadFile(pkgPath)
if err != nil {
return nil, fmt.Errorf("failed to read package.json: %w", err)
}

var pkg PackageJSON
if err := json.Unmarshal(data, &pkg); err != nil {
return nil, fmt.Errorf("failed to parse package.json: %w", err)
}

return &pkg, nil
}

type validationStep struct {
name string
command string
Expand All @@ -19,39 +43,55 @@ type validationStep struct {
}

func (v *ValidationNodeJs) Validate(ctx context.Context, workDir string) (*ValidateResult, error) {
log.Info(ctx, "Starting Node.js validation: build + typecheck + tests")
log.Info(ctx, "Starting Node.js validation")
startTime := time.Now()
var progressLog []string

progressLog = append(progressLog, "🔄 Starting Node.js validation: build + typecheck + tests")
progressLog = append(progressLog, "🔄 Starting Node.js validation")

// Read package.json
pkg, err := readPackageJSON(workDir)
if err != nil {
log.Warnf(ctx, "Could not read package.json: %v. Using defaults.", err)
progressLog = append(progressLog, "⚠️ Could not read package.json, using defaults")
pkg = &PackageJSON{Scripts: map[string]string{
"build": "", "typecheck": "", "test": "",
}}
}

// Build steps based on available scripts
steps := []validationStep{
{
name: "install",
command: "npm install",
errorPrefix: "Failed to install dependencies",
displayName: "Install",
},
{
name: "build",
command: "npm run build --if-present",
errorPrefix: "Failed to run npm build",
displayName: "Build",
},
{
name: "typecheck",
command: "npm run typecheck --if-present",
errorPrefix: "Failed to run client typecheck",
displayName: "Type check",
},
{
name: "tests",
command: "npm run test --if-present",
errorPrefix: "Failed to run tests",
displayName: "Tests",
},
{name: "install", command: "npm install", errorPrefix: "Failed to install dependencies", displayName: "Install"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to parse package.json?

AFAIK, this is functionally equivalent to the old implementation. The old implementation will always work even if thescrips are missing since we are running with --if-present

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted to have a single source of truth, but I agree the code looks less clean , so will roll this back until I have cleaner idea of implementing this

}

if _, ok := pkg.Scripts["build"]; ok {
steps = append(steps, validationStep{
name: "build", command: "npm run build", errorPrefix: "Failed to run build", displayName: "Build",
})
progressLog = append(progressLog, "✓ Found 'build' script")
} else {
progressLog = append(progressLog, "⚠️ No 'build' script, skipping")
}

if _, ok := pkg.Scripts["typecheck"]; ok {
steps = append(steps, validationStep{
name: "typecheck", command: "npm run typecheck", errorPrefix: "Failed typecheck", displayName: "Type check",
})
progressLog = append(progressLog, "✓ Found 'typecheck' script")
} else {
progressLog = append(progressLog, "⚠️ No 'typecheck' script, skipping")
}

if _, ok := pkg.Scripts["test"]; ok {
steps = append(steps, validationStep{
name: "test", command: "npm run test", errorPrefix: "Failed tests", displayName: "Tests",
})
progressLog = append(progressLog, "✓ Found 'test' script")
} else {
progressLog = append(progressLog, "⚠️ No 'test' script, skipping")
}

// Execute steps
for i, step := range steps {
stepNum := fmt.Sprintf("%d/%d", i+1, len(steps))
log.Infof(ctx, "step %s: running %s...", stepNum, step.name)
Expand All @@ -61,28 +101,20 @@ func (v *ValidationNodeJs) Validate(ctx context.Context, workDir string) (*Valid
err := runCommand(ctx, workDir, step.command)
if err != nil {
stepDuration := time.Since(stepStart)
log.Errorf(ctx, "%s failed (duration: %.1fs)", step.name, stepDuration.Seconds())
log.Errorf(ctx, "%s failed (%.1fs)", step.name, stepDuration.Seconds())
progressLog = append(progressLog, fmt.Sprintf("❌ %s failed (%.1fs)", step.displayName, stepDuration.Seconds()))
return &ValidateResult{
Success: false,
Message: step.errorPrefix,
Details: err,
ProgressLog: progressLog,
Success: false, Message: step.errorPrefix, Details: err, ProgressLog: progressLog,
}, nil
}
stepDuration := time.Since(stepStart)
log.Infof(ctx, "✓ %s passed: duration=%.1fs", step.name, stepDuration.Seconds())
log.Infof(ctx, "✓ %s passed: %.1fs", step.name, stepDuration.Seconds())
progressLog = append(progressLog, fmt.Sprintf("✅ %s passed (%.1fs)", step.displayName, stepDuration.Seconds()))
}

totalDuration := time.Since(startTime)
log.Infof(ctx, "✓ all validation checks passed: total_duration=%.1fs, steps=%s",
totalDuration.Seconds(), "build + type check + tests")
log.Infof(ctx, "✓ all validation passed: %.1fs", totalDuration.Seconds())
progressLog = append(progressLog, fmt.Sprintf("✅ All checks passed! Total: %.1fs", totalDuration.Seconds()))

return &ValidateResult{
Success: true,
Message: "All validation checks passed",
ProgressLog: progressLog,
}, nil
return &ValidateResult{Success: true, Message: "All validation checks passed", ProgressLog: progressLog}, nil
}
Loading