From 4cd4196b6e2c58c9b842eeba96df68f52d406e34 Mon Sep 17 00:00:00 2001 From: filip-debricked <135233582+filip-debricked@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:48:50 +0200 Subject: [PATCH] Support Workspaces (#252) * Add initial workspace finder * Add workspace support in groups/finder * Adds workspace logic to finder * Fixes lint errors * Windows fix * Fix lockfile->lockfiles --- go.mod | 1 + go.sum | 2 + .../callgraph/language/java11/strategy.go | 2 +- internal/file/finder.go | 2 +- internal/file/finder_test.go | 8 +- internal/file/groups.go | 26 + internal/file/groups_test.go | 47 ++ .../file/testdata/workspace/package-lock.json | 650 ++++++++++++++++++ internal/file/testdata/workspace/package.json | 12 + .../packages/package_one/package.json | 15 + .../packages/package_two/package.json | 17 + internal/file/workspace.go | 47 ++ internal/file/workspace_test.go | 57 ++ internal/fingerprint/fingerprint_test.go | 2 +- 14 files changed, 881 insertions(+), 7 deletions(-) create mode 100644 internal/file/testdata/workspace/package-lock.json create mode 100644 internal/file/testdata/workspace/package.json create mode 100644 internal/file/testdata/workspace/packages/package_one/package.json create mode 100644 internal/file/testdata/workspace/packages/package_two/package.json create mode 100644 internal/file/workspace.go create mode 100644 internal/file/workspace_test.go diff --git a/go.mod b/go.mod index 01f2db4f..378a24ce 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( golang.org/x/tools v0.19.0 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.2.1 + github.com/becheran/wildmatch-go v1.0.0 ) require ( diff --git a/go.sum b/go.sum index 89d0b7a1..4af1bbf0 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCv github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= +github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc= github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= diff --git a/internal/callgraph/language/java11/strategy.go b/internal/callgraph/language/java11/strategy.go index 0bd41b15..bd7760d0 100644 --- a/internal/callgraph/language/java11/strategy.go +++ b/internal/callgraph/language/java11/strategy.go @@ -110,7 +110,7 @@ func NewStrategy(config conf.IConfig, paths []string, exclusions []string, inclu } func strategyWarning(errMsg string) { - err := fmt.Errorf(errMsg) + err := fmt.Errorf("%s", errMsg) warningColor := color.New(color.FgYellow, color.Bold).SprintFunc() defaultOutputWriter := log.Writer() log.Println(warningColor("Warning: ") + err.Error()) diff --git a/internal/file/finder.go b/internal/file/finder.go index 51788197..21a85569 100644 --- a/internal/file/finder.go +++ b/internal/file/finder.go @@ -194,7 +194,7 @@ func (finder *Finder) GetGroups(options DebrickedOptions) (Groups, error) { return groups, err } } - + groups.AddWorkspaceLockFiles() groups.FilterGroupsByStrictness(options.Strictness) return groups, err diff --git a/internal/file/finder_test.go b/internal/file/finder_test.go index 8a32bba3..c0a2ef4f 100644 --- a/internal/file/finder_test.go +++ b/internal/file/finder_test.go @@ -110,7 +110,7 @@ func TestGetGroups(t *testing.T) { path := "" excludedFiles := []string{"testdata/go/go.mod", "testdata/misc/requirements.txt", "testdata/misc/Cargo.lock"} - const nbrOfGroups = 6 + const nbrOfGroups = 9 fileGroups, err := finder.GetGroups( DebrickedOptions{ @@ -349,7 +349,7 @@ func TestGetGroupsWithStrictFlag(t *testing.T) { name: "StrictnessSetTo0", strictness: StrictAll, testedGroupIndex: 3, - expectedNumberOfGroups: 11, + expectedNumberOfGroups: 14, expectedManifestFile: "composer.json", expectedLockFiles: []string{"composer.lock", "go.mod", "Cargo.lock", "requirements.txt.pip.debricked"}, }, @@ -357,7 +357,7 @@ func TestGetGroupsWithStrictFlag(t *testing.T) { name: "StrictnessSetTo1", strictness: StrictLockAndPairs, testedGroupIndex: 1, - expectedNumberOfGroups: 6, + expectedNumberOfGroups: 9, expectedManifestFile: "", expectedLockFiles: []string{ "composer.lock", "composer.lock", "go.mod", "Cargo.lock", "requirements.txt.pip.debricked", "requirements-dev.txt.pip.debricked", @@ -367,7 +367,7 @@ func TestGetGroupsWithStrictFlag(t *testing.T) { name: "StrictnessSetTo2", strictness: StrictPairs, testedGroupIndex: 0, - expectedNumberOfGroups: 4, + expectedNumberOfGroups: 7, expectedManifestFile: "composer.json", expectedLockFiles: []string{ "composer.lock", "requirements-dev.txt.pip.debricked.lock", "requirements.txt.pip.debricked.lock", diff --git a/internal/file/groups.go b/internal/file/groups.go index 808544c7..51519925 100644 --- a/internal/file/groups.go +++ b/internal/file/groups.go @@ -115,3 +115,29 @@ func (gs *Groups) GetFiles() []string { return files } + +func (gs *Groups) matchWorkspace(workspaceManifest WorkspaceManifest) { + for _, g := range gs.groups { + // If group g is missing lockfile and does not have the same manifest + if len(g.LockFiles) == 0 && g.ManifestFile != workspaceManifest.RootManifest { + match := workspaceManifest.matchManifest(g.ManifestFile) + if match { + g.LockFiles = workspaceManifest.LockFiles + } + } + } +} + +func (gs *Groups) AddWorkspaceLockFiles() { + for _, group := range gs.groups { + workspaces, err := getWorkspaces(group.ManifestFile) + if err == nil && group.HasLockFiles() { + workspaceManifest := WorkspaceManifest{ + LockFiles: group.LockFiles, + RootManifest: group.ManifestFile, + Workspaces: workspaces, + } + gs.matchWorkspace(workspaceManifest) + } + } +} diff --git a/internal/file/groups_test.go b/internal/file/groups_test.go index 30659ea9..a91d61ea 100644 --- a/internal/file/groups_test.go +++ b/internal/file/groups_test.go @@ -184,3 +184,50 @@ func TestMatchGroupsExpected(t *testing.T) { assert.Equal(t, len(testData), len(groups.groups)) } + +func TestGroupsMatchWorkspaces(t *testing.T) { + g1 := NewGroup("package.json", nil, []string{"package-lock.json"}) + g2 := NewGroup("", nil, []string{"lockfile2"}) + g3 := NewGroup("package/file3", nil, []string{}) + g4 := NewGroup("pack/file3", nil, []string{}) + + gs := Groups{} + gs.Add(*g1) + gs.Add(*g2) + gs.Add(*g3) + gs.Add(*g4) + + workspaceManifest := WorkspaceManifest{ + LockFiles: []string{"package-lock.json"}, + RootManifest: "package.json", + Workspaces: []string{"package/*"}, + } + gs.matchWorkspace(workspaceManifest) + for _, g := range gs.groups { + if g.ManifestFile == "package/file3" { + assert.Equal(t, g.LockFiles[0], g1.LockFiles[0]) + } + if g.ManifestFile == "pack/file3" { + assert.Equal(t, len(g3.LockFiles), 0) + } + } + +} + +func TestAddWorkspaceLockFiles(t *testing.T) { + g1 := NewGroup("testdata/workspace/package.json", nil, []string{"testdata/workspace/package-lock.json"}) + g2 := NewGroup("testdata/workspace/packages/package_one/package.json", nil, []string{}) + g3 := NewGroup("testdata/workspace/packages/package_two/package.json", nil, []string{}) + + gs := Groups{} + gs.Add(*g1) + gs.Add(*g2) + gs.Add(*g3) + gs.AddWorkspaceLockFiles() + for _, g := range gs.groups { + assert.Equal(t, len(g.LockFiles), 1) + if len(g.LockFiles) == 1 { + assert.Equal(t, g.LockFiles[0], g1.LockFiles[0]) + } + } +} diff --git a/internal/file/testdata/workspace/package-lock.json b/internal/file/testdata/workspace/package-lock.json new file mode 100644 index 00000000..e625a7c4 --- /dev/null +++ b/internal/file/testdata/workspace/package-lock.json @@ -0,0 +1,650 @@ +{ + "name": "npm-workspaces-test", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "npm-workspaces-test", + "workspaces": [ + "packages/*" + ], + "devDependencies": { + "prettier": "3.3.3", + "rimraf": "6.0.1", + "typescript": "5.5.4" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "20.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", + "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package_one": { + "resolved": "packages/package_one", + "link": true + }, + "node_modules/package_two": { + "resolved": "packages/package_two", + "link": true + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "packages/package_one": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/node": "^20.0.0" + }, + "devDependencies": { + "typescript": "5.5.4" + } + }, + "packages/package_two": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/node": "16.11.40", + "minimist": "^1.2.5", + "package_one": "^1.0.0" + }, + "devDependencies": { + "typescript": "5.5.4" + } + }, + "packages/package_two/node_modules/@types/node": { + "version": "16.11.40", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.40.tgz", + "integrity": "sha512-7bOWglXUO6f21NG3YDI7hIpeMX3M59GG+DzZuzX2EkFKYUnRoxq3EOg4R0KNv2hxryY9M3UUqG5akwwsifrukw==", + "license": "MIT" + } + } +} diff --git a/internal/file/testdata/workspace/package.json b/internal/file/testdata/workspace/package.json new file mode 100644 index 00000000..b9e6664a --- /dev/null +++ b/internal/file/testdata/workspace/package.json @@ -0,0 +1,12 @@ +{ + "name": "npm-workspaces-test", + "private": true, + "devDependencies": { + "prettier": "3.3.3", + "rimraf": "6.0.1", + "typescript": "5.5.4" + }, + "workspaces": [ + "testdata/workspace/packages/*", "testdata\\workspace\\packages\\*" + ] +} diff --git a/internal/file/testdata/workspace/packages/package_one/package.json b/internal/file/testdata/workspace/packages/package_one/package.json new file mode 100644 index 00000000..d74a7c3a --- /dev/null +++ b/internal/file/testdata/workspace/packages/package_one/package.json @@ -0,0 +1,15 @@ +{ + "name": "package_one", + "version": "1.0.0", + "description": "testing workspaces", + "main": "lib/index.js", + "keywords": [], + "author": "test", + "license": "MIT", + "dependencies": { + "@types/node": "^20.0.0" + }, + "devDependencies": { + "typescript": "5.5.4" + } +} diff --git a/internal/file/testdata/workspace/packages/package_two/package.json b/internal/file/testdata/workspace/packages/package_two/package.json new file mode 100644 index 00000000..140b2f19 --- /dev/null +++ b/internal/file/testdata/workspace/packages/package_two/package.json @@ -0,0 +1,17 @@ +{ + "name": "package_two", + "version": "1.0.0", + "description": "test", + "main": "lib/main.js", + "keywords": [], + "author": "test", + "license": "MIT", + "dependencies": { + "package_one": "^1.0.0", + "@types/node": "16.11.40", + "minimist": "^1.2.5" + }, + "devDependencies": { + "typescript": "5.5.4" + } +} diff --git a/internal/file/workspace.go b/internal/file/workspace.go new file mode 100644 index 00000000..6ae6f57a --- /dev/null +++ b/internal/file/workspace.go @@ -0,0 +1,47 @@ +package file + +import ( + "encoding/json" + "os" + + "github.com/becheran/wildmatch-go" +) + +type WorkspaceManifest struct { + RootManifest string + LockFiles []string + Workspaces []string +} + +type NPMPackageJson struct { + Name string `json:"name"` + Workspaces []string `json:"workspaces"` +} + +func (workspaceManifest *WorkspaceManifest) matchManifest(manifestPath string) bool { + for _, workspacePattern := range workspaceManifest.Workspaces { + pattern := wildmatch.NewWildMatch(workspacePattern) + if pattern.IsMatch(manifestPath) { + return true + } + } + + return false +} + +func getWorkspaces(rootManifest string) ([]string, error) { + var npmPackageJson NPMPackageJson + + jsonFile, err := os.Open(rootManifest) + if err != nil { + return nil, err + } + defer jsonFile.Close() + decoder := json.NewDecoder(jsonFile) + err = decoder.Decode(&npmPackageJson) + if err != nil { + return nil, err + } + + return npmPackageJson.Workspaces, nil +} diff --git a/internal/file/workspace_test.go b/internal/file/workspace_test.go new file mode 100644 index 00000000..6be0f133 --- /dev/null +++ b/internal/file/workspace_test.go @@ -0,0 +1,57 @@ +package file + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMatchManifest(t *testing.T) { + wm := WorkspaceManifest{ + RootManifest: "package.json", + LockFiles: []string{"package-lock.json"}, + Workspaces: []string{"package/*", "pkg/internal/*", "pack/internal/package.json"}, + } + cases := []struct { + manifestFile string + expected bool + }{ + { + manifestFile: "package/package_one/package.json", + expected: true, + }, + { + manifestFile: "package_one/package.json", + expected: false, + }, + { + manifestFile: "pkg/package.json", + expected: false, + }, + { + manifestFile: "pkg/internal/package.json", + expected: true, + }, + } + + for _, c := range cases { + t.Run(c.manifestFile, func(t *testing.T) { + match := (&wm).matchManifest(c.manifestFile) + fmt.Println(c.manifestFile) + assert.Equal(t, c.expected, match) + }) + } +} + +func TestGetWorkspaces(t *testing.T) { + workspaces, err := getWorkspaces("testdata/workspace/package.json") + assert.NoError(t, err) + assert.Equal(t, 2, len(workspaces)) + assert.Equal(t, "testdata/workspace/packages/*", workspaces[0]) +} + +func TestGetWorkspacesNoFile(t *testing.T) { + _, err := getWorkspaces("testdata/non_existing_folder/package.json") + assert.Error(t, err) +} diff --git a/internal/fingerprint/fingerprint_test.go b/internal/fingerprint/fingerprint_test.go index a208b2c6..ee850886 100644 --- a/internal/fingerprint/fingerprint_test.go +++ b/internal/fingerprint/fingerprint_test.go @@ -15,7 +15,7 @@ var errorString = "mock error" // Test errors in symlink func mockSymlink(filename string) (bool, error) { - return false, fmt.Errorf(errorString) + return false, fmt.Errorf("%s", errorString) } func TestShouldProcessFile(t *testing.T) { // Create a temporary directory to use for testing