diff --git a/cmd/apps/init.go b/cmd/apps/init.go index 1d5c30efb5..091de6ac00 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -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) @@ -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", diff --git a/libs/apps/initializer/nodejs.go b/libs/apps/initializer/nodejs.go index 1e96f43f72..b30db46caa 100644 --- a/libs/apps/initializer/nodejs.go +++ b/libs/apps/initializer/nodejs.go @@ -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", @@ -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") diff --git a/libs/apps/initializer/nodejs_test.go b/libs/apps/initializer/nodejs_test.go index eb9095453f..c390517b76 100644 --- a/libs/apps/initializer/nodejs_test.go +++ b/libs/apps/initializer/nodejs_test.go @@ -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")) +}