Skip to content

Commit cc28fae

Browse files
authored
Fix catalog: support for nodejs.install (#6718)
1 parent 055a0c0 commit cc28fae

2 files changed

Lines changed: 383 additions & 14 deletions

File tree

pkg/runtime/node/build.go

Lines changed: 166 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/sst/sst/v3/pkg/js"
1616
"github.com/sst/sst/v3/pkg/process"
1717
"github.com/sst/sst/v3/pkg/runtime"
18+
"gopkg.in/yaml.v3"
1819
)
1920

2021
var forceExternal = []string{
@@ -273,7 +274,11 @@ func (r *Runtime) Build(ctx context.Context, input *runtime.BuildInput) (*runtim
273274
}
274275
dependencies := map[string]string{}
275276
for _, pkg := range installPackages {
276-
dependencies[pkg] = resolveInstallVersion(pkg, properties.Install, parsed)
277+
version, err := resolveInstallVersion(pkg, properties.Install, filepath.Dir(src), parsed)
278+
if err != nil {
279+
return nil, err
280+
}
281+
dependencies[pkg] = version
277282
}
278283
outPkg := filepath.Join(input.Out(), "package.json")
279284
outFile, err := os.Create(outPkg)
@@ -320,6 +325,22 @@ func (r *Runtime) Build(ctx context.Context, input *runtime.BuildInput) (*runtim
320325
}, nil
321326
}
322327

328+
type catalogSource struct {
329+
Catalog map[string]string `json:"catalog" yaml:"catalog"`
330+
Catalogs map[string]map[string]string `json:"catalogs" yaml:"catalogs"`
331+
}
332+
333+
type bunPackageJSON struct {
334+
Catalog map[string]string `json:"catalog"`
335+
Catalogs map[string]map[string]string `json:"catalogs"`
336+
Workspaces json.RawMessage `json:"workspaces"`
337+
}
338+
339+
type bunWorkspaces struct {
340+
Catalog map[string]string `json:"catalog"`
341+
Catalogs map[string]map[string]string `json:"catalogs"`
342+
}
343+
323344
func resolveInstallPackages(install map[string]string) []string {
324345
result := make([]string, 0, len(install))
325346
for pkg := range install {
@@ -328,12 +349,150 @@ func resolveInstallPackages(install map[string]string) []string {
328349
return result
329350
}
330351

331-
func resolveInstallVersion(pkg string, install map[string]string, packageJSON js.PackageJson) string {
332-
if version, ok := install[pkg]; ok && version != "" && version != "*" {
333-
return version
352+
func resolveInstallVersion(pkg string, install map[string]string, dir string, packageJSON js.PackageJson) (string, error) {
353+
version := install[pkg]
354+
if version == "" || version == "*" {
355+
version = packageJSON.Dependencies[pkg]
334356
}
335-
if version := packageJSON.Dependencies[pkg]; version != "" {
336-
return version
357+
if version == "" {
358+
return "*", nil
359+
}
360+
if strings.HasPrefix(version, "catalog:") {
361+
var err error
362+
version, err = resolveCatalogVersion(dir, pkg, version)
363+
if err != nil {
364+
return "", err
365+
}
366+
}
367+
for _, prefix := range []string{"catalog:", "workspace:", "file:", "link:", "portal:", "patch:"} {
368+
if strings.HasPrefix(version, prefix) {
369+
return "", fmt.Errorf("could not determine an npm-compatible version for %q in nodejs.install: found %q using %q; pin the version explicitly", pkg, version, prefix)
370+
}
371+
}
372+
return version, nil
373+
}
374+
375+
func resolveCatalogVersion(dir string, pkg string, version string) (string, error) {
376+
workspacePath, err := fs.FindUp(dir, "pnpm-workspace.yaml")
377+
if err == nil {
378+
return resolvePnpmCatalogVersion(workspacePath, pkg, version)
379+
}
380+
resolved, found, err := resolveBunCatalogVersion(dir, pkg, version)
381+
if err != nil {
382+
return "", err
383+
}
384+
if found {
385+
return resolved, nil
386+
}
387+
return "", fmt.Errorf("could not determine an npm-compatible version for %q in nodejs.install: found %q but pnpm-workspace.yaml was not found and no Bun catalog was found in an ancestor package.json; pin the version explicitly", pkg, version)
388+
}
389+
390+
func resolvePnpmCatalogVersion(workspacePath string, pkg string, version string) (string, error) {
391+
data, err := os.ReadFile(workspacePath)
392+
if err != nil {
393+
return "", err
394+
}
395+
var workspace catalogSource
396+
if err := yaml.Unmarshal(data, &workspace); err != nil {
397+
return "", err
398+
}
399+
resolved, ok := resolveCatalogEntry(pkg, version, workspace)
400+
if !ok {
401+
return "", fmt.Errorf("could not determine an npm-compatible version for %q in nodejs.install: found %q but no matching catalog entry exists in pnpm-workspace.yaml; pin the version explicitly", pkg, version)
402+
}
403+
return resolved, nil
404+
}
405+
406+
func resolveBunCatalogVersion(dir string, pkg string, version string) (string, bool, error) {
407+
currentDir := dir
408+
for {
409+
packagePath := filepath.Join(currentDir, "package.json")
410+
data, err := os.ReadFile(packagePath)
411+
if err == nil {
412+
source, found, err := parseBunCatalogSource(data)
413+
if err != nil {
414+
return "", false, err
415+
}
416+
if found {
417+
resolved, ok := resolveCatalogEntry(pkg, version, source)
418+
if !ok {
419+
return "", true, fmt.Errorf("could not determine an npm-compatible version for %q in nodejs.install: found %q but no matching catalog entry exists in %s; pin the version explicitly", pkg, version, packagePath)
420+
}
421+
return resolved, true, nil
422+
}
423+
} else if !os.IsNotExist(err) {
424+
return "", false, err
425+
}
426+
427+
if currentDir == filepath.Dir(currentDir) {
428+
break
429+
}
430+
currentDir = filepath.Dir(currentDir)
431+
}
432+
return "", false, nil
433+
}
434+
435+
func parseBunCatalogSource(data []byte) (catalogSource, bool, error) {
436+
var manifest bunPackageJSON
437+
if err := json.Unmarshal(data, &manifest); err != nil {
438+
return catalogSource{}, false, err
439+
}
440+
source := catalogSource{
441+
Catalog: manifest.Catalog,
442+
Catalogs: manifest.Catalogs,
443+
}
444+
workspaceSource, found, err := parseBunWorkspacesCatalogSource(manifest.Workspaces)
445+
if err != nil {
446+
return catalogSource{}, false, err
447+
}
448+
if found {
449+
if workspaceSource.Catalog != nil {
450+
source.Catalog = workspaceSource.Catalog
451+
}
452+
if workspaceSource.Catalogs != nil {
453+
if source.Catalogs == nil {
454+
source.Catalogs = map[string]map[string]string{}
455+
}
456+
for name, catalog := range workspaceSource.Catalogs {
457+
source.Catalogs[name] = catalog
458+
}
459+
}
460+
}
461+
if source.Catalog == nil && len(source.Catalogs) == 0 {
462+
return catalogSource{}, false, nil
463+
}
464+
return source, true, nil
465+
}
466+
467+
func parseBunWorkspacesCatalogSource(raw json.RawMessage) (catalogSource, bool, error) {
468+
trimmed := strings.TrimSpace(string(raw))
469+
if trimmed == "" || trimmed[0] != '{' {
470+
return catalogSource{}, false, nil
471+
}
472+
var workspaces bunWorkspaces
473+
if err := json.Unmarshal(raw, &workspaces); err != nil {
474+
return catalogSource{}, false, err
475+
}
476+
if workspaces.Catalog == nil && len(workspaces.Catalogs) == 0 {
477+
return catalogSource{}, false, nil
478+
}
479+
return catalogSource{
480+
Catalog: workspaces.Catalog,
481+
Catalogs: workspaces.Catalogs,
482+
}, true, nil
483+
}
484+
485+
func resolveCatalogEntry(pkg string, version string, source catalogSource) (string, bool) {
486+
catalogName := strings.TrimSpace(strings.TrimPrefix(version, "catalog:"))
487+
var catalog map[string]string
488+
if catalogName == "" || catalogName == "default" {
489+
catalog = source.Catalog
490+
if catalog == nil {
491+
catalog = source.Catalogs["default"]
492+
}
493+
} else {
494+
catalog = source.Catalogs[catalogName]
337495
}
338-
return "*"
496+
resolved := catalog[pkg]
497+
return resolved, resolved != ""
339498
}

0 commit comments

Comments
 (0)