Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(init): reify on init new workspace #4892

Merged
merged 1 commit into from
Jun 1, 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
11 changes: 11 additions & 0 deletions docs/content/commands/npm-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ This value is not exported to the environment for child processes.
<!-- automatically generated, do not edit manually -->
<!-- see lib/utils/config/definitions.js -->

#### `workspaces-update`

* Default: true
* Type: Boolean

If set to true, the npm cli will run an update after operations that may
Copy link
Contributor

Choose a reason for hiding this comment

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

what does "run an update" mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it means the npm cli will run a npm update <workspace-name> cmd behind the scenes, to be clear this text is already there, ref: https://docs.npmjs.com/cli/v8/using-npm/config#workspaces-update - in case you want to propose improvements here, I'm due to a follow up PR improving docs and would appreciate the feedback 😊

Copy link
Contributor

Choose a reason for hiding this comment

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

sure, but it doesn't seem clear that "run an update" means this. Perhaps:

Suggested change
If set to true, the npm cli will run an update after operations that may
If set to true, the npm cli will run an update (`npm update <workspace-name>`) after operations that may

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's very close to what I want, I'll follow up with a docs PR once this lands 😊 thanks!

possibly change the workspaces installed to the `node_modules` folder.

<!-- automatically generated, do not edit manually -->
<!-- see lib/utils/config/definitions.js -->

#### `include-workspace-root`

* Default: false
Expand Down
45 changes: 44 additions & 1 deletion lib/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ const libexec = require('libnpmexec')
const mapWorkspaces = require('@npmcli/map-workspaces')
const PackageJson = require('@npmcli/package-json')
const log = require('../utils/log-shim.js')
const updateWorkspaces = require('../workspaces/update-workspaces.js')

const getLocationMsg = require('../exec/get-workspace-location-msg.js')
const BaseCommand = require('../base-command.js')

class Init extends BaseCommand {
static description = 'Create a package.json file'
static params = ['yes', 'force', 'workspace', 'workspaces', 'include-workspace-root']
static params = [
'yes',
'force',
'workspace',
'workspaces',
'workspaces-update',
'include-workspace-root',
]

static name = 'init'
static usage = [
'[--force|-f|--yes|-y|--scope]',
Expand Down Expand Up @@ -46,11 +55,13 @@ class Init extends BaseCommand {
const pkg = await rpj(resolve(this.npm.localPrefix, 'package.json'))
const wPath = filterArg => resolve(this.npm.localPrefix, filterArg)

const workspacesPaths = []
// npm-exec style, runs in the context of each workspace filter
if (args.length) {
for (const filterArg of filters) {
const path = wPath(filterArg)
await mkdirp(path)
workspacesPaths.push(path)
await this.execCreate({ args, path })
await this.setWorkspace({ pkg, workspacePath: path })
}
Expand All @@ -61,9 +72,13 @@ class Init extends BaseCommand {
for (const filterArg of filters) {
const path = wPath(filterArg)
await mkdirp(path)
workspacesPaths.push(path)
await this.template(path)
await this.setWorkspace({ pkg, workspacePath: path })
}

// reify packages once all workspaces have been initialized
await this.update(workspacesPaths)
}

async execCreate ({ args, path }) {
Expand Down Expand Up @@ -196,6 +211,34 @@ class Init extends BaseCommand {

await pkgJson.save()
}

async update (workspacesPaths) {
// translate workspaces paths into an array containing workspaces names
const workspaces = []
for (const path of workspacesPaths) {
const pkgPath = resolve(path, 'package.json')
const { name } = await rpj(pkgPath)
.catch(() => ({}))

if (name) {
workspaces.push(name)
}
}

const {
config,
flatOptions,
localPrefix,
} = this.npm

await updateWorkspaces({
config,
flatOptions,
localPrefix,
npm: this.npm,
workspaces,
})
}
}

module.exports = Init
44 changes: 15 additions & 29 deletions lib/commands/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ const { resolve } = require('path')
const { promisify } = require('util')
const readFile = promisify(require('fs').readFile)

const Arborist = require('@npmcli/arborist')
const reifyFinish = require('../utils/reify-finish.js')

const updateWorkspaces = require('../workspaces/update-workspaces.js')
const BaseCommand = require('../base-command.js')

class Version extends BaseCommand {
Expand Down Expand Up @@ -137,32 +135,20 @@ class Version extends BaseCommand {
return this.list(results)
}

async update (args) {
if (!this.npm.flatOptions.workspacesUpdate || !args.length) {
return
}

// default behavior is to not save by default in order to avoid
// race condition problems when publishing multiple workspaces
// that have dependencies on one another, it might still be useful
// in some cases, which then need to set --save
const save = this.npm.config.isDefault('save')
? false
: this.npm.config.get('save')

// runs a minimalistic reify update, targetting only the workspaces
// that had version updates and skipping fund/audit/save
const opts = {
...this.npm.flatOptions,
audit: false,
fund: false,
path: this.npm.localPrefix,
save,
}
const arb = new Arborist(opts)

await arb.reify({ ...opts, update: args })
await reifyFinish(this.npm, arb)
async update (workspaces) {
const {
config,
flatOptions,
localPrefix,
} = this.npm

await updateWorkspaces({
config,
flatOptions,
localPrefix,
npm: this.npm,
workspaces,
})
}
}

Expand Down
40 changes: 40 additions & 0 deletions lib/workspaces/update-workspaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict'

const Arborist = require('@npmcli/arborist')
const reifyFinish = require('../utils/reify-finish.js')

async function updateWorkspaces ({
config,
wraithgar marked this conversation as resolved.
Show resolved Hide resolved
flatOptions,
localPrefix,
npm,
workspaces,
}) {
if (!flatOptions.workspacesUpdate || !workspaces.length) {
return
}

// default behavior is to not save by default in order to avoid
// race condition problems when publishing multiple workspaces
// that have dependencies on one another, it might still be useful
// in some cases, which then need to set --save
const save = config.isDefault('save')
? false
: config.get('save')

// runs a minimalistic reify update, targetting only the workspaces
// that had version updates and skipping fund/audit/save
const opts = {
...flatOptions,
audit: false,
fund: false,
path: localPrefix,
save,
}
const arb = new Arborist(opts)

await arb.reify({ ...opts, update: workspaces })
await reifyFinish(npm, arb)
}

module.exports = updateWorkspaces
49 changes: 38 additions & 11 deletions tap-snapshots/test/lib/commands/init.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,53 @@ Array []
`

exports[`test/lib/commands/init.js TAP workspaces no args > should print helper info 1`] = `
Array []
`

exports[`test/lib/commands/init.js TAP workspaces no args, existing folder > should print helper info 1`] = `
Array []
`

exports[`test/lib/commands/init.js TAP workspaces post workspace-init reify > should print helper info 1`] = `
Array [
Array [
String(
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See \`npm help init\` for definitive documentation on these fields
and exactly what they do.

Use \`npm install <pkg>\` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
added 1 package in 100ms
),
],
]
`

exports[`test/lib/commands/init.js TAP workspaces no args, existing folder > should print helper info 1`] = `
Array []
exports[`test/lib/commands/init.js TAP workspaces post workspace-init reify > should reify tree on init ws complete 1`] = `
{
"name": "top-level",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "top-level",
"workspaces": [
"a"
]
},
"a": {
"version": "1.0.0",
"license": "ISC",
"devDependencies": {}
},
"node_modules/a": {
"resolved": "a",
"link": true
}
},
"dependencies": {
"a": {
"version": "file:a"
}
}
}

`

exports[`test/lib/commands/init.js TAP workspaces with arg but missing workspace folder > should print helper info 1`] = `
Expand Down
2 changes: 1 addition & 1 deletion tap-snapshots/test/lib/load-all-commands.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ npm init [<@scope>/]<name> (same as \`npx [<@scope>/]create-<name>\`)
Options:
[-y|--yes] [-f|--force]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
[-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root]

aliases: create, innit

Expand Down
2 changes: 1 addition & 1 deletion tap-snapshots/test/lib/npm.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ All commands:
Options:
[-y|--yes] [-f|--force]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
[-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root]

aliases: create, innit

Expand Down
34 changes: 34 additions & 0 deletions test/lib/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ t.test('workspaces', t => {
t.teardown(() => {
npm._mockOutputs.length = 0
})
npm._mockOutputs.length = 0
npm.localPrefix = t.testdir({
'package.json': JSON.stringify({
name: 'top-level',
Expand All @@ -306,6 +307,39 @@ t.test('workspaces', t => {
t.matchSnapshot(npm._mockOutputs, 'should print helper info')
})

t.test('post workspace-init reify', async t => {
const _consolelog = console.log
console.log = () => null
t.teardown(() => {
console.log = _consolelog
npm._mockOutputs.length = 0
delete npm.flatOptions.workspacesUpdate
})
npm.started = Date.now()
npm._mockOutputs.length = 0
npm.flatOptions.workspacesUpdate = true
npm.localPrefix = t.testdir({
'package.json': JSON.stringify({
name: 'top-level',
}),
})

const Init = t.mock('../../../lib/commands/init.js', {
...mocks,
'init-package-json': (dir, initFile, config, cb) => {
t.equal(dir, resolve(npm.localPrefix, 'a'), 'should use the ws path')
return require('init-package-json')(dir, initFile, config, cb)
},
})
const init = new Init(npm)
await init.execWorkspaces([], ['a'])
const output = npm._mockOutputs.map(arr => arr.map(i => i.replace(/[0-9]*ms$/, '100ms')))
Copy link
Member

Choose a reason for hiding this comment

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

nit: this could be a t.cleanSnapshot

t.matchSnapshot(output, 'should print helper info')
const lockFilePath = resolve(npm.localPrefix, 'package-lock.json')
const lockFile = fs.readFileSync(lockFilePath, { encoding: 'utf8' })
t.matchSnapshot(lockFile, 'should reify tree on init ws complete')
})

t.test('no args, existing folder', async t => {
t.teardown(() => {
npm._mockOutputs.length = 0
Expand Down