Skip to content

Commit be69f43

Browse files
committed
Run an optional "postinit" command in a template directory
1 parent cef90e5 commit be69f43

File tree

6 files changed

+97
-124
lines changed

6 files changed

+97
-124
lines changed

cmd/apps/init.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -792,14 +792,6 @@ func runCreate(ctx context.Context, opts createOptions) error {
792792
absOutputDir = destDir
793793
}
794794

795-
// Apply plugin-specific post-processing (e.g., remove config/queries if analytics not selected)
796-
runErr = prompt.RunWithSpinnerCtx(ctx, "Configuring plugins...", func() error {
797-
return applyPlugins(absOutputDir, selectedPlugins, m.GetTemplatePaths())
798-
})
799-
if runErr != nil {
800-
return runErr
801-
}
802-
803795
// Initialize project based on type (Node.js, Python, etc.)
804796
var nextStepsCmd string
805797
projectInitializer := initializer.GetProjectInitializer(absOutputDir)
@@ -946,28 +938,6 @@ func buildPluginStrings(pluginNames []string) (pluginImport, pluginUsage string)
946938
return pluginImport, pluginUsage
947939
}
948940

949-
// applyPlugins removes template directories owned by unselected plugins.
950-
func applyPlugins(projectDir string, pluginNames []string, templatePaths map[string][]string) error {
951-
selectedSet := make(map[string]bool)
952-
for _, name := range pluginNames {
953-
selectedSet[name] = true
954-
}
955-
956-
for plugin, paths := range templatePaths {
957-
if selectedSet[plugin] {
958-
continue
959-
}
960-
for _, p := range paths {
961-
target := filepath.Join(projectDir, p)
962-
if err := os.RemoveAll(target); err != nil && !os.IsNotExist(err) {
963-
return err
964-
}
965-
}
966-
}
967-
968-
return nil
969-
}
970-
971941
// renameFiles maps source file names to destination names (for files that can't use special chars).
972942
var renameFiles = map[string]string{
973943
"_gitignore": ".gitignore",

cmd/apps/init_test.go

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -458,59 +458,6 @@ func TestAppendUniqueNoValues(t *testing.T) {
458458
assert.Equal(t, []string{"a", "b"}, result)
459459
}
460460

461-
func TestApplyPlugins(t *testing.T) {
462-
tests := []struct {
463-
name string
464-
selected []string
465-
templatePaths map[string][]string
466-
expectRemoved []string
467-
expectKept []string
468-
}{
469-
{
470-
name: "unselected plugin directory is removed",
471-
selected: []string{"server"},
472-
templatePaths: map[string][]string{"analytics": {"config/queries"}},
473-
expectRemoved: []string{"config/queries"},
474-
},
475-
{
476-
name: "selected plugin directory is kept",
477-
selected: []string{"analytics", "server"},
478-
templatePaths: map[string][]string{"analytics": {"config/queries"}},
479-
expectKept: []string{"config/queries"},
480-
},
481-
{
482-
name: "empty templatePaths is a no-op",
483-
selected: []string{"server"},
484-
templatePaths: map[string][]string{},
485-
},
486-
}
487-
488-
for _, tc := range tests {
489-
t.Run(tc.name, func(t *testing.T) {
490-
dir := t.TempDir()
491-
492-
// Create all directories referenced in templatePaths
493-
for _, paths := range tc.templatePaths {
494-
for _, p := range paths {
495-
require.NoError(t, os.MkdirAll(filepath.Join(dir, p), 0o755))
496-
}
497-
}
498-
499-
err := applyPlugins(dir, tc.selected, tc.templatePaths)
500-
require.NoError(t, err)
501-
502-
for _, p := range tc.expectRemoved {
503-
_, statErr := os.Stat(filepath.Join(dir, p))
504-
assert.True(t, os.IsNotExist(statErr), "expected %s to be removed", p)
505-
}
506-
for _, p := range tc.expectKept {
507-
_, statErr := os.Stat(filepath.Join(dir, p))
508-
assert.NoError(t, statErr, "expected %s to exist", p)
509-
}
510-
})
511-
}
512-
}
513-
514461
func TestRunManifestOnlyFound(t *testing.T) {
515462
dir := t.TempDir()
516463
manifestPath := filepath.Join(dir, manifest.ManifestFileName)

libs/apps/initializer/nodejs.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func (i *InitializerNodeJs) Initialize(ctx context.Context, workDir string) *Ini
4040
}
4141
}
4242

43+
// Step 3: Run postinit script if defined (fully optional — errors are logged, not fatal)
44+
i.runNpmPostInit(ctx, workDir)
45+
4346
return &InitResult{
4447
Success: true,
4548
Message: "Node.js project initialized successfully",
@@ -102,6 +105,43 @@ func (i *InitializerNodeJs) runAppkitSetup(ctx context.Context, workDir string)
102105
})
103106
}
104107

108+
// runNpmPostInit runs "npm run postinit" if the script is defined in package.json.
109+
// Failures are logged as warnings and never propagate — postinit is fully optional.
110+
func (i *InitializerNodeJs) runNpmPostInit(ctx context.Context, workDir string) {
111+
if !i.hasNpmScript(workDir, "postinit") {
112+
return
113+
}
114+
err := prompt.RunWithSpinnerCtx(ctx, "Running post-init...", func() error {
115+
cmd := exec.CommandContext(ctx, "npm", "run", "postinit")
116+
cmd.Dir = workDir
117+
cmd.Stdout = nil
118+
cmd.Stderr = nil
119+
return cmd.Run()
120+
})
121+
if err != nil {
122+
log.Debugf(ctx, "postinit script failed (non-fatal): %v", err)
123+
}
124+
}
125+
126+
// hasNpmScript reports whether the given script name is defined in the project's package.json.
127+
func (i *InitializerNodeJs) hasNpmScript(workDir, script string) bool {
128+
packageJSONPath := filepath.Join(workDir, "package.json")
129+
data, err := os.ReadFile(packageJSONPath)
130+
if err != nil {
131+
return false
132+
}
133+
134+
var pkg struct {
135+
Scripts map[string]string `json:"scripts"`
136+
}
137+
if err := json.Unmarshal(data, &pkg); err != nil {
138+
return false
139+
}
140+
141+
_, ok := pkg.Scripts[script]
142+
return ok
143+
}
144+
105145
// hasAppkit checks if the project has @databricks/appkit in its dependencies.
106146
func (i *InitializerNodeJs) hasAppkit(workDir string) bool {
107147
packageJSONPath := filepath.Join(workDir, "package.json")

libs/apps/initializer/nodejs_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,60 @@ func TestHasAppkitNoPackageJSON(t *testing.T) {
6565
init := &InitializerNodeJs{}
6666
assert.False(t, init.hasAppkit(tmpDir))
6767
}
68+
69+
func TestHasNpmScript(t *testing.T) {
70+
tests := []struct {
71+
name string
72+
packageJSON string
73+
script string
74+
want bool
75+
}{
76+
{
77+
name: "script present",
78+
packageJSON: `{"scripts": {"postinit": "appkit postinit"}}`,
79+
script: "postinit",
80+
want: true,
81+
},
82+
{
83+
name: "script absent",
84+
packageJSON: `{"scripts": {"build": "tsc"}}`,
85+
script: "postinit",
86+
want: false,
87+
},
88+
{
89+
name: "no scripts section",
90+
packageJSON: `{}`,
91+
script: "postinit",
92+
want: false,
93+
},
94+
{
95+
name: "invalid json",
96+
packageJSON: `not json`,
97+
script: "postinit",
98+
want: false,
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
tmpDir, err := os.MkdirTemp("", "nodejs-test-*")
105+
require.NoError(t, err)
106+
defer os.RemoveAll(tmpDir)
107+
108+
err = os.WriteFile(filepath.Join(tmpDir, "package.json"), []byte(tt.packageJSON), 0o644)
109+
require.NoError(t, err)
110+
111+
i := &InitializerNodeJs{}
112+
assert.Equal(t, tt.want, i.hasNpmScript(tmpDir, tt.script))
113+
})
114+
}
115+
}
116+
117+
func TestHasNpmScriptNoPackageJSON(t *testing.T) {
118+
tmpDir, err := os.MkdirTemp("", "nodejs-test-*")
119+
require.NoError(t, err)
120+
defer os.RemoveAll(tmpDir)
121+
122+
i := &InitializerNodeJs{}
123+
assert.False(t, i.hasNpmScript(tmpDir, "postinit"))
124+
}

libs/apps/manifest/manifest.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ type Plugin struct {
6767
Description string `json:"description"`
6868
Package string `json:"package"`
6969
RequiredByTemplate bool `json:"requiredByTemplate"`
70-
TemplatePaths []string `json:"templatePaths,omitempty"`
7170
Resources Resources `json:"resources"`
7271
}
7372

@@ -206,18 +205,6 @@ func (m *Manifest) CollectResources(pluginNames []string) []Resource {
206205
return resources
207206
}
208207

209-
// GetTemplatePaths returns a map of plugin name to template directory paths.
210-
// Only plugins that declare at least one path are included.
211-
func (m *Manifest) GetTemplatePaths() map[string][]string {
212-
result := make(map[string][]string)
213-
for name, p := range m.Plugins {
214-
if len(p.TemplatePaths) > 0 {
215-
result[name] = p.TemplatePaths
216-
}
217-
}
218-
return result
219-
}
220-
221208
// CollectOptionalResources returns all optional resources for the given plugin names.
222209
func (m *Manifest) CollectOptionalResources(pluginNames []string) []Resource {
223210
seen := make(map[string]bool)

libs/apps/manifest/manifest_test.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -320,34 +320,6 @@ func TestResourceKey(t *testing.T) {
320320
assert.Equal(t, "sql_warehouse", r.VarPrefix())
321321
}
322322

323-
func TestGetTemplatePaths(t *testing.T) {
324-
m := &manifest.Manifest{
325-
Plugins: map[string]manifest.Plugin{
326-
"analytics": {
327-
Name: "analytics",
328-
TemplatePaths: []string{"config/queries"},
329-
},
330-
"server": {
331-
Name: "server",
332-
},
333-
},
334-
}
335-
336-
paths := m.GetTemplatePaths()
337-
assert.Equal(t, map[string][]string{"analytics": {"config/queries"}}, paths)
338-
}
339-
340-
func TestGetTemplatePathsEmpty(t *testing.T) {
341-
m := &manifest.Manifest{
342-
Plugins: map[string]manifest.Plugin{
343-
"server": {Name: "server"},
344-
},
345-
}
346-
347-
paths := m.GetTemplatePaths()
348-
assert.Empty(t, paths)
349-
}
350-
351323
func TestCollectOptionalResources(t *testing.T) {
352324
m := &manifest.Manifest{
353325
Plugins: map[string]manifest.Plugin{

0 commit comments

Comments
 (0)