Review round 1 for the Node build module: registry for other @novox packages and refused bundles (1489), toolchain.reported stream (1483, 1488), re-pin to mesh-sdk 36d6f774; delta part 4 of 4
This commit is contained in:
@@ -45,6 +45,15 @@ type FetchResult struct {
|
||||
// Modules are the TypeScript modules' directories found, and Installed those whose own packages were installed.
|
||||
Modules []string `json:"modules"`
|
||||
Installed []string `json:"installed"`
|
||||
// Refused are the bundles whose packages were not installed, each with why (a module naming packages beyond the SDK
|
||||
// with no package-lock.json). The fetch passes; a build of such a bundle fails naming it (issue 1459).
|
||||
Refused []Refusal `json:"refused"`
|
||||
}
|
||||
|
||||
// Refusal is a module a fetch did not install the packages of.
|
||||
type Refusal struct {
|
||||
Module string `json:"module"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (m *Module) fetchTool() StepTool {
|
||||
@@ -93,13 +102,16 @@ func (m *Module) fetch(ctx context.Context, args map[string]any) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := FetchResult{Workspace: id, Commit: commit, Modules: []string{}, Installed: []string{}}
|
||||
res := FetchResult{Workspace: id, Commit: commit, Modules: []string{}, Installed: []string{}, Refused: []Refusal{}}
|
||||
for _, d := range dirs {
|
||||
res.Modules = append(res.Modules, d)
|
||||
did, err := m.installOwn(ctx, root, filepath.Join(tree, filepath.FromSlash(d)), d)
|
||||
did, refused, err := m.installOwn(ctx, root, filepath.Join(tree, filepath.FromSlash(d)), d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if refused != "" {
|
||||
res.Refused = append(res.Refused, Refusal{Module: d, Reason: refused})
|
||||
}
|
||||
if did {
|
||||
res.Installed = append(res.Installed, d)
|
||||
}
|
||||
@@ -165,27 +177,45 @@ func ownDependencies(dir string) (names []string, pkg map[string]any, err error)
|
||||
return names, pkg, nil
|
||||
}
|
||||
|
||||
// installOwn installs a module's own packages into its directory, or does nothing for a module that has none.
|
||||
func (m *Module) installOwn(ctx context.Context, root, dir, rel string) (bool, error) {
|
||||
// lockProblem says why a module's own packages cannot be installed: it names packages beyond the SDK and has no
|
||||
// package-lock.json. Empty when it names none or has its lock.
|
||||
func lockProblem(dir, rel string) (string, error) {
|
||||
names, _, err := ownDependencies(dir)
|
||||
if err != nil || len(names) == 0 {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "package-lock.json")); err != nil {
|
||||
return fmt.Sprintf("%s names packages beyond the SDK and has no package-lock.json (%s): commit one", rel, strings.Join(names, ", ")), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// installOwn installs a module's own packages into its directory, or does nothing for a module that has none. A module
|
||||
// that names packages and has no lock is not installed and not an error: the reason is answered, the fetch passes and
|
||||
// the build of that bundle fails naming it.
|
||||
func (m *Module) installOwn(ctx context.Context, root, dir, rel string) (installed bool, refused string, err error) {
|
||||
names, pkg, err := ownDependencies(dir)
|
||||
if err != nil || len(names) == 0 {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
for _, n := range names {
|
||||
if strings.HasPrefix(n, "@novox/") {
|
||||
return false, fmt.Errorf("%s depends on %s, and a fetch holds no credential for the mesh's package registry "+
|
||||
"(ADR 0466 §4), so it cannot be installed", rel, n)
|
||||
}
|
||||
if why, err := lockProblem(dir, rel); why != "" || err != nil {
|
||||
return false, why, err
|
||||
}
|
||||
lock, err := os.ReadFile(filepath.Join(dir, "package-lock.json"))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("%s names packages beyond the SDK (%s) and has no package-lock.json: commit one, and "+
|
||||
"the step installs exactly what it names", rel, strings.Join(names, ", "))
|
||||
return false, "", err
|
||||
}
|
||||
if m.Toolchain == nil || m.Toolchain.Registry == nil {
|
||||
return false, "", fmt.Errorf("the module has no package registry to install %s from", rel)
|
||||
}
|
||||
reg, err := m.Toolchain.Registry()
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// A scratch copy, so the module's package.json and lock are never rewritten (dependencies.go installSteps).
|
||||
scratch := filepath.Join(root, "npm-"+strings.NewReplacer("/", "-", ".", "root").Replace(rel))
|
||||
if err := os.MkdirAll(scratch, 0o700); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
for _, k := range []string{"dependencies", "peerDependencies", "optionalDependencies"} {
|
||||
if d, ok := pkg[k].(map[string]any); ok {
|
||||
@@ -196,28 +226,34 @@ func (m *Module) installOwn(ctx context.Context, root, dir, rel string) (bool, e
|
||||
delete(pkg, "scripts")
|
||||
raw, _ := json.Marshal(pkg)
|
||||
if err := os.WriteFile(filepath.Join(scratch, "package.json"), raw, 0o600); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(scratch, "package-lock.json"), lock, 0o600); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
// The registry is given through a config file of its own, never on the command line, and removed after.
|
||||
npmrc := filepath.Join(scratch, ".npmrc-install")
|
||||
if err := os.WriteFile(npmrc, []byte(reg.Npmrc()), 0o600); err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
env := append(m.baseEnv(), "npm_config_cache="+filepath.Join(m.Toolchain.Paths.Cache, "npm"))
|
||||
r := m.Run(ctx, env, "npm", "ci", "--omit=dev", "--omit=peer", "--ignore-scripts", "--no-bin-links",
|
||||
"--no-audit", "--no-fund", "--prefix", scratch)
|
||||
"--no-audit", "--no-fund", "--prefix", scratch, "--userconfig", npmrc)
|
||||
os.Remove(npmrc)
|
||||
if why := ranFailed(r); why != "" {
|
||||
return false, fmt.Errorf("installing the packages of %s (%s): %s", rel, strings.Join(names, ", "), why)
|
||||
return false, "", fmt.Errorf("installing the packages of %s (%s): %s", rel, strings.Join(names, ", "), why)
|
||||
}
|
||||
nm := filepath.Join(scratch, "node_modules")
|
||||
if err := removeSDKCopies(nm); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(dir, "node_modules")); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
if err := os.Rename(nm, filepath.Join(dir, "node_modules")); err != nil {
|
||||
return false, err
|
||||
return false, "", err
|
||||
}
|
||||
return true, os.RemoveAll(scratch)
|
||||
return true, "", os.RemoveAll(scratch)
|
||||
}
|
||||
|
||||
// removeSDKCopies removes every copy of the SDK the install pulled in: the toolchain's is the pin.
|
||||
|
||||
Reference in New Issue
Block a user