Skip to content
Draft
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
36 changes: 0 additions & 36 deletions cmd/apps/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,14 +792,6 @@ func runCreate(ctx context.Context, opts createOptions) error {
absOutputDir = destDir
}

// Apply plugin-specific post-processing (e.g., remove config/queries if analytics not selected)
runErr = prompt.RunWithSpinnerCtx(ctx, "Configuring plugins...", func() error {
return applyPlugins(absOutputDir, selectedPlugins)
})
if runErr != nil {
return runErr
}

// Initialize project based on type (Node.js, Python, etc.)
var nextStepsCmd string
projectInitializer := initializer.GetProjectInitializer(absOutputDir)
Expand Down Expand Up @@ -946,34 +938,6 @@ func buildPluginStrings(pluginNames []string) (pluginImport, pluginUsage string)
return pluginImport, pluginUsage
}

// pluginOwnedPaths maps plugin names to directories they own.
// When a plugin is not selected, its owned paths are removed from the project.
var pluginOwnedPaths = map[string][]string{
"analytics": {"config/queries"},
}

// applyPlugins removes directories owned by unselected plugins.
func applyPlugins(projectDir string, pluginNames []string) error {
selectedSet := make(map[string]bool)
for _, name := range pluginNames {
selectedSet[name] = true
}

for plugin, paths := range pluginOwnedPaths {
if selectedSet[plugin] {
continue
}
for _, p := range paths {
target := filepath.Join(projectDir, p)
if err := os.RemoveAll(target); err != nil && !os.IsNotExist(err) {
return err
}
}
}

return nil
}

// renameFiles maps source file names to destination names (for files that can't use special chars).
var renameFiles = map[string]string{
"_gitignore": ".gitignore",
Expand Down
40 changes: 40 additions & 0 deletions libs/apps/initializer/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ func (i *InitializerNodeJs) Initialize(ctx context.Context, workDir string) *Ini
}
}

// Step 3: Run postinit script if defined (fully optional — errors are logged, not fatal)
i.runNpmPostInit(ctx, workDir)

return &InitResult{
Success: true,
Message: "Node.js project initialized successfully",
Expand Down Expand Up @@ -102,6 +105,43 @@ func (i *InitializerNodeJs) runAppkitSetup(ctx context.Context, workDir string)
})
}

// runNpmPostInit runs "npm run postinit" if the script is defined in package.json.
// Failures are logged as warnings and never propagate — postinit is fully optional.
func (i *InitializerNodeJs) runNpmPostInit(ctx context.Context, workDir string) {
if !i.hasNpmScript(workDir, "postinit") {
return
}
err := prompt.RunWithSpinnerCtx(ctx, "Running post-init...", func() error {
cmd := exec.CommandContext(ctx, "npm", "run", "postinit")
cmd.Dir = workDir
cmd.Stdout = nil
cmd.Stderr = nil
return cmd.Run()
})
if err != nil {
log.Debugf(ctx, "postinit script failed (non-fatal): %v", err)
}
}

// hasNpmScript reports whether the given script name is defined in the project's package.json.
func (i *InitializerNodeJs) hasNpmScript(workDir, script string) bool {
packageJSONPath := filepath.Join(workDir, "package.json")
data, err := os.ReadFile(packageJSONPath)
if err != nil {
return false
}

var pkg struct {
Scripts map[string]string `json:"scripts"`
}
if err := json.Unmarshal(data, &pkg); err != nil {
return false
}

_, ok := pkg.Scripts[script]
return ok
}

// hasAppkit checks if the project has @databricks/appkit in its dependencies.
func (i *InitializerNodeJs) hasAppkit(workDir string) bool {
packageJSONPath := filepath.Join(workDir, "package.json")
Expand Down
57 changes: 57 additions & 0 deletions libs/apps/initializer/nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,60 @@ func TestHasAppkitNoPackageJSON(t *testing.T) {
init := &InitializerNodeJs{}
assert.False(t, init.hasAppkit(tmpDir))
}

func TestHasNpmScript(t *testing.T) {
tests := []struct {
name string
packageJSON string
script string
want bool
}{
{
name: "script present",
packageJSON: `{"scripts": {"postinit": "appkit postinit"}}`,
script: "postinit",
want: true,
},
{
name: "script absent",
packageJSON: `{"scripts": {"build": "tsc"}}`,
script: "postinit",
want: false,
},
{
name: "no scripts section",
packageJSON: `{}`,
script: "postinit",
want: false,
},
{
name: "invalid json",
packageJSON: `not json`,
script: "postinit",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nodejs-test-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

err = os.WriteFile(filepath.Join(tmpDir, "package.json"), []byte(tt.packageJSON), 0o644)
require.NoError(t, err)

i := &InitializerNodeJs{}
assert.Equal(t, tt.want, i.hasNpmScript(tmpDir, tt.script))
})
}
}

func TestHasNpmScriptNoPackageJSON(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nodejs-test-*")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

i := &InitializerNodeJs{}
assert.False(t, i.hasNpmScript(tmpDir, "postinit"))
}