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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ node_modules
bin
test
dist
tmp

.vscode/
.DS_Store
20 changes: 15 additions & 5 deletions core/providers/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ func (p *NodeProvider) Initialize(ctx *generate.GenerateContext) error {
}

func (p *NodeProvider) Detect(ctx *generate.GenerateContext) (bool, error) {
return ctx.App.HasMatch("package.json"), nil
return ctx.App.HasMatch("package.json") || ctx.App.HasMatch("package.json5"), nil
}

func getPackageFileName(app *app.App) string {
if app.HasMatch("package.json5") {
return "package.json5"
}
return "package.json"
}

func (p *NodeProvider) Plan(ctx *generate.GenerateContext) error {
Expand Down Expand Up @@ -261,8 +268,9 @@ func (p *NodeProvider) InstallNodeDeps(ctx *generate.GenerateContext, install *g
})
ctx.Logger.LogInfo("Installing %s@%s with Corepack", pmName, pmVersion)

packageFileName := getPackageFileName(ctx.App)
install.AddCommands([]plan.Command{
plan.NewCopyCommand("package.json"),
plan.NewCopyCommand(packageFileName),
// corepack will detect the package manager version from package.json, safe to assume the user is properly
// specifying the version they want there, no need to check other version specifications.
plan.NewExecShellCommand("npm i -g corepack@latest && corepack enable && corepack prepare --activate"),
Expand Down Expand Up @@ -431,13 +439,15 @@ func (p *NodeProvider) getPackageManager(app *app.App) PackageManager {

func (p *NodeProvider) GetPackageJson(app *app.App) (*PackageJson, error) {
packageJson := NewPackageJson()
if !app.HasMatch("package.json") {
packageFileName := getPackageFileName(app)

if !app.HasMatch("package.json") && !app.HasMatch("package.json5") {
return packageJson, nil
}

err := app.ReadJSON("package.json", packageJson)
err := app.ReadJSON(packageFileName, packageJson)
if err != nil {
return nil, fmt.Errorf("error reading package.json: %w", err)
return nil, fmt.Errorf("error reading %s: %w", packageFileName, err)
}

return packageJson, nil
Expand Down
29 changes: 18 additions & 11 deletions core/providers/node/package_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,13 @@ func (p PackageManager) pruneYarnBerry(ctx *generate.GenerateContext, prune *gen

func (p PackageManager) getPackageJsonFromContext(ctx *generate.GenerateContext) (*PackageJson, error) {
packageJson := NewPackageJson()
if !ctx.App.HasMatch("package.json") {
packageFileName := getPackageFileName(ctx.App)

if !ctx.App.HasMatch("package.json") && !ctx.App.HasMatch("package.json5") {
return packageJson, nil
}

err := ctx.App.ReadJSON("package.json", packageJson)
err := ctx.App.ReadJSON(packageFileName, packageJson)
if err != nil {
return nil, err
}
Expand All @@ -212,6 +214,7 @@ func (p PackageManager) GetInstallFolder(ctx *generate.GenerateContext) []string
func (p PackageManager) SupportingInstallFiles(ctx *generate.GenerateContext) []string {
patterns := []string{
"**/package.json",
"**/package.json5",
"**/package-lock.json",
"**/pnpm-workspace.yaml",
"**/yarn.lock",
Expand Down Expand Up @@ -337,20 +340,24 @@ func (p PackageManager) GetPackageManagerPackages(ctx *generate.GenerateContext,

// usesLocalFile returns true if the package.json has a local dependency (e.g. file:./path/to/package)
func (p PackageManager) usesLocalFile(ctx *generate.GenerateContext) bool {
files, err := ctx.App.FindFiles("**/package.json")
if err != nil {
return false
}
patterns := []string{"**/package.json", "**/package.json5"}

for _, file := range files {
packageJson := &PackageJson{}
err := ctx.App.ReadJSON(file, packageJson)
for _, pattern := range patterns {
files, err := ctx.App.FindFiles(pattern)
if err != nil {
continue
}

if packageJson.hasLocalDependency() {
return true
for _, file := range files {
packageJson := &PackageJson{}
err := ctx.App.ReadJSON(file, packageJson)
if err != nil {
continue
}

if packageJson.hasLocalDependency() {
return true
}
}
}

Expand Down
58 changes: 32 additions & 26 deletions core/providers/node/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ type PnpmWorkspace struct {

// NewWorkspace creates a new workspace from a package.json file
func NewWorkspace(app *app.App) (*Workspace, error) {
packageJson, err := readPackageJson(app, "package.json")
packageFileName := getPackageFileName(app)
packageJson, err := readPackageJson(app, packageFileName)
if err != nil {
return nil, fmt.Errorf("error reading root package.json: %w", err)
return nil, fmt.Errorf("error reading root %s: %w", packageFileName, err)
}

workspace := &Workspace{
Expand Down Expand Up @@ -57,50 +58,55 @@ func NewWorkspace(app *app.App) (*Workspace, error) {
func (w *Workspace) findWorkspacePackages(app *app.App) error {
for _, pattern := range w.Root.PackageJson.Workspaces {
// For each workspace pattern, we need to:
// 1. Find all package.json files in that pattern
// 2. Read each package.json file
// 1. Find all package.json and package.json5 files in that pattern
// 2. Read each package.json/package.json5 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)
for _, pkgPattern := range convertWorkspacePatterns(pattern) {
matches, err := app.FindFiles(pkgPattern)
if err != nil {
continue
}

dir := filepath.Dir(match)
w.Packages = append(w.Packages, &WorkspacePackage{
Path: dir,
PackageJson: packageJson,
})
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 {
// convertWorkspacePatterns converts npm/pnpm workspace patterns to glob patterns for both package.json and package.json5
func convertWorkspacePatterns(pattern string) []string {
// npm/pnpm uses packages/* or packages/** for glob patterns
// - packages/* -> packages/*/package.json (single level)
// - packages/** -> packages/**/package.json (recursive)
var basePattern string
if len(pattern) >= 2 && pattern[len(pattern)-2:] == "/*" {
// Single level pattern (packages/*)
return pattern[:len(pattern)-1] + "*/package.json"
}

if len(pattern) >= 3 && pattern[len(pattern)-3:] == "/**" {
basePattern = pattern[:len(pattern)-1] + "*/"
} else if len(pattern) >= 3 && pattern[len(pattern)-3:] == "/**" {
// Recursive pattern (packages/**)
return pattern[:len(pattern)-2] + "**/package.json"
basePattern = pattern[:len(pattern)-2] + "**/"
} else {
// Direct path or other pattern
basePattern = pattern + "/"
}

// Direct path or other pattern, just append package.json
return filepath.Join(pattern, "package.json")
return []string{
filepath.Join(basePattern, "package.json5"),
filepath.Join(basePattern, "package.json"),
}
}

// readPackageJson reads a package.json file from the given path
Expand Down
66 changes: 33 additions & 33 deletions core/providers/node/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,57 +56,57 @@ func TestWorkspace(t *testing.T) {
}
}

func TestConvertWorkspacePattern(t *testing.T) {
func TestConvertWorkspacePatterns(t *testing.T) {
tests := []struct {
name string
pattern string
expected string
expected []string
}{
{
name: "single level pattern",
pattern: "packages/*",
expected: "packages/*/package.json",
name: "single level pattern",
pattern: "packages/*",
expected: []string{
"packages/*/package.json5",
"packages/*/package.json",
},
},
{
name: "recursive pattern",
pattern: "packages/**",
expected: "packages/**/package.json",
name: "recursive pattern",
pattern: "packages/**",
expected: []string{
"packages/**/package.json5",
"packages/**/package.json",
},
},
{
name: "direct path",
pattern: "packages/foo",
expected: "packages/foo/package.json",
name: "direct path",
pattern: "packages/foo",
expected: []string{
"packages/foo/package.json5",
"packages/foo/package.json",
},
},
{
name: "already has package.json",
pattern: "packages/foo/package.json",
expected: "packages/foo/package.json/package.json",
name: "very short pattern",
pattern: "db",
expected: []string{
"db/package.json5",
"db/package.json",
},
},
{
name: "very short pattern",
pattern: "db",
expected: "db/package.json",
},
{
name: "single character pattern",
pattern: "a",
expected: "a/package.json",
},
{
name: "empty pattern",
pattern: "",
expected: "package.json",
},
{
name: "pattern ending with slash",
pattern: "apps/",
expected: "apps/package.json",
name: "single character pattern",
pattern: "a",
expected: []string{
"a/package.json5",
"a/package.json",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := convertWorkspacePattern(tt.pattern)
result := convertWorkspacePatterns(tt.pattern)
require.Equal(t, tt.expected, result)
})
}
Expand Down
2 changes: 2 additions & 0 deletions examples/node-package-json5/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
package-lock.json
12 changes: 12 additions & 0 deletions examples/node-package-json5/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const express = require('express');

const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
res.send('Hello from package.json5!');
});

app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT}`);
});
17 changes: 17 additions & 0 deletions examples/node-package-json5/package.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Node.js package with JSON5 support
name: "node-package-json5",
version: "1.0.0",
description: "Test package.json5 support",
main: "index.js",
scripts: {
start: "node index.js",
},
dependencies: {
// Simple HTTP server
express: "^4.18.0",
},
engines: {
node: ">=18",
},
}
9 changes: 9 additions & 0 deletions examples/node-package-json5/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"httpCheck": {
"path": "/",
"expected": 200,
"internalPort": 3000
}
}
]
Loading