Skip to content

Commit 3fd4909

Browse files
committed
feat: new npm copy command
1 parent f37f7d2 commit 3fd4909

File tree

8 files changed

+437
-148
lines changed

8 files changed

+437
-148
lines changed

lib/commands/copy.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const Arborist = require('@npmcli/arborist')
2+
const getWorkspaces = require('../workspaces/get-workspaces.js')
3+
const { join, relative, dirname } = require('path')
4+
const packlist = require('npm-packlist')
5+
const fs = require('@npmcli/fs')
6+
7+
const BaseCommand = require('../base-command.js')
8+
9+
class Copy extends BaseCommand {
10+
static description = 'Copy package to new location'
11+
12+
static name = 'copy'
13+
14+
static params = [
15+
'omit',
16+
'workspace',
17+
'workspaces',
18+
'include-workspace-root',
19+
]
20+
21+
static ignoreImplicitWorkspace = false
22+
23+
static usage = ['<destination>']
24+
25+
async exec (args) {
26+
await this.copyTo(args, true, new Set([]))
27+
}
28+
29+
// called when --workspace or --workspaces is passed.
30+
async execWorkspaces (args, filters) {
31+
const workspaces = await getWorkspaces(filters, {
32+
path: this.npm.localPrefix,
33+
})
34+
35+
await this.copyTo(
36+
args,
37+
this.includeWorkspaceRoot,
38+
new Set(workspaces.values()))
39+
}
40+
41+
async copyTo (args, includeWorkspaceRoot, workspaces) {
42+
if (args.length !== 1) {
43+
throw this.usageError('Missing required destination argument')
44+
}
45+
const opts = {
46+
...this.npm.flatOptions,
47+
path: this.npm.localPrefix,
48+
log: this.npm.log,
49+
}
50+
const destination = args[0]
51+
const omit = new Set(this.npm.flatOptions.omit)
52+
53+
const tree = await new Arborist(opts).loadActual()
54+
55+
// map of node to location in destination.
56+
const destinations = new Map()
57+
58+
// calculate the root set of packages.
59+
if (includeWorkspaceRoot) {
60+
const to = join(destination, tree.location)
61+
destinations.set(tree, to)
62+
}
63+
for (const edge of tree.edgesOut.values()) {
64+
if (edge.workspace && workspaces.has(edge.to.realpath)) {
65+
const to = join(destination, edge.to.location)
66+
destinations.set(edge.to, to)
67+
}
68+
}
69+
70+
// copy the root set of packages and their dependencies.
71+
for (const [node, dest] of destinations) {
72+
if (node.isLink && node.target) {
73+
const targetPath = destinations.get(node.target)
74+
if (targetPath == null) {
75+
// This is the first time the link target was seen, it will be the
76+
// only copy in dest, other links to the same target will link to
77+
// this copy.
78+
destinations.set(node.target, dest)
79+
} else {
80+
// The link target is already in the destination
81+
await relativeSymlink(targetPath, dest)
82+
}
83+
} else {
84+
if (node.isWorkspace || node.isRoot) {
85+
// workspace and root packages have not been published so they may
86+
// have files that should be excluded.
87+
await copyPacklist(node.target.realpath, dest)
88+
} else {
89+
// copy the modules files but not dependencies.
90+
const nm = join(node.realpath, 'node_modules')
91+
await fs.cp(node.realpath, dest, {
92+
recursive: true,
93+
errorOnExist: false,
94+
filter: src => src !== nm,
95+
})
96+
}
97+
98+
// add dependency edges to the queue.
99+
for (const edge of node.edgesOut.values()) {
100+
if (!omit.has(edge.type) && edge.to != null) {
101+
destinations.set(
102+
edge.to,
103+
join(
104+
destinations.get(edge.to.parent) || destination,
105+
relative(edge.to.parent.location, edge.to.location)))
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
module.exports = Copy
113+
114+
async function copyPacklist (from, to) {
115+
for (const file of await packlist({ path: from })) {
116+
// packlist will include bundled node_modules. ignore it because we're
117+
// already handling copying dependencies.
118+
if (file.startsWith('node_modules/')) {
119+
continue
120+
}
121+
122+
// using recursive copy because packlist doesn't list directories.
123+
// TODO what is npm's preferred recursive copy?
124+
await fs.cp(
125+
join(from, file),
126+
join(to, file),
127+
{ recursive: true, errorOnExist: false })
128+
}
129+
}
130+
131+
async function relativeSymlink (target, path) {
132+
await fs.mkdir(dirname(path), { recursive: true })
133+
await fs.symlink(
134+
'./' + relative(dirname(path), target),
135+
path // link to create
136+
)
137+
}

lib/utils/cmd-list.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const aliases = {
55

66
// aliases
77
login: 'adduser',
8-
author: 'owner',
9-
home: 'docs',
8+
author: 'owner', home: 'docs',
109
issues: 'bugs',
1110
info: 'view',
1211
show: 'view',
@@ -39,6 +38,10 @@ const aliases = {
3938
'clean-install-test': 'cit',
4039
x: 'exec',
4140
why: 'explain',
41+
cp: 'copy',
42+
}
43+
44+
const affordances = {
4245
la: 'll',
4346
verison: 'version',
4447
ic: 'ci',
@@ -137,6 +140,7 @@ const cmdList = [
137140
'version',
138141
'view',
139142
'whoami',
143+
'copy',
140144
]
141145

142146
const plumbing = ['birthday', 'help-search']

tap-snapshots/smoke-tests/index.js.test.cjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ npm help npm more involved overview
2222
All commands:
2323
2424
access, adduser, audit, bin, bugs, cache, ci, completion,
25-
config, dedupe, deprecate, diff, dist-tag, docs, doctor,
26-
edit, exec, explain, explore, find-dupes, fund, get, help,
27-
hook, init, install, install-ci-test, install-test, link,
28-
ll, login, logout, ls, org, outdated, owner, pack, ping,
29-
pkg, prefix, profile, prune, publish, rebuild, repo,
25+
config, copy, dedupe, deprecate, diff, dist-tag, docs,
26+
doctor, edit, exec, explain, explore, find-dupes, fund, get,
27+
help, hook, init, install, install-ci-test, install-test,
28+
link, ll, login, logout, ls, org, outdated, owner, pack,
29+
ping, pkg, prefix, profile, prune, publish, rebuild, repo,
3030
restart, root, run-script, search, set, set-script,
3131
shrinkwrap, star, stars, start, stop, team, test, token,
3232
uninstall, unpublish, unstar, update, version, view, whoami

tap-snapshots/test/lib/commands/completion.js.test.cjs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Array [
110110
version
111111
view
112112
whoami
113+
copy
113114
login
114115
author
115116
home
@@ -143,30 +144,7 @@ Array [
143144
clean-install-test
144145
x
145146
why
146-
la
147-
verison
148-
ic
149-
innit
150-
in
151-
ins
152-
inst
153-
insta
154-
instal
155-
isnt
156-
isnta
157-
isntal
158-
isntall
159-
install-clean
160-
isntall-clean
161-
hlep
162-
dist-tags
163-
upgrade
164-
udpate
165-
rum
166-
sit
167-
urn
168-
ogr
169-
add-user
147+
cp
170148
),
171149
],
172150
]
@@ -203,7 +181,6 @@ Array [
203181
audit
204182
author
205183
add
206-
add-user
207184
),
208185
],
209186
]

tap-snapshots/test/lib/load-all-commands.js.test.cjs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ npm adduser
3434
Options:
3535
[--registry <registry>] [--scope <@scope>]
3636
37-
aliases: login, add-user
37+
alias: login
3838
3939
Run "npm help adduser" for more info
4040
`
@@ -120,7 +120,7 @@ Options:
120120
[--no-audit] [--foreground-scripts] [--ignore-scripts]
121121
[--script-shell <script-shell>]
122122
123-
aliases: clean-install, ic, install-clean, isntall-clean
123+
alias: clean-install
124124
125125
Run "npm help ci" for more info
126126
`
@@ -153,6 +153,22 @@ alias: c
153153
Run "npm help config" for more info
154154
`
155155

156+
exports[`test/lib/load-all-commands.js TAP load each command copy > must match snapshot 1`] = `
157+
Copy package to new location
158+
159+
Usage:
160+
npm copy <destination>
161+
162+
Options:
163+
[--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]]
164+
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
165+
[-ws|--workspaces] [--include-workspace-root]
166+
167+
alias: cp
168+
169+
Run "npm help copy" for more info
170+
`
171+
156172
exports[`test/lib/load-all-commands.js TAP load each command dedupe > must match snapshot 1`] = `
157173
Reduce duplication in the package tree
158174
@@ -212,8 +228,6 @@ Options:
212228
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
213229
[-ws|--workspaces] [--include-workspace-root]
214230
215-
alias: dist-tags
216-
217231
Run "npm help dist-tag" for more info
218232
`
219233

@@ -351,8 +365,6 @@ npm help <term> [<terms..>]
351365
Options:
352366
[--viewer <viewer>]
353367
354-
alias: hlep
355-
356368
Run "npm help help" for more info
357369
`
358370

@@ -396,7 +408,7 @@ Options:
396408
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
397409
[-ws|--workspaces] [--include-workspace-root]
398410
399-
aliases: create, innit
411+
alias: create
400412
401413
Run "npm help init" for more info
402414
`
@@ -425,7 +437,7 @@ Options:
425437
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
426438
[-ws|--workspaces] [--include-workspace-root]
427439
428-
aliases: add, i, in, ins, inst, insta, instal, isnt, isnta, isntal, isntall
440+
aliases: add, i
429441
430442
Run "npm help install" for more info
431443
`
@@ -508,8 +520,6 @@ Options:
508520
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
509521
[-ws|--workspaces] [--include-workspace-root]
510522
511-
alias: la
512-
513523
Run "npm help ll" for more info
514524
`
515525

@@ -522,7 +532,7 @@ npm adduser
522532
Options:
523533
[--registry <registry>] [--scope <@scope>]
524534
525-
aliases: login, add-user
535+
alias: login
526536
527537
Run "npm help adduser" for more info
528538
`
@@ -568,8 +578,6 @@ npm org ls orgname [<username>]
568578
Options:
569579
[--registry <registry>] [--otp <otp>] [--json] [-p|--parseable]
570580
571-
alias: ogr
572-
573581
Run "npm help org" for more info
574582
`
575583

@@ -767,7 +775,7 @@ Options:
767775
[-ws|--workspaces] [--include-workspace-root] [--if-present] [--ignore-scripts]
768776
[--script-shell <script-shell>]
769777
770-
aliases: run, rum, urn
778+
alias: run
771779
772780
Run "npm help run-script" for more info
773781
`
@@ -969,7 +977,7 @@ Options:
969977
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
970978
[-ws|--workspaces] [--include-workspace-root]
971979
972-
aliases: up, upgrade, udpate
980+
alias: up
973981
974982
Run "npm help update" for more info
975983
`
@@ -986,8 +994,6 @@ Options:
986994
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
987995
[-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root]
988996
989-
alias: verison
990-
991997
Run "npm help version" for more info
992998
`
993999

0 commit comments

Comments
 (0)