Skip to content

Commit e563d61

Browse files
committed
spec: Describe local specifiers
This introduces changes to how `file:` specifiers work. Specifically, they now create symlinks.
1 parent 21719d6 commit e563d61

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

doc/spec/file-specifiers.md

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# `file:` specifiers
2+
3+
`specifier` refers to the value part of the `package.json`'s `dependencies`
4+
object. This is a semver expression for registry dependencies and
5+
URLs and URL-like strings for other types.
6+
7+
### Dependency Specifiers
8+
9+
* A `file:` specifier is either an absolute path (eg `/path/to/thing`, `d:\path\to\thing`):
10+
* An absolute `file:///absolute/path` with any number of leading slashes
11+
being treated as a single slash. That is, `file:/foo/bar` and
12+
`file:///foo/bar` reference the same package.
13+
* … or a relative path (eg `../path/to/thing`, `path\to\subdir`). Leading
14+
slashes on a file specifier will be removed, that is 'file://../foo/bar`
15+
references the same package as same as `file:../foo/bar`. The latter is
16+
considered canonical.
17+
* Attempting to install a specifer that has a windows drive letter will
18+
produce an error on non-Windows systems.
19+
* A valid `file:` specifier points is:
20+
* a valid package file. That is, a `.tar`, `.tar.gz` or `.tgz` containing
21+
`<dir>/package.json`.
22+
* OR, a directory that contains a `package.json`
23+
24+
Relative specifiers are relative to the file they were found in, or, if
25+
provided on the command line, the CWD that the command was run from.
26+
27+
An absolute specifier found in a `package.json` or `npm-shrinkwrap.json` is
28+
probably an error as it's unlikely to be portable between computers and
29+
should warn.
30+
31+
A specifier provided as a command line argument that is on a different drive
32+
is an error. That is, `npm install file:d:/foo/bar` is an error if the
33+
current drive is `c`. The point of this rule is that if we can't produce a
34+
relative path then it's an error.
35+
36+
### Specifier Disambiguation
37+
38+
On the command line, plain paths are allowed. These paths can be ambiguous
39+
as they could be a path, a plain package name or a github shortcut. This
40+
ambiguity is resolved by checking to see if either a directory exists that
41+
contains a `package.json`. If either is the case then the specifier is a
42+
file specifier, otherwise it's a registry or github specifier.
43+
44+
### Specifier Matching
45+
46+
A specifier is considered to match a dependency on disk when the `realpath`
47+
of the fully resolved specifier matches the `realpath` of the package on disk.
48+
49+
### Saving File Specifiers
50+
51+
When saving to both `package.json` and `npm-shrinkwrap.json` they will be
52+
saved using the `file:../relative/path` form, and the relative path will be
53+
relative to the project's root folder. This is particularly important to
54+
note for the `npm-shrinkwrap.json` as it means the specifier there will
55+
be different then the original `package.json` (where it was relative to that
56+
`package.json`).
57+
58+
# No, for `file:` type specifiers, we SHOULD shrinkwrap. Other symlinks we
59+
# should not. Other symlinks w/o the link spec should be an error.
60+
61+
When shrinkwrapping file specifiers, the contents of the destination
62+
package's `node_modules` WILL NOT be included in the shrinkwrap. If you want to lock
63+
down the destination package's `node_modules` you should create a shrinkwrap for it
64+
separately.
65+
66+
This is necessary to support the mono repo use case where many projects file
67+
to the same package. If each project included its own npm-shrinkwrap.json
68+
then they would each have their own distinct set of transitive dependencies
69+
and they'd step on each other any time you ran an install in one or the other.
70+
71+
NOTE: This should not have an effect on shrinkwrapping of other sorts of
72+
shrinkwrapped packages.
73+
74+
### Installation
75+
76+
#### File type specifiers pointing at tarballs
77+
78+
File-type specifiers pointing at a `.tgz` or `.tar.gz or `.tar` file will
79+
install it as a package file in the same way we would a remote tarball. The
80+
checksum of the package file should be recorded so that we can check for updates.
81+
82+
#### File type specifers pointing at directories
83+
84+
File-type specifiers that point at directories will necessarily not do
85+
anything for `fetch` and `extract` phases.
86+
87+
The symlink should be created during the `finalize` phase.
88+
89+
The `preinstall` for file-type specifiers MUST be run AFTER the
90+
`finalize` phase as the symlink may be a relative path reaching outside the
91+
current project root and a symlink that resolves in `.staging` won't resolve
92+
in the package's final resting place.
93+
94+
If the module is inside the package root that we're running the install for then
95+
dependencies of the linked package will be hoisted to the top level as usual.
96+
97+
If the module is outside the package root then dependencies will be installed inside
98+
the linked module's `node_modules` folder.
99+
100+
### Removal
101+
102+
Removal should remove the symlink.
103+
104+
Removal MUST NOT remove the transitive dependencies IF they're installed in
105+
the linked module's `node_modules` folder.
106+
107+
### Listing
108+
109+
In listings they should not include a version as the version is not
110+
something `npm` is concerned about. This also makes them easily
111+
distinguishable from symlinks of packages that have other dependency
112+
specifiers.
113+
114+
If you had run:
115+
116+
```
117+
npm install --save file:../a
118+
```
119+
120+
And then run:
121+
```
122+
npm ls
123+
```
124+
125+
You would see:
126+
127+
```
128+
[email protected] /path/to/example-package
129+
└── a → file:../a
130+
```
131+
132+
```
133+
[email protected] /path/to/example-package
134+
+-- a -> file:../a
135+
```
136+
137+
Of note here: No version is included as the relavent detail is WHERE the
138+
package came from, not what version happened to be in that path.
139+
140+
### Outdated
141+
142+
Local specifiers should only show up in `npm outdated` if they're missing
143+
and when they do, they should be reported as:
144+
145+
```
146+
Package Current Wanted Latest Location
147+
a MISSING LOCAL LOCAL example-package
148+
```
149+
150+
### Updating
151+
152+
If a dependency with a local specifier is already installed then `npm
153+
update` shouldn't do anything. If one is missing then it should be
154+
installed as if you ran `npm install`.

0 commit comments

Comments
 (0)