Add support for yarn package manager (#72)

* Add support for yarn package manager

- Added yarn command handler in cmd/npm/yarn.go
- Added DefaultYarnPackageManagerConfig() in packagemanager/npm.go
- Added yarn dev flag support (--dev/-D) in ParseCommand()
- Added yarn analytics tracking in internal/analytics/event.go
- Registered yarn command in main.go
- Added yarn to alias setup in internal/alias/alias.go
- Added comprehensive test suite in packagemanager/yarn_test.go
- All tests passing (12 yarn tests + existing tests)

Resolves #30

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

* docs: update README with yarn package manager support

Updated documentation to reflect yarn support addition:
- Changed yarn status from '🚧 Planned' to ' Active' in package managers table
- Added 'pmg yarn add <package>' command example
- Added 'yarn add <package-name>' to command list
- Added 'yarn install # Uses yarn.lock' to lockfile examples

This documentation update corresponds to the yarn implementation in the previous commit.

Related to #30

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

* fix: address review feedback for yarn support

- Add support for bare 'yarn' command as manifest install
- Validate yarn install does not accept package names
- Add YarnExtractor for yarn.lock file support

Changes:
- packagemanager/npm.go: Added bare yarn command handling and validation
- packagemanager/yarn_test.go: Added test for bare yarn command
- extractor/ecosystems.go: Added Yarn PackageManagerName and extractor
- extractor/npm.go: Added YarnExtractor implementation
- go.mod, go.sum: Updated dependencies

Addresses review feedback from @Sahilb315
- Bare yarn command now triggers manifest install per docs
- yarn install <package> is now properly rejected
- yarn.lock extraction support added

Related to #30

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

* chore: tidy go.mod and go.sum to remove unused dependencies

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

* fix: add yarn.lock extractor mapping

- Add yarn.lock case in getExtractorForFile switch
- Import yarnlock extractor from osv-scalibr
- Enables lockfile parsing for manifest-based installations

Tested both package-based and manifest-based installations successfully.

Addresses review feedback from @Sahilb315

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

* test: add validation test for yarn install with package name

- Add test case to verify 'yarn install <package>' is rejected
- Ensures invalid syntax returns no install targets
- Confirms IsManifestInstall is false for invalid commands

Addresses review feedback from @Sahilb315

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>

---------

Signed-off-by: shrvansudhakara <shrvansudhakara@gmail.com>
This commit is contained in:
Shrvan Sudhakara
2025-11-05 13:49:19 +05:30
committed by GitHub
parent 12aaba8a05
commit 03bd697ae0
10 changed files with 329 additions and 50 deletions
+25 -1
View File
@@ -36,6 +36,13 @@ func DefaultBunPackageManagerConfig() NpmPackageManagerConfig {
}
}
func DefaultYarnPackageManagerConfig() NpmPackageManagerConfig {
return NpmPackageManagerConfig{
InstallCommands: []string{"install", "add", ""},
CommandName: "yarn",
}
}
type npmPackageManager struct {
Config NpmPackageManagerConfig
}
@@ -57,7 +64,7 @@ func (npm *npmPackageManager) Ecosystem() packagev1.Ecosystem {
}
func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error) {
if len(args) > 0 && (args[0] == "npm" || args[0] == "pnpm" || args[0] == "bun") {
if len(args) > 0 && (args[0] == "npm" || args[0] == "pnpm" || args[0] == "bun" || args[0] == "yarn") {
args = args[1:]
}
@@ -65,6 +72,14 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error
// Since manifest-based installs like 'npm i' are now valid commands
if len(args) < 1 {
if npm.Config.CommandName == "yarn" {
return &ParsedCommand{
Command: command,
InstallTargets: []*PackageInstallTarget{},
IsManifestInstall: true,
ManifestFiles: []string{},
}, nil
}
return &ParsedCommand{
Command: command,
}, nil
@@ -100,6 +115,8 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error
flagSet.StringArrayVarP(&devPackages, "save-dev", "D", nil, "Install dev packages")
case "bun":
flagSet.StringArrayVarP(&devPackages, "dev", "d", nil, "Install dev packages")
case "yarn":
flagSet.StringArrayVarP(&devPackages, "dev", "D", nil, "Install dev packages")
}
err := flagSet.Parse(installArgs)
@@ -115,6 +132,13 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error
isManifestInstall = true
}
// Yarn-specific validation: yarn install does not accept package names
if npm.Config.CommandName == "yarn" && args[installCmdIndex] == "install" && len(packages) > 0 {
return &ParsedCommand{
Command: command,
}, nil
}
// No packages found and not a manifest install
if len(packages) == 0 && !isManifestInstall {
return &ParsedCommand{