Files
pack/core/providers/node/workspace.go
T

125 lines
3.2 KiB
Go

package node
import (
"fmt"
"path/filepath"
"github.com/railwayapp/railpack/core/app"
)
type Workspace struct {
Root *PackageJson
Packages []*WorkspacePackage
PackageJson *PackageJson
}
type WorkspacePackage struct {
Path string
PackageJson *PackageJson
}
type PnpmWorkspace struct {
Packages []string `yaml:"packages"`
}
// NewWorkspace creates a new workspace from a package.json file
func NewWorkspace(app *app.App) (*Workspace, error) {
packageJson, err := readPackageJson(app, "package.json")
if err != nil {
return nil, fmt.Errorf("error reading root package.json: %w", err)
}
workspace := &Workspace{
Root: packageJson,
Packages: []*WorkspacePackage{},
PackageJson: packageJson,
}
// Try to read PNPM workspace config first
if app.HasMatch("pnpm-workspace.yaml") {
var pnpmWorkspace PnpmWorkspace
if err := app.ReadYAML("pnpm-workspace.yaml", &pnpmWorkspace); err == nil && len(pnpmWorkspace.Packages) > 0 {
packageJson.Workspaces = pnpmWorkspace.Packages
}
}
if len(packageJson.Workspaces) > 0 {
if err := workspace.findWorkspacePackages(app); err != nil {
return nil, err
}
}
return workspace, nil
}
// findWorkspacePackages finds all packages in the workspace using the workspace patterns
func (w *Workspace) findWorkspacePackages(app *app.App) error {
for _, pattern := range w.Root.Workspaces {
// For each workspace pattern, we need to:
// 1. Find all package.json files in that pattern
// 2. Read each package.json file
// 3. Add it to our list of packages
pattern = convertWorkspacePattern(pattern)
matches, err := app.FindFiles(pattern)
if err != nil {
continue
}
for _, match := range matches {
packageJson, err := readPackageJson(app, match)
if err != nil {
continue
}
dir := filepath.Dir(match)
w.Packages = append(w.Packages, &WorkspacePackage{
Path: dir,
PackageJson: packageJson,
})
}
}
return nil
}
// convertWorkspacePattern converts npm/pnpm workspace patterns to glob patterns
func convertWorkspacePattern(pattern string) string {
// npm/pnpm uses packages/* or packages/** for glob patterns
// - packages/* -> packages/*/package.json (single level)
// - packages/** -> packages/**/package.json (recursive)
if pattern[len(pattern)-2:] == "/*" {
// Single level pattern (packages/*)
return pattern[:len(pattern)-1] + "*/package.json"
} else if pattern[len(pattern)-3:] == "/**" {
// Recursive pattern (packages/**)
return pattern[:len(pattern)-2] + "**/package.json"
}
// Direct path or other pattern, just append package.json
return filepath.Join(pattern, "package.json")
}
// readPackageJson reads a package.json file from the given path
func readPackageJson(app *app.App, path string) (*PackageJson, error) {
packageJson := NewPackageJson()
if err := app.ReadJSON(path, packageJson); err != nil {
return nil, err
}
return packageJson, nil
}
// HasWorkspaces returns true if this is a workspace root
func (w *Workspace) HasWorkspaces() bool {
return len(w.Root.Workspaces) > 0
}
// GetPackage returns a workspace package by path
func (w *Workspace) GetPackage(path string) *WorkspacePackage {
for _, pkg := range w.Packages {
if pkg.Path == path {
return pkg
}
}
return nil
}