Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 19 additions & 2 deletions experimental/apps-mcp/lib/prompts/apps.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,36 @@ DATABRICKS APPS DEVELOPMENT

invoke_databricks_cli 'experimental apps-mcp tools init-template https://github.com/databricks/cli --template-dir experimental/apps-mcp/templates/appkit --branch main --config-json '{"project_name":"my-app-name","app_description":"my-app-description","sql_warehouse_id":"{{if .WarehouseID}}{{.WarehouseID}}{{end}}"}''

# 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

⚠️ Always use the sequence of commands:
⚠️ Production deployment sequence:

invoke_databricks_cli 'bundle deploy'
invoke_databricks_cli 'bundle run app'

⚠️ 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,102 @@ TypeScript full-stack template powered by **Databricks AppKit** with tRPC for ad
- config/queries/: SQL query files for analytics
- shared/: Shared TypeScript types

## Operational Commands Reference

**⚠️ CRITICAL**: Use ONLY these commands. Do not improvise alternatives.

### Local Development
Copy link
Contributor

Choose a reason for hiding this comment

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

are you sure it is agent's concern? imo it should not know anything but validate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

template must set the way it should be executed, dont you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is certainly the agent's concern. I regularly tell the agent to run the app locally and open it in the browser so I can inspect the progress of the app.

The agent needs to know how to build and run the app locally.


**Command**: `npm run dev`

**When**: Testing app locally during development

**What it does**:
- Starts dev server with hot reload (tsx watch)
- Runs on port 8000
- Uses NODE_ENV=development
- Auto-reloads on file changes

**DO NOT use**:
- ❌ `npm start` - Production only!
- ❌ `databricks bundle run` - Runs on Databricks, not locally
- ❌ Custom commands - Stick to `npm run dev`

**Example**:
```bash
cd my-app
npm install # First time
npm run dev # Start development
# Open http://localhost:8000
```

### Validation (Before Deployment)

**Command**: `databricks experimental apps-mcp tools validate .`

**When**: Before deploying to verify production-readiness

**What it does** (automatically):
1. `npm install`
2. `npm run build` (if exists)
3. `npm run typecheck` (if exists)
4. `npm run test` (if exists)

**Important**: Reads package.json to check which scripts exist. Skips missing ones.

**DO NOT**:
- ❌ Run commands manually one-by-one
- ❌ Skip validation
- ❌ Deploy with failing validations

### Production Deployment

**Commands** (in sequence):
```bash
databricks bundle deploy
databricks bundle run app
```

**Pre-deployment checklist**:
- ✅ Validated locally with `npm run dev`
- ✅ Passed validation
- ✅ Committed to git
- ✅ Bundle config correct

**DO NOT**:
- ❌ Deploy without validation
- ❌ Use `npm start` for deployment
- ❌ Deploy without bundle commands

### Command Decision Tree

```
What do you want to do?
├─ "Test/run locally" → npm run dev
├─ "Check ready to deploy" → databricks experimental apps-mcp tools validate .
Copy link
Contributor

Choose a reason for hiding this comment

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

i'd hint invoke_databricks_cli reference here

├─ "Deploy to production" → databricks bundle deploy && databricks bundle run app
└─ "Type check/lint/test" → Use specific command
```

### Why These Commands?

**`npm run dev` (not `npm start`)**:
- `dev` = tsx watch with hot reload (development)
- `start` = production build (node ./dist/...)

**Validation tool (not manual)**:
- Consistent validation
- Branded MCP output
- Handles missing scripts
- Clear progress

**Bundle commands (not direct deploy)**:
- Manages infrastructure
- Multi-environment support
- Respects app.yaml
- Rollback capabilities

## App Naming Constraints

App names must not exceed 30 characters total (including target prefix).
Expand Down