Skip to content

Commit

Permalink
feat: new npm copy command
Browse files Browse the repository at this point in the history
  • Loading branch information
Caleb ツ Everett committed Apr 18, 2022
1 parent 4ca858c commit ddf68b9
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 9 deletions.
134 changes: 134 additions & 0 deletions lib/commands/copy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const Arborist = require('@npmcli/arborist')
const { join, relative, dirname } = require('path')
const packlist = require('npm-packlist')
const fs = require('@npmcli/fs')

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

class Copy extends BaseCommand {
static description = 'Copy package to new location'

static name = 'copy'

static params = [
'omit',
'workspace',
'workspaces',
'include-workspace-root',
]

static ignoreImplicitWorkspace = false

static usage = ['<destination>']

async exec (args) {
await this.copyTo(args, true, new Set([]))
}

// called when --workspace or --workspaces is passed.
async execWorkspaces (args, filters) {
await this.setWorkspaces(filters)

await this.copyTo(
args,
this.includeWorkspaceRoot,
new Set(this.workspacePaths))
}

async copyTo (args, includeWorkspaceRoot, workspaces) {
if (args.length !== 1) {
throw this.usageError('Missing required destination argument')
}
const opts = {
...this.npm.flatOptions,
path: this.npm.localPrefix,
log: this.npm.log,
}
const destination = args[0]
const omit = new Set(this.npm.flatOptions.omit)

const tree = await new Arborist(opts).loadActual()

// map of node to location in destination.
const destinations = new Map()

// calculate the root set of packages.
if (includeWorkspaceRoot) {
const to = join(destination, tree.location)
destinations.set(tree, to)
}
for (const edge of tree.edgesOut.values()) {
if (edge.workspace && workspaces.has(edge.to.realpath)) {
const to = join(destination, edge.to.location)
destinations.set(edge.to, to)
}
}

// copy the root set of packages and their dependencies.
for (const [node, dest] of destinations) {
if (node.isLink && node.target) {
const targetPath = destinations.get(node.target)
if (targetPath == null) {
// This is the first time the link target was seen, it will be the
// only copy in dest, other links to the same target will link to
// this copy.
destinations.set(node.target, dest)
} else {
// The link target is already in the destination
await relativeSymlink(targetPath, dest)
}
} else {
if (node.isWorkspace || node.isRoot) {
// workspace and root packages have not been published so they may
// have files that should be excluded.
await copyPacklist(node.target.realpath, dest)
} else {
// copy the modules files but not dependencies.
const nm = join(node.realpath, 'node_modules')
await fs.cp(node.realpath, dest, {
recursive: true,
errorOnExist: false,
filter: src => src !== nm,
})
}

// add dependency edges to the queue.
for (const edge of node.edgesOut.values()) {
if (!omit.has(edge.type) && edge.to != null) {
destinations.set(
edge.to,
join(
destinations.get(edge.to.parent) || destination,
relative(edge.to.parent.location, edge.to.location)))
}
}
}
}
}
}
module.exports = Copy

async function copyPacklist (from, to) {
for (const file of await packlist({ path: from })) {
// packlist will include bundled node_modules. ignore it because we're
// already handling copying dependencies.
if (file.startsWith('node_modules/')) {
continue
}

// using recursive copy because packlist doesn't list directories.
// TODO what is npm's preferred recursive copy?
await fs.cp(
join(from, file),
join(to, file),
{ recursive: true, errorOnExist: false })
}
}

async function relativeSymlink (target, path) {
await fs.mkdir(dirname(path), { recursive: true })
await fs.symlink(
'./' + relative(dirname(path), target),
path // link to create
)
}
2 changes: 2 additions & 0 deletions lib/utils/cmd-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const aliases = {
x: 'exec',
why: 'explain',
la: 'll',
cp: 'copy',
verison: 'version',
ic: 'ci',

Expand Down Expand Up @@ -137,6 +138,7 @@ const cmdList = [
'version',
'view',
'whoami',
'copy',
]

const plumbing = ['birthday', 'help-search']
Expand Down
10 changes: 5 additions & 5 deletions smoke-tests/tap-snapshots/test/index.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ npm help npm more involved overview
All commands:
access, adduser, audit, bin, bugs, cache, ci, completion,
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
edit, exec, explain, explore, find-dupes, fund, get, help,
hook, init, install, install-ci-test, install-test, link,
ll, login, logout, ls, org, outdated, owner, pack, ping,
pkg, prefix, profile, prune, publish, rebuild, repo,
config, copy, dedupe, deprecate, diff, dist-tag, docs,
doctor, edit, exec, explain, explore, find-dupes, fund, get,
help, hook, init, install, install-ci-test, install-test,
link, ll, login, logout, ls, org, outdated, owner, pack,
ping, pkg, prefix, profile, prune, publish, rebuild, repo,
restart, root, run-script, search, set, set-script,
shrinkwrap, star, stars, start, stop, team, test, token,
uninstall, unpublish, unstar, update, version, view, whoami
Expand Down
2 changes: 2 additions & 0 deletions tap-snapshots/test/lib/commands/completion.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Array [
version
view
whoami
copy
login
author
home
Expand Down Expand Up @@ -144,6 +145,7 @@ Array [
x
why
la
cp
verison
ic
innit
Expand Down
16 changes: 16 additions & 0 deletions tap-snapshots/test/lib/load-all-commands.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ alias: c
Run "npm help config" for more info
`

exports[`test/lib/load-all-commands.js TAP load each command copy > must match snapshot 1`] = `
Copy package to new location
Usage:
npm copy <destination>
Options:
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
alias: cp
Run "npm help copy" for more info
`

exports[`test/lib/load-all-commands.js TAP load each command dedupe > must match snapshot 1`] = `
Reduce duplication in the package tree
Expand Down
5 changes: 5 additions & 0 deletions tap-snapshots/test/lib/utils/cmd-list.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ Object {
"conf": "config",
"confi": "config",
"config": "config",
"cop": "copy",
"copy": "copy",
"cp": "cp",
"cr": "create",
"cre": "create",
"crea": "create",
Expand Down Expand Up @@ -358,6 +361,7 @@ Object {
"cit": "install-ci-test",
"clean-install": "ci",
"clean-install-test": "cit",
"cp": "copy",
"create": "init",
"ddp": "dedupe",
"dist-tags": "dist-tag",
Expand Down Expand Up @@ -476,6 +480,7 @@ Object {
"version",
"view",
"whoami",
"copy",
],
"plumbing": Array [
"birthday",
Expand Down
26 changes: 22 additions & 4 deletions tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ All commands:
pkg, prefix, profile, prune, publish, rebuild, repo,
restart, root, run-script, search, set, set-script,
shrinkwrap, star, stars, start, stop, team, test, token,
uninstall, unpublish, unstar, update, version, view, whoami
uninstall, unpublish, unstar, update, version, view, whoami,
copy
Specify configs in the ini-formatted file:
/some/config/file/.npmrc
Expand Down Expand Up @@ -65,7 +66,8 @@ All commands:
pkg, prefix, profile, prune, publish, rebuild, repo,
restart, root, run-script, search, set, set-script,
shrinkwrap, star, stars, start, stop, team, test, token,
uninstall, unpublish, unstar, update, version, view, whoami
uninstall, unpublish, unstar, update, version, view, whoami,
copy
Specify configs in the ini-formatted file:
/some/config/file/.npmrc
Expand Down Expand Up @@ -101,7 +103,8 @@ All commands:
pkg, prefix, profile, prune, publish, rebuild, repo,
restart, root, run-script, search, set, set-script,
shrinkwrap, star, stars, start, stop, team, test, token,
uninstall, unpublish, unstar, update, version, view, whoami
uninstall, unpublish, unstar, update, version, view, whoami,
copy
Specify configs in the ini-formatted file:
/some/config/file/.npmrc
Expand Down Expand Up @@ -137,7 +140,8 @@ All commands:
pkg, prefix, profile, prune, publish, rebuild, repo,
restart, root, run-script, search, set, set-script,
shrinkwrap, star, stars, start, stop, team, test, token,
uninstall, unpublish, unstar, update, version, view, whoami
uninstall, unpublish, unstar, update, version, view, whoami,
copy
Specify configs in the ini-formatted file:
/some/config/file/.npmrc
Expand Down Expand Up @@ -286,6 +290,20 @@ All commands:
Run "npm help config" for more info
copy Copy package to new location
Usage:
npm copy <destination>
Options:
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces] [--include-workspace-root]
alias: cp
Run "npm help copy" for more info
dedupe Reduce duplication in the package tree
Usage:
Expand Down
Loading

0 comments on commit ddf68b9

Please sign in to comment.