feat(build): support nested Node.js projects
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
7da8d7e843
commit
231d43fda1
5 changed files with 141 additions and 5 deletions
|
|
@ -60,6 +60,17 @@ func TestDetectProjectType_Good(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, build.ProjectTypeTaskfile, projectType)
|
||||
})
|
||||
|
||||
t.Run("detects nested Node.js projects", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
nested := ax.Join(dir, "apps", "web")
|
||||
require.NoError(t, ax.MkdirAll(nested, 0o755))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0o644))
|
||||
|
||||
projectType, err := DetectProjectType(fs, dir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, build.ProjectTypeNode, projectType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDetectProjectType_Bad(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func (b *NodeBuilder) Name() string {
|
|||
//
|
||||
// ok, err := b.Detect(io.Local, ".")
|
||||
func (b *NodeBuilder) Detect(fs io.Medium, dir string) (bool, error) {
|
||||
return build.IsNodeProject(fs, dir), nil
|
||||
return build.IsNodeProject(fs, dir) || b.resolveNodeProjectDir(fs, dir) != "", nil
|
||||
}
|
||||
|
||||
// Build runs the project build script once per target and collects artifacts
|
||||
|
|
@ -60,7 +60,12 @@ func (b *NodeBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
|
|||
return nil, coreerr.E("NodeBuilder.Build", "failed to create output directory", err)
|
||||
}
|
||||
|
||||
packageManager, err := b.resolvePackageManager(cfg.FS, cfg.ProjectDir)
|
||||
projectDir := b.resolveNodeProjectDir(cfg.FS, cfg.ProjectDir)
|
||||
if projectDir == "" {
|
||||
projectDir = cfg.ProjectDir
|
||||
}
|
||||
|
||||
packageManager, err := b.resolvePackageManager(cfg.FS, projectDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -92,7 +97,7 @@ func (b *NodeBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
|
|||
env = append(env, core.Sprintf("VERSION=%s", cfg.Version))
|
||||
}
|
||||
|
||||
output, err := ax.CombinedOutput(ctx, cfg.ProjectDir, env, command, args...)
|
||||
output, err := ax.CombinedOutput(ctx, projectDir, env, command, args...)
|
||||
if err != nil {
|
||||
return artifacts, coreerr.E("NodeBuilder.Build", command+" build failed: "+output, err)
|
||||
}
|
||||
|
|
@ -104,6 +109,50 @@ func (b *NodeBuilder) Build(ctx context.Context, cfg *build.Config, targets []bu
|
|||
return artifacts, nil
|
||||
}
|
||||
|
||||
// resolveNodeProjectDir locates the directory containing package.json.
|
||||
// It prefers the project root, then searches nested directories to depth 2.
|
||||
func (b *NodeBuilder) resolveNodeProjectDir(fs io.Medium, projectDir string) string {
|
||||
if fs.IsFile(ax.Join(projectDir, "package.json")) {
|
||||
return projectDir
|
||||
}
|
||||
|
||||
return b.findNodeProjectDir(fs, projectDir, 0)
|
||||
}
|
||||
|
||||
// findNodeProjectDir searches for a package.json within nested directories.
|
||||
func (b *NodeBuilder) findNodeProjectDir(fs io.Medium, dir string, depth int) string {
|
||||
if depth >= 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
entries, err := fs.List(dir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := entry.Name()
|
||||
if name == "node_modules" || core.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
|
||||
candidateDir := ax.Join(dir, name)
|
||||
if fs.IsFile(ax.Join(candidateDir, "package.json")) {
|
||||
return candidateDir
|
||||
}
|
||||
|
||||
if nested := b.findNodeProjectDir(fs, candidateDir, depth+1); nested != "" {
|
||||
return nested
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// resolvePackageManager selects the package manager from lockfiles.
|
||||
//
|
||||
// packageManager := b.resolvePackageManager(io.Local, ".")
|
||||
|
|
|
|||
|
|
@ -80,6 +80,18 @@ func TestNode_NodeBuilderDetect_Good(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.False(t, detected)
|
||||
})
|
||||
|
||||
t.Run("detects nested package.json projects", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
nested := ax.Join(dir, "apps", "web")
|
||||
require.NoError(t, ax.MkdirAll(nested, 0o755))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0o644))
|
||||
|
||||
builder := NewNodeBuilder()
|
||||
detected, err := builder.Detect(fs, dir)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, detected)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNode_NodeBuilderBuild_Good(t *testing.T) {
|
||||
|
|
@ -228,3 +240,44 @@ func TestNode_NodeBuilderBuildDefaults_Good(t *testing.T) {
|
|||
assert.Equal(t, runtime.GOOS, artifacts[0].OS)
|
||||
assert.Equal(t, runtime.GOARCH, artifacts[0].Arch)
|
||||
}
|
||||
|
||||
func TestNode_NodeBuilderBuild_Good_NestedProject(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test in short mode")
|
||||
}
|
||||
|
||||
binDir := t.TempDir()
|
||||
setupFakeNodeToolchain(t, binDir)
|
||||
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
projectDir := t.TempDir()
|
||||
nestedDir := ax.Join(projectDir, "apps", "web")
|
||||
require.NoError(t, ax.MkdirAll(nestedDir, 0o755))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nestedDir, "package.json"), []byte(`{"name":"nested-app","scripts":{"build":"node build.js"}}`), 0o644))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nestedDir, "build.js"), []byte(`console.log("nested build")`), 0o644))
|
||||
|
||||
outputDir := t.TempDir()
|
||||
logDir := t.TempDir()
|
||||
logPath := ax.Join(logDir, "node-nested.log")
|
||||
t.Setenv("NODE_BUILD_LOG_FILE", logPath)
|
||||
|
||||
builder := NewNodeBuilder()
|
||||
cfg := &build.Config{
|
||||
FS: io.Local,
|
||||
ProjectDir: projectDir,
|
||||
OutputDir: outputDir,
|
||||
Name: "nested-app",
|
||||
Version: "v1.2.3",
|
||||
}
|
||||
|
||||
artifacts, err := builder.Build(context.Background(), cfg, []build.Target{{OS: "linux", Arch: "amd64"}})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, artifacts, 1)
|
||||
assert.FileExists(t, artifacts[0].Path)
|
||||
|
||||
content, err := ax.ReadFile(logPath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(content), "PWD="+nestedDir)
|
||||
assert.Contains(t, string(content), "GOOS=linux")
|
||||
assert.Contains(t, string(content), "GOARCH=amd64")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
|
|||
projectType ProjectType
|
||||
detected bool
|
||||
}{
|
||||
{ProjectTypeNode, IsNodeProject(fs, dir) || HasSubtreeNpm(fs, dir)},
|
||||
{ProjectTypeDocs, IsMkDocsProject(fs, dir)},
|
||||
{ProjectTypeDocker, IsDockerProject(fs, dir)},
|
||||
{ProjectTypeLinuxKit, IsLinuxKitProject(fs, dir)},
|
||||
|
|
|
|||
|
|
@ -45,6 +45,17 @@ func TestDiscovery_Discover_Good(t *testing.T) {
|
|||
assert.Equal(t, []ProjectType{ProjectTypeNode}, types)
|
||||
})
|
||||
|
||||
t.Run("detects nested Node.js project", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
nested := ax.Join(dir, "apps", "web")
|
||||
require.NoError(t, ax.MkdirAll(nested, 0755))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644))
|
||||
|
||||
types, err := Discover(fs, dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeNode}, types)
|
||||
})
|
||||
|
||||
t.Run("detects PHP project", func(t *testing.T) {
|
||||
dir := setupTestDir(t, "composer.json")
|
||||
types, err := Discover(fs, dir)
|
||||
|
|
@ -203,6 +214,17 @@ func TestDiscovery_PrimaryType_Good(t *testing.T) {
|
|||
assert.Equal(t, ProjectTypeGo, primary)
|
||||
})
|
||||
|
||||
t.Run("returns node for nested package.json project", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
nested := ax.Join(dir, "apps", "web")
|
||||
require.NoError(t, ax.MkdirAll(nested, 0755))
|
||||
require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644))
|
||||
|
||||
primary, err := PrimaryType(fs, dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ProjectTypeNode, primary)
|
||||
})
|
||||
|
||||
t.Run("returns empty string for empty directory", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
primary, err := PrimaryType(fs, dir)
|
||||
|
|
@ -537,7 +559,7 @@ func TestDiscovery_DiscoverFull_Good(t *testing.T) {
|
|||
|
||||
result, err := DiscoverFull(fs, dir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeWails, ProjectTypeGo}, result.Types)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeWails, ProjectTypeGo, ProjectTypeNode}, result.Types)
|
||||
assert.Equal(t, "wails", result.PrimaryStack)
|
||||
assert.True(t, result.HasFrontend)
|
||||
assert.True(t, result.Markers["wails.json"])
|
||||
|
|
@ -556,7 +578,7 @@ func TestDiscovery_DiscoverFull_Good(t *testing.T) {
|
|||
|
||||
result, err := DiscoverFull(fs, dir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeGo}, result.Types)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeGo, ProjectTypeNode}, result.Types)
|
||||
assert.True(t, result.HasSubtreeNpm)
|
||||
assert.True(t, result.HasFrontend)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue