Skip to content
This repository has been archived by the owner on Nov 3, 2022. It is now read-only.

feat: automatically detect workspace roots #28

Merged
merged 1 commit into from
Jan 26, 2022
Merged
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
68 changes: 56 additions & 12 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const walkUp = require('walk-up-path')
const ini = require('ini')
const nopt = require('nopt')
const mkdirp = require('mkdirp-infer-owner')
const mapWorkspaces = require('@npmcli/map-workspaces')
const rpj = require('read-package-json-fast')

/* istanbul ignore next */
const myUid = process.getuid && process.getuid()
Expand Down Expand Up @@ -543,23 +545,65 @@ class Config {
return
}

const cliWorkspaces = this[_get]('workspaces', 'cli')

for (const p of walkUp(this.cwd)) {
// walk up until we have a nm dir or a pj file
const hasAny = (await Promise.all([
stat(resolve(p, 'node_modules'))
.then(st => st.isDirectory())
.catch(() => false),
stat(resolve(p, 'package.json'))
.then(st => st.isFile())
.catch(() => false),
])).some(is => is)
if (hasAny) {
const hasNodeModules = await stat(resolve(p, 'node_modules'))
.then((st) => st.isDirectory())
.catch(() => false)

const hasPackageJson = await stat(resolve(p, 'package.json'))
.then((st) => st.isFile())
.catch(() => false)
Comment on lines +551 to +557
Copy link
Contributor

@ruyadorno ruyadorno Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about handling root links? tbh I'm not sure what the supported usages are but I'm aware that arborist does support some form of root link node:

ref: https://github.com/npm/cli/blob/latest/workspaces/arborist/lib/arborist/build-ideal-tree.js#L384

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we use stat here instead of lstat we'll resolve symlinks during the lookups and require that the package.json or node_modules correctly resolve to a file or directory, respectively. there's no change in behavior there so i think this should be just fine


if (!this.localPrefix && (hasNodeModules || hasPackageJson)) {
this.localPrefix = p
return

// if workspaces are disabled, return now
if (cliWorkspaces === false) {
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this inside the for loop?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh it's so that we set localPrefix to the very first walkUp we find no matter what, fencepost issue which I'll back away from slowly cause I'm very bad at them.

}

// otherwise, continue the loop
continue
}

if (this.localPrefix && hasPackageJson) {
// if we already set localPrefix but this dir has a package.json
// then we need to see if `p` is a workspace root by reading its package.json
// however, if reading it fails then we should just move on
const pkg = await rpj(resolve(p, 'package.json')).catch(() => false)
if (!pkg) {
continue
}

const workspaces = await mapWorkspaces({ cwd: p, pkg })
for (const w of workspaces.values()) {
if (w === this.localPrefix) {
// see if there's a .npmrc file in the workspace, if so log a warning
const hasNpmrc = await stat(resolve(this.localPrefix, '.npmrc'))
.then((st) => st.isFile())
.catch(() => false)

if (hasNpmrc) {
this.log.warn(`ignoring workspace config at ${this.localPrefix}/.npmrc`)
ruyadorno marked this conversation as resolved.
Show resolved Hide resolved
}

// set the workspace in the default layer, which allows it to be overridden easily
const { data } = this.data.get('default')
data.workspace = [this.localPrefix]
this.localPrefix = p
this.log.info(`found workspace root at ${this.localPrefix}`)
ruyadorno marked this conversation as resolved.
Show resolved Hide resolved
// we found a root, so we return now
return
}
}
}
}

this.localPrefix = this.cwd
if (!this.localPrefix) {
this.localPrefix = this.cwd
}
}

loadUserConfig () {
Expand Down
Loading