Skip to content

Commit a895616

Browse files
authored
Adds support for unconventional tarball paths (#457)
* Adds support for unconventional tarball paths * Fixes test server * Fixes detection * Adds a few extra tests * Bumps the versions * Adds explicit imports for URL
1 parent 076f8a5 commit a895616

File tree

22 files changed

+384
-135
lines changed

22 files changed

+384
-135
lines changed

CHANGELOG.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,37 @@
66

77
- Packages will now only be built when one of their dependencies is changed in some way. Note that this includes both direct dependencies and transitive dependencies, which might trigger unintuitive rebuilds in some case (for example, since `node-sass` depends on `lodash.assign`, upgrading `lodash.assign` will trigger a rebuild). This will be improved in a later release by introducing a new `runtime` field for the `dependenciesMeta` object that will exclude the package from the build key computation (feel free to start setting this flag as of now, even if it won't have any effect until then).
88

9-
- Registry hostnames finally aren't part of the lockfile anymore. It means that you can switch the registry at any time by changing the `npmRegistryServer` settings. One unfortunate side effect is that NPM Enterprise cannot be supported by default anymore (they use their own weird conventions), and as such you will need to enable `npmRegistryEnterprise: true` in your settings if you're in this case. Send complaints to npm, as we don't have the power to fix this without making the experience worse for everyone else.
9+
- Registry hostnames finally aren't part of the lockfile anymore. It means that you can switch the registry at any time by changing the `npmRegistryServer` settings. One unfortunate limitation is that this doesn't apply to registries that use non-standard paths for their archives (ie `/@scope/name/-/name-version.tgz`). One such example is NPM Enterprise, which will see the full path being stored in the lockfile.
1010

11-
- The `--frozen-lockfile` option will now properly report when the lockfile would be changed because of entry removals (it would previously only reject new entries, not removals)
11+
- The `--immutable` option (new name for `--frozen-lockfile`) will now properly report when the lockfile would be changed because of entry removals (it would previously only reject new entries, not removals).
1212

1313
### Notable changes
1414

1515
- The meaning of `devDependencies` is slightly altered. Until then dev dependencies were described as "dependencies we only use in development". Given that we now advocate for all your packages to be stored within the repository (in order to guarantee reproducible builds), this doesn't really make sense anymore. As a result, our description of dev dependencies is now "dependencies that aren't installed by the package consumers". It doesn't really change anything else than the name, but the more you know.
1616

17+
- One particular note is that you cannot install production dependencies only at the moment. We plan to add back this feature at a later time, but given that Zero-Installs means that your repository contains all your packages (prod & dev), the priority isn't as high as it used to be.
18+
1719
- Running `yarn link <package>` now has a semi-permanent effect in that `<package>` will be added as a dependency of your active workspace (using the new `portal:` protocol). Apart from that the workflow stays the same, meaning that running `yarn link` somewhere will add the local path to the local registry, and `yarn link <package>` will add a dependency to the previously linked package.
1820

19-
- The lockfiles now generated should be compatible with Yaml, while staying compatible with old-style lockfiles. If a lockfile is generated that cannot be parsed by a YAML parser, please open an issue and we'll look to fix it.
21+
- To disable such a link, just remove its `resolution` entry and run `yarn install` again.
22+
23+
- The Yarn configuration has been revamped and *will not read the `.npmrc` files anymore.* This used to cause a lot of confusion as to where the configuration was coming from, so the logic is now very simple: Yarn will look in the current directory and all its ancestors for `.yarnrc.yml` files.
24+
25+
- Note that the configuration files are now called `.yarnrc.yml` and thus are expected to be valid YAML. The available settings are listed [here](https://next.yarnpkg.com/configuration/yarnrc).
26+
27+
- The lockfiles now generated should be compatible with Yaml, while staying compatible with old-style lockfiles. Old-style lockfiles will be automatically migrated, but that will require some round-trips to the registry to obtain more information that wasn't stored previously, so the first install will be slightly slower.
28+
29+
- The cache files are now zip instead of tgz. This has an impact on cold install performances, because the currently available registries don't support it, which requires us to convert it on our side. Zero-Install is one way to offset this cost, and we're hoping that registries will consider offering zip as an option in the future.
2030

21-
- The Yarn configuration has been revamped and *will not read the `.npmrc` files anymore.* This caused a lot of confusion as to where the configuration was coming from, so the logic is now very simple: Yarn will look in the current directory and all its ancestors for `.yarnrc` files.
31+
- We chose zip because of its perfect combination in terms of tooling ubiquity and random access performances (tgz would require to decompress the whole archive to access a single file).
2232

2333
### Package manifests (`package.json`)
2434

25-
- Two new fields are now supported in the `publishConfig` key of your manifests: the `main` and `module` fields will be used to replace the value of their respective top-level counterparts in the manifest shipped along with the generated file.
35+
To see a comprehensive documentation about each possible field, please check our [documentation](https://next.yarnpkg.com/configuration/manifest).
36+
37+
- Two new fields are now supported in the `publishConfig` key of your manifests: the `main`, `bin`, and `module` fields will be used to replace the value of their respective top-level counterparts in the manifest shipped along with the generated file.
38+
39+
- The `typings` and `types` fields will also be replaced if you use the [TypeScript plugin](https://github.com/yarnpkg/berry/tree/master/packages/plugin-typescript).
2640

2741
- Two new fields are now supported at the root of the manifest: `dependenciesMeta` and `peerDependenciesMeta` (`peerDependenciesMeta` actually was supported in Yarn 1 as well, but `dependenciesMeta` is a new addition). These fields are meant to store dependency settings unique to each package.
2842

@@ -41,6 +55,7 @@
4155
- The `peerDependenciesMeta[].optional` field is a boolean flag; setting it to `true` will stop the package manager from emitting a warning when the specified peer dependency is missing (you typically want to use it if you provide optional integrations with specific third-party packages and don't want to pollute your users' installs with a bunch of irrelevant warnings). This settings is package-specific.
4256

4357
- The `resolutions` field no longer support the glob syntax within its patterns, as it was redundant with its own glob-less syntax and caused unnecessary confusion.
58+
4459
```diff
4560
{
4661
"resolutions": {
@@ -79,7 +94,7 @@
7994
- Running `yarn up <package>` will upgrade `<package>` in all of your workspaces at once (only if they already use the specified package - those that don't won't see it being added). Adding the `-i` flag will also cause Yarn to ask you to confirm for each workspace.
8095

8196
- Running `yarn config --why` will tell you the source for each value in your configuration. We recommend using it when you're not sure to understand why Yarn would have a particular settings.
82-
97+
8398
- Running `yarn pack` will no longer always include *nested* README, CHANGELOG, LICENSE or LICENCE files (note that those files will still be included if found at the root of the workspace being packed, as is usually the case). If you rely on this ([somewhat unintended](https://github.com/npm/npm-packlist/blob/270f534bc70dfb1d316682226332fd05e75e1b14/index.js#L162-L168)) behavior you can add those files manually to the `files` field of your `package.json`.
8499

85100
### Miscellaneous

packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ exports.getPackageHttpArchivePath = async function getPackageHttpArchivePath(
139139
if (!packageVersionEntry)
140140
throw new Error(`Unknown version "${version}" for package "${name}"`);
141141

142+
const localName = name.replace(/^@[^\/]+\//, ``);
143+
142144
const serverUrl = await exports.startPackageServer();
143-
const archiveUrl = `${serverUrl}/${name}/-/${name}-${version}.tgz`;
145+
const archiveUrl = `${serverUrl}/${name}/-/${localName}-${version}.tgz`;
144146

145147
return archiveUrl;
146148
};
@@ -217,7 +219,9 @@ exports.startPackageServer = function startPackageServer(): Promise<string> {
217219
[version as string]: Object.assign({}, packageVersionEntry.packageJson, {
218220
dist: {
219221
shasum: await exports.getPackageArchiveHash(name, version),
220-
tarball: await exports.getPackageHttpArchivePath(name, version),
222+
tarball: localName === `unconventional-tarball`
223+
? (await exports.getPackageHttpArchivePath(name, version)).replace(`/-/`, `/tralala/`)
224+
: await exports.getPackageHttpArchivePath(name, version),
221225
},
222226
}),
223227
};
@@ -344,8 +348,11 @@ exports.startPackageServer = function startPackageServer(): Promise<string> {
344348
scope,
345349
localName,
346350
};
347-
} else if (match = url.match(/^\/(?:(@[^\/]+)\/)?([^@\/][^\/]*)\/-\/\2-(.*)\.tgz$/)) {
348-
const [_, scope, localName, version] = match;
351+
} else if (match = url.match(/^\/(?:(@[^\/]+)\/)?([^@\/][^\/]*)\/(-|tralala)\/\2-(.*)\.tgz$/)) {
352+
const [_, scope, localName, split, version] = match;
353+
354+
if (localName === `unconventional-tarball` && split === `-`)
355+
return null;
349356

350357
return {
351358
type: RequestType.PackageTarball,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* @flow */
2+
3+
module.exports = require(`./package.json`);
4+
5+
for (const key of [`dependencies`, `devDependencies`, `peerDependencies`]) {
6+
for (const dep of Object.keys(module.exports[key] || {})) {
7+
// $FlowFixMe The whole point of this file is to be dynamic
8+
module.exports[key][dep] = require(dep);
9+
}
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "unconventional-tarball",
3+
"version": "1.0.0"
4+
}

packages/acceptance-tests/pkg-tests-specs/sources/auth.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const {
22
fs: {writeFile},
3-
tests: {getPackageArchivePath, getPackageHttpArchivePath, getPackageDirectoryPath},
43
} = require('pkg-tests-core');
54

65
const AUTH_TOKEN = `686159dc-64b3-413e-a244-2de2b8d1c36f`;

packages/acceptance-tests/pkg-tests-specs/sources/protocols/npm.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {xfs} from '@yarnpkg/fslib';
2+
13
describe(`Protocols`, () => {
24
describe(`npm:`, () => {
35
test(
@@ -33,5 +35,45 @@ describe(`Protocols`, () => {
3335
},
3436
),
3537
);
38+
39+
test(
40+
`it should allow fetching packages that have an unconventional url (semver)`,
41+
makeTemporaryEnv(
42+
{
43+
dependencies: {[`unconventional-tarball`]: `1.0.0`},
44+
},
45+
async ({path, run, source}) => {
46+
await run(`install`);
47+
48+
await expect(source(`require('unconventional-tarball')`)).resolves.toMatchObject({
49+
name: `unconventional-tarball`,
50+
version: `1.0.0`,
51+
});
52+
53+
await xfs.removePromise(`${path}/.yarn`);
54+
await run(`install`);
55+
},
56+
),
57+
);
58+
59+
test(
60+
`it should allow fetching packages that have an unconventional url (tag)`,
61+
makeTemporaryEnv(
62+
{
63+
dependencies: {[`unconventional-tarball`]: `latest`},
64+
},
65+
async ({path, run, source}) => {
66+
await run(`install`);
67+
68+
await expect(source(`require('unconventional-tarball')`)).resolves.toMatchObject({
69+
name: `unconventional-tarball`,
70+
version: `1.0.0`,
71+
});
72+
73+
await xfs.removePromise(`${path}/.yarn`);
74+
await run(`install`);
75+
},
76+
),
77+
);
3678
});
3779
});

packages/plugin-npm/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"semver": "^5.6.0"
1010
},
1111
"version": "2.0.0-rc.2",
12+
"nextVersion": {
13+
"semver": "2.0.0-rc.3",
14+
"nonce": "119175428621803"
15+
},
1216
"repository": {
1317
"type": "git",
1418
"url": "ssh://[email protected]/yarnpkg/berry.git"

packages/plugin-npm/sources/NpmFetcher.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {Fetcher, FetchOptions, MinimalFetchOptions} from '@yarnpkg/core';
2+
import {Locator, MessageName} from '@yarnpkg/core';
3+
import {httpUtils, structUtils, tgzUtils} from '@yarnpkg/core';
4+
import semver from 'semver';
5+
import {URL} from 'url';
6+
7+
import {PROTOCOL} from './constants';
8+
import * as npmConfigUtils from './npmConfigUtils';
9+
10+
export class NpmHttpFetcher implements Fetcher {
11+
supports(locator: Locator, opts: MinimalFetchOptions) {
12+
if (!locator.reference.startsWith(PROTOCOL))
13+
return false;
14+
15+
const url = new URL(locator.reference);
16+
17+
if (!semver.valid(url.pathname))
18+
return false;
19+
if (!url.searchParams.has(`archiveUrl`))
20+
return false;
21+
22+
return true;
23+
}
24+
25+
getLocalPath(locator: Locator, opts: FetchOptions) {
26+
return null;
27+
}
28+
29+
async fetch(locator: Locator, opts: FetchOptions) {
30+
const expectedChecksum = opts.checksums.get(locator.locatorHash) || null;
31+
32+
const [packageFs, releaseFs, checksum] = await opts.cache.fetchPackageFromCache(
33+
locator,
34+
expectedChecksum,
35+
async () => {
36+
opts.report.reportInfoOnce(MessageName.FETCH_NOT_CACHED, `${structUtils.prettyLocator(opts.project.configuration, locator)} can't be found in the cache and will be fetched from the remote server`);
37+
return await this.fetchFromNetwork(locator, opts);
38+
},
39+
);
40+
41+
return {
42+
packageFs,
43+
releaseFs,
44+
prefixPath: npmConfigUtils.getVendorPath(locator),
45+
checksum,
46+
};
47+
}
48+
49+
async fetchFromNetwork(locator: Locator, opts: FetchOptions) {
50+
const archiveUrl = new URL(locator.reference).searchParams.get(`archiveUrl`);
51+
if (archiveUrl === null)
52+
throw new Error(`Assertion failed: The archiveUrl querystring parameter should have been available`);
53+
54+
const sourceBuffer = await httpUtils.get(archiveUrl, {
55+
configuration: opts.project.configuration,
56+
});
57+
58+
return await tgzUtils.makeArchive(sourceBuffer, {
59+
stripComponents: 1,
60+
prefixPath: npmConfigUtils.getVendorPath(locator),
61+
});
62+
}
63+
}

0 commit comments

Comments
 (0)